Add some Blender mesh generation scraps

This commit is contained in:
Chris Hodapp 2021-07-02 18:09:36 -04:00
parent 6eb77d7566
commit 7e5b4bed2d
2 changed files with 301 additions and 0 deletions

23
blender_scraps/loader.py Normal file
View File

@ -0,0 +1,23 @@
# Meant to be used with Blender's Python API
import sys
import importlib
import bpy
ext_path = "/home/hodapp/source/automata_scratch/blender_scraps"
if ext_path not in sys.path:
sys.path.append(ext_path)
import menger_cube_ish
menger_cube_ish = importlib.reload(menger_cube_ish)
v,f = menger_cube_ish.cube_iterate(4)
mesh = bpy.data.meshes.new('mesh_thing')
mesh.from_pydata(v, [], f)
mesh.update(calc_edges=True)
for edge in mesh.edges:
v = list(edge.vertices)
edge.crease = 0.9
obj = bpy.data.objects.new('obj_thing', mesh)
bpy.context.scene.collection.objects.link(obj)

View File

@ -0,0 +1,278 @@
# 2021-06-28, Chris Hodapp
import numpy as np
# Run this in Blender. See something like loader.py.
# This is a royal clusterfucked jumble *but* it generates a cube
# something like the first stage of a Menger sponge. There is a lot
# of extra work being done here.
# Known bugs:
# - Doesn't set the crease properly always?
# - I need to properly denote which faces *don't* need built
# (but I still need to run most code paths on all faces because
# of the structures they build up)
# - I handle only the main 4 vertices and no others
# Adjust 'f' to set the size of the central hole. (f=1/3 is 'normal')
#f = 1/4
def cube_thing(vert_accum, face_accum, base_idxs, vert_between=None, f=1/4):
xform = lambda v: v + np.array([2.0, 0.0, 0.0])
v0,v1,v2,v3 = base_idxs
v4 = len(vert_accum)
v5 = v4+1
v6 = v5+1
v7 = v6+1
vert_accum.extend([xform(vert_accum[i]) for i in base_idxs])
faces = [
(v0,v1,v2,v3),
(v4,v5,v1,v0),
(v5,v6,v2,v1),
(v7,v6,v5,v4),
(v3,v2,v6,v7),
(v0,v3,v7,v4),
]
# key = vertex, value = list of adjacent faces (counter-clockwise)
vert_to_faces = {
v0: [5,1,0],
v1: [1,2,0],
v2: [0,2,4],
v3: [0,4,5],
v4: [3,1,5],
v5: [2,1,3],
v6: [4,2,3],
v7: [4,3,5],
}
# key = (vert idx 1, vert idx 2).
# value = index of vertex factor 'f' between 1 & 2.
# (key is *not* symmetrical!)
if vert_between is None:
vert_between = {}
# faces_inner_vertex[i] will have the corresponding face-split vertices
# nearest to the vertices in faces[i]
face_inner_vertex = [None for _ in faces]
for f_idx, vert_idxs in enumerate(faces):
n = len(vert_idxs)
face_inner_vertex[f_idx] = [None]*len(vert_idxs)
# Split each edge:
print(f"{vert_idxs}:")
for i, v in enumerate(vert_idxs):
# v = 'current' vertex index.
# vp = 'previous' vertex, vn = 'next' vertex:
vp = vert_idxs[(i + n - 1) % n]
vn = vert_idxs[(i + 1) % n]
coord_v = vert_accum[v]
coord_vp = vert_accum[vp]
coord_vn = vert_accum[vn]
print(f"{v}:{coord_v} {vp}:{coord_vp} {vn}:{coord_vn}")
# Go f of way from v to vn:
if (v,vn) not in vert_between:
coord_vnf = (1-f)*coord_v + f*coord_vn
vert_between[(v, vn)] = len(vert_accum)
vert_accum.append(coord_vnf)
# and from v to vp:
if (v,vp) not in vert_between:
coord_vpf = (1-f)*coord_v + f*coord_vp
vert_between[(v, vp)] = len(vert_accum)
vert_accum.append(coord_vpf)
# Now, go 'across' those new edge splits too.
# In particular, if starting at vertex i, and vertex j is next,
# then join between:
# - i's split to its *previous* vertex
# - j's split to its *next* vertex
for i, vi in enumerate(vert_idxs):
j = (i + 1) % n
vj = vert_idxs[j]
vi_prev = vert_idxs[(i + n - 1) % n]
vj_next = vert_idxs[(j + 1) % n]
vi_split = vert_between[(vi, vi_prev)]
vj_split = vert_between[(vj, vj_next)]
coord = (1-f)*vert_accum[vi_split] + f*vert_accum[vj_split]
# vb = our new vertex between vi_split & vj_split:
vb = len(vert_accum)
vert_between[(vi_split, vj_split)] = vb
face_inner_vertex[f_idx][i] = vb
vert_accum.append(coord)
# note that this is also between two others:
vert_between[(vert_between[(vi,vj)], vert_between[(vi_prev,vj_next)])] = vb
# As we do this, make corner faces, because we know the vertices:
face_accum.append((vi, vert_between[(vi, vj)], vb, vi_split))
# and make faces between those:
for i, vi in enumerate(vert_idxs):
vi_next = vert_idxs[(i + 1) % n]
vi_prev = vert_idxs[(i + n - 1) % n]
vi_prev2 = vert_idxs[(i + n - 2) % n]
vi_split = vert_between[(vi, vi_next)]
vi_split2 = vert_between[(vi_prev, vi_prev2)]
vb_i = vert_between[(vi_split, vi_split2)]
vb_i2 = vert_between[(vi_split2, vi_split)]
face_accum.append((
vert_between[(vi, vi_prev)],
vb_i,
vb_i2,
vert_between[(vi_prev, vi)],
))
# key = corner vertex index, value = nearest 'inside' corner vertex
# (i.e. of the 8 'inside' vertices, the nearest to this corner -
# always diagonally away through the cube's interior)
vertex_to_inner = {}
# face_idx_opp[i] gives the face that is *opposite* face i
face_idx_opp = [None for _ in faces]
# face_remap_opp[i] gives the vertices for face_idx_opp[i] -
# as indices into faces[face_idx_opp[i]], and in an order that moves
# in lockstep with faces[i], i.e. opposite winding order and always with
# 'opposite' vertices at the same index.
face_remap_opp = [None for _ in faces]
# TODO: Feels a little wrong to hard-code?
for vi in range(8):
v = list(vert_to_faces.keys())[vi]
# f_inc = all faces incident on corner v
f_inc = vert_to_faces[v]
for i,f0_idx in enumerate(f_inc):
# Look at the *other* two incident faces:
f1_idx = f_inc[(i + 1) % 3]
f2_idx = f_inc[(i - 1) % 3]
f1 = faces[f1_idx]
f2 = faces[f2_idx]
# Find v in both these faces:
v_idx1 = f1.index(v)
v_idx2 = f2.index(v)
# We want the 'opposite' vertex: one adjacent to v, but not in face f0.
# It's adjacent to v in both f1 and f2, so choices are limited.
v1p = f1[(v_idx1 - 1) % len(f1)]
v1n = f1[(v_idx1 + 1) % len(f1)]
v2p = f2[(v_idx2 - 1) % len(f2)]
v2n = f2[(v_idx2 + 1) % len(f2)]
for v1 in (v1p, v1n):
for v2 in (v2p, v2n):
if v1 == v2:
v_opp = v1
# Find the 'opposite' face to f0 too. (v_opp is incident on it,
# but it's the only face that isn't f1 or f2).
f_inc2 = vert_to_faces[v_opp]
for f_try in f_inc2:
if f_try == f1_idx or f_try == f2_idx:
continue
f_opp_idx = f_try
face_idx_opp[f0_idx] = f_opp_idx
face_idx_opp[f_opp_idx] = f0_idx
# Since they are *opposite* faces but they have consistent winding order,
# if we start at corresponding vertices (i.e. v and v_opp) and walk in
# *opposite* directions, we will walk to corresponding vertices.
f0 = faces[f0_idx]
f_opp = faces[f_opp_idx]
n = len(f0)
v_idx = f0.index(v)
v_opp_idx = f_opp.index(v_opp)
face_remap_opp[f0_idx] = [(v_opp_idx + v_idx - j) % n for j in range(n)]
# I don't even know... mark out the redundant ways of reaching the
# same 8 inner vertices.
for vi in range(8):
v = list(vert_to_faces.keys())[vi]
# f_inc = all faces incident on corner v
f_inc = vert_to_faces[v]
for k,f0_idx in enumerate(f_inc):
# Again, for some face incident on v, find the opposite face and
# walk around its corresponding opposite vertices.
f0 = faces[f0_idx]
f_opp_idx = face_idx_opp[f0_idx]
remap = face_remap_opp[f0_idx]
f_opp = [faces[f_opp_idx][j] for j in remap]
# That is, f0[i] and f_opp[i] are opposite vertices (they have one
# edge between them and sit on opposite faces).
print(f"face {f0_idx}: {f0} opposite {f_opp}")
# Now, translate this to their corresponding nearest face-split vertices
# on their respective faces.
f0_split = face_inner_vertex[f0_idx]
f_opp_split = [face_inner_vertex[f_opp_idx][j] for j in remap]
print(f"face {f0_idx} inner: {f0_split} opposite {f_opp_split}")
# Make 'inner' vertices nearest f0_split & f_opp_split:
for i,(m,n) in enumerate(zip(f0_split, f_opp_split)):
equiv = [(m,n)]
# Find 'neighbor' faces to the i'th vertex:
for f1_idx in vert_to_faces[f0[i]]:
if f1_idx == f0_idx:
# Ignore the same face.
continue
f1_opp_idx = face_idx_opp[f1_idx]
print(f"f0_idx={f0_idx}, f0[i]={f0[i]}, f1_idx={f1_idx} opposite={f1_opp_idx}")
# Find f0[i] in that neighbor face f1...
f1_v_idx = faces[f1_idx].index(f0[i])
# ...so we can find this neighbor face's face-split vertex
# that is nearest v:
v_f1_split = face_inner_vertex[f1_idx][f1_v_idx]
# and then find the vertex across from that:
remap2 = face_remap_opp[f1_idx][f1_v_idx]
v_opp_split = face_inner_vertex[f1_opp_idx][remap2]
print(f" nearest split vertex: {v_f1_split}, opp {v_opp_split}")
equiv.append((v_f1_split, v_opp_split))
found = None
for m2,n2 in equiv:
if (m2,n2) in vert_between:
found = vert_between[(m2,n2)]
break
if found is None:
vert = (1-f)*vert_accum[m] + f*vert_accum[n]
found = len(vert_accum)
vert_accum.append(vert)
for m2,n2 in equiv:
vert_between[(m2,n2)] = found
print(f"equivalent between verts {m},{n}: {equiv}")
# Pick a vertex:
vi = 3
v = list(vert_to_faces.keys())[vi]
f_inc = vert_to_faces[v]
for k,f0_idx in enumerate(f_inc):
# Again, for some face incident on v, find the opposite face and
# walk around its corresponding opposite vertices.
f0 = faces[f0_idx]
f_opp_idx = face_idx_opp[f0_idx]
remap = face_remap_opp[f0_idx]
f_opp = [faces[f_opp_idx][j] for j in remap]
# That is, f0[i] and f_opp[i] are opposite vertices (they have one
# edge between them and sit on opposite faces).
# Now, translate this to their corresponding nearest face-split vertices
# on their respective faces.
f0_split = face_inner_vertex[f0_idx]
f_opp_split = [face_inner_vertex[f_opp_idx][j] for j in remap]
for i,_ in enumerate(f0):
j = (i + 1) % len(f0)
face_accum.append((
f0_split[i],
f0_split[j],
vert_between[(f0_split[j], f_opp_split[j])],
vert_between[(f0_split[i], f_opp_split[i])],
))
face_accum.append((
f_opp_split[j],
f_opp_split[i],
vert_between[(f_opp_split[i], f0_split[i])],
vert_between[(f_opp_split[j], f0_split[j])],
))
return vert_accum, face_accum, (v4,v5,v6,v7), vert_between
base_verts = [
(-1, -1, -1),
(-1, -1, 1),
(-1, 1, 1),
(-1, 1, -1),
]
base_verts = [np.array(t) for t in base_verts]
def cube_iterate(count=4):
f = []
btw = {}
v = base_verts.copy()
idxs = [i for i,_ in enumerate(base_verts)]
for i in range(count):
print(i)
v,f,idxs,btw = cube_thing(v, f, idxs, btw)
v = [tuple(i) for i in v]
return v,f