dreamcast/blender_shadow_volume.py

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)
"""