Exemple #1
0
    def arc_left(
        self, arc_angle, radius=None, center=None, start_slant=None, end_slant=None,
    ):
        if (
            (radius is None and center is None)
            or (radius is not None and center is not None)
        ):
            raise TypeError('You must specify exactly one of center or radius.')

        arc_angle = Angle(arc_angle)
        # Create a radius vector, which is a vector from the arc center to the
        # current position. Subtract to find the center, then rotate the radius
        # vector to find the arc end point.
        if center is None:
            if arc_angle < 0:
                radius = -abs(radius)
            v_radius = vec.neg(vec.perp(self._vector(radius)))
            center = vec.sub(self._position, v_radius)
        elif radius is None:
            v_radius = vec.vfrom(center, self._position)
            radius = vec.mag(v_radius)
            if arc_angle < 0:
                radius = -radius

        endpoint = vec.add(center, vec.rotate(v_radius, arc_angle.rad))

        self._arc(
            center,
            radius,
            endpoint,
            arc_angle,
            start_slant=start_slant,
            end_slant=end_slant,
        )
Exemple #2
0
    def arc_to(self, endpoint, center=None, start_slant=None, end_slant=None):
        """
        Draw an arc ending at the specified point, starting tangent to the
        current position and heading.
        """
        if points_equal(self._position, endpoint):
            return
        # Handle unspecified center.
        # We need to find the center of the arc, so we can find its radius. The
        # center of this arc is uniquely defined by the intersection of two
        # lines:
        # 1. The first line is perpendicular to the pen heading, passing
        #    through the pen position.
        # 2. The second line is the perpendicular bisector of the pen position
        #    and the target arc end point.
        v_pen = self._vector()
        v_perp = vec.perp(self._vector())
        v_chord = vec.vfrom(self._position, endpoint)
        if center is None:
            midpoint = vec.div(vec.add(self._position, endpoint), 2)
            v_bisector = vec.perp(v_chord)
            center = intersect_lines(
                self._position,
                vec.add(self._position, v_perp),
                midpoint,
                vec.add(midpoint, v_bisector),
            )

        # Determine true start heading. This may not be the same as the
        # original pen heading in some circumstances.
        assert not points_equal(center, self._position)
        v_radius_start = vec.vfrom(center, self._position)
        v_radius_perp = vec.perp(v_radius_start)
        if vec.dot(v_radius_perp, v_pen) < 0:
            v_radius_perp = vec.neg(v_radius_perp)
        start_heading = math.degrees(vec.heading(v_radius_perp))
        self.turn_to(start_heading)
        # Refresh v_pen and v_perp based on the new start heading.
        v_pen = self._vector()
        v_perp = vec.perp(self._vector())

        # Calculate the arc angle.
        # The arc angle is double the angle between the pen vector and the
        # chord vector. Arcing to the left is a positive angle, and arcing to
        # the right is a negative angle.
        arc_angle = 2 * math.degrees(vec.angle(v_pen, v_chord))
        radius = vec.mag(v_radius_start)
        # Check which side of v_pen the goes toward.
        if vec.dot(v_chord, v_perp) < 0:
            arc_angle = -arc_angle
            radius = -radius

        self._arc(
            center,
            radius,
            endpoint,
            arc_angle,
            start_slant,
            end_slant,
        )
Exemple #3
0
    def arc_left(
        self, arc_angle, radius=None, center=None, start_slant=None, end_slant=None,
    ):
        if (
            (radius is None and center is None) or
            (radius is not None and center is not None)
        ):
            raise TypeError('You must specify exactly one of center or radius.')

        arc_angle = Angle(arc_angle)
        # Create a radius vector, which is a vector from the arc center to the
        # current position. Subtract to find the center, then rotate the radius
        # vector to find the arc end point.
        if center is None:
            if arc_angle < 0:
                radius = -abs(radius)
            v_radius = vec.neg(vec.perp(self._vector(radius)))
            center = vec.sub(self._position, v_radius)
        elif radius is None:
            v_radius = vec.vfrom(center, self._position)
            radius = vec.mag(v_radius)
            if arc_angle < 0:
                radius = -radius

        endpoint = vec.add(center, vec.rotate(v_radius, arc_angle.rad))

        self._arc(
            center,
            radius,
            endpoint,
            arc_angle,
            start_slant=start_slant,
            end_slant=end_slant,
        )
Exemple #4
0
    def arc_to(self, endpoint, center=None, start_slant=None, end_slant=None):
        """
        Draw an arc ending at the specified point, starting tangent to the
        current position and heading.
        """
        if points_equal(self._position, endpoint):
            return
        # Handle unspecified center.
        # We need to find the center of the arc, so we can find its radius. The
        # center of this arc is uniquely defined by the intersection of two
        # lines:
        # 1. The first line is perpendicular to the pen heading, passing
        #    through the pen position.
        # 2. The second line is the perpendicular bisector of the pen position
        #    and the target arc end point.
        v_pen = self._vector()
        v_perp = vec.perp(self._vector())
        v_chord = vec.vfrom(self._position, endpoint)
        if center is None:
            midpoint = vec.div(vec.add(self._position, endpoint), 2)
            v_bisector = vec.perp(v_chord)
            center = intersect_lines(
                self._position,
                vec.add(self._position, v_perp),
                midpoint,
                vec.add(midpoint, v_bisector),
            )

        # Determine true start heading. This may not be the same as the
        # original pen heading in some circumstances.
        assert not points_equal(center, self._position)
        v_radius_start = vec.vfrom(center, self._position)
        v_radius_perp = vec.perp(v_radius_start)
        if vec.dot(v_radius_perp, v_pen) < 0:
            v_radius_perp = vec.neg(v_radius_perp)
        start_heading = math.degrees(vec.heading(v_radius_perp))
        self.turn_to(start_heading)
        # Refresh v_pen and v_perp based on the new start heading.
        v_pen = self._vector()
        v_perp = vec.perp(self._vector())

        # Calculate the arc angle.
        # The arc angle is double the angle between the pen vector and the
        # chord vector. Arcing to the left is a positive angle, and arcing to
        # the right is a negative angle.
        arc_angle = 2 * math.degrees(vec.angle(v_pen, v_chord))
        radius = vec.mag(v_radius_start)
        # Check which side of v_pen the goes toward.
        if vec.dot(v_chord, v_perp) < 0:
            arc_angle = -arc_angle
            radius = -radius

        self._arc(
            center,
            radius,
            endpoint,
            arc_angle,
            start_slant,
            end_slant,
        )
 def rebound(self, normal, point=None, restitution=1):
     # Split into normal and tangential components.
     tangent = vec.perp(normal)
     v_tangent = vec.proj(self.velocity, tangent)
     v_normal = vec.proj(self.velocity, normal)
     # Invert normal component and recombine, with restitution.
     v_normal = vec.neg(v_normal)
     self.velocity = vec.add(v_tangent, vec.mul(v_normal, restitution))
     # If the particle is partially inside the wall, move it out.
     if point is not None:
         v = vec.vfrom(point, self.pos)
         if vec.mag2(v) < self.radius ** 2:
             v = vec.norm(v, self.radius)
             self.pos = vec.add(point, v)
 def rebound(self, normal, point=None, restitution=1):
     # Split into normal and tangential components.
     tangent = vec.perp(normal)
     v_tangent = vec.proj(self.velocity, tangent)
     v_normal = vec.proj(self.velocity, normal)
     # Invert normal component and recombine, with restitution.
     v_normal = vec.neg(v_normal)
     self.velocity = vec.add(
         v_tangent,
         vec.mul(v_normal, restitution),
     )
     # If the particle is partially inside the wall, move it out.
     if point is not None:
         v = vec.vfrom(point, self.pos)
         if vec.mag2(v) < self.radius**2:
             v = vec.norm(v, self.radius)
             self.pos = vec.add(point, v)
    def join_with_arc(self, other):
        # Special case coincident arcs.
        if points_equal(self.center, other.center):
            if not (
                float_equal(self.radius, other.radius) and
                float_equal(self.width, other.width)
            ):
                self.end_joint_illegal = True
                other.start_joint_illegal = True
                return

            r = vec.vfrom(self.center, self.b)
            if self.radius < 0:
                r = vec.neg(r)
            v_left = vec.norm(r, self.radius - self.width / 2)
            self.b_left = other.a_left = Point(*vec.add(self.center, v_left))
            v_right = vec.norm(r, self.radius + self.width / 2)
            self.b_right = other.a_right = Point(*vec.add(self.center, v_right))
            return

        c1, r1 = self.offset_circle_left()
        c2, r2 = other.offset_circle_left()
        points_left = intersect_circles(c1, r1, c2, r2)
        if len(points_left) > 0:
            p = Point(*closest_point_to(self.b, points_left))
            self.b_left = other.a_left = p

        c1, r1 = self.offset_circle_right()
        c2, r2 = other.offset_circle_right()
        points_right = intersect_circles(c1, r1, c2, r2)
        if len(points_right) > 0:
            p = Point(*closest_point_to(self.b, points_right))
            self.b_right = other.a_right = p

        if len(points_left) == 0 or len(points_right) == 0:
            self.end_joint_illegal = True
            other.start_joint_illegal = True
Exemple #8
0
    def join_with_arc(self, other):
        # Special case coincident arcs.
        if points_equal(self.center, other.center):
            if not (
                float_equal(self.radius, other.radius)
                and float_equal(self.width, other.width)
            ):
                self.end_joint_illegal = True
                other.start_joint_illegal = True
                return

            r = vec.vfrom(self.center, self.b)
            if self.radius < 0:
                r = vec.neg(r)
            v_left = vec.norm(r, self.radius - self.width / 2)
            self.b_left = other.a_left = Point(*vec.add(self.center, v_left))
            v_right = vec.norm(r, self.radius + self.width / 2)
            self.b_right = other.a_right = Point(*vec.add(self.center, v_right))
            return

        c1, r1 = self.offset_circle_left()
        c2, r2 = other.offset_circle_left()
        points_left = intersect_circles(c1, r1, c2, r2)
        if len(points_left) > 0:
            p = Point(*closest_point_to(self.b, points_left))
            self.b_left = other.a_left = p

        c1, r1 = self.offset_circle_right()
        c2, r2 = other.offset_circle_right()
        points_right = intersect_circles(c1, r1, c2, r2)
        if len(points_right) > 0:
            p = Point(*closest_point_to(self.b, points_right))
            self.b_right = other.a_right = p

        if len(points_left) == 0 or len(points_right) == 0:
            self.end_joint_illegal = True
            other.start_joint_illegal = True
 def offset_line_right(self):
     w = vec.neg(self._width_vector())
     return (
         vec.add(self.a, w),
         vec.add(self.b, w),
     )
Exemple #10
0
def hook(pen, slant_angle, arc_angle, distance, adjust_inside=0, adjust_outside=0):
    """
    Draw a hook shape.

    Each hook has two arcs that meet at a point, with curvature in the
    same direction. They are connected at the other end by a line, creating two
    corners. The pen starts on the left corner, pointing toward the right
    corner. The width of the pen is also used as the maximum width of the hook.

    The `slant_angle` argument is 90 degrees for a hook with a "straight" base.
    For slanted bases toward the inside corner, 0 < slant_angle < 90.
    For slanted bases toward the outside corner, 90 < slant_angle < 180.

    `arc_angle` is the arc angle of the inside arc. If `arc_angle` is negative,
    then the hook curves to the right instead.

    `distance` is the arc length along the inside arc.

    `sharp_angle` adjusts the "sharpness" i.e. acuteness of the angle of the
    hook tip. Negative angles make it "blunter".
    """
    slant_angle = Angle(slant_angle)
    arc_angle = Angle(arc_angle)

    old_mode = pen.mode
    hook_width = old_mode.width


    # The pen starts at the "middle" of the hook, a point along the base that
    # is in the middle of the inside and outside bounding circles.
    base_heading = pen.heading
    base_middle = pen.position

    # Calculate the radius.
    circumference = distance / (arc_angle.theta / 360)
    radius = circumference / (2 * math.pi)

    # Trace the inside curve to find the hook tip and some other
    # important points.
    temp_pen = pen.copy()
    temp_pen.turn_left(slant_angle)
    temp_pen.arc_left(arc_angle, radius)
    center_arc = temp_pen.last_segment()

    tip = center_arc.b
    center = center_arc.center

    # Calculate the inside and outside corner position. The outside corner is located
    # along the base line, intersecting the outer circle. The outer circle is
    # concentric to the inner arc's circle, but with radius larger by the
    # hook_width.
    v_base = vec.rotate((1, 0), base_heading.rad)
    switch_corners = (
        (slant_angle > 0 and arc_angle < 0) or
        (slant_angle < 0 and arc_angle > 0)
    )

    if switch_corners:
        v_base = vec.neg(v_base)

    base_right = vec.add(base_middle, v_base)

    points_inside = intersect_circle_line(
        center, abs(center_arc.radius) - (hook_width / 2),
        base_middle, base_right,
    )
    points_outside = intersect_circle_line(
        center, abs(center_arc.radius) + (hook_width / 2),
        base_middle, base_right,
    )

    # Take the intersection point that is the most "forward" along the base
    # from the inside corner.
    def forwardness(p):
        v_p = vec.vfrom(base_middle, p)
        return vec.dot(v_p, v_base),

    inside_corner = max(points_inside, key=forwardness)
    outside_corner = max(points_outside, key=forwardness)

    # Adjust for having started in the middle of the hook. Move to where the
    # corner of the hook is at the pen start position.
    if not switch_corners:
        offset = vec.vfrom(inside_corner, base_middle)
    else:
        offset = vec.vfrom(outside_corner, base_middle)
    inside_corner = vec.add(inside_corner, offset)
    outside_corner = vec.add(outside_corner, offset)
    tip = vec.add(tip, offset)
    center = vec.add(center, offset)

    # Draw the hook.
    pen.set_mode(old_mode.outliner_mode())
    # Base.
    pen.move_to(inside_corner)
    pen.line_to(outside_corner)
    # Outer arc.
    pen.turn_toward(center)
    if arc_angle > 0:
        pen.turn_right(90 + adjust_outside)
    else:
        pen.turn_left(90 + adjust_outside)
    pen.arc_to(tip)
    # Inner arc.
    pen.turn_to(center_arc.end_heading + 180)
    if arc_angle > 0:
        pen.turn_right(adjust_inside)
    else:
        pen.turn_left(adjust_inside)

    pen.arc_to(inside_corner)

    pen.set_mode(old_mode)
Exemple #11
0
 def offset_line_right(self):
     w = vec.neg(self._width_vector())
     return (
         vec.add(self.a, w),
         vec.add(self.b, w),
     )