Discrete Vectors

Trying again.

The following should be a correct 3D conversion of Daniel Shiffman’s 2D Perlin noise flow field presented in this tutorial.

All I’m asking is:

  • How to compute and apply the 3D noised angle to the particle object ?

I would really really appreciate if someone could help.

EDIT:

  • I’ve stored the 3D vectors in a 2D array list as suggested in the video posted earlier.
  • I’m applying the same combination of cos(), sin(), absin()
  • Following the same instructions, particles are created on top of the bounding box (y = 0) and are removed if leaving the bounding box. New particles are created instead.

However, no matter what I do (playing with noiseDetail(), noiseScale, scale of the flow field…), the output is never like what’s shown in the video and barely looks like a flow field.

Again, this has to do with the computation of the 3D noise based angle.

add_library('peasycam')

def setup():
    global cols, rows, layers, noiseScale, flowfield, scl, particles, b, W, H, D
    hint(DISABLE_DEPTH_TEST)
    size(800, 800, P3D)
    frameRate(1000)
    stroke('0x41a5ff')
    strokeWeight(1.5)
    
    noiseDetail(8, 4)
    
    cam = PeasyCam(this, 900)
    
    #Bounding Box dimensions
    W, H, D = 400, 400, 400
    
    #Bounding Box
    b = createShape(BOX, W, H, D)
    b.setFill(color(255, 0))
    b.setStroke(color(255))
    b.setStrokeWeight(.2)

    particleCount = 1000
    noiseScale = .1
    scl = 100
    cols = floor(W / scl)
    rows = floor(H / scl)
    layers = floor(D / scl)
    
    #Collection of particle objects
    particles = [Particle() for i in range(particleCount)]
    
    #3D array list (1 for each dimension)    
    flowfield = [[[] for z in range(layers)] for i in range(cols*rows)]

    #Computing fixed flow field
    for x in range(rows):
        for y in range(cols):
            index = x + y * cols
            for z in range(layers):
                
                #Computing angle (probably not correct)
                angle = noise(x *noiseScale, y *noiseScale, z* noiseScale) * TWO_PI
  
                #Combination of cos() sin() absin() (as suggested in the video)
                v = PVector(cos(angle), sin(angle), abs(sin(angle))).setMag(.7)
           
                #Storing in 2D array list (as suggested in the video)  
                flowfield[index][z] = v    
                

def draw():
    background(14)
    
    #Displaying Bounding Box
    shape(b)
    
    #Translating because of PeasyCam
    pushMatrix()
    translate(-W>>1, -H>>1, -D>>1)
                
    #Running particle objects
    for p in particles:
        p.follow()
        p.update()
        p.edges()
        p.render()

    
    popMatrix()
    
    
class Particle(object):
    def __init__(self):
        self.location = PVector(random(W), 5, random(D)) #Particles falling from top (as suggested in the video)
        self.velocity = PVector(0)
        self.acceleration = PVector()
        self.angle = PVector()
        self.maxspeed = 4
    
    def update(self):
        self.velocity.add(self.acceleration)
        self.velocity.limit(self.maxspeed)
        self.location.add(self.velocity)
        self.acceleration.mult(0)
        
        
    def applyForce(self, force):
        self.acceleration.add(force)
        
        
    def follow(self):
        x = floor(self.location.x / scl)
        y = floor(self.location.y / scl)
        z = floor(self.location.z / scl)
        
        #Safety (for some reasons x, y or z sometimes goes out of the bounding box)
        if x < cols and y < rows and z < layers:
            
            #Applying corresponding force based on particle location
            idx = x + y * cols
            n = flowfield[idx][z]
            self.applyForce(n)
        
        
    def render(self):    
        point(self.location.x, self.location.y, self.location.z)
       
       
    def edges(self):
        
        #As suggested in the video:
        #Removing particles from array list if outside Bounding box
        #Appending new Particle() object instead 
        if self.location.x > W or self.location.x < 0 or self.location.y > H or self.location.y < 0 or self.location.z > D or self.location.z < 0:
            del particles[particles.index(self)]
            particles.append(Particle())

1 Like

Thanks for the wonderful thread, @solub! Made me want to experiment with 3D flows!

# based on code by solub at
# https://discourse.processing.org/t/discrete-vectors/3942/22
add_library('peasycam')

n_line, n_point = 20, 300
factor = .04
offset, offset2  = 1, 200
da = [HALF_PI, QUARTER_PI, 0,  -QUARTER_PI, - HALF_PI]

def setup():
    size(600, 600, P3D)
    cam = PeasyCam(this, 1200)

def draw():
    background(255)    
    translate(-400, -200)
    paths = [[] for i in range(n_line)]
    offset, ofsset2 = mouseX / 100.0, mouseY / 100.0
    py = 0
    for y in range(n_line):
        px = 0
        pz = y * 25
        for x in range(n_point):
          n = int(round(map(noise(x * factor + offset, y * factor + offset), 0, 1, -1, 5)))
          d = PVector.fromAngle(da[n]).setMag(3)
          n2 = int(round(map(noise(x * factor + offset2, y * factor + offset2), 0, 1, -1, 5)))
          d2 = PVector.fromAngle(da[n]).setMag(3)
          px += d.x     
          py += d.y  
          pz += d2.y
          if py > 60 or py < 0: py -= d.y
          paths[y].append(PVector(px, pz, py,))
        py = 0
    
    noFill()  
    for w, pts in enumerate(paths):
        strokeWeight((len(paths) - w) / 4.0 + 1)
        beginShape()
        for x, y, z in pts:
            vertex(x, y, z)
        endShape()
    # for w, pts in enumerate(paths):
    #     for i, (x, y, z) in enumerate(pts[:-1]):
    #         nx, ny, nz = pts[i + 1]
    #         line(x, y, z, nx, ny, nz)

# based on code by solub at
# https://discourse.processing.org/t/discrete-vectors/3942/22

add_library('peasycam')

n_line, n_point = 10, 100
factor = .005
offset, offset2  = 1, 200
da = [HALF_PI, QUARTER_PI, 0,  -QUARTER_PI, - HALF_PI]
# da = [QUARTER_PI, HALF_PI + QUARTER_PI, PI, PI + QUARTER_PI, PI + HALF_PI]

def setup():
    size(600, 600, P3D)
    colorMode(HSB)
    cam = PeasyCam(this, 900)

def draw():
    background(0)    
    paths = [[] for i in range(n_line)]
    offset, offset2 = mouseX / 100.0, mouseY / 100.0
    for y in range(n_line):
        for x in range(n_line):
            py =  map(y, 0, n_line -1, -200, 200)
            px =  map(x, 0, n_line -1, -200, 200)
            pz = 0
            path = []
            stroke(255)
            circle(px, py, 5)
            for p in range(n_point):
                a2 = da[int(round(map(noise(px * factor + offset,
                                    py * factor + offset2,
                                    pz * factor), 0, 1, -1, 5)))]
                a1 = da[int(round(map(noise(px * factor + offset,
                                    py * factor + offset2,
                                    pz * factor), 0, 1, -1, 5)))]
                xd = cos(a2) * sin(a1)
                yd = cos(a2) * cos(a1)
                zd = sin(a2)
                d = PVector(xd, yd, zd).setMag(3)
                px += d.x     
                py += d.y  
                pz += d.z #if (pz + d.z) > 0 else -d.z
                path.append(PVector(px, py, pz,))
            paths.append(path)
    
    noFill()  
    for w, pts in enumerate(paths):
        strokeWeight(2)
        beginShape()
        for x, y, z in pts:
            stroke(z % 255, 255, 255)
            vertex(x, y, z)
        endShape()

5 Likes

@villares (Oh, I didn’t do much. Thank you for your enthusiastic feedback !)

This last output looks really nice. Originally, I got interested in this topic after seeing architects using this technique to design angular wooden shelves flowing along interior walls (cf: the gif above from Oct’18). Really like how simple experiments with Processing can turn into real life projects or piece of furniture.

2 Likes