def intersect_curves(nodes1, nodes2): r"""Intersect two parametric B |eacute| zier curves. Args: nodes1 (numpy.ndarray): The nodes in the first curve. nodes2 (numpy.ndarray): The nodes in the second curve. Returns: numpy.ndarray: ``Nx2`` array of intersection parameters. Each row contains a pair of values :math:`s` and :math:`t` (each in :math:`\left[0, 1\right]`) such that the curves intersect: :math:`B_1(s) = B_2(t)`. Raises: NotImplementedError: If the "intersection polynomial" is all zeros -- which indicates coincident curves. """ nodes1 = _curve_helpers.full_reduce(nodes1) nodes2 = _curve_helpers.full_reduce(nodes2) num_nodes1, _ = nodes1.shape num_nodes2, _ = nodes2.shape swapped = False if num_nodes1 > num_nodes2: nodes1, nodes2 = nodes2, nodes1 swapped = True coeffs = normalize_polynomial(to_power_basis(nodes1, nodes2)) if np.all(coeffs == 0.0): raise NotImplementedError(_COINCIDENT_ERR) _check_non_simple(coeffs) t_vals = roots_in_unit_interval(coeffs) final_s = [] final_t = [] for t_val in t_vals: (x_val, y_val), = _curve_helpers.evaluate_multi(nodes2, np.asfortranarray([t_val])) s_val = locate_point(nodes1, x_val, y_val) if s_val is not None: _resolve_and_add(nodes1, s_val, final_s, nodes2, t_val, final_t) result = np.zeros((len(final_s), 2), order='F') if swapped: final_s, final_t = final_t, final_s result[:, 0] = final_s result[:, 1] = final_t return result
def locate_point(nodes, x_val, y_val): r"""Find the parameter corresponding to a point on a curve. .. note:: This assumes that the curve :math:`B(s, t)` defined by ``nodes`` lives in :math:`\mathbf{R}^2`. Args: nodes (numpy.ndarray): The nodes defining a B |eacute| zier curve. x_val (float): The :math:`x`-coordinate of the point. y_val (float): The :math:`y`-coordinate of the point. Returns: Optional[float]: The parameter on the curve (if it exists). """ # First, reduce to the true degree of x(s) and y(s). zero1 = _curve_helpers.full_reduce(nodes[:, [0]]) - x_val zero2 = _curve_helpers.full_reduce(nodes[:, [1]]) - y_val # Make sure we have the lowest degree in front, to make the polynomial # solve have the fewest number of roots. if zero1.shape[0] > zero2.shape[0]: zero1, zero2 = zero2, zero1 # If the "smallest" is a constant, we can't find any roots from it. if zero1.shape[0] == 1: # NOTE: We assume that callers won't pass ``nodes`` that are # degree 0, so if ``zero1`` is a constant, ``zero2`` won't be. zero1, zero2 = zero2, zero1 power_basis1 = poly_to_power_basis(zero1[:, 0]) all_roots = roots_in_unit_interval(power_basis1) if all_roots.size == 0: return # NOTE: We normalize ``power_basis2`` because we want to check for # "zero" values, i.e. f2(s) == 0. power_basis2 = normalize_polynomial(poly_to_power_basis(zero2[:, 0])) near_zero = np.abs(polynomial.polyval(all_roots, power_basis2)) index = np.argmin(near_zero) if near_zero[index] < _ZERO_THRESHOLD: return all_roots[index]
def _call_function_under_test(nodes): from bezier import _curve_helpers return _curve_helpers.full_reduce(nodes)