def dual_contour_2d_find_best_vertex(f, f_normal, x, y): if not ADAPTIVE: return V2(x + 0.5, y + 0.5) # Evaluate x0y0 = f(x + 0.0, y + 0.0) x0y1 = f(x + 0.0, y + 1.0) x1y0 = f(x + 1.0, y + 0.0) x1y1 = f(x + 1.0, y + 1.0) # For each edge, identify where there is a sign change changes = [] if (x0y0 > 0) != (x0y1 > 0): changes.append([x + 0, y + adapt(x0y0, x0y1)]) if (x1y0 > 0) != (x1y1 > 0): changes.append([x + 1, y + adapt(x1y0, x1y1)]) if (x0y0 > 0) != (x1y0 > 0): changes.append([x + adapt(x0y0, x1y0), y + 0]) if (x0y1 > 0) != (x1y1 > 0): changes.append([x + adapt(x0y1, x1y1), y + 1]) if len(changes) <= 1: return None # For each sign change location v[i], we find the normal n[i]. normals = [] for v in changes: n = f_normal(v[0], v[1]) normals.append([n.x, n.y]) v = solve_qef_2d(x, y, changes, normals) return v
def dual_contour_3d_find_best_vertex(f, f_normal, x, y, z): if not ADAPTIVE: return V3(x + 0.5, y + 0.5, z + 0.5) # Evaluate f at each corner v = np.empty((2, 2, 2)) for dx in (0, 1): for dy in (0, 1): for dz in (0, 1): v[dx, dy, dz] = f(x + dx, y + dy, z + dz) # For each edge, identify where there is a sign change. # There are 4 edges along each of the three axes changes = [] for dx in (0, 1): for dy in (0, 1): if (v[dx, dy, 0] > 0) != (v[dx, dy, 1] > 0): changes.append( (x + dx, y + dy, z + adapt(v[dx, dy, 0], v[dx, dy, 1]))) for dx in (0, 1): for dz in (0, 1): if (v[dx, 0, dz] > 0) != (v[dx, 1, dz] > 0): changes.append( (x + dx, y + adapt(v[dx, 0, dz], v[dx, 1, dz]), z + dz)) for dy in (0, 1): for dz in (0, 1): if (v[0, dy, dz] > 0) != (v[1, dy, dz] > 0): changes.append( (x + adapt(v[0, dy, dz], v[1, dy, dz]), y + dy, z + dz)) if len(changes) <= 1: return None # For each sign change location v[i], we find the normal n[i]. # The error term we are trying to minimize is sum( dot(x-v[i], n[i]) ^ 2) # In other words, minimize || A * x - b || ^2 where A and b are a matrix and vector # derived from v and n normals = [] for v in changes: n = f_normal(v[0], v[1], v[2]) normals.append([n.x, n.y, n.z]) return solve_qef_3d(x, y, z, changes, normals)
def edge_to_boundary_vertex(edge): """Returns the vertex in the middle of the specified edge""" # Find the two vertices specified by this edge, and interpolate between # them according to adapt, as in the 2d case v0, v1 = EDGES[edge] f0 = f_eval[v0] f1 = f_eval[v1] t0 = 1 - adapt(f0, f1) t1 = 1 - t0 vert_pos0 = VERTICES[v0] vert_pos1 = VERTICES[v1] return V3(x + vert_pos0[0] * t0 + vert_pos1[0] * t1, y + vert_pos0[1] * t0 + vert_pos1[1] * t1, z + vert_pos0[2] * t0 + vert_pos1[2] * t1)
case = ((1 if x0y0 > 0 else 0) + (2 if x0y1 > 0 else 0) + (4 if x1y0 > 0 else 0) + (8 if x1y1 > 0 else 0)) # Several of the cases are inverses of each other where solid is non solid and visa versa # They have the same boundary, which cuts down the cases a bit. # But we swap the direction of the boundary, so that edges are always winding clockwise around the solid. # Getting those swaps correct isn't needed for our simple visualizations, but is important in other uses cases # particularly in 3d. if case is 0 or case is 15: # All outside / inside return [] if case is 1 or case is 14: # Single corner return [ Edge(V2(x + 0 + adapt(x0y0, x1y0), y), V2(x + 0, y + adapt(x0y0, x0y1))).swap(case is 14) ] if case is 2 or case is 13: # Single corner return [ Edge(V2(x + 0, y + adapt(x0y0, x0y1)), V2(x + adapt(x0y1, x1y1), y + 1)).swap(case is 11) ] if case is 4 or case is 11: # Single corner return [ Edge(V2(x + 1, y + adapt(x1y0, x1y1)), V2(x + adapt(x0y0, x1y0), y + 0)).swap(case is 13) ] if case is 8 or case is 7: