Spis treści

Moje małe pole siłowe

Pole siłowe to pojęcie występujące nie tylko w fizyce czy chemii. Może być ono równiez wykorzystane na wiele twórczych sposobów w sztuce generatywnej.

Czym jest pole siłowe

Jeden z esejów Tylera Hobbsa dotyczy właśnie pola siłowego i pokazuje prosty przykład użycia koncepcji pola do generowania interesujących obrazów. Dzisiaj pójdę za tą inspiracją i spróbuję stworzyć swoje małe pole siłowe.

Z punktu widzenia fizyki pole siłowe jest polem wektorowym, które w każdym punkcie przestrzeni działa na ciało pewną siłą. Pole takie może być opisane jako fukcja, która każdemu punktowi przestrzeni przypisuje pewien wektor. Wektor ten reprezentuje siłę, jaka działa na ciało znajdujące się w danym punkcie przestrzeni.

Najbardziej znanym polem tego typu jest oczywiście pole grawitacyjne. Innymi są pole magnetyczne czy pole elektryczne. Pojęcia te pamiętam jeszcze z podstawówki - dziś wykorzystam je do stworzenia grafiki.

Jak je stworzyć

Nie powinno to być nic trudnego.

  • Potraktuję płaszczyznę jak siatkę (wystającą nieco poza obszar obrazu, który zamierzam stworzyć)
  • w każdym punkcie (a raczej “kwardacie”) tej siatki zapamiętam siłę, która w tym punkcie działa
    • mogę ją zdefiniować przy pomocy funkcji matematycznej lub
    • wykorzystać funkcję noise()

Siatka

Statka będzie rozpięta poza obszar rysowania o pewną część szerokości/wysokości obszaru - nazwę tę część offset (będzie miała wartość 0.3).

Szerokość/wysokość jednego “oczka” siatki również zdefiniuję jako część szrokości/wysokości obszaru - nazwę tę część resolution (będzie miała wartość 0.05).

W każdym “oczku” siatki będę przechowywać dwie wartości z przedziału (0, 1) - będą to współrzędne dx i dy swobodnego wektora siły “zaczepionego” w środku oczka.

Oto konfiguracja siatki:

1
2
3
4
5
6
const GridConf = {
  // space beyond size (part of width)
  offset: 0.3,
  // size of cell (part of width)
  resolution: 0.05,
}

W ten sposób tworzę siatkę (klasa Grid) przekazując w konstruktorze obiekt GridConf (jako gc):

1
2
3
4
5
6
7
8
9
  constructor(gc) {
    this.gc = gc
    let {offset, resolution} = gc
    let [leftx, rightx] = [int(-offset * s.wi), int(s.wi + offset * s.wi)]
    var [topy, boty] = [int(-offset * s.hi), int(s.hi + offset * s.hi)]
    this.ncols = int((rightx - leftx) / (resolution * s.wi))
    this.nrows =  int((boty - topy) / (resolution*s.hi))
    this.grid = new Array(this.ncols*this.nrows)
  }

Siła

Wartość dx - czyli pozioma składowa siły - powinna się łagodnie zmieniać na przestrzeni dwuwymiarowej, dlatego do określenia wartości dx w danym “oczku” używam dwuargumentowej funkcji noise(): jako argumenty przekazuję współrzędne płaszczyzny “noise” (przesuwam się po niej krokami dxxoff w kierunku poziomym i dxyoff w kierunku pionowym).

Podobnie jest z pionową składową siły - genruję ją poruszając się po płaszczyźnie “noise” krokami dyxoff oraz dyyoff.

Oto konfiguracja siatki:

1
2
3
4
5
6
7
8
const NoiseConf = {
  // noise steps for force dx 
  dxxoff: 0.01,
  dxyoff: 0.03,
  // noise steps for force dy
  dyxoff: 0.2,
  dyyoff: 0.1,
}

Siatkę - strukturę dwuwymiarową - reprezentuję jako zwykłą jednowymiarową tablicę, przeliczając współrzędne [kolumna, wiersz] na odpowiedni indeks w tablicy. Służą do tego funkcje

  • force(c, r) - zwraca wartość siły dla podanej kolumny c i wiersza r
  • setForce(c, r, v) - ustawia wartość v siły dla podanej kolumny c i wiersza r
1
2
3
4
5
6
7
  force(c, r) {
    return this.grid[c * this.nrows + r]
  }

  setForce(c, r, v) {
    this.grid[c * this.nrows + r] = v
  }

W ten sposób wypełniam tablicę reprezentującą siatkę, przekazując NoiseConf jako nc. Wędrówkę po płaszczyźnie “noise” rozpoczynam z “punktów” (0, 0):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

  init(nc) {
    this.nc = nc
    let [dx_x, dx_y, dy_x, dy_y] = [1,23, 2,4]

    for (let c = 0; c < this.ncols; c++) {
      for (let r = 0; r < this.nrows; r++) {
        let force =  [
            noise(dx_x + c * nc.dxxoff, dx_y + r * nc.dxyoff),
            noise(dy_x + c * nc.dyxoff, dy_y + r * nc.dyyoff)]

        this.setForce(c, r, force)
      }
      
    }
  }

Rysuję wektory siły (od zielonej do czerwonej kropki):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  draw() {
    let [cellwi, cellhi] = [
      this.gc.resolution*s.wi,  
      this.gc.resolution*s.hi]

    for (let c = 0; c < this.ncols; c++) {
      for (let r = 0; r < this.nrows; r++) {
        let [nx, ny] = this.force(c, r)
        let [x, y] = [
          int((c + 0.5) * cellwi), 
          int((r + 0.5) * cellhi)]
        let dx = map(nx, 0, 1, -cellwi, cellwi) 
        let dy = map(ny, 0, 1, -cellhi, cellhi) 
        this.draw_vec(x, y, dx, dy)
      }
    }
  }

  draw_vec(x, y, dx, dy) {
    push()
    strokeWeight(1)
    stroke('black')
    line(x, y, x + dx, y + dy) 
    strokeWeight(3)
    stroke('green')
    point(x, y)
    stroke('red')
    point(x + dx, y + dy)
    pop()
  }

/posts/pole-silowe/pole.png

Zastosowania

Co teraz zrobię z polem siłowym? Jak wykorzystam go do stworzenia czegoś interesującego?

O tym już wkrótce w kolejnym wpisie. Zapraszam!


Ten wpis jest częścią serii strengthfield.

Wszystkie wpisy w tej serii: