PD: I thought about getting ridd of the for loop, and use anemone to trigger the component. I’d have to pass a custom class out of the component and feed it back in, but and after doing some research I found the process a bit intimidating, but if it’s the way to go I’ll do it.
private void RunScript(List<Point3d> agLoc, double radius, int iterations, ref object A)
{
List<Charge> charges = new List<Charge>();
List<Agent> agents = new List<Agent>();
List<Agent> agentsTemp = new List<Agent>();
DataTree<Point3d> trails = new DataTree<Point3d>();
foreach(Point3d pt in agLoc)
agents.Add(new Agent(new Vector3d(pt), Vector3d.ZAxis, radius));
for(int step = 0; step < iterations; step++){
foreach(Agent ag in agents){
agentsTemp.Clear();
agentsTemp = new List<Agent>(agents);
agentsTemp.Remove(ag);
charges.Clear();
foreach(Agent agT in agentsTemp)
charges.AddRange(agT.charges);
Vector3d force = ag.getForce(charges);
ag.move(force);
}
}
int t = 0;
foreach(Agent ag in agents){
GH_Path path = new GH_Path(t);
trails.AddRange(ag.history, path);
t++;
}
A = trails;
}
// <Custom additional code>
public class Agent{
public Vector3d pos;
public Vector3d dir;
public List<Point3d> history = new List<Point3d>();
public List<Charge> charges = new List<Charge>();
int trailLen = 10;
double radius = 10;
public Agent (Vector3d _pos, Vector3d _dir, double _radius){
pos = _pos;
dir = _dir;
radius = _radius;
}
public void move(Vector3d force){
//añadir locaciones a historia
this.history.Add(new Point3d(this.pos));
if (this.history.Count > trailLen)
this.history.RemoveAt(0);
//añadir cargas de historia
this.charges.Add(new Charge(new Point3d(this.pos), 1));
if (this.charges.Count > trailLen)
this.charges.RemoveAt(0);
foreach(Charge ch in this.charges)
ch.val = Math.Sqrt(ch.val);
force.Unitize();
force *= 0.5;
this.dir.Unitize();
this.dir = dir + force;
this.pos = Vector3d.Add(this.pos, this.dir);
}
public Vector3d getForce(List<Charge> charges){
Vector3d sum = Vector3d.Zero;
foreach(Charge charge in charges){
Vector3d dir = Vector3d.Subtract(new Vector3d(charge.loc), this.pos);
double distance = new Point3d(this.pos).DistanceTo(charge.loc);
if (distance > radius)
continue;
Interval itv = new Interval(radius, 0);
double normalized = itv.NormalizedParameterAt(distance);
normalized *= normalized;
sum = Vector3d.Add(sum, dir * normalized);
}
return sum;
}
}
public class Charge{
public Point3d loc;
public double val;
public Charge (Point3d _loc, double _val){
loc = _loc;
val = _val;
}
}
**EDIT: Added code
With: List<Agent> agents = new List<Agent>();
you are deleting/replacing the whole list, and then also with: for(int step = 0; step < iterations; step++){
you are making it recalculate all the iterations… both happens everytime the c# component is executed!
You should use public lists like: public List<Agent> agents;
and do: agents = new List<Agent>();
when you really want so, not all the times.
(For ex, if agents==null, it means you never initialized that list, so you should do that. Or if agents.Count is different from agLoc.Count it means something changed. both cases should trigger a new list creation… otherwise, use old and already existing lists!)
Also with for cycle, you should start from last iteration done last time, like. for(int step=old_iter;step<iterations;step++){
and have a: public int old_iter;
Thank you! What you say makes a lot of sense.
When I try to change the List to public List I get a lot of errors.
Do I have to declare them somewhere else?
Is there a way to know if any input has changed and trigger a ‘reset’ event?
I kindof did it as you suggested within the first if statement, but I’m not sure it’s the best way to go about it.
private void RunScript(List<Point3d> agLoc, double radius, int iterations, bool reset, ref object A)
{
if(reset == true || iterations < oldStep){
Reset();
}
int step;
if(agents == null){
agents = new List<Agent>();
foreach(Point3d pt in agLoc)
agents.Add(new Agent(new Vector3d(pt), Vector3d.ZAxis, radius));
}
for(step = oldStep; step < iterations; step++){
foreach(Agent ag in agents){
if(agentsTemp != null)
agentsTemp.Clear();
agentsTemp = new List<Agent>(agents);
agentsTemp.Remove(ag);
if(charges != null)
charges.Clear();
charges = new List<Charge>();
foreach(Agent agT in agentsTemp)
charges.AddRange(agT.charges);
Vector3d force = ag.getForce(charges);
ag.move(force);
}
}
oldStep = step;
if(trails == null)
trails = new DataTree<Point3d>();
trails.Clear();
int t = 0;
foreach(Agent ag in agents){
GH_Path path = new GH_Path(t);
trails.AddRange(ag.history, path);
t++;
}
A = trails;
}
// <Custom additional code>
public List<Charge> charges;
public List<Agent> agents;
public List<Agent> agentsTemp;
public DataTree<Point3d> trails;
public int oldStep = 0;
public void Reset(){
charges = null;
agents = null;
agentsTemp = null;
trails = null;
oldStep = 0;
this.GrasshopperDocument.ExpireSolution();
return;
}
public class Agent{
public Vector3d pos;
public Vector3d dir;
public List<Point3d> history = new List<Point3d>();
public List<Charge> charges = new List<Charge>();
int trailLen = 10;
double radius = 10;
public Agent (Vector3d _pos, Vector3d _dir, double _radius){
pos = _pos;
dir = _dir;
radius = _radius;
}
public void move(Vector3d force){
//añadir locaciones a historia
this.history.Add(new Point3d(this.pos));
if (this.history.Count > trailLen)
this.history.RemoveAt(0);
//añadir cargas de historia
this.charges.Add(new Charge(new Point3d(this.pos), 1));
if (this.charges.Count > trailLen)
this.charges.RemoveAt(0);
foreach(Charge ch in this.charges)
ch.val = Math.Sqrt(ch.val);
force.Unitize();
force *= 0.5;
this.dir.Unitize();
this.dir = dir + force;
this.pos = Vector3d.Add(this.pos, this.dir);
}
public Vector3d getForce(List<Charge> charges){
Vector3d sum = Vector3d.Zero;
foreach(Charge charge in charges){
Vector3d dir = Vector3d.Subtract(new Vector3d(charge.loc), this.pos);
double distance = new Point3d(this.pos).DistanceTo(charge.loc);
if (distance > radius)
continue;
Interval itv = new Interval(radius, 0);
double normalized = itv.NormalizedParameterAt(distance);
normalized *= normalized;
sum = Vector3d.Add(sum, dir * normalized);
}
return sum;
}
}
public class Charge{
public Point3d loc;
public double val;
public Charge (Point3d _loc, double _val){
loc = _loc;
val = _val;
}
}
Well, just think about the default behavior. The ‘Runscript’ is called on any recomputation. Your Agent object is a singleton instance unaffected from that. Now, whenever the ‘Runscript’ is called, you basically just call a method in your Agent which deals with that change. It could check if any field in the object has a different value.
As an alternative you could also make properties out of your fields and override the setter which only invoke a re-computation if the value is different:
private int _myVal = -1;
public int MyVal
{
get {return _myVal;}
set
{
// If it is different...
if (value != _myVal)
{
_myVal= value;
// ... then do something special
DoSomething();
}
}
}
// Inside Runscript you just call
agent.MyVal= 42;
A third alternative is using ‘Events’, although this is a bit of an overkill
Edit:
Some more advices:
Check your code style. It’s not wrong like this, but you are mixing underscore notation which is problematic. Underscore variables are usually used as private/internal class fields. It indicates being ‘class global’. You are doing the opposite, which is really confusing…
Refactor your code. Use more methods. Encapsulate better. It is really hard to follow what you are trying to do. Use comments on demand.
‘Charge’ is a very small data structure and should rather be a ‘struct’ not a ‘class’. You can also use a Point4d.