Python 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.

```add_library('igeo')

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

for i in range(100) :
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.

```add_library('igeo')

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

for i in range(100) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

IAttractor(-50,0,0).intensity(10).clr(1.0,0,0) #attractor
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)
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.

```add_library('igeo')

size(480, 360, IG.GL)

IAttractor(-50,0,0).intensity(10).clr(1.0,0,0) #attractor
IAttractor(50,0,0).intensity(-10).clr(0,0,1.0) #repulsion

# last 3 inputs are sample number in x, y, z.
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).

```add_library('igeo')

size(480, 360, IG.GL)

IAttractor(-50,0,0).intensity(10).clr(1.0,0,0) #attractor
IAttractor(50,0,0).intensity(-10).clr(0,0,1.0) #repulsion

visualizer = 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.

```add_library('igeo')

size(480, 360, IG.GL)

IAttractor(0,0,0).noDecay()

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).

```add_library('igeo')

size(480, 360, IG.GL)

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

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).

```add_library('igeo')

size(480, 360, IG.GL)

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

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.

```add_library('igeo')

size(480, 360, IG.GL)

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

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

```add_library('igeo')

size(480, 360, IG.GL)

IG.open("curve_field1.3dm")

for i in range(IG.curveNum()) :
ICurveAttractorField(IG.curve(i)).intensity(10).linear(50)

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.

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.curveNum()) :
ICurveAttractorField(IG.curve(i)).intensity(10).linear(50)

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

IG.open("curve_field1.3dm")

for i in range(IG.curveNum()) :
ICurveTangentField(IG.curve(i)).intensity(10).linear(50)

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

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

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.curveNum()) :
ICurveTangentField(IG.curve(i)).intensity(10).linear(50)

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

IG.open("curve_field1.3dm")

for i in range(IG.curveNum()) :
ICurveCurlField(IG.curve(i)).intensity(10).linear(50)

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

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

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.curveNum()) :
ICurveCurlField(IG.curve(i)).intensity(10).linear(50)

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

for i in range(1000) :
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

```add_library('igeo')

size(480, 360, IG.GL)

IG.open("curve_field2.3dm")

for i range(IG.curveNum()) :
ICurveTangentField(IG.curve(i)).intensity(10).linear(50)

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

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.curveNum()) :
ICurveTangentField(IG.curve(i)).intensity(10).linear(50)

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

IG.open("curve_field2.3dm")

for i in range(IG.curveNum()) :
ICurveTangentField(IG.curve(i)).linear(5).intensity(10)

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

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.curveNum()) :
ICurveTangentField(IG.curve(i)).linear(5).intensity(10)

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

for i in range(1000) :
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 ).

```add_library('igeo')

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

field = ICompoundField()
for i in range(IG.curveNum()) :

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

```add_library('igeo')

size(480, 360, IG.GL)

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

field = ICompoundField()
for i in range(IG.curveNum()) :

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

field = ICompoundField()
for i in range(30) :
dir = IRand.pt(-1,-1,1,1)
pos = IRand.pt(-20,-20,20,20)

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

```add_library('igeo')

size(480, 360, IG.GL)

IG.darkBG()

field = ICompoundField()
for i in range(30) :
dir = IRand.pt(-1,-1,1,1)
pos = IRand.pt(-20,-20,20,20)

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

for i in range(2000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

IG.darkBG()

field = ICompoundField()
for i in range(30) :

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

for i in range(2000) :
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).

```add_library('igeo')

size(480, 360, IG.GL)

IG.darkBG()

field = ICompoundField()
for i in range(30) :
if IRand.pct(40) :
elif IRand.pct(50) :
else :

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

for i in range(2000) :
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

```add_library('igeo')

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

for i in range(IG.surfaceNum()) :
ISurfaceUTangentField(IG.surface(i)).linear(50).intensity(10)

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

```

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.surfaceNum()) :
ISurfaceUTangentField(IG.surface(i)).linear(50).intensity(10)

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.surfaceNum()) :
ISurfaceVTangentField(IG.surface(i)).linear(50).intensity(10)

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.surfaceNum()) :
ISurfaceNormalField(IG.surface(i)).linear(50).intensity(10)

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

for i in range(1000) :
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.

```add_library('igeo')

size(480, 360, IG.GL)

IG.open("surface_field1.3dm")

for i in range(IG.surfaceNum()) :
I2DSurfaceSlopeField(IG.surface(i)).linear(50).intensity(10)

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

```add_library('igeo')

size(480, 360, IG.GL)

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

for i in range(IG.surfaceNum()) :
I2DSurfaceSlopeField(IG.surface(i)).linear(50).intensity(10)

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

for i in range(2000) :
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

```add_library('igeo')

size(480, 360, IG.GL)

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

fieldCurves = IG.layer("field").curves()
for crv in fieldCurves :
ICurveTangentField(crv).linear(20).intensity(50)

points = IG.layer("particle").points()
for pt in points :
IParticleTrajectory(pt).fric(0.2).clr(pt.clr())
pt.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

```add_library('igeo')

size(480, 360, IG.GL)

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

fieldCurves = IG.layer("field").curves()
for crv in fieldCurves :
ICurveTangentField(crv).linear(20).intensity(50)

divisionNum = 50;
particleCurves = IG.layer("particle").curves()
for crv in particleCurves :
for j in range(divisionNum) :
pos = crv.pt( 1.0/divisionNum*j )
IParticleTrajectory(pos).fric(0.2).clr(crv.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

```add_library('igeo')

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

fieldCurves = IG.layer("field").curves()
for crv in fieldCurves :
ICurveTangentField(crv).linear(20).intensity(50)

geometries = IG.layer("particle").geometries() # all geometries in "particle" layer
for geo in geometries :
IParticleTrajectory([geo]).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

```add_library('igeo')

size(480, 360, IG.GL)

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

fieldSurfaces = IG.layer("field").surfaces()
for srf in fieldSurfaces :
I2DSurfaceSlopeField(srf).linear(50).intensity(10)

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

geometries = IG.layer("particle").geometries()
for geo in geometries :
b = IBoid([geo]).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)