def synfig_outline(bline, st_val, origin_p, outer_width_p, sharp_cusps_p, expand_p, r_tip0_p, r_tip1_p, homo_width_p, fr): """ Calculates the points for the outline layer as in Synfig: https://github.com/synfig/synfig/blob/678cc3a7b1208fcca18c8b54a29a20576c499927/synfig-core/src/modules/mod_geometry/outline.cpp Args: bline_point (common.Bline.Bline) : Synfig format bline points of outline layer st_val (dict) : Lottie format outline stored in this origin_p (common.Param.Param) : Lottie format origin of outline layer outer_width_p (common.Param.Param) : Lottie format outer width sharp_cusps_p (common.Param.Param) : sharp cusps in Synfig format expand_p (common.Param.Param) : Lottie format expand parameter r_tip0_p (common.Param.Param) : Round tip[0] in Synfig format r_tip1_p (common.Param.Param) : Round tip[1] in Synfig format homo_width_p (common.Param.Param) : Homogeneous width in Synfig format fr (int) : Frame number Returns: (None) """ EPSILON = 0.000000001 SAMPLES = 50 CUSP_TANGENT_ADJUST = 0.025 CUSP_THRESHOLD = 0.40 SPIKE_AMOUNT = 4 ROUND_END_FACTOR = 4 bline_list = bline.get_list_at_frame(fr) outer_width = to_Synfig_axis(outer_width_p.get_value(fr), "real") expand = to_Synfig_axis(expand_p.get_value(fr), "real") sharp_cusps = sharp_cusps_p.get_value(fr) r_tip0 = r_tip0_p.get_value(fr) r_tip1 = r_tip1_p.get_value(fr) homo_width = homo_width_p.get_value(fr) gv = get_outline_grow(fr) # Setup chunk list side_a, side_b = [], [] # Check if looped loop = bline.get_loop() # Iterators end_it = len(bline_list) next_it = 0 if loop: iter_it = end_it - 1 else: iter_it = next_it next_it += 1 first_point = bline_list[iter_it] first_tangent = bline_list[0].get_tangent2() last_tangent = first_point.get_tangent1() # If we are looped and drawing sharp cusps, we 'll need a value # for the incoming tangent. This code fails if we have a "degraded" spline # with just one vertex, so we avoid such case. if loop and sharp_cusps and last_tangent.is_equal_to(Vector(0, 0)) and len(bline_list) > 1: prev_it = iter_it prev_it -= 1 prev_it %= len(bline_list) prev_point = bline_list[prev_it] curve = Hermite(prev_point.get_vertex(), first_point.get_vertex(), prev_point.get_tangent2(), first_point.get_tangent1()) last_tangent = curve.derivative(1.0 - CUSP_TANGENT_ADJUST) first = not loop while next_it != end_it: bp1 = bline_list[iter_it] bp2 = bline_list[next_it] # Setup tangents prev_t = bp1.get_tangent1() iter_t = bp1.get_tangent2() next_t = bp2.get_tangent1() split_flag = bp1.get_split_tangent_angle() or bp1.get_split_tangent_radius() # If iter_it.t2 == 0 and next.t1 == 0, this is a straight line if iter_t.is_equal_to(Vector(0, 0)) and next_t.is_equal_to(Vector(0, 0)): iter_t = next_t = bp2.get_vertex() - bp1.get_vertex() # If the 2 points are on top of each other, ignore this segment # leave 'first' true if was before if iter_t.is_equal_to(Vector(0, 0)): iter_it = next_it iter_it %= len(bline_list) next_it += 1 continue # Setup the curve curve = Hermite(bp1.get_vertex(), bp2.get_vertex(), iter_t, next_t) # Setup width's iter_w = gv*(bp1.get_width() * outer_width * 0.5 + expand) next_w = gv*(bp2.get_width() * outer_width * 0.5 + expand) if first: first_tangent = curve.derivative(CUSP_TANGENT_ADJUST) # Make cusps as necassary if not first and \ sharp_cusps and \ split_flag and \ ((not prev_t.is_equal_to(iter_t)) or (iter_t.is_equal_to(Vector(0, 0)))) and \ (not last_tangent.is_equal_to(Vector(0, 0))): curr_tangent = curve.derivative(CUSP_TANGENT_ADJUST) t1 = last_tangent.perp().norm() t2 = curr_tangent.perp().norm() cross = t1*t2.perp() perp = (t1 - t2).mag() if cross > CUSP_THRESHOLD: p1 = bp1.get_vertex() + t1*iter_w p2 = bp1.get_vertex() + t2*iter_w side_a.append([line_intersection(p1, last_tangent, p2, curr_tangent), Vector(0, 0), Vector(0, 0)]) elif cross < -CUSP_THRESHOLD: p1 = bp1.get_vertex() - t1*iter_w p2 = bp1.get_vertex() - t2*iter_w side_b.append([line_intersection(p1, last_tangent, p2, curr_tangent), Vector(0, 0), Vector(0, 0)]) elif cross > 0.0 and perp > 1.0: amount = max(0.0, cross/CUSP_THRESHOLD) * (SPIKE_AMOUNT - 1.0) + 1.0 side_a.append([bp1.get_vertex() + (t1 + t2).norm()*iter_w*amount, Vector(0, 0), Vector(0, 0)]) elif cross < 0 and perp > 1: amount = max(0.0, -cross/CUSP_THRESHOLD) * (SPIKE_AMOUNT - 1.0) + 1.0 side_b.append([bp1.get_vertex() - (t1 + t2).norm()*iter_w*amount, Vector(0, 0), Vector(0, 0)]) # Precalculate positions and coefficients length = 0.0 points = [] dists = [] n = 0.0 itr = 0 while n < 1.000001: points.append(curve.value(n)) if n: length += (points[itr] - points[itr-1]).mag() dists.append(length) n += 1.0/SAMPLES itr += 1 length += (curve.value(1) - points[itr-1]).mag() div_length = 1 if length > EPSILON: div_length = 1.0 / length # Might not need /3 for the tangents generated finally - VERY IMPORTANT # Make the outline pt = curve.derivative(CUSP_TANGENT_ADJUST) / 3 n = 0.0 itr = 0 while n < 1.000001: t = curve.derivative(min(max(n, CUSP_TANGENT_ADJUST), 1.0 - CUSP_TANGENT_ADJUST)) / 3 d = t.perp().norm() k = dists[itr] * div_length if not homo_width: k = n w = (next_w - iter_w)*k + iter_w if False and n: # create curve a = points[itr-1] + d*w b = points[itr] + d*w tk = (b - a).mag() * div_length side_a.append([b, pt*tk, -t*tk]) a = points[itr-1] - d*w b = points[itr] - d*w tk = (b - a).mag() * div_length side_b.append([b, pt*tk, -t*tk]) else: side_a.append([points[itr] + d*w, Vector(0, 0), Vector(0, 0)]) side_b.append([points[itr] - d*w, Vector(0, 0), Vector(0, 0)]) pt = t itr += 1 n += 1.0/SAMPLES last_tangent = curve.derivative(1.0 - CUSP_TANGENT_ADJUST) side_a.append([curve.value(1.0) + last_tangent.perp().norm()*next_w, Vector(0, 0), Vector(0, 0)]) side_b.append([curve.value(1.0) - last_tangent.perp().norm()*next_w, Vector(0, 0), Vector(0, 0)]) first = False iter_it = next_it iter_it %= len(bline_list) next_it += 1 if len(side_a) < 2 or len(side_b) < 2: return origin_cur = origin_p.get_value(fr) move_to(side_a[0][0], st_val, origin_cur) if loop: add(side_a, st_val, origin_cur) add_reverse(side_b, st_val, origin_cur) else: # Insert code for adding end tip if r_tip1: bp = bline_list[-1] vertex = bp.get_vertex() tangent = last_tangent.norm() w = gv*(bp.get_width() * outer_width * 0.5 + expand) a = vertex + tangent.perp()*w b = vertex - tangent.perp()*w p1 = a + tangent*w*(ROUND_END_FACTOR/3.0) p2 = b + tangent*w*(ROUND_END_FACTOR/3.0) tan = tangent*w*(ROUND_END_FACTOR/3.0) # replace the last point side_a[-1] = [a, Vector(0, 0), tan] add(side_a, st_val, origin_cur) add([[b, -tan, Vector(0, 0)]], st_val, origin_cur) else: add(side_a, st_val, origin_cur) # Insert code for adding beginning tip if r_tip0: bp = bline_list[0] vertex = bp.get_vertex() tangent = first_tangent.norm() w = gv*(bp.get_width() * outer_width * 0.5 + expand) a = vertex - tangent.perp()*w b = vertex + tangent.perp()*w p1 = a - tangent*w*(ROUND_END_FACTOR/3.0) p2 = b - tangent*w*(ROUND_END_FACTOR/3.0) tan = -tangent*w*(ROUND_END_FACTOR/3.0) # replace the first point side_b[0] = [a, Vector(0, 0), tan] add_reverse(side_b, st_val, origin_cur) add([[b, -tan, Vector(0, 0)]], st_val, origin_cur) else: add_reverse(side_b, st_val, origin_cur)
def get_list_at_frame(self, fr): """ Returns the Bline list at a particular frame Refer: https://github.com/synfig/synfig/blob/15607089680af560ad031465d31878425af927eb/synfig-core/src/synfig/valuenodes/valuenode_bline.cpp """ EPSILON = 0.0000001 ret_list = [] first_flag = True rising = [False] next_scale = 1.0 first = BlinePoint(Vector(0, 0), 1, True, False, Vector(0, 0), Vector(0, 0)) first.set_origin(100) iterr = 0 while iterr != self.get_len(): entry = self.get_entry_list()[iterr] amount = entry["ActivepointList"].amount_at_time(fr, rising) assert (amount >= 0.0) assert (amount <= 1.0) # It's fully on if amount > 1.0 - EPSILON: if first_flag: first_iter = iterr first = prev = self.get_blinepoint(iterr, fr) first_flag = False ret_list.append(copy.deepcopy(first)) iterr += 1 continue curr = self.get_blinepoint(iterr, fr) if next_scale != 1.0: ret_list[-1].set_split_tangent_both(True) ret_list[-1].set_tangent2(prev.get_tangent2() * next_scale) ret_list.append(copy.deepcopy(curr)) ret_list[-1].set_split_tangent_both(True) ret_list[-1].set_tangent2(curr.get_tangent2()) ret_list[-1].set_tangent1(curr.get_tangent1() * next_scale) next_scale = 1.0 else: ret_list.append(copy.deepcopy(curr)) prev = curr # It's partly on elif amount > 0.0: blp_here_off = BlinePoint(Vector(0, 0), 1, True, False, Vector(0, 0), Vector(0, 0)) blp_here_now = BlinePoint(Vector(0, 0), 1, True, False, Vector(0, 0), Vector(0, 0)) blp_prev_off = BlinePoint(Vector(0, 0), 1, True, False, Vector(0, 0), Vector(0, 0)) dist_from_begin = 0 dist_from_end = 0 if not rising[0]: try: on_time = self.get_entry_list( )[iterr]["ActivepointList"].find_prev(fr).get_time() except Exception as e: on_time = settings.SOT try: off_time = self.get_entry_list( )[iterr]["ActivepointList"].find_next(fr).get_time() except Exception as e: off_time = settings.EOT else: try: off_time = self.get_entry_list( )[iterr]["ActivepointList"].find_prev(fr).get_time() except Exception as e: off_time = settings.SOT try: on_time = self.get_entry_list( )[iterr]["ActivepointList"].find_next(fr).get_time() except Exception as e: on_time = settings.EOT blp_here_on = self.get_blinepoint(iterr, on_time) end_iter = iterr end_iter += 1 while end_iter != self.get_len(): if self.get_entry_list( )[end_iter]["ActivepointList"].amount_at_time(fr) > amount: break end_iter += 1 if end_iter == self.get_len(): if self.get_loop() and (not first_flag): end_iter = first_iter else: end_iter = self.get_len() - 1 blp_next_off = self.get_blinepoint(end_iter, off_time) begin_iter = iterr blp_prev_off.set_origin(100) while True: if begin_iter == 0: if self.get_loop(): begin_iter = self.get_len() else: break begin_iter -= 1 dist_from_begin += 1 if begin_iter == iterr: break if self.get_entry_list()[begin_iter][ "ActivepointList"].amount_at_time(fr) > amount: blp_prev_off = self.get_blinepoint( begin_iter, off_time) break if blp_prev_off.get_origin() == 100: if first_flag: begin_iter = 0 else: begin_iter = first_iter blp_prev_off = self.get_blinepoint(begin_iter, off_time) curve = Hermite(blp_prev_off.get_vertex(), blp_next_off.get_vertex(), blp_prev_off.get_tangent2(), blp_next_off.get_tangent1()) blp_here_off.set_vertex(curve.value(blp_here_on.get_origin())) blp_here_off.set_width( (blp_next_off.get_width() - blp_prev_off.get_width()) * blp_here_on.get_origin() + blp_prev_off.get_width()) blp_here_off.set_tangent1( curve.derivative(blp_here_on.get_origin())) blp_here_off.set_tangent2( curve.derivative(blp_here_on.get_origin())) if begin_iter == (iterr - 1) or dist_from_begin == 1: prev_tangent_scalar = self.linear_interpolation( blp_here_on.get_origin(), 1.0, amount) else: prev_tangent_scalar = self.linear_interpolation( blp_here_on.get_origin() - prev.get_origin(), 1.0, amount) if end_iter == (iterr + 1) or dist_from_end == 1: next_tangent_scalar = self.linear_interpolation( 1.0 - blp_here_on.get_origin(), 1.0, amount) elif self.get_len() != (iterr + 1): nextt = self.get_blinepoint(iterr + 1, fr) next_tangent_scalar = self.linear_interpolation( nextt.get_origin() - blp_here_on.get_origin(), 1.0, amount) else: next_tangent_scalar = self.linear_interpolation( blp_next_off.get_origin() - blp_here_on.get_origin(), 1.0, amount) next_scale = next_tangent_scalar # My second try off_coord_sys = [] on_coord_sys = [] curr_coord_sys = [] end_pos_at_off_time = self.get_blinepoint( end_iter, off_time).get_vertex() begin_pos_at_off_time = self.get_blinepoint( begin_iter, off_time).get_vertex() off_coord_origin = (begin_pos_at_off_time + end_pos_at_off_time) / 2 off_coord_sys.append( (begin_pos_at_off_time - end_pos_at_off_time).norm()) off_coord_sys.append(off_coord_sys[0].perp()) end_pos_at_on_time = self.get_blinepoint(end_iter, on_time).get_vertex() begin_pos_at_on_time = self.get_blinepoint( begin_iter, on_time).get_vertex() on_coord_origin = (begin_pos_at_on_time + end_pos_at_on_time) / 2 on_coord_sys.append( (begin_pos_at_on_time - end_pos_at_on_time).norm()) on_coord_sys.append(on_coord_sys[0].perp()) end_pos_at_current_time = self.get_blinepoint(end_iter, fr).get_vertex() begin_pos_at_current_time = self.get_blinepoint( begin_iter, fr).get_vertex() curr_coord_origin = (begin_pos_at_current_time + end_pos_at_current_time) / 2 curr_coord_sys.append((begin_pos_at_current_time - end_pos_at_current_time).norm()) curr_coord_sys.append(curr_coord_sys[0].perp()) # swapping temp = curr_coord_sys[0][1] curr_coord_sys[0][1] = curr_coord_sys[1][0] curr_coord_sys[1][0] = temp trans_on_point = Vector(0, 0) trans_off_point = Vector(0, 0) trans_on_t1 = Vector(0, 0) trans_off_t1 = Vector(0, 0) trans_on_t2 = Vector(0, 0) trans_off_t2 = Vector(0, 0) trans_on_point = self.transform_coords( blp_here_on.get_vertex(), trans_on_point, on_coord_origin, on_coord_sys) trans_off_point = self.transform_coords( blp_here_off.get_vertex(), trans_off_point, off_coord_origin, off_coord_sys) trans_on_t1 = self.transform_coords(blp_here_on.get_tangent1(), trans_on_t1, Vector(0, 0), on_coord_sys) trans_off_t1 = self.transform_coords( blp_here_off.get_tangent1(), trans_off_t1, Vector(0, 0), off_coord_sys) if blp_here_on.get_split_tangent_both(): trans_on_t2 = self.transform_coords( blp_here_on.get_tangent2(), trans_on_t2, Vector(0, 0), on_coord_sys) trans_off_t2 = self.transform_coords( blp_here_off.get_tangent2(), trans_off_t2, Vector(0, 0), off_coord_sys) tmp = Vector(0, 0) tmp = self.untransform_coords( self.linear_interpolation(trans_off_point, trans_on_point, amount), tmp, curr_coord_origin, curr_coord_sys) blp_here_now.set_vertex(tmp) tmp = Vector(0, 0) tmp = self.untransform_coords( self.radial_interpolation(trans_off_t1, trans_on_t1, amount), tmp, Vector(0, 0), curr_coord_sys) blp_here_now.set_tangent1(tmp) # blp_here_now.set_tangent1(self.radial_interpolation(blp_here_off.get_tangent1(), blp_here_on.get_tangent1(), amount)) if blp_here_on.get_split_tangent_both(): blp_here_now.set_split_tangent_both(True) tmp = Vector(0, 0) tmp = self.untransform_coords( self.radial_interpolation(trans_off_t2, trans_on_t2, amount), tmp, Vector(0, 0), curr_coord_sys) blp_here_now.set_tangent2(tmp) else: blp_here_now.set_split_tangent_both(False) blp_here_now.set_origin(blp_here_on.get_origin()) blp_here_now.set_width( self.linear_interpolation(blp_here_off.get_width(), blp_here_on.get_width(), amount)) if first_flag: blp_here_now.set_tangent1(blp_here_now.get_tangent1() * prev_tangent_scalar) first_iter = iterr first = prev = blp_here_now first_flag = False ret_list.append(copy.deepcopy(blp_here_now)) iterr += 1 continue ret_list[-1].set_split_tangent_both(True) ret_list[-1].set_tangent2(prev.get_tangent2() * prev_tangent_scalar) ret_list.append(copy.deepcopy(blp_here_now)) ret_list[-1].set_split_tangent_both(True) ret_list[-1].set_tangent1(blp_here_now.get_tangent1() * prev_tangent_scalar) prev = blp_here_now iterr += 1 if next_scale != 1: ret_list[-1].set_split_tangent_both(True) ret_list[-1].set_tangent2(prev.get_tangent2() * next_scale) return ret_list