def test_spline_fitting(self): # Test a real spline interpolation xs = np.linspace(0.0, 2 * math.pi, 100) ys = np.array([math.sin(x) for x in xs]) points = [geom.Vertex((xs[i], ys[i], 0.0)) for i in range(len(xs))] cpoints_chunks = fit.fit_bezier_spline(points, mmath.interp_bezier_curve_2, 2) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [geom.BaseCurve(list(verts), mmath.interp_bezier_curve_2)] curve = geom.SuperCurve(curves) # Compute the current spline spline_ts = np.linspace(0.0, 1.0, 100) spline_points = [curve.t(t) for t in spline_ts] spline_xs = [p.x for p in spline_points] spline_ys = [p.y for p in spline_points] # Compute the control points cpointsx = [cp[0] for cp in cpoints] cpointsy = [cp[1] for cp in cpoints] # Actually plot the graph if DRAW_GRAPHS: plt.plot(spline_xs, spline_ys) # Approximate function plt.plot(xs, ys) # Original function plt.plot(cpointsx, cpointsy, "o") plt.show()
def plot_beizer_spline_2d(points, function, n, figname, steps=100): points_xs = [p[0] for p in points] points_ys = [p[1] for p in points] cpoints_chunks = fit.fit_bezier_spline(points, function, n) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [geom.BaseCurve(list(verts), function)] curve = geom.SuperCurve(curves) spline_ts = np.linspace(0.0, 1.0, steps) spline_points = [curve.t(t) for t in spline_ts] curve_xs = [p.x for p in spline_points] curve_ys = [p.y for p in spline_points] # Compute the control points cpoints_xs = [cp[0] for cp in cpoints] cpoints_ys = [cp[1] for cp in cpoints] plt.plot(points_xs, points_ys, "o", color="blue", label="Input points") plt.plot(cpoints_xs, cpoints_ys, "o", color="red", label="Control points") plt.plot(curve_xs, curve_ys, color="green", label="Bezier spline") plt.grid() plt.legend() plt.savefig(OUT_GRAPH_DIR + figname) plt.show()
def plot_bezier_spline_3d(points, function, n, figname, steps=100): cpoints_chunks = fit.fit_bezier_spline(points, mmath.bezier_curve_3, 5) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [geom.BaseCurve(list(verts), mmath.bezier_curve_3)] curve = geom.SuperCurve(curves) # Compute the current spline spline_ts = np.linspace(0.0, 1.0, steps) spline_points = [curve.t(t) for t in spline_ts] spline_xs = [p.x for p in spline_points] spline_ys = [p.y for p in spline_points] spline_zs = [p.z for p in spline_points] # Compute the current control points cpointsx = [cp[0] for cp in cpoints] cpointsy = [cp[1] for cp in cpoints] cpointsz = [cp[2] for cp in cpoints] # Draw the graph fig = plt.figure() ax = fig.gca(projection='3d') ax.plot([p[0] for p in points], [p[1] for p in points], [p[2] for p in points], "o", color="blue", label="Input points") ax.plot(spline_xs, spline_ys, spline_zs, color="green", label='Bezier spline') ax.plot(cpointsx, cpointsy, cpointsz, "o", color="red", label="Control points") plt.grid() plt.legend() plt.savefig(OUT_GRAPH_DIR + figname) plt.show()
def plot_beizer_spline_2d(points, function, n, figname, steps=100): points_xs = [p[0] for p in points] points_ys = [p[1] for p in points] cpoints_chunks = fit.fit_bezier_spline(points, function, n) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [geom.BaseCurve(list(verts), function)] curve = geom.SuperCurve(curves) spline_ts = np.linspace(0.0, 1.0, steps) spline_points = [curve.t(t) for t in spline_ts] curve_xs = [p.x for p in spline_points] curve_ys = [p.y for p in spline_points] # Compute the control points cpoints_xs = [cp[0] for cp in cpoints] cpoints_ys = [cp[1] for cp in cpoints] plt.plot(points_xs, points_ys, "o", color = "blue", label = "Input points") plt.plot(cpoints_xs, cpoints_ys, "o", color = "red", label = "Control points") plt.plot(curve_xs, curve_ys, color = "green", label = "Bezier spline") plt.grid() plt.legend() plt.savefig(OUT_GRAPH_DIR + figname) plt.show()
def test_spline_fitting(self): # Test a real spline interpolation xs = np.linspace(0.0, 2 * math.pi, 100) ys = np.array([math.sin(x) for x in xs]) points = [geom.Vertex((xs[i], ys[i], 0.0)) for i in range(len(xs))] cpoints_chunks = fit.fit_bezier_spline(points, mmath.interp_bezier_curve_2, 2) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [ geom.BaseCurve(list(verts), mmath.interp_bezier_curve_2) ] curve = geom.SuperCurve(curves) # Compute the current spline spline_ts = np.linspace(0.0, 1.0, 100) spline_points = [curve.t(t) for t in spline_ts] spline_xs = [p.x for p in spline_points] spline_ys = [p.y for p in spline_points] # Compute the control points cpointsx = [cp[0] for cp in cpoints] cpointsy = [cp[1] for cp in cpoints] # Actually plot the graph if DRAW_GRAPHS: plt.plot(spline_xs, spline_ys) # Approximate function plt.plot(xs, ys) # Original function plt.plot(cpointsx, cpointsy, "o") plt.show()
def test_spline_fitting_3d(self): """Fit a complex 3d curve with a bezier spline of degree three (5 curves).""" # Generate a cool 3d curve theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) zs = np.linspace(-2, 2, 100) r = zs ** 2 + 1 xs = r * np.sin(theta) ys = r * np.cos(theta) # Fit the data points = list(zip(xs, ys, zs)) cpoints_chunks = fit.fit_bezier_spline(points, mmath.bezier_curve_3, 5) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [geom.BaseCurve(list(verts), mmath.bezier_curve_3)] curve = geom.SuperCurve(curves) # Compute the current spline spline_ts = np.linspace(0.0, 1.0, 100) spline_points = [curve.t(t) for t in spline_ts] spline_xs = [p.x for p in spline_points] spline_ys = [p.y for p in spline_points] spline_zs = [p.z for p in spline_points] # Compute the current control points cpointsx = [cp[0] for cp in cpoints] cpointsy = [cp[1] for cp in cpoints] cpointsz = [cp[2] for cp in cpoints] # Draw the graph if DRAW_GRAPHS: fig = plt.figure() ax = fig.gca(projection="3d") ax.plot(xs, ys, zs, label="parametric curve") ax.plot(spline_xs, spline_ys, spline_zs, label="Fitted spline") ax.plot(cpointsx, cpointsy, cpointsz, "o", label="Control points") plt.legend() plt.show()
def test_spline_fitting_3d(self): '''Fit a complex 3d curve with a bezier spline of degree three (5 curves).''' # Generate a cool 3d curve theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) zs = np.linspace(-2, 2, 100) r = zs**2 + 1 xs = r * np.sin(theta) ys = r * np.cos(theta) # Fit the data points = list(zip(xs, ys, zs)) cpoints_chunks = fit.fit_bezier_spline(points, mmath.bezier_curve_3, 5) curves = [] cpoints = [] for chunk in cpoints_chunks: cpoints += chunk verts = map(lambda x: geom.Vertex(x), chunk) curves += [geom.BaseCurve(list(verts), mmath.bezier_curve_3)] curve = geom.SuperCurve(curves) # Compute the current spline spline_ts = np.linspace(0.0, 1.0, 100) spline_points = [curve.t(t) for t in spline_ts] spline_xs = [p.x for p in spline_points] spline_ys = [p.y for p in spline_points] spline_zs = [p.z for p in spline_points] # Compute the current control points cpointsx = [cp[0] for cp in cpoints] cpointsy = [cp[1] for cp in cpoints] cpointsz = [cp[2] for cp in cpoints] # Draw the graph if DRAW_GRAPHS: fig = plt.figure() ax = fig.gca(projection='3d') ax.plot(xs, ys, zs, label='parametric curve') ax.plot(spline_xs, spline_ys, spline_zs, label='Fitted spline') ax.plot(cpointsx, cpointsy, cpointsz, "o", label="Control points") plt.legend() plt.show()
def run(bm): '''Run the algorithm on the given bmesh (blender mesh), returns the patches (TODO in a data structure we still need to decide)''' verts_list = list(bm.verts) faces_list = list(bm.faces) verts_indexes = [v.index for v in verts_list] # Extract the skeleton mesh. patches, macro_edges, singular_verts = extract_base_mesh(bm) def calculate_patches(macro_edges, bm): boundaries = set(sum(macro_edges, ())) # Now we need to understand which vertex belong to which face. We use the connected components approach. patch_verts_attribution = partition_mesh_vertices(verts_indexes, boundaries, bm) # Now we need to understand which superedges belong to which face. patches = compute_patch_edges(patch_verts_attribution, macro_edges) # We now need to reorder the vertices of each face so that we can build a spline on them. for i, part in enumerate(patches): try: patches[i] = reorder_patch_edges(part) except: patches[i] = None # Filter empty and quadrangualte. patches = [p for p in patches if p] patches = quadrangulate_patches_keep_separate_edges(patches, verts_list) return patch_verts_attribution, patches MIN_VERTS = 20 threshold = PATCH_THRESHOLD * size_estimate(bm) print("Using Threshold:", threshold) def is_patch_big_enough(patch): '''Returns if all the edges of the patch are big enough for it to be splitted.''' return all(len(edge) >= 5 for edge in flat_patch_edges(patch)) def can_simplify(patch_verts): '''Returns wheter the patch contains enough point to be simplified.''' return patch_verts and len(patch_verts) > MIN_VERTS patch_verts_attribution, patches = calculate_patches(macro_edges, bm) skip_patches = [] # Now that we defined the patches we should iterate to improve the error. result = [] patch_improved = True #patch_improved = False while patch_improved: patch_improved = False print("Number of patches", len(patches)) for i,current_patch in enumerate(patches): current_attr = patch_verts_attribution[i] if all([current_patch not in skip_patches, can_simplify(current_attr), is_patch_big_enough(current_patch), compute_patch_error(flat_patch_edges(current_patch), bm, current_attr) > threshold]): #pr("ERROR", compute_patch_error(current_patch, bm, current_attr)) try: old_edges = sum(current_patch,[]) new_edges = split_patch_4(current_patch, current_attr, bm) new_macro_edges = set(macro_edges) for edge in old_edges: if edge in new_macro_edges: new_macro_edges.remove(edge) if edge[::-1] in macro_edges: new_macro_edges.remove(edge[::-1]) for edge in new_edges: if edge[::-1] not in new_macro_edges: new_macro_edges.add(edge) patch_verts_attribution, patches = calculate_patches(new_macro_edges, bm) macro_edges = new_macro_edges patch_improved = True break except Exception as e: print("Patch discarded") skip_patches += [current_patch] result = patches #We need to fit the patch edges with lower degree curves #edges = set(sum(result, [])) edges = set(sum(sum(result, []), [])) edges = sorted(edges, key=len) edge_correspondence = {} old_verts = [geom.Vertex((v.co.x, v.co.y, v.co.z)) for v in verts_list] # TODO Disable this. Returns the patches without the edge fitting. #return old_verts, result, old_verts, result # Output values new_verts = [] new_edges = [] new_patches = [] SAMPLES = 20 spline_threshold = SPLINE_THRESHOLD * size_estimate(bm) THRESHOLD_DISTANCE = VERTEX_MERGE_THRESHOLD * size_estimate(bm) edge_conversion = {} def merge_all_edges(edges): if not edges: return [] result = edges[0] for e in edges[1:]: result += e[1:] return tuple(result) for i,edge in enumerate(edges): if edge_conversion.get(edge): pass else: cpoints = tuple([old_verts[i] for i in edge]) def squash_chunks(chunks): if len(chunks) == 1: return chunks[0] return chunks[0][:-1] + squash_chunks(chunks[1:]) new_cpoints = [] error = float("inf") n_splines = 1 try: while error > spline_threshold: cpoints_chunks = fit.fit_bezier_spline(cpoints, mmath.interp_bezier_curve_2, n_splines) verts_chunks = [[geom.Vertex(p) for p in chunk] for chunk in cpoints_chunks] verts_chunks[0][0] = cpoints[0] verts_chunks[-1][-1] = cpoints[-1] # Increase the precision at the next step n_splines += 1 # We need to use the original extremes control points to ensure continuity new_cpoints = squash_chunks(verts_chunks) # Check the error and fallback to default spline if issues new_curve = geom.generate_spline(new_cpoints, mmath.interp_bezier_curve_2) new_curve_points = list(geom.sample_curve_samples(new_curve, len(cpoints))) error = errors.simple_max_error(new_curve_points, cpoints) except: new_cpoints = cpoints print("Compressing edge: %s/%s. n.verts: %s -> %s" % (i+1, len(edges), len(cpoints), len(new_cpoints))) new_verts += new_cpoints new_verts_indexes = tuple([new_verts.index(v) for v in new_cpoints]) edge_conversion[edge] = new_verts_indexes edge_conversion[edge[::-1]] = new_verts_indexes[::-1] final_patches = [] # Convert the edges with the approximated ones. for patch in result: converted_edges = [[edge_conversion[edge] for edge in edges] for edges in patch] final_patches += [flat_patch_edges(converted_edges)] # TODO Remember to disable this. #return new_verts, final_patches, new_verts, final_patches def generate_convert(threshold_index, prev_index): def convert(index): if index < threshold_index: return index elif index == threshold_index: return prev_index else: return index - 1 return convert def update_edge(e, convert): return tuple([convert(i) for i in e]) def update_patch(p, convert): return [update_edge(e, convert) for e in p] def my_index(l, element): """Returns the element in the list, otherwise -1""" return l.index(element) if element in l else -1 def my_index_closest(l, element): temp = list(l) mod = lambda x: x[0] * x[0] + x[1] * x[1] + x[2] * x[2] closest = sorted(temp, key=lambda x: mod(x - element))[0] return l.index(closest) if mod(element - closest) < THRESHOLD_DISTANCE else -1 def recursive_max(l): if isinstance(l, (int, float)): return l return max((recursive_max(e) for e in l)) # Filter out vertices which are not unique. final_verts = [] for i, vert in enumerate(new_verts): index = my_index_closest(final_verts, vert) if final_verts else -1 if index == -1: final_verts += [vert] else: # The element is not in the list. This means that we need to decrement all the indexes and replace i with index. convert_func = generate_convert(len(final_verts), index) for i, p in enumerate(final_patches): final_patches[i] = update_patch(p, convert_func) input_size = compute_input_mesh_size(bm) output_size = compute_output_mesh_size(final_patches) print("Statistics ------------------------------------------") print("Input Vertices: ", len(verts_list)) print("Input Faces: ", len(faces_list)) print("Output Vertices: ", len(final_verts)) print("Output Faces: ", len(final_patches)) print("Input Size: ", input_size) print("Output Size: ", output_size) print("Compression Ratio:", (input_size / output_size)) print("-----------------------------------------------------") old_pathces = [flat_patch_edges(p) for p in result] return old_verts, old_pathces, final_verts, final_patches
def run(bm): '''Run the algorithm on the given bmesh (blender mesh), returns the patches (TODO in a data structure we still need to decide)''' verts_list = list(bm.verts) faces_list = list(bm.faces) verts_indexes = [v.index for v in verts_list] # Extract the skeleton mesh. patches, macro_edges, singular_verts = extract_base_mesh(bm) def calculate_patches(macro_edges, bm): boundaries = set(sum(macro_edges, ())) # Now we need to understand which vertex belong to which face. We use the connected components approach. patch_verts_attribution = partition_mesh_vertices( verts_indexes, boundaries, bm) # Now we need to understand which superedges belong to which face. patches = compute_patch_edges(patch_verts_attribution, macro_edges) # We now need to reorder the vertices of each face so that we can build a spline on them. for i, part in enumerate(patches): try: patches[i] = reorder_patch_edges(part) except: patches[i] = None # Filter empty and quadrangualte. patches = [p for p in patches if p] patches = quadrangulate_patches_keep_separate_edges( patches, verts_list) return patch_verts_attribution, patches MIN_VERTS = 20 threshold = PATCH_THRESHOLD * size_estimate(bm) print("Using Threshold:", threshold) def is_patch_big_enough(patch): '''Returns if all the edges of the patch are big enough for it to be splitted.''' return all(len(edge) >= 5 for edge in flat_patch_edges(patch)) def can_simplify(patch_verts): '''Returns wheter the patch contains enough point to be simplified.''' return patch_verts and len(patch_verts) > MIN_VERTS patch_verts_attribution, patches = calculate_patches(macro_edges, bm) skip_patches = [] # Now that we defined the patches we should iterate to improve the error. result = [] patch_improved = True #patch_improved = False while patch_improved: patch_improved = False print("Number of patches", len(patches)) for i, current_patch in enumerate(patches): current_attr = patch_verts_attribution[i] if all([ current_patch not in skip_patches, can_simplify(current_attr), is_patch_big_enough(current_patch), compute_patch_error(flat_patch_edges(current_patch), bm, current_attr) > threshold ]): #pr("ERROR", compute_patch_error(current_patch, bm, current_attr)) try: old_edges = sum(current_patch, []) new_edges = split_patch_4(current_patch, current_attr, bm) new_macro_edges = set(macro_edges) for edge in old_edges: if edge in new_macro_edges: new_macro_edges.remove(edge) if edge[::-1] in macro_edges: new_macro_edges.remove(edge[::-1]) for edge in new_edges: if edge[::-1] not in new_macro_edges: new_macro_edges.add(edge) patch_verts_attribution, patches = calculate_patches( new_macro_edges, bm) macro_edges = new_macro_edges patch_improved = True break except Exception as e: print("Patch discarded") skip_patches += [current_patch] result = patches #We need to fit the patch edges with lower degree curves #edges = set(sum(result, [])) edges = set(sum(sum(result, []), [])) edges = sorted(edges, key=len) edge_correspondence = {} old_verts = [geom.Vertex((v.co.x, v.co.y, v.co.z)) for v in verts_list] # TODO Disable this. Returns the patches without the edge fitting. #return old_verts, result, old_verts, result # Output values new_verts = [] new_edges = [] new_patches = [] SAMPLES = 20 spline_threshold = SPLINE_THRESHOLD * size_estimate(bm) THRESHOLD_DISTANCE = VERTEX_MERGE_THRESHOLD * size_estimate(bm) edge_conversion = {} def merge_all_edges(edges): if not edges: return [] result = edges[0] for e in edges[1:]: result += e[1:] return tuple(result) for i, edge in enumerate(edges): if edge_conversion.get(edge): pass else: cpoints = tuple([old_verts[i] for i in edge]) def squash_chunks(chunks): if len(chunks) == 1: return chunks[0] return chunks[0][:-1] + squash_chunks(chunks[1:]) new_cpoints = [] error = float("inf") n_splines = 1 try: while error > spline_threshold: cpoints_chunks = fit.fit_bezier_spline( cpoints, mmath.interp_bezier_curve_2, n_splines) verts_chunks = [[geom.Vertex(p) for p in chunk] for chunk in cpoints_chunks] verts_chunks[0][0] = cpoints[0] verts_chunks[-1][-1] = cpoints[-1] # Increase the precision at the next step n_splines += 1 # We need to use the original extremes control points to ensure continuity new_cpoints = squash_chunks(verts_chunks) # Check the error and fallback to default spline if issues new_curve = geom.generate_spline( new_cpoints, mmath.interp_bezier_curve_2) new_curve_points = list( geom.sample_curve_samples(new_curve, len(cpoints))) error = errors.simple_max_error(new_curve_points, cpoints) except: new_cpoints = cpoints print("Compressing edge: %s/%s. n.verts: %s -> %s" % (i + 1, len(edges), len(cpoints), len(new_cpoints))) new_verts += new_cpoints new_verts_indexes = tuple( [new_verts.index(v) for v in new_cpoints]) edge_conversion[edge] = new_verts_indexes edge_conversion[edge[::-1]] = new_verts_indexes[::-1] final_patches = [] # Convert the edges with the approximated ones. for patch in result: converted_edges = [[edge_conversion[edge] for edge in edges] for edges in patch] final_patches += [flat_patch_edges(converted_edges)] # TODO Remember to disable this. #return new_verts, final_patches, new_verts, final_patches def generate_convert(threshold_index, prev_index): def convert(index): if index < threshold_index: return index elif index == threshold_index: return prev_index else: return index - 1 return convert def update_edge(e, convert): return tuple([convert(i) for i in e]) def update_patch(p, convert): return [update_edge(e, convert) for e in p] def my_index(l, element): """Returns the element in the list, otherwise -1""" return l.index(element) if element in l else -1 def my_index_closest(l, element): temp = list(l) mod = lambda x: x[0] * x[0] + x[1] * x[1] + x[2] * x[2] closest = sorted(temp, key=lambda x: mod(x - element))[0] return l.index(closest) if mod(element - closest) < THRESHOLD_DISTANCE else -1 def recursive_max(l): if isinstance(l, (int, float)): return l return max((recursive_max(e) for e in l)) # Filter out vertices which are not unique. final_verts = [] for i, vert in enumerate(new_verts): index = my_index_closest(final_verts, vert) if final_verts else -1 if index == -1: final_verts += [vert] else: # The element is not in the list. This means that we need to decrement all the indexes and replace i with index. convert_func = generate_convert(len(final_verts), index) for i, p in enumerate(final_patches): final_patches[i] = update_patch(p, convert_func) input_size = compute_input_mesh_size(bm) output_size = compute_output_mesh_size(final_patches) print("Statistics ------------------------------------------") print("Input Vertices: ", len(verts_list)) print("Input Faces: ", len(faces_list)) print("Output Vertices: ", len(final_verts)) print("Output Faces: ", len(final_patches)) print("Input Size: ", input_size) print("Output Size: ", output_size) print("Compression Ratio:", (input_size / output_size)) print("-----------------------------------------------------") old_pathces = [flat_patch_edges(p) for p in result] return old_verts, old_pathces, final_verts, final_patches