1Thanks @makoho,
Implementing a 3D flowfield seems to be indeed a solution of choice. However that suggestion is not without problems.
- 1/ The example pictures above show vectors rotating around a center, not flowing along an extended path. I could put “walls” around the bounding box (to prevent vectors to flow outside the edges) and try to divert the vectors to the center when reaching an edge. But with noise-based angles pushing them back right after against the walls again… the end result would be a mess.
A solution could be to mimic a vortex motion with the sine and cosine of the noise-based angles. Just like in the following video (from 1:15 to 1:25):
- 2 / I tried to reproduce the effect with
dir = PVector(cos(n), sin(n), abs(sin(n)))
as suggested in the video but it looks somehow different (last picture below). I’m not even sure I’m implementing the 3D flowfield correctly.
for iz, z in enumerate(range(0, D, zstep)):
for iy, y in enumerate(range(0, H, ystep)):
for ix, x in enumerate(range(0, W, xstep)):
n = map(noise(x * factor, y * factor, z * factor), 0, 1, 0, PI)
flowfield[ix][iy][iz] = n
Full script
#add_library('PostFX')
add_library('peasycam')
W, H, D = 600, 1200, 700
factor = .002
scl = 10
ystep = int(H / scl)
xstep = int(W / scl)
zstep = int(D / scl)
flowfield = [[[[] for x in range(scl)] for y in range(scl)] for z in range(scl)]
def setup():
global particles, flowfield, cam, fx, bbox, boxes
size(1400, 900, P3D)
hint(DISABLE_DEPTH_TEST)
frameRate(1000)
background(20)
smooth(8)
fx = PostFX(this)
cam = PeasyCam(this, 1500)
particles = [Particle() for i in range(260)]
bbox = createShape(BOX, W, H, D)
bbox.setFill(color(2, 170, 255, 0))
bbox.setStroke(color(255, 100))
bbox.setStrokeWeight(.5)
boxes = createShape(GROUP)
for iz, z in enumerate(range(0, D, zstep)):
for iy, y in enumerate(range(0, H, ystep)):
for ix, x in enumerate(range(0, W, xstep)):
a = map(noise(x * factor, y * factor, z * factor), 0, 1, 0, PI)
dvector = PVector(cos(a), sin(a), cos(a)).setMag(10)
flowfield[ix][iy][iz] = dvector
nbox = createShape(BOX, xstep, ystep, zstep)
nbox.setFill(color(1, 130, 255, 1))
nbox.setStroke(color(255, 1))
nbox.setStrokeWeight(.4)
nbox.translate(x-(W/2)+(xstep/2), y-(H/2)+(ystep/2), z-(D/2)+(zstep/2))
boxes.addChild(nbox)
def draw():
background(0)
for p in particles:
p.follow()
p.update()
p.edges()
p.render()
shape(boxes)
shape(bbox)
#fx.render().bloom(0.01, 10, 90).compose()
#fx.render().saturationVibrance(0.5, .5).compose()
#hint(DISABLE_DEPTH_TEST)
cam.beginHUD()
fill(255)
textSize(14)
text(frameRate, 30, height - 40)
cam.endHUD()
class Particle(object):
def __init__(self):
self.location = PVector(random(W)-(W/2), random(H)-(H/2), random(D)-(D/2))
self.velocity = PVector()
self.acceleration = PVector()
self.maxspeed = 14
self.loclist = []
self.type = random(1)
def update(self):
self.velocity.add(self.acceleration).limit(self.maxspeed)
self.location.add(self.velocity)
self.acceleration.mult(0)
self.loclist.append(PVector(self.location.x, self.location.y, self.location.z))
def follow(self):
x = floor((self.location.x + (W/2)) / xstep)
y = floor((self.location.y + (H/2)) / ystep)
z = floor((self.location.z + (D/2)) / zstep)
if x < scl and y < scl and z < scl:
dvector = flowfield[x][y][z]
self.velocity.add(dvector)
def updateLast(self):
for i in range(len(self.loclist)-1):
self.loclist[i+1].x = self.location.x
self.loclist[i+1].y = self.location.y
self.loclist[i+1].z = self.location.z
def render(self):
if self.type > .9:
if len(self.loclist) > 60:
del self.loclist[0]
for i in range(len(self.loclist) - 1):
c = lerpColor(color(230, 5, 106), color(255, 225, 155), .8 - norm(i, 0, len(self.loclist) - 1))
stroke(c, i*10)
strokeWeight(1 - map(i, 0, len(self.loclist)-1, 1, 0))
line(self.loclist[i].x, self.loclist[i].y, self.loclist[i].z, self.loclist[i+1].x, self.loclist[i+1].y, self.loclist[i+1].z)
else:
strokeWeight(3)
stroke(0, 255, 253)
point(self.location.x, self.location.y, self.location.z)
def edges(self):
if self.location.x > W/2:
self.location.x = 0 - W/2
self.updateLast()
if self.location.x < -W/2:
self.location.x = W/2
self.updateLast()
if self.location.y > H/2:
self.location.y = -H/2
self.updateLast()
if self.location.y < -H/2:
self.location.y = H/2
self.updateLast()
if self.location.z > D/2:
self.location.z = -D/2
self.updateLast()
if self.location.z < -D/2:
self.location.z = D/2
self.updateLast()
dir = PVector(cos(n), sin(n), cos(n))
dir = PVector(cos(n), sin(n), abs(sin(n)))
-
3/ How can I create a discrete vector in 3D ? In 2D I was mapping the noise-based angle against a range of 7 integers (each corresponding to the choice of a specific discrete angle).
angle = vectors[x][y]
index = int(round(map(angle, 0, TWO_PI, 0, 7)))
discrete_angles = [QUARTER_PI, HALF_PI, PI - QUARTER_PI, \
PI, PI + QUARTER_PI, PI + HALF_PI, \
TWO_PI - QUARTER_PI, TWO_PI]
discrete_vector = PVector.fromAngle(discrete_angles[index]).setMag(2.5)
self.velocity.add(discrete_vector)
In 3D however, PVector.fromAngle()
and heading()
can’t be used. As a result I’m unable to transform my 3D PVector()
to a specific angle.
Any guidance on that matter would be greatly appreciated.