Making a scene with grass made with a hair particle system in a fluid simulation. I was wondering if there is any way for the hair particles to be able to sway based on the fluid simulation. If so how would I go about it? What physics should I be playing with?
1 Answer
tl,dr
You can't, the fluid simulation system is not meant to interact with non-fluid object/forces/particles.
Keep in mind, that software such as Houdini is free for the apprentice version and allows to export geometry to Blender (as an obj sequence e.g.).
This answer assumes you are a proficient Blender user, given the complex context of the question. Adress unclear things in the comments and I'll extend the answer.
The Manual Way
Manually scatter force field in your scene. Such as Wind. Rotate them manually and keyframe their strength to mimic the flow of the water. Force fields influence hair particles.
This will probably be the fastest way in Blender internally.
The Hack
Keeping the Fluid Domain setting "Generate Speed Vector" box checked opens up a Hack.
The wrong movements in the demo gif arent limitations, but I didn't want to pursue the matter further. (My obstacle needed different geometry.)
Create the Vector Pass for the render layer. Then place an orthogonal camera directly above your scene. We will output the speed of the surface into an image sequence with the following compositing nodes. Don't forget to choose (e.g.) OpenEXR as to not loose negative or higher values.
Choose a small output Dimension (10x15 in my case) and render it.
LTR: image sequence of speed vectors; orthographic camera directly from above, left-lower corner matches (0, 0); render settings
With this method we only get the speed vectors for the surface, so it still won't come close to mimic a realistic result.
Now, we will grab the direction values of each pixel, and place a wind force field at the corresponding 3d position.
First, create some Wind Force fields, since my image is 15x10 pixels, I need 150 forcefields.
import bpy
w, h = 15, 10
for x in range(0, w):
for y in range(0, h):
bpy.ops.object.effector_add(type='WIND')
pass
fields = []
for ob in bpy.data.objects:
if ob.name[:5] == "Field":
fields.append(ob)
i = 0
for x in range(0, w):
for y in range(0, h):
fields[i].name = str(x).zfill(4) + "_" + str(y).zfill(4)
fields[i].location = (x, y, 0)
i += 1
I saved my motion images to "//motion####.exr". Sadly, image sequences don't update on frame change from python, so we will have to load each image separately. Create a new image "motion_image", then execute the code. The comments should make the functionality clear.
import bpy
import mathutils
# method for reading a pixels values
# an image (as in bpy.data.images) has the attribute pixels, a list of values
# in pixels, rgba values come after each other (they are not grouped)
# rows aren't grouped either, they come after each other
# [0x0_r, 0x0_g, 0x0_b, 0x0_a, 0x1_r, 0x1_b ... 15x10_a]
# more info here: http://blenderscripting.blogspot.de/2012/08/adjusting-image-pixels-internally-in.html
def getPixel(pixels, x, y, width):
idx = (x + y * width) * 4
return pixels[idx], pixels[idx + 1], pixels[idx + 2], pixels[idx + 3]
scn = bpy.data.scenes['Scene']
# grab our prepared image and its dimensions
img = bpy.data.images['motion_image']
width, height = img.size[0], img.size[1]
for frame in range(scn.frame_start, scn.frame_end):
# this may take a while, so here the update in the console
print(frame)
# load the image from disk into our Blender image
img.filepath = "//motion/" + str(frame).zfill(4) + ".exr"
bpy.context.scene.frame_set(frame)
bpy.context.scene.update()
img.reload()
img.update()
# see CoDEmanX's comment: https://blenderartists.org/forum/archive/index.php/t-268545.html
pixels = list(img.pixels[:])
# loop through all pixels
for x in range(0, width):
for y in range(0, height):
r, g, b, a = getPixel(img.pixels, x, y, width)
# access the wind force fields through our naming system
ob = bpy.data.objects[str(x).zfill(4) + "_" + str(y).zfill(4)]
v = mathutils.Vector((r, g, b))
# convert the direction to a quaternion_rotation and calculate the strength
ob.rotation_quaternion = v.normalized().to_track_quat('Z', 'X')
ob.field.strength = v.magnitude * 200
ob.keyframe_insert('rotation_quaternion')
ob.field.keyframe_insert('strength')
Don't forget to set all forcefield object to quaternion rotations. ⎇ Alt LMB on a property to change it for all selected objects.
I also offseted my forcefield (0.5, 0.5) to center them in the pixels.
Now we have lots of crazy forcefields which we can use for further experimentation.