308 lines
8.5 KiB
Python
308 lines
8.5 KiB
Python
import bpy
|
|
import bmesh
|
|
from mathutils import Vector
|
|
from collections import defaultdict
|
|
from itertools import combinations, chain
|
|
|
|
def sprint(*text):
|
|
screen = bpy.data.screens['Scripting']
|
|
for area in screen.areas:
|
|
if area.type != "CONSOLE":
|
|
continue
|
|
override = {'screen': screen, 'area': area}
|
|
with bpy.context.temp_override(**override):
|
|
bpy.ops.console.scrollback_append(text=" ".join(map(str, text)))
|
|
|
|
print = sprint
|
|
|
|
def create_shadow_volume_mesh(light, o: bpy.types.Object, vertex_indices=None):
|
|
mesh = bpy.data.meshes.new("test")
|
|
|
|
if vertex_indices is None:
|
|
vertex_indices = range(len(o.data.vertices))
|
|
|
|
length = len(vertices)
|
|
mesh.vertices.add(1 + length)
|
|
origin = mesh.vertices[0]
|
|
origin.co = Vector(light.location)
|
|
|
|
mesh.edges.add(length)
|
|
|
|
for i, v in enumerate(vertices):
|
|
v = o.data.vertices[ix]
|
|
world_v = Vector(o.matrix_world @ v.co)
|
|
mesh.vertices[1 + i].co = world_v
|
|
mesh.edges[i].vertices = [0, 1 + i]
|
|
|
|
object = bpy.data.objects.new("test", mesh)
|
|
|
|
bpy.context.scene.collection.objects.link(object)
|
|
|
|
def cast_ray(light, o, ix):
|
|
v = o.data.vertices[ix]
|
|
start = Vector(o.matrix_world @ v.co)
|
|
ray = start - light.location
|
|
#ray = Vector((0, 0, 0)) - light.location
|
|
ray.normalize()
|
|
end = start + (ray * 10)
|
|
return start, end
|
|
|
|
def shadow_volume_mesh_rays(light, o: bpy.types.Object, loop):
|
|
length = len(loop)
|
|
mesh = bpy.data.meshes.new("test")
|
|
#mesh.vertices.add(length * 2)
|
|
#mesh.edges.add(length)
|
|
|
|
vertices = [0] * length * 2
|
|
|
|
for i, ix in enumerate(loop):
|
|
start, end = cast_ray(light, o, ix)
|
|
vertices[i * 2 + 0] = start
|
|
vertices[i * 2 + 1] = end
|
|
|
|
bm = bmesh.new()
|
|
#bm.from_mesh(mesh)
|
|
for i in range(len(loop)):
|
|
i1 = i
|
|
i2 = (i + 1) % len(loop)
|
|
bm.faces.new([
|
|
bm.verts.new(vertices[i1 * 2 + 0]), # start
|
|
bm.verts.new(vertices[i2 * 2 + 0]), # start
|
|
bm.verts.new(vertices[i2 * 2 + 1]), # end
|
|
bm.verts.new(vertices[i1 * 2 + 1]), # end
|
|
])
|
|
bm.to_mesh(mesh)
|
|
bm.free()
|
|
object = bpy.data.objects.new("test", mesh)
|
|
|
|
bpy.context.scene.collection.objects.link(object)
|
|
|
|
def polygon_edges(l):
|
|
for i in range(len(l)):
|
|
j = (i + 1) % len(l)
|
|
yield frozenset((l[i], l[j]))
|
|
|
|
def polygons_by_edge_pairs(polygons):
|
|
pairs = defaultdict(list)
|
|
for i, polygon in enumerate(polygons):
|
|
for edge in polygon_edges(polygon.vertices):
|
|
pairs[edge].append(i)
|
|
return list(pairs.items())
|
|
|
|
def face_indicators(light, o: bpy.types.Object):
|
|
indicators = []
|
|
for i, normal in enumerate(o.data.polygon_normals):
|
|
n = o.matrix_world.to_3x3() @ normal.vector
|
|
n.normalize()
|
|
a = o.data.polygons[i].vertices[0]
|
|
v = o.matrix_world @ o.data.vertices[a].co
|
|
#d = v.dot(n)
|
|
#indicator = n.dot(light.location) + d
|
|
indicator = n.dot(light.location - v)
|
|
indicators.append(indicator)
|
|
return indicators
|
|
|
|
def edge_indices(o):
|
|
return {
|
|
frozenset(edge.vertices): i
|
|
for i, edge in enumerate(o.data.edges)
|
|
}
|
|
|
|
def object_silhouette(light, o: bpy.types.Object):
|
|
indicators = face_indicators(light, o)
|
|
edges = []
|
|
for edge, polygons in polygons_by_edge_pairs(o.data.polygons):
|
|
assert len(polygons) == 2, polygons
|
|
a, b = polygons
|
|
if (indicators[a] > 0) != (indicators[b] > 0):
|
|
edges.append(edge)
|
|
assert len(set(edges)) == len(edges)
|
|
return edges, indicators
|
|
|
|
def delete_test_objects(collection):
|
|
for o in collection.objects:
|
|
if o.name.startswith("test"):
|
|
collection.objects.unlink(o)
|
|
|
|
def edge_loop(edges):
|
|
loop = list(edges.pop())
|
|
while True:
|
|
for i, (a, b) in enumerate(edges):
|
|
if a == loop[-1]:
|
|
if b in loop:
|
|
return loop
|
|
loop.append(b)
|
|
elif b == loop[-1]:
|
|
if a in loop:
|
|
return loop
|
|
loop.append(a)
|
|
else:
|
|
continue
|
|
del edges[i]
|
|
break
|
|
else:
|
|
return None
|
|
|
|
def append(l, a):
|
|
if l[0] == -1:
|
|
assert l[1] == -1
|
|
l[0] = a
|
|
else:
|
|
assert l[1] == -1
|
|
l[1] = a
|
|
|
|
def make_list(length):
|
|
l = []
|
|
for i in range(length):
|
|
ll = [-1, -1]
|
|
l.append(ll)
|
|
return l
|
|
|
|
def edge_loop_graph(edges, num_vertices):
|
|
edges_by_vertices = make_list(num_vertices)
|
|
for i, (a, b) in enumerate(edges):
|
|
append(edges_by_vertices[a], i)
|
|
append(edges_by_vertices[b], i)
|
|
return edges_by_vertices
|
|
|
|
def neq(a, b, y):
|
|
assert a != -1
|
|
assert b != -1
|
|
if a == y:
|
|
assert b != y
|
|
return b
|
|
else:
|
|
assert a != y
|
|
return a
|
|
|
|
def edge_loop2_inner(edges, graph, ix, visited_edges):
|
|
loop = []
|
|
while True:
|
|
loop.append(ix)
|
|
visited_edges[ix] = True
|
|
|
|
a, b = edges[ix]
|
|
next_ix_a = neq(*graph[a], ix)
|
|
next_ix_b = neq(*graph[b], ix)
|
|
if not visited_edges[next_ix_a]:
|
|
ix = next_ix_a
|
|
continue
|
|
elif not visited_edges[next_ix_b]:
|
|
ix = next_ix_b
|
|
continue
|
|
else:
|
|
break
|
|
|
|
print("inner", loop)
|
|
return loop
|
|
|
|
def next_unvisited(visited):
|
|
for i, v in enumerate(visited):
|
|
if v == False:
|
|
return i
|
|
return -1
|
|
|
|
def edge_loop2(edges, graph):
|
|
visited_edges = [False] * len(edges)
|
|
loops = []
|
|
while True:
|
|
start = next_unvisited(visited_edges)
|
|
if start == -1:
|
|
break
|
|
loops.append(edge_loop2_inner(edges, graph, start, visited_edges))
|
|
return loops
|
|
|
|
def edge_loops(edges):
|
|
edges = list(edges)
|
|
loops = []
|
|
while edges:
|
|
loop = edge_loop(edges)
|
|
if loop is None:
|
|
break
|
|
loops.append(loop)
|
|
return loops
|
|
|
|
def object_end_caps(light, o: bpy.types.Object, indicators):
|
|
front = bpy.data.meshes.new("front")
|
|
back = bpy.data.meshes.new("back")
|
|
|
|
bm_front = bmesh.new()
|
|
bm_back = bmesh.new()
|
|
|
|
for i, polygon in enumerate(o.data.polygons):
|
|
assert len(polygon.vertices) == 4
|
|
a = o.matrix_world @ o.data.vertices[polygon.vertices[0]].co
|
|
b = o.matrix_world @ o.data.vertices[polygon.vertices[1]].co
|
|
c = o.matrix_world @ o.data.vertices[polygon.vertices[2]].co
|
|
d = o.matrix_world @ o.data.vertices[polygon.vertices[3]].co
|
|
if indicators[i] > 0:
|
|
face = [
|
|
bm_front.verts.new(a),
|
|
bm_front.verts.new(b),
|
|
bm_front.verts.new(c),
|
|
bm_front.verts.new(d),
|
|
]
|
|
bm_front.faces.new(face)
|
|
else:
|
|
#ray = Vector((0, 0, 0)) - light.location
|
|
ray_a = a - light.location
|
|
ray_a.normalize()
|
|
ray_b = b - light.location
|
|
ray_b.normalize()
|
|
ray_c = c - light.location
|
|
ray_c.normalize()
|
|
ray_d = d - light.location
|
|
ray_d.normalize()
|
|
face = [
|
|
bm_back.verts.new(a + (ray_a * 10)),
|
|
bm_back.verts.new(b + (ray_b * 10)),
|
|
bm_back.verts.new(c + (ray_c * 10)),
|
|
bm_back.verts.new(d + (ray_d * 10)),
|
|
]
|
|
bm_back.faces.new(face)
|
|
|
|
bm_front.to_mesh(front)
|
|
bm_front.free()
|
|
object_front = bpy.data.objects.new("test_front", front)
|
|
bpy.context.scene.collection.objects.link(object_front)
|
|
|
|
bm_back.to_mesh(back)
|
|
bm_back.free()
|
|
object_back = bpy.data.objects.new("test_back", back)
|
|
bpy.context.scene.collection.objects.link(object_back)
|
|
|
|
light = bpy.context.scene.objects['Light']
|
|
cube = bpy.context.scene.objects['Torus']
|
|
|
|
delete_test_objects(bpy.context.scene.collection)
|
|
edges, indicators = object_silhouette(light, cube)
|
|
|
|
object_end_caps(light, cube, indicators)
|
|
|
|
for loop in edge_loops(edges):
|
|
print("loop", len(loop))
|
|
shadow_volume_mesh_rays(light, cube, loop)
|
|
|
|
graph = edge_loop_graph(edges, len(cube.data.vertices))
|
|
|
|
"""
|
|
print(graph)
|
|
loops = edge_loop2(edges, graph)
|
|
obj = bpy.context.edit_object
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
for loop in loops:
|
|
for edge_ix in loop:
|
|
edge = edges[edge_ix]
|
|
for e in bm.edges:
|
|
if frozenset((e.verts[0].index, e.verts[1].index)) == frozenset(edge):
|
|
e.select = True
|
|
bmesh.update_edit_mesh(obj.data)
|
|
"""
|
|
|
|
"""
|
|
obj = bpy.context.edit_object
|
|
bm = bmesh.from_edit_mesh(obj.data)
|
|
bm.faces[4].select = True
|
|
bmesh.update_edit_mesh(obj.data)
|
|
"""
|