Tutorials (back to the list of tutorials)

## Field Operation (requires iGeo version 8.1.0 or higher)

### Simple Field: Gravity and Attractor

Forces applied to particles like gravity, an attractor and a repulsion force can been seen as a type of force fields. A force field defines a 3D vector force on every location in 3D space. Gravity provides a same vector force everywhere no matter where a particle is located.

An attractor and a repulsion force defines a 3D vector force towards the position of attractor/repulsion from the position of a particle.

Besides creating gravity and a attractor in previous example codes, iGeo library has a class of gravity, IGravity and a class of attractor IAttractor as a type of generic fields. The example below shows a code to use IGravity class. You can specify the gravity vector by x, y and z in the constructor. This code also use a type of a particle class IParticleTrajectory, which creates trajectory lines of the particle.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);
IG.duration(50);
new IGravity(0,0,-10); // direction and intensity of gravity

for(int i=0; i < 100; i++){
new IParticleTrajectory(IRand.pt(-100,100));// random points from (-100,-100,-100) to (100,100,100)
}
```

The code below is an example of IAttractor. You can specify the position of attractor in the constructor and the intensity of the attractor by intensity(double) method. If you put negative number in the intensity, it provides a repulsion force instead of an attraction force.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);
IG.duration(100);
new IAttractor(0,0,0).intensity(10); // attractor at (0,0,0) with intensity 10

for(int i=0; i < 100; i++){
new IParticleTrajectory(IRand.pt(-100,100));// random points from (-100,-100,-100) to (100,100,100)
}
```

### Visualizing Field

It's typical to describe forces as fields in some physics such as electromagnetism. Mathematically a field is defined as a function which takes a position (3D vector) in space as an input and returns a force vector as an output.

Because the output vectors can vary at different positions, you'd want to sample the output at many positions to comprehend the whole field. iGeo library has a class of IFieldVisualizer to have a 3D matrix of sampled vectors to visualize fields. To use IFieldVisualizer, you specify x, y, z coordinates of two corners for a 3D matrix to sample vectors. As default it creates 10x10x10 matrix within the specified bounding box.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IAttractor(-50,0,0).intensity(10).clr(1.0,0,0);//attractor
new IAttractor(50,0,0).intensity(-10).clr(0,0,1.0);//repulsion

// creating 3D matrix. default is 10x10x10.
// input argument is (minX, minY, minZ, maxX, maxY, maxZ)
new IFieldVisualizer(-100,-100,-100,100,100,100);
```

You can show only 2D vector field by putting same z coordinates for the bounding box and specifying sample number in z to be 1.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IAttractor(-50,0,0).intensity(10).clr(1.0,0,0);//attractor
new IAttractor(50,0,0).intensity(-10).clr(0,0,1.0);//repulsion

// last 3 inputs are sample number in x, y, z.
new IFieldVisualizer(-100, -50, 0, 100, 50, 0, 40, 20, 1);
```

You can also change min/max colors to show the intensity by the method colorRange(min red, min green, min blue, max red, max green, max blue). Although the length of sampled vector is all same as default, you can change it to be proportional to the intensity of the field by putting false to the method fixLength(boolean).

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IAttractor(-50,0,0).intensity(10).clr(1.0,0,0);//attractor
new IAttractor(50,0,0).intensity(-10).clr(0,0,1.0);//repulsion

IFieldVisualizer visualizer =
new IFieldVisualizer(-100, -50, 0, 100, 50, 0, 40, 20, 1);

// color of min/max intensty. inputs are (min red, green, blue, max red, green, blue)
visualizer.colorRange(0.0,0.0,0.0, 0.0,1.0,0.);

// use variable length; now length is proportioanl to the intensity
visualizer.fixLength(false);
```

### Decay of Field from Defining Geometry

Some types of fields are defined based on geometry such as a point (like an attractor), curve and surface (described later). This type of fields can have control of intensity based on the distance from the geometry as decay. The field classes in iGeo library provides three types of decay.
• No decay.
• Linear decay.
• Gaussian decay.

No decay provides constant intensity. This is a default decay type of most of fields class in the library. You can also specify this type by noDecay() method.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IAttractor(0,0,0).noDecay();

new IFieldVisualizer(-100,-100,0,100,100,0,20,20,1).fixLength(false);
```

The second decay type is linear decay. This type is specified with a parameter of threshold. When the distance from the geometry is equal to the threshold, the intensity becomes zero. You can specify this type by linearDecay(threshold). There is also an alias of this method linear(threshold).

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IAttractor(0,0,0).linearDecay(100);

new IFieldVisualizer(-100,-100,0,100,100,0,20,20,1).fixLength(false);
```

The third decay type is Gaussian decay. This type is specified with a parameter of threshold as well. The intensity follows the Gaussian function and the threshold is equal to double of the standard deviation. Even when the distance is larger than the threshold, the intensity doesn't become zero but gradually gets closer to zero. You can specify this type by gaussianDecay(threshold). There is also an alias of this method gaussian(threshold).

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IAttractor(0,0,0).gaussianDecay(100);

new IFieldVisualizer(-100,-100,0,100,100,0,20,20,1).fixLength(false);
```

### Field Defined by Points

Attactor is one of a point based field. Another point based field is IPointCurlField which creates curling force field around the point and axial direction. This field is defined by a position vector and axial direction vector. A force applied to a particle is a cross vector of the axis and a vector from the particle to the position of the field.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

new IPointCurlField(new IVec(0,0,0), new IVec(0,0,1)).gaussianDecay(100);

new IFieldVisualizer(-100,-100,0,100,100,0,20,20,1).fixLength(false);
```

### Field Defined by Curves (Attractor, Tangent, Curl)

You can use a curve as the source geometry of a field. Three different types of fields defined by a curve are shown below.
• Curve attractor field.
• Curve tangent field.
• Curve curl field.

First, a curve attractor field attracts a particle to the closest point on the curve. The class in the library is ICurveAttractorField. You put one curve as an input to build the curve attractor. The curve attractor field is visualized below.

The sample input file used below is this file.
curve_field1.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.open("curve_field1.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveAttractorField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

The code below shows the response of particles to the curve attractor fields with trajectory lines.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field1.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveAttractorField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

The second curve field is a curve tangent field. This field defines a force towards the direction of a tangent vector of the curve at the closest point from the given particle position. The class of this type of field is ICurveTangentField. The field is visualized below.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.open("curve_field1.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveTangentField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

The below example is to show the response of particles to the field.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field1.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveTangentField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

The third curve field is a curve curl field. This defines a force by a cross vector of a tangent vector on the curve and the difference vector from the closest point on the curve to the given position of a particle. As result, the force is always perpendicular to the tangent and the particle shows a curling behavior. The class of this field is ICurveCurlField. The below shows visualization of the field.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.open("curve_field1.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveCurlField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,-1, 20,20,1, 40,40,2);
```

The example blow shows the response of particles to the field.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field1.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveCurlField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,-1, 20,20,1, 40,40,2);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

### Multiple Summed Fields

Some more examples blow show a case when multiple curve fields exist. When you simply instantiate multiple fields, an actual force applied to a particle is vector summation of all force vectors from each field. A different way to combine multiple fields is described in the next section of "Compound Field".

The sample input file of multiple curves used below is this file.
curve_field2.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.open("curve_field2.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveTangentField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field2.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveTangentField(IG.curve(i)).intensity(10).linear(50);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

Vector summation of multiple fields sometimes changes the behavior drastically especially when fields have no decay, because some local area responding to an adjacent source curve is equally influenced by other source curves in farther location. Another pair of examples below shows when you limit the influence by putting a smaller threshold.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.open("curve_field2.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveTangentField(IG.curve(i)).linear(5).intensity(10);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field2.3dm");

for(int i=0; i < IG.curveNum(); i++){
new ICurveTangentField(IG.curve(i)).linear(5).intensity(10);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

### Compound Field

Another way to apply multiple fields to particles is to use ICompoundField. Instead of summation of all fields, this compound field choose the closest field measuring the distance from a particle to a source geometry of each field if the field has source geometry (some types of field don't have a source geometry, such as gravity). A compound field tends to show distinct boundaries between areas where different fields are applied. On the other hand, simple summation of multiple fields tends to smooth out boundaries.

The code below is a example of a compound field. To add a field as a member of compound field, you use add( field ).

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);
IG.open("curve_field2.3dm");

ICompoundField field = new ICompoundField();
for(int i=0; i < IG.curveNum(); i++){
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field2.3dm");

ICompoundField field = new ICompoundField();
for(int i=0; i < IG.curveNum(); i++){
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

A gravity field doesn't have a source geometry as mentioned above because gravity generates a constant force everywhere. However, you can specify a point as a source geometry to create a compound field. If you feed a position vector and the gravity direction vector to the constructor IGravity(position, direction) you can provide a source geometry.

The code below shows an example of construction of a compound field out of multiple gravity fields.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

ICompoundField field = new ICompoundField();
for(int i=0; i < 30; i++){
IVec dir = IRand.pt(-1,-1,1,1);
IVec pos = IRand.pt(-20,-20,20,20);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();

ICompoundField field = new ICompoundField();
for(int i=0; i < 30; i++){
IVec dir = IRand.pt(-1,-1,1,1);
IVec pos = IRand.pt(-20,-20,20,20);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 2000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

Another example of a compound field is with multiple IPointCurlField.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();

ICompoundField field = new ICompoundField();
for(int i=0; i < 30; i++){
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 2000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

Or you can mix different types of fields together randomly with a probability switch IRand.pct(percent).

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();

ICompoundField field = new ICompoundField();
for(int i=0; i < 30; i++){
if(IRand.pct(40)){
}
else if(IRand.pct(50)){
}
else{
}
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 2000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

### Field Defined by Surfaces (Tangent, Normal, Slope)

A surface can be used as a source geometry of a field. Three different types of fields defined by a curve are shown below.
• Surface tangent field (in U or V direction).
• Surface normal field.
• Surface slope field (2D force).

The sample code below shows a surface tangent field of ISurfaceUTangentField. This field generates forces towards the U tangent vectors of the surface.

The sample surface file used below is this file.
surface_field1.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);
IG.open("surface_field1.3dm");

for(int i=0; i < IG.surfaceNum(); i++){
new ISurfaceUTangentField(IG.surface(i)).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,-2, 20,20,0, 20,20,2);
```

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("surface_field1.3dm");

for(int i=0; i < IG.surfaceNum(); i++){
new ISurfaceUTangentField(IG.surface(i)).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,-2, 20,20,0, 20,20,2);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,-5,20,20,0)).fric(0.1).clr(1.0,0.7);
}
```

The code below shows another surface tangent field in V direction using ISurfaceVTangentField class.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("surface_field1.3dm");

for(int i=0; i < IG.surfaceNum(); i++){
new ISurfaceVTangentField(IG.surface(i)).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,-2, 20,20,0, 20,20,2);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,-5,20,20,0)).fric(0.1).clr(1.0,0.7);
}
```

The next surface field is a surface normal field of ISurfaceNormalField, which generates forces in the direction of surface normal vectors.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("surface_field1.3dm");

for(int i=0; i < IG.surfaceNum(); i++){
new ISurfaceNormalField(IG.surface(i)).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,-2, 20,20,0, 20,20,2);

for(int i=0; i < 1000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,-5,20,20,0)).fric(0.1).clr(1.0,0.7);
}
```

Another surface field is a surface slope field of I2DSurfaceSlopeField, which generates 2D vector only in XY direction (no force in Z direction) by the slope of a surface. If the surface has a valley in Z direction, that part acts as attractor and a hill causes a repulsion force.

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.open("surface_field1.3dm");

for(int i=0; i < IG.surfaceNum(); i++){
new I2DSurfaceSlopeField(IG.surface(i)).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);
```

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("surface_field1.3dm");

for(int i=0; i < IG.surfaceNum(); i++){
new I2DSurfaceSlopeField(IG.surface(i)).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

for(int i=0; i < 2000; i++){
new IParticleTrajectory(IRand.pt(-20,-20,20,20)).fric(0.1).clr(1.0,0.7);
}
```

### Initialize Particles with Points

An initial condition of particles is an important factor for control of particle behavior. The example codes so far above initialize the location of particles by random numbers. Instead of random numbers, you can specify the location by imported point geometries from an external file. The code below imports a Rhino which contains curves for force fields and points for initial locations of particles. Those geometries are extracted by layers, named "field" and "particle" (ref: Import Geometry by Layers). A new particle is instantiated at the location of each point. Imported points are deleted after creating new particles because the particle itself has a point object inside.

The sample file of curves and points used below is this file.
curve_field_init1.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field_init1.3dm");

ICurve[] fieldCurves = IG.layer("field").curves();
for(int i=0; i < fieldCurves.length; i++){
new ICurveTangentField(fieldCurves[i]).linear(20).intensity(50);
}

IPoint[] points = IG.layer("particle").points();
for(int i=0; i < points.length; i++){
new IParticleTrajectory(points[i]).fric(0.2).clr(points[i].clr());
points[i].del();
}
```

The following is the initial location of points specified in the rhino file.

### Initialize Particles with Points on Curves

Initialization of particles can be done by imported curves as well. Locations on the imported curves are sampled by pt() method and stored in IVec variables (ref: Point on NURBS Curve). The following example code samples equally divided multiple locations on the imported curves. The number of division is specified by the integer variable divisionNum.

The sample file of curves used below is this file. The curves for field and particle are stored in different layers.
curve_field_init2.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.open("curve_field_init2.3dm");

ICurve[] fieldCurves = IG.layer("field").curves();
for(int i=0; i < fieldCurves.length; i++){
new ICurveTangentField(fieldCurves[i]).linear(20).intensity(50);
}

int divisionNum = 50;
ICurve[] particleCurves = IG.layer("particle").curves();
for(int i=0; i < particleCurves.length; i++){
for(int j=0; j < divisionNum; j++){
IVec pos = particleCurves[i].pt( 1.0/divisionNum*j );
new IParticleTrajectory(pos).fric(0.2).clr(particleCurves[i].clr());
}
}
```

### Initialize Particles with Surfaces and Meshes

You can not only specify initial location of particles and swarm particles (boids) but also put your geometry as representation of a particle and let the geometry move around. When you instantiate a particle class (IParticle, IBoid, IParticleTrajectory, IBoidTrajectory) with a geometry instance (IPoint, ICurve, ISurface, IMesh, IBrep), the input geometry behave as a particle. As default, the input geometry moves and rotates towards the velocity direction of the particle. Internally a particle is attached to the input geometry at its center to react to external forces and the positive Y direction of the geometry is set as the front orientation of the particle.

The following is the example code to create particle (IParticleTrajectory) with input geometries in a specific layer in a input 3dm file. This code extracts instances of IGeometry from the specified layer in the internal server. IGeometry is a super class of IPoint, ICurve, ISurface, IMesh and IBrep. The method IG.geometries() or IG.layer(layerName).geometries() returns all geometries inside the internal server or inside the specified layer. The return value is an array containing any type of geometry and it can be mixture of different types if any.

The sample file of curves used below is this file.
curve_field_init3.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);
IG.duration(250);
IG.darkBG();
IG.open("curve_field_init3.3dm");

ICurve[] fieldCurves = IG.layer("field").curves();
for(int i=0; i < fieldCurves.length; i++){
new ICurveTangentField(fieldCurves[i]).linear(20).intensity(50);
}

IGeometry[] geometries = IG.layer("particle").geometries(); // all geometries in "particle" layer
for(int i=0; i < geometries.length; i++){
new IParticleTrajectory(geometries[i]).fric(0.2); // create a particle out of each geometry
}
```

The image below shows the initial geometries from the input file.

Each input geometry is moved and rotated as a particle by the force field.

Another example below shows a code to convert input geometries into swarm agent (IBoid instance) to distribute geometries under the surface field condition and the swarming logic.

The sample file of curves used below is this file.
curve_field_init4.3dm

```import processing.opengl.*;
import igeo.*;

size(480, 360, IG.GL);

IG.darkBG();
IG.duration(700);
IG.open("curve_field_init4.3dm");

ISurface[] fieldSurfaces = IG.layer("field").surfaces();
for(int i=0; i < fieldSurfaces.length; i++){
new I2DSurfaceSlopeField(fieldSurfaces[i]).linear(50).intensity(10);
}

new IFieldVisualizer(-20,-20,0, 20,20,0, 40,40,1);

IGeometry[] geometries = IG.layer("particle").geometries();
for(int i=0; i < geometries.length; i++){
IBoid b = new IBoid(geometries[i]).fric(0.2);
b.cohesionRatio(5.0);
b.cohesionDist(5.5);
b.separationRatio(25.0);
b.separationDist(5.0);
b.alignmentRatio(15.0);
b.alignmentDist(7.0);
}
```

Initial state.

Distribution under the surface field condition and the swarming logic. Geometries loosely gather around the valley areas of the field surface but keeping distance to other geometries by the swarm algorithm.

(back to the list of tutorials)