5
$\begingroup$

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?

$\endgroup$
7
$\begingroup$

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

enter image description here

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.
enter image description here

Choose a small output Dimension (10x15 in my case) and render it.

enter image description here
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. ⎇ AltLMB 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.

enter image description here

Now we have lots of crazy forcefields which we can use for further experimentation.

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.