Alright, so I’m trying to map boids on a surface to make a flocking behavior on a surface for my new plugin
(based on long Nguyen’s workshop)
My approach was to convert positions to UV parameters by remapping x,y values of points in 2d to surface domain in U and V.
thanks.
here is the code
using System;
using System.Collections.Generic;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
namespace SurfaceTrails2.FlockingMapToSurface
{
public class GhcFlockingSimulation : GH_Component
{
private FlockSystem flockSystem;
public GhcFlockingSimulation()
: base(
"Flocking Map To Surface",
"FlockingMapToSurface",
"Flocking Map To Surface",
"YFAtools",
"AgentBased")
{
}
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddBooleanParameter("Reset", "Reset", "Reset", GH_ParamAccess.item, false);
pManager.AddBooleanParameter("Play", "Play", "Play", GH_ParamAccess.item, false);
pManager.AddBooleanParameter("3D", "3D", "3D", GH_ParamAccess.item, true);
pManager.AddSurfaceParameter("srf", "srf", "srf", GH_ParamAccess.item);
pManager.AddIntegerParameter("Count", "Count", "Number of Agents", GH_ParamAccess.item, 50);
pManager.AddNumberParameter("Timestep", "Timestep", "Timestep", GH_ParamAccess.item, 0.02);
pManager.AddNumberParameter("Neighbourhood Radius", "Neighbourhood Radius", "Neighbourhood Radius", GH_ParamAccess.item, 3.5);
pManager.AddNumberParameter("Alignment", "Alignment", "Alignment", GH_ParamAccess.item, 0.5);
pManager.AddNumberParameter("Cohesion", "Cohesion", "Cohesion", GH_ParamAccess.item, 0.5);
pManager.AddNumberParameter("Separation", "Separation", "Separation", GH_ParamAccess.item, 0.5);
pManager.AddNumberParameter("Separation Distance", "Separation Distance", "Separation Distance", GH_ParamAccess.item, 1.5);
pManager.AddCircleParameter("Repellers", "Repellers", "Repellers", GH_ParamAccess.list);
pManager[10].Optional = true;
pManager.AddBooleanParameter("Use Parallel", "Use Parallel", "Use Parallel", GH_ParamAccess.item, false);
pManager.AddBooleanParameter("Use R-Tree", "Use R-Tree", "Use R-Tree", GH_ParamAccess.item, false);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
pManager.AddTextParameter("Info", "Info", "Information", GH_ParamAccess.item);
pManager.AddPointParameter("Positions", "Positions", "The agent positions", GH_ParamAccess.list);
pManager.AddVectorParameter("Velocities", "Velocities", "The agent veloctiies", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
// ===============================================================================================
// Read input parameters
// ===============================================================================================
bool iReset = true;
bool iPlay = false;
bool i3D = false;
Surface baseSurface = null;
int iCount = 0;
double iTimestep = 0.0;
double iNeighbourhoodRadius = 0.0;
double iAlignment = 0.0;
double iCohesion = 0.0;
double iSeparation = 0.0;
double iSeparationDistance = 0.0;
List<Circle> iRepellers = new List<Circle>();
bool iUseParallel = false;
bool iUseRTree = false;
DA.GetData("Reset", ref iReset);
DA.GetData("Play", ref iPlay);
DA.GetData("3D", ref i3D);
DA.GetData("srf", ref baseSurface);
DA.GetData("Count", ref iCount);
DA.GetData("Timestep", ref iTimestep);
DA.GetData("Neighbourhood Radius", ref iNeighbourhoodRadius);
DA.GetData("Alignment", ref iAlignment);
DA.GetData("Cohesion", ref iCohesion);
DA.GetData("Separation", ref iSeparation);
DA.GetData("Separation Distance", ref iSeparationDistance);
DA.GetDataList("Repellers", iRepellers);
DA.GetData("Use Parallel", ref iUseParallel);
DA.GetData("Use R-Tree", ref iUseRTree);
// ===============================================================================================
// Read input parameters
// ===============================================================================================
if (iReset || flockSystem == null)
{
flockSystem = new FlockSystem(iCount, i3D);
}
else
{
// ===============================================================================================
// Assign the input parameters to the corresponding variables in the "flockSystem" object
// ===============================================================================================
flockSystem.Timestep = iTimestep;
flockSystem.NeighbourhoodRadius = iNeighbourhoodRadius;
flockSystem.AlignmentStrength = iAlignment;
flockSystem.CohesionStrength = iCohesion;
flockSystem.SeparationStrength = iSeparation;
flockSystem.SeparationDistance = iSeparationDistance;
flockSystem.Repellers = iRepellers;
flockSystem.UseParallel = iUseParallel;
// ===============================================================================
// Update the flock
// ===============================================================================
if (iUseRTree)
flockSystem.UpdateUsingRTree();
else
flockSystem.Update();
if (iPlay) ExpireSolution(true);
}
// ===============================================================================
// Output the agent positions and velocities so we can see them on display
// ===============================================================================
List<GH_Point> positions = new List<GH_Point>();
List<GH_Vector> velocities = new List<GH_Vector>();
List<GH_Point> surfacePositions = new List<GH_Point>();
var surface = baseSurface.ToNurbsSurface();
foreach (FlockAgent agent in flockSystem.Agents)
{
positions.Add(new GH_Point(agent.Position));
velocities.Add(new GH_Vector(agent.Velocity));
// ===============================================================================
// Position on surface
// ===============================================================================
Interval u = new Interval(0, 1);
Interval v = new Interval(0, 1);
surface.SetDomain(0, u);
surface.SetDomain(1, v);
var remappedU = NumberOperations.remap(0,1,surface.Domain(0).T0,surface.Domain(0).T1,agent.Position.X);
var remappedV = NumberOperations.remap(0,1,surface.Domain(1).T0,surface.Domain(1).T1,agent.Position.Y);
surfacePositions.Add(new GH_Point(surface.PointAt(remappedU, remappedV)/*surface.PointAt(agent.Position.X, agent.Position.Y*/));
}
DA.SetDataList("Positions", surfacePositions);
DA.SetDataList("Velocities", velocities);
}
protected override System.Drawing.Bitmap Icon { get { return Properties.Resources._28_8_18_FlockSimulation; } }
public override Guid ComponentGuid { get { return new Guid("7066d2ac-3c0a-47db-b22f-4d8c25bf8baa"); } }
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Rhino.Geometry;
namespace SurfaceTrails2.FlockingMapToSurface
{
public class FlockSystem
{
public List Agents;
public double Timestep;
public double NeighbourhoodRadius;
public double AlignmentStrength;
public double CohesionStrength;
public double SeparationStrength;
public double SeparationDistance;
public List<Circle> Repellers;
public bool UseParallel;
public FlockSystem(int agentCount, bool is3D)
{
Agents = new List<FlockAgent>();
if (is3D)
for (int i = 0; i < agentCount; i++)
{
FlockAgent agent = new FlockAgent(
Util.GetRandomPoint(0.0, 30.0, 0.0, 30.0, 0.0, 30.0),
Util.GetRandomUnitVector() * 4.0);
agent.FlockSystem = this;
Agents.Add(agent);
}
else
for (int i = 0; i < agentCount; i++)
{
FlockAgent agent = new FlockAgent(
Util.GetRandomPoint(0.0, 30.0, 0.0, 30.0, 0.0, 0.0),
Util.GetRandomUnitVectorXY() * 4.0);
agent.FlockSystem = this;
Agents.Add(agent);
}
}
private List<FlockAgent> FindNeighbours(FlockAgent agent)
{
List<FlockAgent> neighbours = new List<FlockAgent>();
foreach (FlockAgent neighbour in Agents)
if (neighbour != agent && neighbour.Position.DistanceTo(agent.Position) < NeighbourhoodRadius)
neighbours.Add(neighbour);
return neighbours;
}
private void ComputeAgentDesiredVelocity(FlockAgent agent)
{
List<FlockAgent> neighbours = FindNeighbours(agent);
agent.ComputeDesiredVelocity(neighbours);
}
public void Update()
{
if (UseParallel)
Parallel.ForEach(Agents, ComputeAgentDesiredVelocity);
else
foreach (FlockAgent agent in Agents)
ComputeAgentDesiredVelocity(agent);
// Once the desired velocity for each agent has been computed, we update each position and velocity
foreach (FlockAgent agent in Agents)
agent.UpdateVelocityAndPosition();
}
public void UpdateUsingRTree()
{
/* First, build the R-Tree */
RTree rTree = new RTree();
for (int i = 0; i < Agents.Count; i++)
rTree.Insert(Agents[i].Position, i);
/* Then, we use the R-Tree to find the neighbours
and compute the desired velocity */
foreach (FlockAgent agent in Agents)
{
List<FlockAgent> neighbours = new List<FlockAgent>();
EventHandler<RTreeEventArgs> rTreeCallback =
(object sender, RTreeEventArgs args) =>
{
if (Agents[args.Id] != agent)
neighbours.Add(Agents[args.Id]);
};
rTree.Search(new Sphere(agent.Position, NeighbourhoodRadius), rTreeCallback);
agent.ComputeDesiredVelocity(neighbours);
}
// Once the desired velocity for each agent has been computed, we can update each position and velocity
foreach (FlockAgent agent in Agents) agent.UpdateVelocityAndPosition();
}
}
}
using System.Collections.Generic;
using Rhino.Geometry;
namespace SurfaceTrails2.FlockingMapToSurface
{
public class FlockAgent
{
public Point3d Position;
public Vector3d Velocity;
public FlockSystem FlockSystem;
private Vector3d desiredVelocity;
private double boundingBoxSize = 1;
public double BoundingBox
{
get { return boundingBoxSize;} set { boundingBoxSize=value; } }
public FlockAgent(Point3d position, Vector3d velocity)
{
Position = position;
Velocity = velocity;
}
public void UpdateVelocityAndPosition()
{
Velocity = 0.97 * Velocity + 0.03 * desiredVelocity;
if (Velocity.Length > 8.0) Velocity *= 8.0 / Velocity.Length;
else if (Velocity.Length < 4.0) Velocity *= 4.0 / Velocity.Length;
Position += Velocity * FlockSystem.Timestep;
}
public void ComputeDesiredVelocity(List<FlockAgent> neighbours)
{
// First, reset the desired velocity to 0
desiredVelocity = new Vector3d(0.0, 0.0, 0.0);
// ===============================================================================
// Pull the agent back if it gets out of the bounding box
// ===============================================================================
if (Position.X < 0.0)
desiredVelocity += new Vector3d(-Position.X, 0.0, 0.0);
else if (Position.X > boundingBoxSize)
desiredVelocity += new Vector3d(boundingBoxSize - Position.X, 0.0, 0.0);
if (Position.Y < 0.0)
desiredVelocity += new Vector3d(0.0, -Position.Y, 0.0);
else if (Position.Y > boundingBoxSize)
desiredVelocity += new Vector3d(0.0, boundingBoxSize - Position.Y, 0.0);
if (Position.Z < 0.0)
desiredVelocity += new Vector3d(0.0, 0.0, -Position.Z);
else if (Position.Z > boundingBoxSize)
desiredVelocity += new Vector3d(0.0, 0.0, boundingBoxSize - Position.Z);
// ===============================================================================
// If there are no neighbours nearby, the agent will maintain its veloctiy,
// else it will perform the "alignment", "cohension" and "separation" behaviours
// ===============================================================================
if (neighbours.Count == 0)
desiredVelocity += Velocity; // maintain the current velocity
else
{
// -------------------------------------------------------------------------------
// "Alignment" behavior
// -------------------------------------------------------------------------------
Vector3d alignment = Vector3d.Zero;
foreach (FlockAgent neighbour in neighbours)
alignment += neighbour.Velocity;
// We divide by the number of neighbours to actually get their average velocity
alignment /= neighbours.Count;
desiredVelocity += FlockSystem.AlignmentStrength * alignment;
// -------------------------------------------------------------------------------
// "Cohesion" behavior
// -------------------------------------------------------------------------------
Point3d centre = Point3d.Origin;
foreach (FlockAgent neighbour in neighbours)
centre += neighbour.Position;
// We divide by the number of neighbours to actually get their centre of mass
centre /= neighbours.Count;
Vector3d cohesion = centre - Position;
desiredVelocity += FlockSystem.CohesionStrength * cohesion;
// -------------------------------------------------------------------------------
// "Separation" behavior
// -------------------------------------------------------------------------------
Vector3d separation = Vector3d.Zero;
foreach (FlockAgent neighbour in neighbours)
{
double distanceToNeighbour = Position.DistanceTo(neighbour.Position);
if (distanceToNeighbour < FlockSystem.SeparationDistance)
{
Vector3d getAway = Position - neighbour.Position;
/* We scale the getAway vector by inverse of distanceToNeighbour to make
the getAway vector bigger as the agent gets closer to its neighbour */
separation += getAway / (getAway.Length * distanceToNeighbour);
}
}
desiredVelocity += FlockSystem.SeparationStrength * separation;
}
// ===============================================================================
// Avoiding the obstacles (repellers)
// ===============================================================================
foreach (Circle repeller in FlockSystem.Repellers)
{
double distanceToRepeller = Position.DistanceTo(repeller.Center);
Vector3d repulsion = Position - repeller.Center;
// Repulstion gets stronger as the agent gets closer to the repeller
repulsion /= (repulsion.Length * distanceToRepeller);
// Repulsion strength is also proportional to the radius of the repeller circle/sphere
// This allows the user to tweak the repulsion strength by tweaking the radius
repulsion *= 30.0 * repeller.Radius;
desiredVelocity += repulsion;
}
}
}
}