# Pattern Attractors

**URL:** <https://discourse.processing.org/t/pattern-attractors/2535>\
**Category:** Project Guidance\
**Created:** [August 10, 2018, 4:13pm UTC](https://discourse.processing.org/t/pattern-attractors/2535 "2018-08-10T16:13:02Z")\
**Posts on this page:** 1\
**Showing post:** 18

<div class="post-metadata">

**Author:** ![WakeMeAtThree](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/wakemeatthree/32/38_2.png) [@WakeMeAtThree](https://discourse.processing.org/u/WakeMeAtThree)\
**Post date:** [August 15, 2018, 11:00am UTC](https://discourse.processing.org/t/pattern-attractors/2535/18 "2018-08-15T11:00:14Z")

</div>

Hello all!

Curve attractors used to be the `"Hello, world"` of grasshopper sketches, it’s interesting to see how simplified it was to do when it was embedded in Rhino versus making it from scratch here. I’ll be using processing-python mode to do this because it’s much simpler to explain.

After setting up your grid of rectangles and ellipses, you need a way of drawing a curve from a small set of points. The way of drawing it should include a way to access the interpolated points for later use. One way of doing that is, let’s say you start with a list of four points as follows:

```python
    n = 4
    curvePoints = [PVector(1.0*i*width/n,random(height)) for i in range(n+1)]

```

Then you can define a number for `resolution` where you find all interpolation points in between these points:

```python
    resolution = 50
    interpolations = [lerpVectors(curvePoints,
                                  1.0*i/(resolution-1)) for i in range(resolution)]

```

`lerpVectors()` is essentially a function that takes in a _list of points_ to interpolate in between and an interpolation _amount_ (shout to @jeremydouglass for showing me this technique [here](https://discourse.processing.org/t/processing-unfolding-columns/610/6?u=wakemeatthree))

```python
def lerpVectors(vecs, amt):
    if(len(vecs)==1): return vecs[0]
    
    spacing = 1.0/(len(vecs)-1);
    lhs = floor(amt / spacing);
    rhs = ceil(amt / spacing);
    
    try:
        return PVector.lerp(vecs[lhs], vecs[rhs], amt%spacing/spacing);
    except:
        return PVector.lerp(vecs[constrain(lhs, 0, len(vecs)-2)], vecs[constrain(rhs, 1, len(vecs)-1)], amt);

```

![linearoutput](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/a/af32b96ef744d20f82131f584ae1f8da82e37777.png)

This function as it is will return linear interpolations, hence just a linear output. There are other kinds of interpolations (check [this article out](http://paulbourke.net/miscellaneous/interpolation/)). I will use instead a custom cosine interpolation to get some simple curves, but you can play with that and read more from the article.

```python
def cosineLerpVec(v1,v2,amt):
    x = lerp(v1.x,v2.x,amt)
    y = cosineLerp(v1.y,v2.y,amt)
    return PVector(x,y)
def cosineLerp(y1,y2,mu):
    mu2 = (1-cos(mu*PI))/2
    return y1*(1-mu2)+y2*mu2

def lerpVectors(vecs, amt):
    if(len(vecs)==1): return vecs[0]
    
    spacing = 1.0/(len(vecs)-1);
    lhs = floor(amt / spacing);
    rhs = ceil(amt / spacing);
    
    try:
        return cosineLerpVec(vecs[lhs], vecs[rhs], amt%spacing/spacing);
    except:
        return cosineLerpVec(vecs[constrain(lhs, 0, len(vecs)-2)], vecs[constrain(rhs, 1, len(vecs)-1)], amt);

```

![cosineoutput](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/3/3cc0f9bf23f8d82d9b23f5c47ae86c1bc84472f6.png)

Now the attractor part becomes simple. What we need to do is measure distances of each point in the grid with a point on the curve. The point in the grid will take the minimum distance to the curve. (`ellipsePositions` is a list of X by Y grid points):

```python
    # Get distances
    distances = []
    for i in ellipsePositions:
        d = []
        
        # Get all distances to curve points
        for j in interpolations:
            d.append(PVector.dist(i,j))
        
        # Get minimum distance (closest point to curve)
        distances.append(min(d))
    
    # Normalize distances for later scaling
    distNormed = [ map(i,min(distances),max(distances),0.0,1.0)
                   for i in distances ]

```

The result can now be easily displayed if we call distances and ellipse positions together:

```python
    for i,j in zip(distNormed,ellipsePositions):
        diameter = i*spaceX
        with pushStyle():
            fill(0)
            ellipse(j.x,j.y,diameter,diameter)
        rect(j.x,j.y,spaceX,spaceX)

```

![finaloutput](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/9/97fb9741057ced97305f19dcf7f7d8b843eda63e.png)

Code in the end.

> **Summary**
>
> ```python
> def setup():
> size(400,400)
> background(255)
> noFill()
> rectMode(CENTER)
>     
> X = 25
> Y = 25
>     
> spaceX = 1.0*width/X
> spaceY = 1.0*height/Y
>     
> ellipsePositions = [PVector(i*spaceX,j*spaceY) for i in range(X+1) for j in range(Y+1)]
> n = 4
> curvePoints = [PVector(1.0*i*width/n,random(height)) for i in range(n+1)]
> resolution = 50
> interpolations = [lerpVectors(curvePoints,
> 1.0*i/(resolution-1)) for i in range(resolution)]
> # Get distances
> distances = []
> for i in ellipsePositions:
> d = []
>         
> # Get all distances to curve points
> for j in interpolations:
> d.append(PVector.dist(i,j))
>         
> # Get minimum distance (closest point to curve)
> distances.append(min(d))
>     
> # Normalize distances for later scaling
> distNormed = [ map(i,min(distances),max(distances),0.0,1.0)
> for i in distances ]
> # Display curve
> with beginShape():
> for i in interpolations:
> vertex(i.x,i.y)
>     
> # Display ellipses
> for i,j in zip(distNormed,ellipsePositions):
> diameter = i*spaceX
> with pushStyle():
> fill(0)
> ellipse(j.x,j.y,diameter,diameter)
> rect(j.x,j.y,spaceX,spaceX)
>     
> def cosineLerpVec(v1,v2,amt):
> x = lerp(v1.x,v2.x,amt)
> y = cosineLerp(v1.y,v2.y,amt)
> return PVector(x,y)
> def cosineLerp(y1,y2,mu):
> mu2 = (1-cos(mu*PI))/2
> return y1*(1-mu2)+y2*mu2
> 
> def lerpVectors(vecs, amt):
> if(len(vecs)==1): return vecs[0]
>     
> spacing = 1.0/(len(vecs)-1);
> lhs = floor(amt / spacing);
> rhs = ceil(amt / spacing);
>     
> try:
> return cosineLerpVec(vecs[lhs], vecs[rhs], amt%spacing/spacing);
> except:
> return cosineLerpVec(vecs[constrain(lhs, 0, len(vecs)-2)], vecs[constrain(rhs, 1, len(vecs)-1)], amt);
> 
> ```

I tried to break down thought process on this example as much as possible. Let me know if you need a clarification on anything that’s not clear. You could go off on a lot of tangents here: animate it, superimpose it, change display (doesn’t have to be ellipses diameters) etc. I’m interested to see what you do with this.

---

_[View the full topic](https://discourse.processing.org/t/pattern-attractors/2535)._
