0
$\begingroup$

I'd like to know how to make a "flat" sort of scanning beam that is cut off by various items it interacts with. For example, say that the beam emits from a single point and is shaped like a triangle as it spreads. If, say, a cube were placed within the beam, a lack-of-beam (er, shadow) would be cast, somewhat similar to an object obscuring a spotlight beam. [EDIT The attached image shows something somewhat similar to what I'm going for.]

If anybody was able to comprehend that, does anyone know how to make such an effect?

enter image description here

$\endgroup$
  • 1
    $\begingroup$ Could you add some reference image? $\endgroup$ – cgslav May 23 '18 at 18:13
  • $\begingroup$ Do you mean something like this? $\endgroup$ – Robin Betts May 23 '18 at 18:32
  • $\begingroup$ @Robin Betts Sort of, except "filled in." Original post updated with somewhat accurate reference image. $\endgroup$ – Legoman May 24 '18 at 15:07
7
$\begingroup$

Similiar to icYou520's answer you could create a special lamp and add a volumetric container. Since this is very render-intense here comes another hack.

enter image description here
Model by jahjavjaz

Is is a planar ngon which move vertices away from the center untill they detect a collision. This is done by a somewhat interactive python handler.

enter image description here

Execute the first code sample. The beginning holds some constants like the name of the object and the maximum distance of the rays. Increase the Radius step size will result in more vertices, and more calculation time.

import bpy
import bmesh
from mathutils.bvhtree import BVHTree
import mathutils
import math
import numpy as np

EPSILON = 0.00001
MAXIMUM_DISTANCE = 10

object_name = 'light_plane'
radius_start = math.pi*1/4
radius_end   = math.pi*3/4
radius_step  = math.pi/256

angles = [(math.cos(a), math.sin(a)) for a in np.arange(radius_start, radius_end + 0.00001, radius_step)]

def create_mesh_obj(bm, p_name = "from_bmesh"):
    scn = bpy.context.scene
    me = bpy.data.meshes.new(name = p_name)
    bm.to_mesh(me)

    if (p_name not in bpy.data.objects):
        ob = bpy.data.objects.new(name = p_name, object_data = me)
        scn.objects.link(ob)
    else:
        ob = bpy.data.objects[p_name]
        ob.data = me
    scn.update()
    return ob

def create_vertice_shapes(angles):
    bm = bmesh.new()
    for a in angles:
        bm.verts.new((a[0], a[1], 0))
    bm.verts.new((0, 0, 0))
    ob = create_mesh_obj(bm)
    bm.free()
#    basis = ob.shape_key_add(from_mix=False)
#    basis.name = "basis"
    return ob

ob = create_vertice_shapes(angles)
ob.name = object_name

The script give us an arc of vertices and a center vertices. Make a face. F

enter image description here

The second snippet contains the method to detect collision using the bvhtree from the mathutils module. Make sure, the objects name is correct.

Uncommenting one the last three lines will give us a certain result.

  1. bpy.app.handlers.scene_update_pre.append(my_handler)
    This line registers the function as a handler on scene update. After uncommenting this line and running the script you will see a live update of the calculation. Depending on your scene this will be computationally expensive.
  2. bpy.app.handlers.frame_change_pre.append(my_handler)
    This line registers the function as a handler on framechange. Useful, if you only want to see the update on playback to view the animation, or if you want to render the animation.
  3. my_handler(bpy.context.scene)
    This line executes the function immediately, and only once.
import bpy
import bmesh
from mathutils.bvhtree import BVHTree
import mathutils
import math
import numpy as np

EPSILON = 0.00001
MAXIMUM_DISTANCE = 5

object_name = 'light_plane'
radius_start = math.pi*1/4
radius_end   = math.pi*3/4
radius_step  = math.pi/256


angles = [(math.cos(a), math.sin(a)) for a in np.arange(radius_start, radius_end + 0.00001, radius_step)]
print(angles)

def set_positions(ob, angles, obstacles):
    trees = []
    for obstacle in obstacles:
        bm = bmesh.new()
        bm.from_object(obstacle, bpy.context.scene)
        bmesh.ops.transform(bm, matrix=obstacle.matrix_world, verts=bm.verts)
        trees.append(BVHTree.FromBMesh(bm, epsilon=EPSILON))

    for idx, a in enumerate(angles):
        min_dist = MAXIMUM_DISTANCE
        v_loc = mathutils.Vector((a[0], a[1], 0)) * MAXIMUM_DISTANCE
        for bvh in trees:
            v = (ob.matrix_world * mathutils.Vector((a[0], a[1], 0))) - ob.location
            loc, no, i, d = bvh.ray_cast(ob.location, v)
            if d is not None:
                if d < min_dist:
                    min_dist = d
                    v_loc = ob.matrix_world.inverted() * loc
        ob.data.vertices[idx].co = v_loc


def my_handler(scene):
    ob = bpy.data.objects[object_name]
    selection = [oo for oo in bpy.data.objects if oo != ob]
    print(ob.location)
    set_positions(ob, angles, selection)

for h in bpy.app.handlers.scene_update_pre:
    bpy.app.handlers.scene_update_pre.remove(h)
for h in bpy.app.handlers.frame_change_pre:
    bpy.app.handlers.frame_change_pre.remove(h)
#bpy.app.handlers.scene_update_pre.append(my_handler)
#bpy.app.handlers.frame_change_pre.append(my_handler)
#my_handler(bpy.context.scene)
$\endgroup$
  • $\begingroup$ Unfortunately, I'm terrible with code. I ran the first script and it gave me the arc. I made the face, then I ran the second script...and nothing happened. $\endgroup$ – Legoman May 28 '18 at 17:00
  • $\begingroup$ @Legoman Did you remember to uncomment the second last line by removing the # symbol. Did you check that the created object was correctly named "light_plane"? Did you look in the console for error messages? $\endgroup$ – Leander May 28 '18 at 17:02
  • $\begingroup$ Yes, tried all of those. Does running the script multiple times (trying each of the uncommenting options) mess things up? $\endgroup$ – Legoman May 28 '18 at 17:58
  • $\begingroup$ If you have trouble reproducing the steps you can open up a new question particularising this specific problem. $\endgroup$ – Leander Jun 6 '18 at 8:09
2
$\begingroup$

I came up with a hacky way to do it. (I am also working on another solution that would work much better but running into problems with that one. I will update this answer with a 2nd option when I figure it out).

I just created a plane scaled on Y to make it thin and subdivided about 6 times.

I created a very small and sharp spot light.

Put a hair particle system on the thin plane and chose the spot light as the object. (More details on how to do this ).

Make sure your particles system is set to faces and even distribution. Then just put as many particles (lights) until it makes a straight line. I used about 350.

Then I made another plane for the "filled in part". The node set up is just an emission shader with some noise and wave node mixed in.

This way creates LOTS of noise and computer gets slow. So may need very high samples and will have to clamp indirect and possibly direct lights.

enter image description here

enter image description here

enter image description here

enter image description here

$\endgroup$
  • $\begingroup$ Yeeks, that's not the most practical method....looking forward to your second method! $\endgroup$ – Legoman May 25 '18 at 20:30

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.