From ffb747e681cfac7bc8b0571042ca2246b2aa1825 Mon Sep 17 00:00:00 2001 From: Jacob Sundstrom Date: Sun, 7 Jul 2019 16:33:36 -0700 Subject: [PATCH] Updates after moving Boids to its own repo --- {classExtensions => ClassExtensions}/array.sc | 0 .../arrayedCollection.sc | 0 .../buffer.sc | 0 {classExtensions => ClassExtensions}/date.sc | 0 {classExtensions => ClassExtensions}/file.sc | 0 ClassExtensions/midiFunc.sc | 55 ++ .../sequenceableCollection.scd | 0 .../string.sc | 0 {classes => Classes}/SinglePole.sc | 0 {classes => Classes}/WriteFFT.sc | 0 HelpSource/Classes/Boids2D.schelp | 366 ------------ HelpSource/Classes/Boids3D.schelp | 368 ------------ .../Guides/Boids2D_spatializerGuide.schelp | 124 ---- classes/Automator.sc | 345 ----------- classes/Boids2D.sc | 546 ----------------- classes/Boids3D.sc | 552 ------------------ 16 files changed, 55 insertions(+), 2301 deletions(-) rename {classExtensions => ClassExtensions}/array.sc (100%) rename {classExtensions => ClassExtensions}/arrayedCollection.sc (100%) rename {classExtensions => ClassExtensions}/buffer.sc (100%) rename {classExtensions => ClassExtensions}/date.sc (100%) rename {classExtensions => ClassExtensions}/file.sc (100%) create mode 100644 ClassExtensions/midiFunc.sc rename {classExtensions => ClassExtensions}/sequenceableCollection.scd (100%) rename {classExtensions => ClassExtensions}/string.sc (100%) rename {classes => Classes}/SinglePole.sc (100%) rename {classes => Classes}/WriteFFT.sc (100%) delete mode 100644 HelpSource/Classes/Boids2D.schelp delete mode 100644 HelpSource/Classes/Boids3D.schelp delete mode 100644 HelpSource/Guides/Boids2D_spatializerGuide.schelp delete mode 100644 classes/Automator.sc delete mode 100644 classes/Boids2D.sc delete mode 100644 classes/Boids3D.sc diff --git a/classExtensions/array.sc b/ClassExtensions/array.sc similarity index 100% rename from classExtensions/array.sc rename to ClassExtensions/array.sc diff --git a/classExtensions/arrayedCollection.sc b/ClassExtensions/arrayedCollection.sc similarity index 100% rename from classExtensions/arrayedCollection.sc rename to ClassExtensions/arrayedCollection.sc diff --git a/classExtensions/buffer.sc b/ClassExtensions/buffer.sc similarity index 100% rename from classExtensions/buffer.sc rename to ClassExtensions/buffer.sc diff --git a/classExtensions/date.sc b/ClassExtensions/date.sc similarity index 100% rename from classExtensions/date.sc rename to ClassExtensions/date.sc diff --git a/classExtensions/file.sc b/ClassExtensions/file.sc similarity index 100% rename from classExtensions/file.sc rename to ClassExtensions/file.sc diff --git a/ClassExtensions/midiFunc.sc b/ClassExtensions/midiFunc.sc new file mode 100644 index 0000000..92a8829 --- /dev/null +++ b/ClassExtensions/midiFunc.sc @@ -0,0 +1,55 @@ ++ MIDIFunc { + + ccNRPN {|ccNum, action| + var list, seq, ccNum_in = 0, valIn = 0; + list = #[99, 98, 6, 38]; // the channels the 14-bit message (and it's ccNum) come in on + seq = Pseq(list, 1).asStream; // don't edit this yet... probably a more simple way to do this + CCResponder({arg src, chan, num, val; + if(num == seq.next) { + switch(num) + {99} {ccNum_in = ccNum_in | (val << 7)} + {98} {ccNum_in = ccNum_in | val} + {6} {valIn = valIn | (val << 7) } + {38} { + valIn = valIn | val; + if(ccNum_in == ccNum) { + action.value(ccNum_in, valIn); + }; + ccNum_in = valIn = 0; + seq.reset; // reset the sequence + }; + } { + seq.reset; + }; + }, num: list + ); + } + + // ccNRPN {arg func, ccNum, chan, srcID, argTemplate, dispatcher; + // var list, seq, ccNum_in = 0, valIn = 0, idx = 0; + // list = #[99, 98, 6, 38]; // the channels the 14-bit message (and it's ccNum) come in on + // seq = Pseq(list, 1).asStream; // don't edit this yet... probably a more simple way to do this + // MIDIFunc.cc({|val, num, chan, src| + // if(num == list[idx]) { + // switch(num) + // {99} {ccNum_in = ccNum_in | (val << 7)} + // {98} {ccNum_in = ccNum_in | val} + // {6} {valIn = valIn | (val << 7) } + // {38} { + // valIn = valIn | val; + // if(ccNum_in == ccNum) { + // func.value(ccNum_in, valIn); + // }; + // ccNum_in = valIn = 0; + // // seq.reset; // reset the sequence + // idx = 0; + // }; + // idx = idx+1; + // } { + // // seq.reset; + // idx = 0; + // }; + // }); + // }; + +} diff --git a/classExtensions/sequenceableCollection.scd b/ClassExtensions/sequenceableCollection.scd similarity index 100% rename from classExtensions/sequenceableCollection.scd rename to ClassExtensions/sequenceableCollection.scd diff --git a/classExtensions/string.sc b/ClassExtensions/string.sc similarity index 100% rename from classExtensions/string.sc rename to ClassExtensions/string.sc diff --git a/classes/SinglePole.sc b/Classes/SinglePole.sc similarity index 100% rename from classes/SinglePole.sc rename to Classes/SinglePole.sc diff --git a/classes/WriteFFT.sc b/Classes/WriteFFT.sc similarity index 100% rename from classes/WriteFFT.sc rename to Classes/WriteFFT.sc diff --git a/HelpSource/Classes/Boids2D.schelp b/HelpSource/Classes/Boids2D.schelp deleted file mode 100644 index 1f4a5cb..0000000 --- a/HelpSource/Classes/Boids2D.schelp +++ /dev/null @@ -1,366 +0,0 @@ -TITLE:: Boids2D -summary:: A 2D-optimized flocking algorithm -categories:: Collective Motion -related:: Classes/BoidUnit2D, Classes/Boids3D, Classes/BoidUnit3D, Guides/Boids2D_spatializerGuide - -DESCRIPTION:: -Boids2D is an implementation of the Reynolds boids algorithm, optimized for two dimensions. Flock-properties, such as maximum velocity or the weights for the three rules, are Gaussian-distributed such that the mean is the value provided and the standard deviation is that 5% of the mean. - -CLASSMETHODS:: - -METHOD:: new -Create a new flock. - -ARGUMENT:: numBoids -Number of members of the flock (BoidUnit2Ds). - -ARGUMENT:: timestep -The timestep of each calculation, used internally to calculate a working maximum velocity. When running moveFlock() in a loop, this is the interval of waiting to get movement at speed. If this is set lower than the wait interval in a loop, the flock appears to move in slow motion and visa versa. - -ARGUMENT:: centerInstinct -The weight of the center instinct rule. The center instinct is the amount of weight each boid gives to moving toward the center of mass of the entire flock. Making this negative makes the boids move away from the center of mass. Often works best at 0 for a bounded system. - -ARGUMENT:: innerDistance -The weight of the inner distance rule. The inner distance rule moves each boid away from one another such that they maintain some distance from one another. Making this negative attracts the boids to each other. - -ARGUMENT:: matchVelocity -The weight of the velocity matching rule. The velocity matching rule applies to thee velocity vector, both magnitude (speed) and angle (direction). - -RETURNS:: A Boids2D. - -NOTE:: -centerInstinct, innerDistance, and matchVelocity are the EMPHASIS::mean:: of the flock with all members being Gaussian-distributed. The standard deviation is 5% of the mean. -:: - -CODE:: -f = Boids2D(20, 0.2, 1, 1, 1); // a new flock of 20 members, a timestep of 0.2, and each rule having a mean of 1. -:: - -INSTANCEMETHODS:: - -METHOD:: addBoid -Add a new member to the flock. - -ARGUMENT:: initPos -The initial position. If nil, the boid is added at the center of mass of all the agents. - -RETURNS:: A Boids2D - -METHOD:: removeBoid -Remove an agent from the flock - -ARGUMENT:: index -The index of the agent to remove. If nil, the last agent in the list is removed. - -METHOD:: boids -The list of BoidUnits (agents) in the flock. - -RETURNS:: An Array of BoidUnit2D's. - -METHOD:: bounds -The bounds of the space in which the flock moves in meters. - -ARGUMENT:: dim -The new dimensions in a two element array. [1000, 1000] is a square with 1000 meters on each side such that the range and domain are both +/-500. When using the getter, bounds() would return [[-500,500], [-500,500]] - -RETURNS:: An Array (getter) or a Boids2D (setter) - -METHOD:: wrap -Boolean on whether or not to wrap the agents in the space. Defaults to false. - -ARGUMENT:: boolean - -RETURNS:: A Boids2D - -METHOD:: timestep -The timestep used in calculating positions relative the maximum velocity. - -ARGUMENT:: time -A Float describing the interval of time with which to calculate the movement of the flock. This can be different than the interval at which moveFlock() is looped such that momentum is preserved when moving in "slow motion". - -RETURNS:: A Float (getter) or a Boids2D (setter) - - - - - - -SUBSECTION::Flocking Rules and Moving the Flock - -METHOD:: centerInstinct -The weight of the center-seeking rule, Gaussian-distributed. - -RETURNS:: A Boids2D - -DISCUSSION:: The center instinct is the amount of weight each agent gives to moving toward the center of mass of the six nearest members, regardless of their absolute distance. Making this negative causes the agents move away from the center of mass of their neighbors. Often works best close to 0 for a bounded system. When this is set high in an unbounded (wrapped) system, the agents have a tendency to oscillate at the extremes of the space. - -METHOD:: innerDistance -The weight of the inner distance rule, Gaussian-distributed. - -RETURNS:: A Boids2D - -DISCUSSION::The inner distance rule moves each agent away from one another such that they maintain some distance from one another. Making this negative attracts the agents to each other. - -METHOD:: matchVelocity -The weight of the velocity-matching rule, Gaussian-distributed. The velocity is matched in both speed and direction. - -RETURNS:: A Boids2D - -DISCUSSION::The inner distance rule moves each agent away from one another such that they maintain some distance from one another. Making this negative attracts the agents to each other. - -METHOD:: maxVelocity -The maximum velocity of the agents in meters per second; defaults to 100 m/s. This is adjusted internally to a working maximum velocity and is Gaussian-distributed. - -ARGUMENT:: val -Speed in meters per second. - -RETURNS:: A Boids2D - -METHOD:: minSpace -The minimum space between agents in meters, Gaussian-distributed. - -ARGUMENT:: val -The minimum space between agents in meters. - -RETURNS:: A Boids2D - -DISCUSSION::Neighboring agents within this distance from one another will move away from each other; agents outside of this radius have no effect. Note that this amount can be overcome by other forces, for instance if a very strong attractor is present. - -METHOD:: moveFlock -The method that, when called, calculates the new velocity vectors and adds them to the positions. The func argument is evaluated EMPHASIS::after:: all other operations and the boids have been moved. - -ARGUMENT:: func -A Function that is called after evaluation of the new positions. It is passed this instance of Boids2D. - -RETURNS:: A Boids2D - -CODE:: -// move the flock and post its new center of mass -f.moveFlock({|flock| - flock.centerOfMass.postln; -}); -:: - - - - - - - -SUBSECTION:: Targets and Obstacles - -NOTE:: There may be changes the argument names for obstacles and targets, specifically code::gravity:: and code::repulsion::, to code::strength:: so that they are consistent.:: - -METHOD:: targets -An array of the targets. Each target is a Dictionary of two elements: \pos and \strength. - -RETURNS:: An Array of Dictionaries - -METHOD:: addTarget -Add a target. Each target is a Dictionary containing two elements: \pos (A RealVector2D of the x and y position) and \strength (a Float of the strength of the target). - -ARGUMENT:: pos -An Array that describes the position of the target in (x,y). This is converted internally to a RealVector2D. - -ARGUMENT:: gravity -A Float describing the strength of the target. - -RETURNS:: A Boids2D - -NOTE:: -Internally, the position is converted from an Array to a RealVector2D to speed up computation. Likewise, the Dictionary created stores the gravity value as strength for cleaner code in the class. - -The EMPHASIS::field:: of the gravity falls off at a rate of 1/EMPHASIS::r:: where EMPHASIS::r:: is the radius in meters. -:: - -CODE:: -f.addTarget([rand(200), rand(200)], rrand(1,3)); // add a target randomly -f.targets[0].postln; // prints Dictionary[ (pos -> RealVector2D[ 91, 27 ]), (strength -> 2) ] -:: - -METHOD:: editTarget -Edit a target at an index. - -ARGUMENT:: index -The index of the target one wishes to edit. If this is nil, nothing happens and a warning is thrown. - -ARGUMENT:: pos -The position of the target. If this is nil, the position remains the same. - -ARGUMENT:: gravity -The gravity of the target. If this is nil, the gravity remains the same. - -RETURNS:: A Boids2D - -METHOD:: removeTarget -Remove a target - -ARGUMENT:: index -The index of the target in the list of targets to remove. If index is nil, remove the last target in the list. - -METHOD:: clearTargets -Clear the list of targets. - -RETURNS:: a Boids2D - - -METHOD:: obstacles -An array of the obstacles. Each target is a Dictionary of two elements: \pos and \strength. - -RETURNS:: An Array of Dictionaries - -METHOD:: addObstacle -Add a target. Each target is a Dictionary containing two elements: \pos (A RealVector2D of the x and y position) and \strength (a Float of the strength of the target). - -ARGUMENT:: pos -An Array that describes the position of the target in (x,y). This is converted internally to a RealVector2D. - -ARGUMENT:: repulsion -A Float describing the repulsion of the obstacle. - -RETURNS:: A Boids2D - -NOTE:: -Internally, the position is converted from an Array to a RealVector2D to speed up computation. Likewise, the Dictionary created stores the repulsion value as strength for cleaner code in the class. - -The EMPHASIS::field:: of repulsion falls off at a rate of 1/EMPHASIS::r^2:: where EMPHASIS::r:: is the radius in meters. -:: - -CODE:: -f.addObstacle([rand(200), rand(200)], rrand(1,3)); // add a target randomly -f.targets[0].postln; // prints Dictionary[ (pos -> RealVector2D[ 91, 27 ]), (strength -> 2) ] -:: - -METHOD:: editObstacle -Edit an obstacle at an index. - -ARGUMENT:: index -The index of the obstacle one wishes to edit. If this is nil, nothing happens and a warning is thrown. - -ARGUMENT:: pos -The position of the obstacle. If this is nil, the position remains the same. - -ARGUMENT:: repulsion -The gravity of the obstacle. If this is nil, the gravity remains the same. - -RETURNS:: A Boids2D - -METHOD:: removeObstacle -Remove a obstacle. - -ARGUMENT:: index -The index of the obstacle in the list of obstacles to remove. If index is nil, remove the last obstacle in the list. - -METHOD:: clearObstacles -Clear the list of obstacles. - -RETURNS:: a Boids2D - - - - - - - - - - - - - -SUBSECTION:: Attributes of the flock - -METHOD:: centerOfMass -The position of the center of mass of the flock. - -RETURNS:: A RealVector2D - -METHOD:: boids -The array of BoidUnits; i.e. an array of the agents in the flock. - -RETURNS:: An Array - -METHOD:: getPanVals -A convenience method that RETURNS an array of two-element arrays of values useful for use as a panner in the form [dist, angle] where EMPHASIS::dist:: is the distance from the origin in meters and EMPHASIS::angle:: is the angle about the origin in radians. Also see BoidUnit2D.getPanVals. - -WARNING:: For math reasons, positive motion is EMPHASIS::counterclockwise:: while negative motion is EMPHASIS::clockwise::.:: - -RETURNS:: An Array - - -METHOD:: sizeOfFlock -A convenience method to return the size of the flock. Identical to numBoids(). - -RETURNS:: an Integer - -METHOD:: numBoids -The number of members in the flock. Identical to sizeOfFlock(). - -RETURNS:: An Integer - -METHOD:: visualizer -Show a visualization of the flock. The agents are shown as wedges with the point being the position and the angle being the angle of the velocity. Targets are shown as blue circles and obstacles as red circles. - -ARGUMENT:: showLabels -A Boolean, defaults to false. If true, the index of the agent/target/obstacle is positioned along with the wedge or circle. - -ARGUMENT:: returnWindow -A Boolean, defaults to false. If true, return the Window instead of the instance. - -RETURNS:: A Boids2D or a Window. - -METHOD:: info -Print some basic info about the flock. Will probably be removed in the future. - -code:: -// f.info prints -Boid Info:::: - numBoids: 20 - timestep: 0.2 s - centerInstinct: 0 - innerDistance: 1 - matchVelocity: 1 - maxVelocity: 5 m/s - minSpace: 1 m -:: - -RETURNS:: A Boids2D - - - - -EXAMPLES:: - -code:: -f = Boids2D.new(20, 0.2); // a new flock -f.maxVelocity = 150; // speed it up -x = {|flock| - flock.boids[0].pos.postln; // print the position of the first boid -}; - -r = fork { - loop { - f.moveFlock(x); // move the flock and evaluate x each time - 0.2.wait; // wait a bit (timestep) - }; -}; - -f.visualizer(false); // a visualizer without the labels - -f.addTarget([0,0], 1); // add a target at the origin with a gravity of 1 -f.addObstacle([100,-100], 2); // add an obstacle at (100,-100) with a repulsion of 2 -f.innerDistance = 5; // make them like each other less -f.matchVelocity = 0; // no velocity matching - -r.stop; // stop the loop and thus, stop the flock - -/* - Job 1:21 - And said, Naked came I out of my mother's womb, and naked shall I return thither: - the LORD gave, and the LORD hath taken away; blessed be the name of the LORD. -*/ -f.addBoid; // add a boid -f.removeBoid; // remove a boid -:: - - -PRIVATE:: prFillBoidList, prCalcVec, prArcTan, prArcTan2, prInverseSquare, prDoRules, boidList diff --git a/HelpSource/Classes/Boids3D.schelp b/HelpSource/Classes/Boids3D.schelp deleted file mode 100644 index 674b996..0000000 --- a/HelpSource/Classes/Boids3D.schelp +++ /dev/null @@ -1,368 +0,0 @@ -TITLE:: Boids3D -summary:: A 3D-optimized flocking algorithm -categories:: Collective Motion -related:: Classes/BoidUnit3D, Classes/Boids2D, Classes/BoidUnit2D - -DESCRIPTION:: -Boids3D is an implementation of the Reynolds boids algorithm, optimized for three dimensions. Flock-properties, such as maximum velocity or the weights for the three rules, are Gaussian-distributed such that the mean is the value provided and the standard deviation is that 5% of the mean. - -CLASSMETHODS:: - -METHOD:: new -Create a new flock. - -ARGUMENT:: numBoids -Number of members of the flock. - -ARGUMENT:: timestep -The timestep of each calculation, used internally to calculate a working maximum velocity. When running moveFlock() in a loop, this is the interval of waiting to get movement at speed. If this is set lower than the wait interval in a loop, the flock appears to move in slow motion and visa versa. - -ARGUMENT:: centerInstinct -The weight of the center instinct rule. The center instinct is the amount of weight each boid gives to moving toward the center of mass of the entire flock. Making this negative makes the boids move away from the center of mass. Often works best at 0 for a bounded system. - -ARGUMENT:: innerDistance -The weight of the inner distance rule. The inner distance rule moves each boid away from one another such that they maintain some distance from one another. Making this negative attracts the boids to each other. - -ARGUMENT:: matchVelocity -The weight of the velocity matching rule. The velocity matching rule applies to thee velocity vector, both magnitude (speed) and angle (direction). - -RETURNS:: A Boids3D. - -NOTE:: -centerInstinct, innerDistance, and matchVelocity are the EMPHASIS::mean:: of the flock with all members being Gaussian-distributed. The standard deviation is 5% of the mean. -:: - -CODE:: -f = Boids3D(20, 0.2, 1, 1, 1); // a new flock of 20 members, a timestep of 0.2, and each rule having a mean of 1. -:: - -INSTANCEMETHODS:: - -METHOD:: addBoid -Add a new member to the flock. - -ARGUMENT:: initPos -The initial position. If nil, the boid is added at the center of mass of all the agents. - -RETURNS:: A Boids3D - -METHOD:: removeBoid -Remove an agent from the flock - -ARGUMENT:: index -The index of the agent to remove. If nil, the last agent in the list is removed. - -METHOD:: boids -The list of BoidUnits (agents) in the flock. - -RETURNS:: An Array of BoidUnit3D's. - -METHOD:: bounds -The bounds of the space in which the flock moves in meters. - -ARGUMENT:: dim -The new dimensions in a two element array. [1000, 1000] is a square with 1000 meters on each side such that the range and domain are both +/-500. When using the getter, bounds() would return [[-500,500], [-500,500]] - -RETURNS:: An Array (getter) or a Boids3D (setter) - -METHOD:: wrap -Boolean on whether or not to wrap the agents in the space. Defaults to false. - -ARGUMENT:: boolean - -RETURNS:: A Boolean (getter) or a Boids3D (setter) - -METHOD:: timestep -The timestep used in calculating positions relative the maximum velocity. - -ARGUMENT:: time -A Float describing the interval of time with which to calculate the movement of the flock. This can be different than the interval at which moveFlock() is looped such that momentum is preserved when moving in "slow motion". - -RETURNS:: A Float (getter) or a Boids3D (setter) - - - - - - -SUBSECTION::Flocking Rules and Moving the Flock - -METHOD:: centerInstinct -The weight of the center-seeking rule, Gaussian-distributed. - -RETURNS:: A Boids3D - -DISCUSSION:: The center instinct is the amount of weight each agent gives to moving toward the center of mass of the six nearest members, regardless of their absolute distance. Making this negative causes the agents move away from the center of mass of their neighbors. Often works best close to 0 for a bounded system. When this is set high in an unbounded (wrapped) system, the agents have a tendency to oscillate at the extremes of the space. - -METHOD:: innerDistance -The weight of the inner distance rule, Gaussian-distributed. - -RETURNS:: A Boids3D - -DISCUSSION::The inner distance rule moves each agent away from one another such that they maintain some distance from one another. Making this negative attracts the agents to each other. - -METHOD:: matchVelocity -The weight of the velocity-matching rule, Gaussian-distributed. The velocity is matched in both speed and direction. - -RETURNS:: A Boids3D - -DISCUSSION::The inner distance rule moves each agent away from one another such that they maintain some distance from one another. Making this negative attracts the agents to each other. - -METHOD:: maxVelocity -The maximum velocity of the agents in meters per second; defaults to 100 m/s. This is adjusted internally to a working maximum velocity and is Gaussian-distributed. - -ARGUMENT:: val -Speed in meters per second. - -RETURNS:: A Boids3D - -METHOD:: minSpace -The minimum space between agents in meters, Gaussian-distributed. - -ARGUMENT:: val -The minimum space between agents in meters. - -RETURNS:: A Boids3D - -DISCUSSION::Neighboring agents within this distance from one another will move away from each other; agents outside of this radius have no effect. Note that this amount can be overcome by other forces, for instance if a very strong attractor is present. - -METHOD:: moveFlock -The method that, when called, calculates the new velocity vectors and adds them to the positions. The func argument is evaluated EMPHASIS::after:: all other operations and the boids have been moved. - -ARGUMENT:: func -A Function that is called after evaluation of the new positions. It is passed this instance of Boids3D. - -RETURNS:: A Boids3D - -CODE:: -// move the flock and post its new center of mass -f.moveFlock({|flock| - flock.centerOfMass.postln; -}); -:: - - - - - - - -SUBSECTION:: Targets and Obstacles - -NOTE:: There may be changes the argument names for obstacles and targets, specifically code::gravity:: and code::repulsion::, to code::strength:: so that they are consistent.:: - -METHOD:: targets -An array of the targets. Each target is a Dictionary of two elements: \pos and \strength. - -RETURNS:: An Array of Dictionaries - -METHOD:: addTarget -Add a target. Each target is a Dictionary containing two elements: \pos (A RealVector3D of the x, y, and z positions) and \strength (a Float of the strength of the target). - -ARGUMENT:: pos -An Array that describes the position of the target in (x,y, z). This is converted internally to a RealVector3D. - -ARGUMENT:: gravity -A Float describing the strength of the target. - -RETURNS:: A Boids3D - -NOTE:: -Internally, the position is converted from an Array to a RealVector3D to speed up computation. Likewise, the Dictionary created stores the gravity value as strength for cleaner code in the class. The CODE::pos:: EMPHASIS::must:: be a three-element array. - -The EMPHASIS::field:: of the gravity falls off at a rate of 1/EMPHASIS::r:: where EMPHASIS::r:: is the radius in meters. -:: - -CODE:: -f.addTarget([rand(200), rand(200), rand(200)], rrand(1,3)); // add a target randomly -f.targets[0].postln; // prints Dictionary[ (pos -> RealVector3D[ 91, 27, 64 ]), (strength -> 2) ] -:: - -METHOD:: editTarget -Edit a target at an index. - -ARGUMENT:: index -The index of the target one wishes to edit. If this is nil, nothing happens and a warning is thrown. - -ARGUMENT:: pos -The position of the target. If this is nil, the position remains the same. - -ARGUMENT:: gravity -The gravity of the target. If this is nil, the gravity remains the same. - -RETURNS:: A Boids3D - -METHOD:: removeTarget -Remove a target - -ARGUMENT:: index -The index of the target in the list of targets to remove. If index is nil, remove the last target in the list. - -METHOD:: clearTargets -Clear the list of targets. - -RETURNS:: a Boids3D - - -METHOD:: obstacles -An array of the obstacles. Each target is a Dictionary of two elements: \pos and \strength. - -RETURNS:: An Array of Dictionaries - -METHOD:: addObstacle -Add a target. Each target is a Dictionary containing two elements: \pos (A RealVector3D of the x, y, and z positions) and \strength (a Float of the strength of the target). - -ARGUMENT:: pos -An Array that describes the position of the target in (x,y, z). This is converted internally to a RealVector3D. - -ARGUMENT:: repulsion -A Float describing the repulsion of the obstacle. - -RETURNS:: A Boids3D - -NOTE:: -Internally, the position is converted from an Array to a RealVector3D to speed up computation. Likewise, the Dictionary created stores the repulsion value as strength for cleaner code in the class. The CODE::pos:: EMPHASIS::must:: be a three-element array. - -The EMPHASIS::field:: of repulsion falls off at a rate of 1/EMPHASIS::r^2:: where EMPHASIS::r:: is the radius in meters. -:: - -CODE:: -f.addObstacle([rand(200), rand(200), rand(200)], rrand(1,3)); // add a target randomly -f.targets[0].postln; // prints Dictionary[ (pos -> RealVector3D[ 91, 27, 64 ]), (strength -> 2) ] -:: - -METHOD:: editObstacle -Edit an obstacle at an index. - -ARGUMENT:: index -The index of the obstacle one wishes to edit. If this is nil, nothing happens and a warning is thrown. - -ARGUMENT:: pos -The position of the obstacle. If this is nil, the position remains the same. - -ARGUMENT:: repulsion -The gravity of the obstacle. If this is nil, the gravity remains the same. - -RETURNS:: A Boids3D - -METHOD:: removeObstacle -Remove a obstacle. - -ARGUMENT:: index -The index of the obstacle in the list of obstacles to remove. If index is nil, remove the last obstacle in the list. - -METHOD:: clearObstacles -Clear the list of obstacles. - -RETURNS:: a Boids3D - - - - - - - - - - - - - - -SUBSECTION:: Attributes of the flock - -METHOD:: centerOfMass -The position of the center of mass of the flock. - -RETURNS:: A RealVector3D - -METHOD:: boids -The array of BoidUnits; i.e. an array of the agents in the flock. - -RETURNS:: An Array - -METHOD:: getPanVals -A convenience method that returns an array of three-element arrays of values useful for use as a panner in the form [dist, azi, angle] where EMPHASIS::dist:: is the distance from the origin in meters, EMPHASIS::angle:: is the angle about the origin in radians, and EMPHASIS::azi:: is the elevation in radians with the horizon at CODE::x=0::. - -Also see BoidUnit3D.getPanVals. - -RETURNS:: An Array - -WARNING:: For math reasons, positive motion is EMPHASIS::counterclockwise:: while negative motion is EMPHASIS::clockwise::.:: - -METHOD:: sizeOfFlock -A convenience method to return the size of the flock. Identical to numBoids(). - -RETURNS:: an Integer - -METHOD:: numBoids -The number of members in the flock. Identical to sizeOfFlock(). - -RETURNS:: An Integer - -METHOD:: visualizer -Show a visualization of the flock. The agents are shown as wedges with the point being the position and the angle being the angle of the velocity. Targets are shown as blue circles and obstacles as red circles. - -ARGUMENT:: showLabels -A Boolean, defaults to false. If true, the index of the agent/target/obstacle is positioned along with the wedge or circle. - -ARGUMENT:: returnWindow -A Boolean, defaults to false. If true, return the Window instead of the instance. - -RETURNS:: A Boids3D or a Window. - -METHOD:: info -Print some basic info about the flock. Will probably be removed in the future. - -code:: -// f.info prints -Boid Info:::: - numBoids: 20 - timestep: 0.2 s - centerInstinct: 0 - innerDistance: 1 - matchVelocity: 1 - maxVelocity: 5 m/s - minSpace: 1 m -:: - -RETURNS:: A Boids3D - - - - -EXAMPLES:: - -code:: -f = Boids3D.new(20, 0.2); // a new flock -f.maxVelocity = 150; // speed it up -x = {|flock| - flock.boids[0].pos.postln; // print the position of the first boid -}; - -r = fork { - loop { - f.moveFlock(x); // move the flock and evaluate x each time - 0.2.wait; // wait a bit (timestep) - }; -}; - -f.visualizer(false); // a visualizer without the labels - -f.addTarget([0,0,0], 1); // add a target at the origin with a gravity of 1 -f.addObstacle([100,-100,150], 2); // add an obstacle at (100,-100) with a repulsion of 2 -f.innerDistance = 5; // make them like each other less -f.matchVelocity = 0; // no velocity matching - -r.stop; // stop the loop and thus, stop the flock - -/* - Job 1:21 - And said, Naked came I out of my mother's womb, and naked shall I return thither: - the LORD gave, and the LORD hath taken away; blessed be the name of the LORD. -*/ -f.addBoid; // add a boid -f.removeBoid; // remove a boid -:: - - -PRIVATE:: prFillBoidList, prCalcVec, prArcTan, prArcTan2, prInverseSquare, prDoRules, boidList diff --git a/HelpSource/Guides/Boids2D_spatializerGuide.schelp b/HelpSource/Guides/Boids2D_spatializerGuide.schelp deleted file mode 100644 index 1315dfd..0000000 --- a/HelpSource/Guides/Boids2D_spatializerGuide.schelp +++ /dev/null @@ -1,124 +0,0 @@ -TITLE:: Guide To Using Boids2D as a Spatializer -summary:: Tips and tricks to use Boids2D as a sound spatializer. -related:: Classes/BoidUnit3D, Classes/Boids2D, Classes/BoidUnit2D - -SECTION:: Using Boids2D to spatialize sound. - -One thing you'll notice that the Boids classes are EMPHASIS::generalized:: such that they do not include dedicated mechanisms for spatialzing sound (except for the getPanVals() method). So you must roll your own. - -CODE:: -b = Boids2D(20, 0.1); // 20 "birds" (boids), a timestep of 0.1 seconds. The timestep is used to calculate movement -b.bounds = [1000, 1000]; // the bounds in meters [x, y]. 1000 works well (yes, 1km! I have to fix that...) -b.maxVelocity = 150; // max velocity in meters per second -b.minSpace = 20; // the minimum amount of space the Boids want between each other in meters - -// we can also add targets and obstacles. Adding a target at the origin works pretty well and makes sure they boids don't stray too far. -b.addTarget([0,0], 1); // args: Cartesian coordinates, gravity -b.targets; // list the targets -// edit the target -b.editTarget(0, target: [10,10], gravity: 1.5); // the index of the target you want to edit in .targets, the new coordinates (optional), and the new strength (optional). - - -// then the three rules -b.centerInstinct = 1; // the weight of the center seeking rule -b.innerDistance = 1; // the weight of the inner distance rule -b.matchVelocity = 1; // the weight of the match velocity rule - -// the above tend to work well when you have their center instinct negative (they don't want the center of the flock) or a high inner distance (want to keep distance) but also have a strong target. So that they're attracted to the target but want to stay away from each other. That tends to make things interesting. - -r = Routine({ - loop { - b.moveFlock({|thisFlock| - var boids, thisBoid, panVals; - // moveFlock takes a function which is passed the instance of Boids2D - // you can access the BoidUnits (the "birds") by getting them with the .boids method - boids = thisFlock.boids; // the array of the "birds", in this case, 20 of them - thisBoid = boids[0]; // look at just the first one in the list. Order of the list has virtually no meaning. - panVals = thisBoid.getPanVals; // get the values that are useful for panning: angle (in radians) and magnitude (distance from origin in meters). - panVals.postln; // print out the values - // thisBoid.vel.norm.postln; // you can also get the velocity in m/s by taking the norm of the velocity vector (Doppler, if it's important) - - // note that the class works from the unit circle so that 0 radians is directly to the right and positive motion is counterclockwise - // to use them in a panner, you'd just send them to a Synth with the proper values - // synth.set(\angle, panVals[0], \dist, panVals[1]); // send them to a synth. Do math here or there. - - // further note that boids that are very close to the center will pan erratically; when I do it I use PanAz and scale the width so that if the boid is - // close to the center, it starts sending to more and more speakers so it would sound like it's moving through the space instead of jumping across. - // like this: - // width = dist.linlin(10,100,num,width); // scale width with distance where num is the number of speakers one is using and width is the max width that is set elsewhere - }); - 0.1.wait; // wait a time equal to the timestep - }; -}).play; - -b.visualizer; // let's see it. Black wedges are the boids, red circles are the targets. - -r.stop; // stop the loop (you can also resume by just calling again) -b.free; // free the flock (clears up stuff, stops any routines) -:: - -SECTION:: Other Cool Stuff - -Since one passes a function to the moveFlock() method, one can do all sorts of things there. Here, we have three targets and several random obstacles. The function in moveFlock() moves the targets and adjusts their gravity such that when no agents are close by (attacking, one can imagine), their gravity grows. When agents are nearby, the strength begins to drop. This oscillation of gravity along with obstacles in the field makes for really interesting movement. - -CODE:: -var flock, targets, func; - -flock = Boids2D(30, 0.1); // a new flock -targets = (); // an Event to make life easy - -// set some stuff -flock.centerInstinct = 0; -flock.innerDistance = 2; -flock.matchVelocity = 0.5; -flock.minSpace = 20; -flock.maxVelocity = 100; - -// add some random obstacles -5.do{ - flock.addObstacle(Array.fill(2, {rrand(-350,350)}), rrand(2.0, 5.0)); -}; - -// get some targets -targets.startingSize = flock.targets.size; // remember how big our list is now -3.do{flock.addTarget([0,0], 1)}; // make some new targets -targets.targets = flock.targets[targets.startingSize..]; // get only our new targets -targets.targets = 3.collect{|i| - var index = i+targets.startingSize; - [flock.targets[index], index]; // save the target and the index (pain in the ass...) This way we only edit these targets in case there are others -}; -targets.mags = Array.fill(3, {Pbrown(50, 300, 5, inf).asStream}); // get some streams for magnitudes -targets.angles = Array.fill(3, {rand(2pi)}); // get some angles -targets.points = 3.collect{|i| Polar(targets.angles[i], targets.mags[i].next).asPoint}; // points as polar coordinates is easier - -func = {|flock| - targets.targets.do{|array, i| - var target = array[0], index = array[1]; - flock.boids.do{|boid| - var dist; - dist = boid.pos.dist(target.at(\pos)); // get the distance - if(dist < 20) { - flock.editTarget(index, gravity: target.at(\strength)-0.07); - }; - }; - targets.points[i] = Polar(targets.mags[i].next, targets.angles[i]).asPoint; // get a new Point - flock.editTarget(index, [targets.points[i].x, targets.points[i].y], gravity: target.at(\strength)+rand(0.08)); // edit it - if(i%2==0) { - targets.angles[i] = targets.angles[i]+(rand(pi/180)); // edit it again - } { - targets.angles[i] = targets.angles[i]-(rand(pi/180)); // edit once more - }; - }; -}; - -r = fork { - loop { - flock.moveFlock(func); // move the flock - flock.timestep.wait; // wait the timestep - }; -}; - -flock.visualizer; // look at it -// flock.timestep = 0.5; // slow motion! - -:: diff --git a/classes/Automator.sc b/classes/Automator.sc deleted file mode 100644 index ceb4030..0000000 --- a/classes/Automator.sc +++ /dev/null @@ -1,345 +0,0 @@ -Automator { - var floppedNodes, <>normalizedNodes, name; - var window, envView, key, playRoutine, 0) && (view.index != (view.value[0].size-1))) { - // remove the values at the index in normalizedNodes - 2.do{|i| - normalizedNodes[i].removeAt(view.index); - }; - this.prUpdateView; - }; - - // on esc, despect all (doesn't work???) - if (unicode == 27) { - defer {view.selectIndex(-1)}; - }; - }); - - envView.keyUpAction_({ - key = -1; // set it so that nothing happens when we release any key - }); - ///////////////////////////////////////////////////////// - - window - .view.deleteOnClose_(false); // don't destory when we close (but must do so manually) - } - - // show the GUI and allow editing - show {|str| - if (str.notNil) {window.name = str.asString}; // give it a name - window.front; - } - - getEnv { - ^Env(floppedNodes[1], floppedNodes[0].differentiate[1..], curve); - } - - // a function to evaluate when the envelope view is edited - envViewAction { - // set our variables in this instance every time we edit - normalizedNodes = envView.value; - floppedNodes = this.unnormalize; - nodes = floppedNodes.flop; - this.prUpdateView; // update the GUI - - // if we select the first or last node, don't allow the x position to be adjusted!! <----------------------------------------------- - } - - // digest the nodes into a form that's passable to an EnvelopeView - normalize {|nodes, max| - var normalized, min, diff, range; - normalized = 2.collect{|i| - if (i==1) { - if(max.notNil) { - min = nodes[i].minItem; - nodes[i].normalize(0, abs(min-nodes[i].maxItem)/abs(min-max)); // normalize for our range (don't move nodes) - } { - nodes[i].normalize; // otherwise normalize and move the nodes - }; - } { - nodes[i].sort.normalize; // normalize the time domain - }; - }; - ^normalized; - } - - // go the other way (pass in normalized?) - unnormalize { - var tmp = Array.newClear(2); - tmp[0] = envView.value[0]*totalTime; - tmp[1] = (envView.value[1]*abs(minVal-maxVal)) + minVal; - ^tmp; - } - - // if a view exists, change the display to account for the new info - prUpdateView { - var labels, step; - if (envView.notNil) { - defer { - envView.value_(normalizedNodes); - labels = nodes.collect{|coordinate, i| - "(%, %)".format(coordinate[0].round(0.001), coordinate[1].round(0.001)); // make a string with the correctly formatted label - }; - envView.strings_(labels); // set it - envView.curves = curve; // reset the curve, as well - env = this.getEnv; // get the new Envelope - }; - }; - } - - free { - this.stop; - if(window.notNil) { - window.close; // close and destory the window containing the EnvelopeView manually - }; - } - - // save the nodes to a file - save {|path| - var dict = (), archive; // an empty dictionary - if(path.notNil) { - archive = ZArchive.write(path); // make a new ZArchive - dict.nodes = nodes; // save the nodes - dict.curve = curve; // save the curve - archive.writeItem(dict); // write the entire dictionary - archive.writeClose; // close the file - } { - ^"Path must not be nil!".error; - }; - } - - // load nodes from a file - loadOldArchive {|path| - var dict; - if(path.notNil) { - dict = Object.readArchive(path); // read it - nodes = dict.nodes; // set the values - curve = dict.curve; // set the values - - ////////////////////////////////////////////// - // not exactly sure why we need to do this here but otherwise the array is created that starts with a bunch of 0's - normalizedNodes = envView.value; - floppedNodes = this.unnormalize; - nodes = floppedNodes.flop; - env = this.getEnv; - ////////////////////////////////////////////// - } { - ^"Path must not be nil!".error; - }; - } - - /////////////////////////////// - // special setter methods - /////////////////////////////// - nodes_ {|nodes| - // set nodes and normalize - nodes = nodes; - floppedNodes = nodes.flop; - normalizedNodes = this.normalize(floppedNodes); - totalTime = floppedNodes[0].maxItem; - maxVal = floppedNodes[1].maxItem; - } - - // change the total time, keeping the proportions - totalTime_ {|time| - totalTime = time; - // floppedNodes = [normalizedNodes[0]*totalTime, normalizedNodes[1]*maxVal]; // adjust the times - floppedNodes[0] = normalizedNodes[0]*totalTime; - nodes = floppedNodes.flop; // set the nodes - this.prUpdateView; // update the node labels - } - - maxVal_ {|val| - this.setMaxVal(val, false); // don't adjust nodes in the vertical by default. If you want to, use setMaxVal(val, true) - } - - // reset the max value and scale everything accordingly - setMaxVal {|val, adjustNodes = false| - maxVal = val; // set the maximum value - // use different methods depending on if we want to adjust nodes - if(adjustNodes) { - normalizedNodes = this.normalize(floppedNodes); - } { - normalizedNodes = this.normalize(floppedNodes, maxVal); - }; - this.prUpdateView; // update the node labels - } - - curve_ {|curveVal| - curve = curveVal; - if (envView.notNil) { - defer { - envView.curves = curve; // reset it - this.prUpdateView; - }; - }; - } - -} diff --git a/classes/Boids2D.sc b/classes/Boids2D.sc deleted file mode 100644 index 64fe937..0000000 --- a/classes/Boids2D.sc +++ /dev/null @@ -1,546 +0,0 @@ -/////////////////////////////////////////// -/// Boids class -/////////////////////////////////////////// -/////////////////////////////////////////// -/* - Usage: - - b = Boids(numBoids: 10); // an initial number of boids. Flock size can be arbitrarily increased by using addBoid() - f = {|thisInstance| - thisInstace.info; // print info about this flock - boids.postln; // the list of BoidUnits - }; - b.moveFlock(f); -*/ - - -Boids2D { - var boidList, thatBoid.pos, \vel->thatBoid.vel, \dist->dist]; // store in a dictionary - - //////// RULE 1 //////////////////////////////////// - // nothing else to do! - - //////// RULE 2 //////////////////////////////////// - // if the absolute value of the distance is less than the threshold - if (dist < thisBoid.instincts.at(\minSpace)) { - vec = vec + ((thisBoid.pos-thatBoid.pos)*(thisBoid.instincts.at(\minSpace)/(dist**2))); // calculate the difference vector - count = count+1; // keep counting the boids in the vicinity - }; - - //////// RULE 3 //////////////////////////////////// - // nothing else to do! - - nIdx = nIdx + 1; // increment the index - }; - }; - - // for rules 1 and 3 - neighbors.sortBy(\dist); // sort neighbors by distance - nearestNeighbors = 6.collect{|j| - // get the 6 nearest neighbors, regardless of distance - [neighbors[j].at(\pos), neighbors[j].at(\vel)]; - }; - nearestNeighbors = nearestNeighbors.flop; // [positions, velocities] - - //////// RULE 1 //////////////////////////////////// - // for the six nearest, get their average positions and velocities - posAvg = nearestNeighbors[0].sum/6; // sum and divide - thisBoid.centerOfMass = thisBoid.instincts.at(\centerInstinct) * posAvg * 0.01; // set it for rule 1 - - //////// RULE 2 //////////////////////////////////// - vec = vec/count; // average the vector - thisBoid.innerDistance = thisBoid.instincts.at(\innerDistance) * vec; // set the innerDistance vector in each BoidUnit - - //////// RULE 3 //////////////////////////////////// - velAvg = nearestNeighbors[1].sum/6; - thisBoid.matchVelocity = thisBoid.instincts.at(\matchVelocity) * velAvg; // send one eigth of the magnitude - }; - - centerOfMass = posSum/numBoids; // get the center of mass - centerOfVel = velSum/numBoids; // get the avgerage velocity - } - - prFillBoidList {|num| - num.do{ - var boid; - boid = BoidUnit2D.rand(bounds, centerInstinct, innerDistance, matchVelocity, gauss(workingMaxVelocity, workingMaxVelocity*0.1)); - boidList.add(boid); // add it to the list - }; - } - - addBoid {|initPos| - var boid, initVel; - initPos = initPos ? centerOfMass; // place it near the center of the flock - initVel = centerOfVel; // give it the average velocity of the flock - boid = BoidUnit2D.new(initVel, initPos, bounds, centerOfMass, gauss(workingMaxVelocity, workingMaxVelocity*0.1)); - boidList.add(boid); // add it - numBoids = numBoids + 1; // increment numBoids - - // set the instinct attributes - boid.centerInstinct = gauss(centerInstinct, centerInstinct*0.05); - boid.innerDistance = gauss(innerDistance, innerDistance*0.05); - boid.matchVelocity = gauss(matchVelocity, matchVelocity*0.05); - } - - removeBoid {|index| - if (index.isNil) { - boidList.pop; // if no arg, remove the last BoidUnit - } { - boidList.removeAt(index); // else, remove at the index - }; - numBoids = numBoids - 1; // decrement numBoids - } - - sizeOfFlock { - ^boidList.size; // return the size of the flock - } - - moveFlock {|func| - this.prDoRules; // do them all with two loops - - // move AFTER we've calculated what will happen - boidList.do{|boid| - boid.moveBoid(targets, obstacles); // tell the boid to calculate and move it's position - }; - func.(this); // evaluate the function while passing this instance - } - - // calculate the new values but don't send them to the BoidUnits - calcFlock {|func| - this.prDoRules; // do all rules - func.(this); // evaluate the function while passing this instance - } - - getPanVals { - ^boidList.collect{|boid| - boid.getPanVals; // get the pan values - }; - } - - // creates a rectangle of size [dim] - bounds_ {|dim| - var rect, xLen, yLen; - if(dim.isArray) { - if(dim.size==2) { - // we're good - xLen = dim[0]; - yLen = dim[1]; - }; - } { - "dim must be a two element array".error; // else something is wrong - ^nil; // return nill - }; - - // create the bounds of a rectangle with the given dimensions with the origin at the center - rect = [[-0.5*xLen, 0.5*xLen], [-0.5*yLen, 0.5*yLen]]; - bounds = rect; // set the new bounds - // set the bounds in each BoidUnit - boidList.do{|boid| - boid.bounds = bounds; // set it in each Boid - }; - } - - /////////////////////////////////// - // targeting - /////////////////////////////////// - addTarget {|pos, gravity| - if(pos.isNil or: gravity.isNil) - {"Insuffient arguments: %, %: no target was added!".format(pos, gravity).warn; ^this}; - targets.add(Dictionary.with(*[\pos->RealVector2D.newFrom(pos[..1]), \strength->gravity])); - } - - clearTargets { - targets.clear; // clear the list - } - - removeTarget {|index| - if(index.isNil) { - targets.pop; // remove the last index - } { - targets.removeAt(index); // remove at the index - }; - } - - editTarget {|index, pos, gravity| - if(index.isNil) {"Index is nil: no targets were edited!".warn}; // throw a warning if insufficent args were supplied - if(pos.notNil) {targets[index].add(\pos->RealVector2D.newFrom(pos[..1]))}; // should check here if target is a Vector or not - if(gravity.notNil) {targets[index].add(\strength->gravity)}; // edit the gravity parameter - } - - ///////////////////////////////////////// - ///// obstacles - ////////////////////////////////////////// - addObstacle {|pos, repulsion| - if(pos.isNil or: repulsion.isNil) - {"Insuffient arguments: %, %: no obstacle was added!".format(pos, repulsion).warn; ^this}; - obstacles.add(Dictionary.with(*[\pos->RealVector2D.newFrom(pos[..1]), \strength->repulsion])); - } - - clearObstacles { - obstacles.clear; // clear the list - } - - removeObstacle {|index| - if(index.isNil) { - obstacles.pop; // remove the last index - } { - obstacles.removeAt(index); // remove at the index - }; - } - - editObstacle {|index, pos, repulsion| - if(index.isNil) {"Index is nil: no obstacles were edited!".warn}; // throw a warning if insufficent args were supplied - if(pos.notNil) {obstacles[index].add(\pos->RealVector2D.newFrom(pos[..1]))}; // should check here if target is a Vector or not - if(repulsion.notNil) {obstacles[index].add(\strength->repulsion)}; // edit the repulsion parameter - } - - // print the variable information for this flock - info { - var str = "Boid Info::::\n"; - str = str ++ "\tnumBoids: %\n".format(numBoids); - str = str ++ "\ttimestep: % s\n".format(timestep); - str = str ++ "\tcenterInstinct: %\n".format(centerInstinct); - str = str ++ "\tinnerDistance: %\n".format(innerDistance); - str = str ++ "\tmatchVelocity: %\n".format(matchVelocity); - str = str ++ "\tmaxVelocity: % m/s\n".format(maxVelocity); - str = str ++ "\tminSpace: % m\n".format(minSpace); - str.postln; // print it - } - - ///////////////////////////////// - // custom setter methods - ///////////////////////////////// - maxVelocity_ {|val| - maxVelocity = val; // maxVelocity is the maximum length of the velocity vector - workingMaxVelocity = maxVelocity * timestep; - boidList.do{|boid| - boid.maxVelocity = gauss(workingMaxVelocity, workingMaxVelocity*0.1); // set it in each individual boid - }; - } - - minSpace_ {|val| - minSpace = val; - boidList.do{|boid| - boid.instincts.add(\minSpace->gauss(minSpace, minSpace*0.05)); - }; - } - - wrap_ {|boolean| - wrap = boolean; - boidList.do(_.wrap_(boolean)); - } - - centerInstinct_ {|val| - centerInstinct = val; // set it here (the average) - boidList.do{|boid| - boid.instincts.add(\centerInstinct->gauss(val, val*0.1)); - }; - } - - innerDistance_ {|val| - innerDistance = val; // set it here (the average) - boidList.do{|boid| - boid.instincts.add(\innerDistance->gauss(val, val*0.1)); - }; - } - - matchVelocity_ {|val| - matchVelocity = val; // set it here (the average) - boidList.do{|boid| - boid.instincts.add(\matchVelocity->gauss(val, val*0.1)); - }; - } - - // to ensure we return this instance if setting - timestep_ {|time| - timestep = time; - } - - // visualizer - visualizer {|showLabels = false, returnWindow = false| - var window, loop, availableBounds, size, getNormalizedPos, makeCircle, makeLabel; - availableBounds = Window.availableBounds; - size = availableBounds.width/3; - window = Window("Flock Visualizer", Rect(availableBounds.width-size,availableBounds.height-size,size,size)).front; - window.view.background_(Color.white); - - // functions - getNormalizedPos = {|pos| - [(pos.x+bounds[0][0].abs)/(bounds[0][0].abs*2), 1 - ((pos.y+bounds[1][0].abs)/(bounds[1][0].abs*2))]; - }; - - makeCircle = {|normalizedPos| - Pen.addOval(Rect(window.bounds.width*normalizedPos[0], window.bounds.height*normalizedPos[1], 5, 5)); - }; - - makeLabel = {|label, normalizedPos, color| - Pen.stringAtPoint(label.asString, Point(window.bounds.width*normalizedPos[0] + 3, window.bounds.height*normalizedPos[1] + 3), color: color); - }; - - // draw the boids - window.drawFunc = { - // plot the boids as black wedges - boidList.do{|boid, i| - var normalizedPos, color; - color = Color.black; - Pen.color = color; - normalizedPos = getNormalizedPos.(boid.pos); // normalize the position for the window - Pen.addWedge( - Point(window.bounds.width*normalizedPos[0], window.bounds.height*normalizedPos[1]), // point - 7.5, // radius (pixels) - (-1*boid.vel.theta) - 3.5342917352885, // start angle (angle - sizeOfAngle/2 (pi/8) - pi) for visualizer corrections - 0.78539816339745 // size of angle (pi/4) - ); - if(showLabels) { - makeLabel.(i, normalizedPos, color); // make a label - }; - Pen.perform(\fill); - }; - - // plot the targets as blue circles - targets.do{|target, i| - var normalizedPos, color; - color = Color.fromHexString("4989FF"); - Pen.color = color; - normalizedPos = getNormalizedPos.(target.at(\pos)); // normalize the position for the window - makeCircle.(normalizedPos); - if(showLabels) {makeLabel.(i, normalizedPos, color)}; // make a label - Pen.perform(\fill); - }; - - // plot the obstacles as red circles - obstacles.do{|obstacle, i| - var normalizedPos, color; - color = Color.fromHexString("FF4949"); - Pen.color = color; - normalizedPos = getNormalizedPos.(obstacle.at(\pos)); // normalize the position for the window - makeCircle.(normalizedPos); - if(showLabels) {makeLabel.(i, normalizedPos, color)}; // make a label - Pen.perform(\fill); - }; - }; - - loop = { - loop {window.refresh; timestep.wait}; - }.fork(AppClock); - - window.onClose_({loop.stop}); - if(returnWindow) {^window}; - } - - ///////////////////////////// - // custom getter methods - ///////////////////////////// - boidList { - ^boidList.asArray; - } - - boids { - ^boidList.asArray; - } - - targets { - ^targets.asArray; - } - - obstacles { - ^obstacles.asArray; - } - -} - -//////////////////////////////////////////////////////////// -// not directly used but rather used by Boids -//////////////////////////////////////////////////////////// -BoidUnit2D { - var <>vel, <>pos, centerInstinct, <>innerDistance, <>matchVelocity, <>wrap, <>instincts; - - *new {|vel, pos, bounds, centerOfMass, maxVelocity = 5| - ^super.newCopyArgs(vel, pos, bounds, centerOfMass, maxVelocity).init; - } - - *rand {|bounds, centerOfMass, innerDistance, matchVelocity, maxVelocity = 5| - ^super.new.init(bounds, centerOfMass, innerDistance, matchVelocity, maxVelocity); - } - - init {|...args| - bounds = bounds ? args[0] ? [[-500,500],[-500,500]]; // [ [xmin, xmax], [ymin, ymax]] - vel = vel ? RealVector2D.newFrom(Array.fill(2, {rrand(0.0,3.0)})); - pos = pos ? RealVector.rand(2,-1*bounds[0][0], bounds[0][0]).asRealVector2D; - maxVelocity = maxVelocity ? args[4] ? 5; // max velocity - - // if these are not set, set them - centerOfMass = args[2] ? RealVector.rand(2,-10,10).asRealVector2D; - innerDistance = args[3] ? RealVector.rand(2,-10,10).asRealVector2D; - matchVelocity = args[4] ? RealVector.rand(2,-10,10).asRealVector2D; - - instincts = Dictionary[\centerInstinct->gauss(1, 0.1), \innerDistance->gauss(1, 0.1), \matchVelocity->gauss(1, 0.1), \minSpace->gauss(10,0.5)]; // individualized weights - centerInstinct = centerOfMass/100; // set this here - vel = vel.limit(maxVelocity); // limit the size of the velocity vector - wrap = wrap ? false; // default to no wrapping - } - - prBound { - var vec = RealVector2D.zero; // a zero vector - 2.do{|i| - var amount; - if(pos[i] < bounds[i][0]) { - amount = bounds[i][0] + pos[i].abs; // how far off are we - vec[i] = amount; // change zero for this - } { - if(pos[i] > bounds[i][1]) { - amount = bounds[i][1] - pos[i]; // how far off are we - vec[i] = amount; // change zero for this - }; - }; - }; - vel = vel + vec; // add the vectors in velocity-space - } - - // wrap coordinates - prWrap { - 2.do{|i| - if(pos[i] < bounds[i][0]) { - pos[i] = bounds[i].abs.sum + pos[i]; - } { - if(pos[i] > bounds[i][1]) { - pos[i] = pos[i] - bounds[i].abs.sum; - }; - }; - }; - } - - moveBoid {|targets, obstacles| - if (targets.isEmpty.not) {vel = vel + this.calcTargets(targets)}; // if there are targets, calculate the vector - if (obstacles.isEmpty.not) {vel = vel + this.calcObstacles(obstacles)}; // if there are obstacles, calculate the vector - vel = vel + centerInstinct + innerDistance + matchVelocity; // sum the vectors and get a new velocity - if(wrap) {this.prWrap} {this.prBound}; // wrap or bound - vel = vel.limit(maxVelocity); // speed limit - pos = pos + vel; // get the new position - } - - getPanVals { - var zero = RealVector2D.zero; - ^[pos.theta, pos.dist(zero)]; // return the angle in radians and the distance from the origin - } - - calcObstacles {|obstacles| - var vec = RealVector2D.zero, distFromTarget, gravity, diff; - obstacles.do{|obstacle| - vec = this.prCalcVec(obstacle, vec, \obstacle); - }; - ^vec; // return the vector - } - - calcTargets {|targets| - var vec = RealVector2D.zero, distFromTarget, gravity, diff; - targets.do{|target| - vec = this.prCalcVec(target, vec, \target); - }; - ^vec; // return the vector - } - - prCalcVec {|object, vec, type| - var distFromTarget, diff, gravity; - distFromTarget = pos.dist(object.at(\pos)).max(0.001); // get the distance from the object - switch (type) - {\target} { - diff = object.at(\pos)-pos; // get the diff - gravity = ((object.at(\strength)*100)/distFromTarget).max(0); // 1/r - } - {\obstacle} { - diff = pos-object.at(\pos); // get the diff - gravity = this.prInverseSquare(distFromTarget, object.at(\strength)*1000).max(0); // 1/r^2 - }; - ^vec + ((diff/diff.norm)*gravity); // return - } - - // get the distance between two boids - dist {|boid2D| - ^pos.dist(boid2D.pos); - } - - ///////////////////////////////// - // gravity/repulsion scaling functions - ///////////////////////////////// - prInverseSquare {|dist = 1, gravity = 1| - ^gravity/(dist**2); - } - - prArcTan {|dist = 1, gravity = 1, scalar = 10| - gravity = gravity.reciprocal*scalar; - dist = (dist*gravity)-gravity; - gravity = atan(-1*dist); - ^(gravity/3)+0.5; - } - - prArcTan2 {|dist = 1, gravity = 1, scalar = 5| - // scalar is where the arctangent function passes through 0 normally - dist = (dist-(gravity*scalar)); - gravity = atan(-1*dist*gravity.reciprocal); - ^(gravity/3)+0.5; - } - - ///////////////////////////////// - // custom setter methods - ///////////////////////////////// - centerOfMass_ {|vec| - centerOfMass = vec; // get the perceived center of mass for this BoidUnit - // each time we get a new center of mass, recalculate the first vector offset - centerInstinct = centerOfMass/100; // get the vector that moves it 1% toward the center of the flock (this can be weighted??) - } - - maxVelocity_ {|val| - maxVelocity = val; // set it - vel = vel.limit(maxVelocity); // limit it if it's bigger - } - - bounds_ {|val| - bounds = val; - } -} diff --git a/classes/Boids3D.sc b/classes/Boids3D.sc deleted file mode 100644 index 8b865a0..0000000 --- a/classes/Boids3D.sc +++ /dev/null @@ -1,552 +0,0 @@ -/////////////////////////////////////////// -/// Boids class -/////////////////////////////////////////// -/////////////////////////////////////////// -/* - Usage: - - b = Boids(numBoids: 10); // an initial number of boids. Flock size can be arbitrarily increased by using addBoid() - f = {|thisInstance| - thisInstace.info; // print info about this flock - boids.postln; // the list of BoidUnits - }; - b.moveFlock(f); -*/ - - -Boids3D { - var timestep, boidList, thatBoid.pos, \vel->thatBoid.vel, \dist->dist]; // store in a dictionary - - //////// RULE 1 //////////////////////////////////// - // nothing else to do! - - //////// RULE 2 //////////////////////////////////// - // if the absolute value of the distance is less than the threshold - if (dist < thisBoid.instincts.at(\minSpace)) { - vec = vec + ((thisBoid.pos-thatBoid.pos)*(thisBoid.instincts.at(\minSpace)/(dist**2))); // calculate the difference vector - count = count+1; // keep counting the boids in the vicinity - }; - - //////// RULE 3 //////////////////////////////////// - // nothing else to do! - - nIdx = nIdx + 1; // increment the index - }; - }; - - // for rules 1 and 3 - neighbors.sortBy(\dist); // sort neighbors by distance - nearestNeighbors = 6.collect{|j| - // get the 6 nearest neighbors, regardless of distance - [neighbors[j].at(\pos), neighbors[j].at(\vel)]; - }; - nearestNeighbors = nearestNeighbors.flop; // [positions, velocities] - - //////// RULE 1 //////////////////////////////////// - // for the six nearest, get their average positions and velocities - posAvg = nearestNeighbors[0].sum/6; // sum and divide - thisBoid.centerOfMass = thisBoid.instincts.at(\centerInstinct) * posAvg * 0.01; // set it for rule 1 - - //////// RULE 2 //////////////////////////////////// - vec = vec/count; // average the vector - thisBoid.innerDistance = thisBoid.instincts.at(\innerDistance) * vec; // set the innerDistance vector in each BoidUnit - - //////// RULE 3 //////////////////////////////////// - velAvg = nearestNeighbors[1].sum/6; - thisBoid.matchVelocity = thisBoid.instincts.at(\matchVelocity) * velAvg; // send one eigth of the magnitude - }; - - centerOfMass = posSum/numBoids; - centerOfVel = velSum/numBoids; - } - - prFillBoidList {|num| - num.do{ - var boid; - boid = BoidUnit3D.rand(bounds, centerInstinct, innerDistance, matchVelocity, gauss(workingMaxVelocity, workingMaxVelocity*0.1)); - boidList.add(boid); // add it to the list - }; - } - - addBoid {|initPos| - var boid, initVel; - initPos = initPos ? centerOfMass; // place it near the center of the flock - initVel = centerOfVel; // the average velocity - boid = BoidUnit3D.new(initVel, initPos, bounds, centerOfMass, gauss(workingMaxVelocity, workingMaxVelocity*0.1)); - boidList.add(boid); // add it - numBoids = numBoids + 1; // increment numBoids - - // set the instinct attributes - boid.centerInstinct = gauss(centerInstinct, centerInstinct*0.05); - boid.innerDistance = gauss(innerDistance, innerDistance*0.05); - boid.matchVelocity = gauss(matchVelocity, matchVelocity*0.05); - } - - removeBoid {|index| - if (index.isNil) { - boidList.pop; // if no arg, remove the last BoidUnit - } { - boidList.removeAt(index); // else, remove at the index - }; - numBoids = numBoids - 1; // decrement numBoids - } - - sizeOfFlock { - ^boidList.size; // return the size of the flock - } - - moveFlock {|func| - // this.prGetCenterOfMass; // rule 1 - // this.prGetInnerDistance; // rule 2 - // this.prGetVelocityMatch; // rule 3 - this.prDoRules; // do them all with two loops - - // move AFTER we've calculated what will happen - boidList.do{|boid| - boid.moveBoid(targets, obstacles); // tell the boid to calculate and move it's position - }; - func.(this); // evaluate the function while passing this instance - } - - // calculate the new values but don't send them to the BoidUnits - calcFlock {|func| - // this.prGetCenterOfMass; // rule 1 - // this.prGetInnerDistance; // rule 2 - // this.prGetVelocityMatch; // rule 3 - this.prDoRules; // do all rules - func.(this); // evaluate the function while passing this instance - } - - getPanVals { - ^boidList.collect{|boid| - boid.getPanVals; // get the pan values - }; - } - - // creates a rectangle of size [dim] - bounds_ {|dim| - var rect, xLen, yLen, zLen; - if(dim.isArray) { - if(dim.size==3) { - // we're good - xLen = dim[0]; - yLen = dim[1]; - zLen = dim[2]; - }; - } { - "dim must be a three element array".error; // else something is wrong - ^nil; // return nill - }; - - // create the bounds of a rectangle with the given dimensions with the origin at the center - rect = [[-0.5*xLen, 0.5*xLen], [-0.5*yLen, 0.5*yLen], [-0.5*zLen, 0.5*zLen]]; - bounds = rect; // set the new bounds - // set the bounds in each BoidUnit - boidList.do{|boid| - boid.bounds = bounds; // set it in each Boid - }; - } - - /////////////////////////////////// - // targeting - /////////////////////////////////// - addTarget {|pos, gravity| - if(pos.isNil or: gravity.isNil) - {"Insuffient arguments: %, %: no target was added!".format(pos, gravity).warn; ^this}; - targets.add(Dictionary.with(*[\pos->RealVector3D.newFrom(pos[..2]), \strength->gravity])); - } - - clearTargets { - targets.clear; // clear the list - } - - removeTarget {|index| - if(index.isNil) { - targets.pop; // remove the last index - } { - targets.removeAt(index); // remove at the index - }; - } - - editTarget {|index, pos, gravity| - if(index.isNil) {"Index is nil: no targets were edited!".warn}; // throw a warning if insufficent args were supplied - if(pos.notNil) {targets[index].add(\pos->RealVector3D.newFrom(pos[..2]))}; // should check here if target is a Vector or not - if(gravity.notNil) {targets[index].add(\strength->gravity)}; // edit the gravity parameter - } - - ///////////////////////////////////////// - ///// obstacles - ////////////////////////////////////////// - addObstacle {|pos, repulsion| - if(pos.isNil or: repulsion.isNil) - {"Insuffient arguments: %, %: no obstacle was added!".format(pos, repulsion).warn; ^this}; - obstacles.add(Dictionary.with(*[\pos->RealVector3D.newFrom(pos[..2]), \strength->repulsion])); - } - - clearObstacles { - obstacles.clear; // clear the list - } - - removeObstacle {|index| - if(index.isNil) { - obstacles.pop; // remove the last index - } { - obstacles.removeAt(index); // remove at the index - }; - } - - editObstacle {|index, pos, repulsion| - if(index.isNil) {"Index is nil: no obstacles were edited!".warn}; // throw a warning if insufficent args were supplied - if(pos.notNil) {obstacles[index].add(\pos->RealVector3D.newFrom(pos[..2]))}; // should check here if target is a Vector or not - if(repulsion.notNil) {obstacles[index].add(\strength->repulsion)}; // edit the repulsion parameter - } - - // print the variable information for this flock - info { - var str = "Boid Info::::\n"; - str = str ++ "\tnumBoids: %\n".format(numBoids); - str = str ++ "\ttimestep: % s\n".format(timestep); - str = str ++ "\tcenterInstinct: %\n".format(centerInstinct); - str = str ++ "\tinnerDistance: %\n".format(innerDistance); - str = str ++ "\tmatchVelocity: %\n".format(matchVelocity); - str = str ++ "\tmaxVelocity: % m/s\n".format(maxVelocity); - str = str ++ "\tminSpace: % m\n".format(minSpace); - str.postln; // print it - } - - ///////////////////////////////// - // custom setter methods - ///////////////////////////////// - maxVelocity_ {|val| - maxVelocity = val; // maxVelocity is the maximum length of the velocity vector - workingMaxVelocity = maxVelocity * timestep; - boidList.do{|boid| - boid.maxVelocity = gauss(workingMaxVelocity, workingMaxVelocity*0.1); // set it in each individual boid - }; - } - - minSpace_ {|val| - minSpace = val; - boidList.do(_.instincts.add(\minSpace->gauss(minSpace, minSpace*0.05))); - } - - wrap_ {|boolean| - wrap = boolean; - boidList.do(_.wrap_(boolean)); - } - - centerInstinct_ {|val| - centerInstinct = val; // set it here (the average) - boidList.do{|boid| - boid.instincts.add(\centerInstinct->gauss(val, val*0.1)); - }; - } - - innerDistance_ {|val| - innerDistance = val; // set it here (the average) - boidList.do{|boid| - boid.instincts.add(\innerDistance->gauss(val, val*0.1)); - }; - } - - matchVelocity_ {|val| - matchVelocity = val; // set it here (the average) - boidList.do{|boid| - boid.instincts.add(\matchVelocity->gauss(val, val*0.1)); - }; - } - - // visualizer - visualizer {|whichDimensions = #[0,1], showLabels = false, returnWindow = false| - var window, loop, availableBounds, size, getNormalizedPos, makeCircle, makeLabel, plotX, plotY; - availableBounds = Window.availableBounds; - size = availableBounds.width/3; - window = Window("Dimensions: % : %".format(whichDimensions[0], whichDimensions[1]), Rect(availableBounds.width-size,availableBounds.height-size,size,size)).front; - window.view.background_(Color.white); - - // get the dimensions - plotX = whichDimensions[0]; - plotY = whichDimensions[1]; - - // functions - getNormalizedPos = {|pos| - [(pos.x+bounds[0][0].abs)/(bounds[0][0].abs*2), 1 - ((pos.y+bounds[1][0].abs)/(bounds[1][0].abs*2))]; - }; - - makeCircle = {|normalizedPos| - Pen.addOval(Rect(window.bounds.width*normalizedPos[0], window.bounds.height*normalizedPos[1], 5, 5)); - }; - - makeLabel = {|label, normalizedPos, color| - Pen.stringAtPoint(label.asString, Point(window.bounds.width*normalizedPos[0] + 3, window.bounds.height*normalizedPos[1] + 3), color: color); - }; - - // draw the boids - window.drawFunc = { - // plot the boids as black wedges - boidList.do{|boid, i| - var normalizedPos, color; - color = Color.black; - Pen.color = color; - normalizedPos = getNormalizedPos.(boid.pos[plotX..plotY]); - Pen.addWedge( - Point(window.bounds.width*normalizedPos[0], window.bounds.height*normalizedPos[1]), // point - 7.5, // radius (pixels) - (-1*atan2(boid.vel[plotY], boid.vel[plotX])) - 3.5342917352885, // start angle (angle - pi/8 - pi) for visualizer corrections - 0.78539816339745 // size of angle (pi/4) - ); - if(showLabels) { - makeLabel.(i, normalizedPos, color); - }; - Pen.perform(\fill); - }; - - // plot the targets as blue circles - targets.do{|target, i| - var normalizedPos, color; - color = Color.fromHexString("4989FF"); - Pen.color = color; - normalizedPos = getNormalizedPos.(target.at(\pos)[plotX..plotY]); - makeCircle.(normalizedPos); // make the circle - if(showLabels) {makeLabel.(i, normalizedPos, color)}; - Pen.perform(\fill); - }; - - //////// - // plot the obstacles as red circles - //////// - obstacles.do{|obstacle, i| - var normalizedPos, color; - color = Color.fromHexString("FF4949"); - Pen.color = color; - normalizedPos = getNormalizedPos.(obstacle.at(\pos)[plotX..plotY]); - makeCircle.(normalizedPos); // make the cirlce - if(showLabels) {makeLabel.(i, normalizedPos, color)}; - Pen.perform(\fill); - }; - }; - - loop = { - loop {window.refresh; timestep.wait}; - }.fork(AppClock); - - window.onClose_({loop.stop}); - if(returnWindow) {^window}; - } - - ///////////////////////////// - // custom getter methods - ///////////////////////////// - boidList { - ^boidList.asArray; - } - - boids { - ^boidList.asArray; - } - - targets { - ^targets.asArray; - } - - obstacles { - ^obstacles.asArray; - } - -} - -//////////////////////////////////////////////////////////// -// not directly used but rather used by Boids -//////////////////////////////////////////////////////////// -BoidUnit3D { - var <>vel, <>pos, centerInstinct, <>innerDistance, <>matchVelocity, <>wrap, <>instincts; - - *new {|vel, pos, bounds, centerOfMass, maxVelocity = 5| - ^super.newCopyArgs(vel, pos, bounds, centerOfMass, maxVelocity).init; - } - - *rand {|bounds, centerOfMass, innerDistance, matchVelocity, maxVelocity = 5| - ^super.new.init(bounds, centerOfMass, innerDistance, matchVelocity, maxVelocity); - } - - init {|...args| - bounds = bounds ? args[0] ? [[-500,500],[-500,500],[-500,500]]; // [ [xmin, xmax], [ymin, ymax]] - vel = vel ? RealVector3D.newFrom(Array.fill(3, {rrand(0.0,3.0)})); - pos = pos ? RealVector.rand(3,-1*bounds[0][0], bounds[0][0]).asRealVector3D; - maxVelocity = maxVelocity ? args[4] ? 100; // max velocity - - // if these are not set, set them - centerOfMass = args[2] ? RealVector.rand(3,-10,10).asRealVector3D; - innerDistance = args[3] ? RealVector.rand(3,-10,10).asRealVector3D; - matchVelocity = args[4] ? RealVector.rand(3,-10,10).asRealVector3D; - - instincts = Dictionary[\centerInstinct->gauss(1, 0.1), \innerDistance->gauss(1, 0.1), \matchVelocity->gauss(1, 0.1), \minSpace->gauss(10,0.5)]; // individualized weights - centerInstinct = centerOfMass/100; // set this here - vel = vel.limit(maxVelocity); // limit the size of the velocity vector - wrap = wrap ? false; // default to no wrapping - } - - prBound { - var vec = RealVector3D.zero; // a zero vector - 3.do{|i| - var amount; - if(pos[i] < bounds[i][0]) { - amount = bounds[i][0] + pos[i].abs; // how far off are we - vec[i] = amount; // change zero for this - } { - if(pos[i] > bounds[i][1]) { - amount = bounds[i][1] - pos[i]; // how far off are we - vec[i] = amount; // change zero for this - }; - }; - }; - vel = vel + vec; // add the vectors in velocity-space - } - - // wrap coordinates - prWrap { - 3.do{|i| - if(pos[i] < bounds[i][0]) { - pos[i] = bounds[i].abs.sum + pos[i]; - } { - if(pos[i] > bounds[i][1]) { - pos[i] = pos[i] - bounds[i].abs.sum; - }; - }; - }; - } - - moveBoid {|targets, obstacles| - if (targets.isEmpty.not) {vel = vel + this.calcTargets(targets)}; // if there are targets, calculate the vector - if (obstacles.isEmpty.not) {vel = vel + this.calcObstacles(obstacles)}; // if there are obstacles, calculate the vector - vel = vel + centerInstinct + innerDistance + matchVelocity; // sum the vectors and get a new velocity - if(wrap) {this.prWrap} {this.prBound}; // wrap or bound - vel = vel.limit(maxVelocity); // speed limit - pos = pos + vel; // get the new position - } - - getPanVals { - var zero = RealVector3D.zero; - ^[pos.theta, pos.azi, pos.dist(zero)]; // return the angle in radians and the distance from the origin - } - - calcObstacles {|obstacles| - var vec = RealVector3D.zero, distFromTarget, gravity, diff; - obstacles.do{|obstacle| - vec = this.prCalcVec(obstacle, vec, \obstacle); - }; - ^vec; // return the vector - } - - calcTargets {|targets| - var vec = RealVector3D.zero, distFromTarget, gravity, diff; - targets.do{|target| - vec = this.prCalcVec(target, vec, \target); - }; - ^vec; // return the vector - } - - prCalcVec {|object, vec, type| - var distFromTarget, diff, gravity; - distFromTarget = pos.dist(object.at(\pos)).max(0.001); // get the distance from the object - switch (type) - {\target} { - diff = object.at(\pos)-pos; // get the diff - gravity = ((object.at(\strength)*100)/distFromTarget).max(0); // 1/r - } - {\obstacle} { - diff = pos-object.at(\pos); // get the diff - gravity = this.prInverseSquare(distFromTarget, object.at(\strength)*1000).max(0); // 1/r^2 - }; - ^vec + ((diff/diff.norm)*gravity); // return - } - - // get the distance between two boids - dist {|boid3D| - ^pos.dist(boid3D.pos); - } - - ///////////////////////////////// - // gravity/repulsion scaling functions - ///////////////////////////////// - prInverseSquare {|dist = 1, gravity = 1| - ^gravity/(dist**2); - } - - prArcTan {|dist = 1, gravity = 1, scalar = 10| - gravity = gravity.reciprocal*scalar; - dist = (dist*gravity)-gravity; - gravity = atan(-1*dist); - ^(gravity/3)+0.5; - } - - prArcTan2 {|dist = 1, gravity = 1, scalar = 5| - // scalar is where the arctangent function passes through 0 normally - dist = (dist-(gravity*scalar)); - gravity = atan(-1*dist*gravity.reciprocal); - ^(gravity/3)+0.5; - } - - ///////////////////////////////// - // custom setter methods - ///////////////////////////////// - centerOfMass_ {|vec| - centerOfMass = vec; // get the perceived center of mass for this BoidUnit - // each time we get a new center of mass, recalculate the first vector offset - centerInstinct = centerOfMass/100; // get the vector that moves it 1% toward the center of the flock (this can be weighted??) - } - - maxVelocity_ {|val| - maxVelocity = val; // set it - vel = vel.limit(maxVelocity); // limit it if it's bigger - } - - bounds_ {|val| - bounds = val; - } -}