# Hasty conversion from the Rust in prosha/src/examples.rs & Barbs # The 'main' vertices are fine (easily verified by making barbs() # always exit early on the limit check) . Something is wrong with the # barbs. base_incr and barb_incr seem fine on their own. but even # the first iteration of barb() seems to produce garbage geometry. # Fairly sure self.sides is wrong. I've checked it against the Rust # repeatedly and my best guess is that the rotation matrices are being # constructed differently; I don't know what the Rust code uses (it's # an external library) whereas mine does it from quaternions. import numpy as np import xform # Mnemonics: X = np.array([1.0, 0.0, 0.0]) Y = np.array([0.0, 1.0, 0.0]) Z = np.array([0.0, 0.0, 1.0]) class Barbs(object): def __init__(self): # Incremental transform from each stage to the next: self.base_incr = (xform.Transform(). translate(0, 0, 1). rotate(Z, 0.15). rotate(X, 0.1). scale(0.95)) self.barb_incr = (xform.Transform(). translate(0, 0, 0.5). rotate(Y, -0.2). scale(0.8)) # 'Base' vertices, used throughout: self.base = np.array([ [-0.5, -0.5, 0.0], [-0.5, 0.5, 0.0], [ 0.5, 0.5, 0.0], [ 0.5, -0.5, 0.0], ]) # self.sides[i] gives transformation from a 'base' layer to # the i'th side (0 to 3): self.sides = [ xform.Transform(). rotate(Z, -i * np.pi/2). rotate(Y, -np.pi/2). translate(0.5, 0.0, 0.5) for i in range(4) ] # Face & vertex accumulators: self.faces = [] # self.faces will be a list of tuples (each one of length 4 # and containing indices into self.verts) self.verts = [] # self.verts will be a list of np.array of shape (3,) but will # be converted last-minute to tuples. (Why: we need to refer # to prior vertices and arithmetic is much easier from an # array, but Blender eventually needs tuples.) def run(self, iters) -> (list, list): # Make seed vertices, use them for 'bottom' face, and recurse: self.verts.extend(self.base) self.faces.append((0, 1, 2, 3)) self.main(iters, xform.Transform(), [0,1,2,3]) verts = [tuple(v) for v in self.verts] faces = [tuple(f) for f in self.faces] return verts, faces def limit_check(self, xform: xform.Transform, iters) -> bool: # Assume all scales are the same (for now) sx,_,_ = xform.get_scale() return sx < 0.005 # or iters <= 0 def main(self, iters, xform, bound): if self.limit_check(xform, iters): # Note opposite winding order verts = [bound[i] for i in [3,2,1,0]] self.faces.append(verts) return xform2 = xform.compose(self.base_incr) g = xform2.apply_to(self.base) a0 = len(self.verts) self.verts.extend(g) # TODO: Turn this to a cleaner loop? self.main(iters - 1, xform2, [a0, a0 + 1, a0 + 2, a0 + 3]) self.barb(iters - 1, xform.compose(self.sides[0]), [bound[0], bound[1], a0 + 1, a0 + 0]) self.barb(iters - 1, xform.compose(self.sides[1]), [bound[1], bound[2], a0 + 2, a0 + 1]) self.barb(iters - 1, xform.compose(self.sides[2]), [bound[2], bound[3], a0 + 3, a0 + 2]) self.barb(iters - 1, xform.compose(self.sides[3]), [bound[3], bound[0], a0 + 0, a0 + 3]) def barb(self, iters, xform, bound): # DEBUG: This is set True while testing until I figure out # other problems if True or self.limit_check(xform, iters): # Note opposite winding order verts = [bound[i] for i in [3,2,1,0]] self.faces.append(verts) return xform2 = xform.compose(self.barb_incr) g = xform2.apply_to(self.base) offset = len(self.verts) self.verts.extend(g) # Connect parallel faces: n = len(self.base) for i, b0 in enumerate(bound): j = (i + 1) % n b1 = bound[j] a0 = offset + i a1 = offset + j self.faces.append([a0, a1, b1, b0]) it2 = 1 # replace with iters-1 once fixed... self.barb(it2, xform2, [offset, offset + 1, offset + 2, offset + 3])