--- /dev/null
+from math import cos, pi, sin
+
+import pygame
+
+from vectors import (
+ Polygon, Rect, Shapes, StrokeCircleSegment, StrokeRoundLine, StrokeSquareLine
+)
+
+tau = 2 * pi
+
+
+laptop_single = Shapes(
+ [
+ Rect((2, 3), (16.4, 1)),
+ Rect((2, 4), (1, 8.1)),
+ Rect((17.4, 4), (1, 8.1)),
+ Rect((2, 12.1), (16.4, 1)),
+ Polygon([(2, 13.1), (18.4, 13.1), (22.4, 18.1), (6, 18.1)]),
+ ]
+)
+laptop_double = Shapes(
+ [
+ Rect((2, 3), (16.4, 1)),
+ Rect((2, 4), (1, 8.1)),
+ Rect((17.4, 4), (1, 8.1)),
+ Rect((2, 12.1), (16.4, 1)),
+ Polygon([(2, 13.1), (3, 13.1), (6.2, 17.1), (5.2, 17.1)]),
+ Polygon([(17.4, 13.1), (18.4, 13.1), (21.6, 17.1), (20.4, 17.1)]),
+ Polygon([(5.2, 17.1), (21.6, 17.1), (22.4, 18.1), (6, 18.1)]),
+ ]
+)
+laptop_vertical = Shapes(
+ [
+ Rect((0.5, 1), (17.75, 1)),
+ Rect((0.5, 2), (1, 14.4)),
+ Rect((9, 2), (1, 14.4)),
+ Rect((17.25, 2), (1, 14.4)),
+ Rect((0.5, 16.4), (17.75, 1)),
+ Polygon([(2, 17.4), (6, 22.4), (20.75, 22.4), (16.75, 17.4)]),
+ ]
+)
+FINGER_RADIUS = 1.25
+NUM_VERTICES = 32
+touchscreen = Shapes(
+ [
+ StrokeCircleSegment((12, 16), 5, 0, tau * 3 / 8, 1, NUM_VERTICES),
+ StrokeRoundLine(
+ (12 + cos(tau * 3 / 8) * 5, 16 + sin(tau * 3 / 8) * 5),
+ (
+ 4 + cos(tau * 3 / 8) * FINGER_RADIUS,
+ 13 + sin(tau * 3 / 8) * FINGER_RADIUS,
+ ),
+ 1,
+ NUM_VERTICES,
+ ),
+ StrokeCircleSegment(
+ (4, 13), FINGER_RADIUS, tau * 3 / 8, tau * 7 / 8, 1, NUM_VERTICES
+ ),
+ StrokeRoundLine(
+ (
+ 4 + cos(tau * 7 / 8) * FINGER_RADIUS,
+ 13 + sin(tau * 7 / 8) * FINGER_RADIUS,
+ ),
+ (7, 13.5),
+ 1,
+ NUM_VERTICES,
+ ),
+ StrokeRoundLine((7, 13.5), (7, 6), 1, NUM_VERTICES),
+ StrokeCircleSegment((8.25, 6), FINGER_RADIUS, pi, tau, 1, NUM_VERTICES),
+ StrokeRoundLine((9.5, 6), (9.5, 11), 1, NUM_VERTICES),
+ StrokeCircleSegment(
+ (11, 11.5), FINGER_RADIUS, tau * 5 / 8, tau * 7 / 8, 1, NUM_VERTICES
+ ),
+ StrokeCircleSegment(
+ (13.25, 12), FINGER_RADIUS, tau * 5 / 8, tau * 7 / 8, 1, NUM_VERTICES
+ ),
+ StrokeCircleSegment(
+ (15.5, 12.5), FINGER_RADIUS, tau * 5 / 8, tau, 1, NUM_VERTICES
+ ),
+ StrokeRoundLine((16.75, 12.5), (17, 16), 1, NUM_VERTICES),
+ StrokeCircleSegment((8.25, 6), 3, pi, tau, 1, NUM_VERTICES),
+ StrokeCircleSegment((8.25, 6), 5, pi, tau, 1, NUM_VERTICES),
+ ]
+)
+stylus = Shapes(
+ [
+ StrokeCircleSegment((3, 3), 1.5, tau * 3 / 8, tau * 7 / 8, 1, NUM_VERTICES),
+ StrokeRoundLine(
+ (3 + cos(tau * 3 / 8) * 1.5, 3 + sin(tau * 3 / 8) * 1.5),
+ (16 + cos(tau * 3 / 8) * 1.5, 16 + sin(tau * 3 / 8) * 1.5),
+ 1,
+ NUM_VERTICES,
+ ),
+ StrokeRoundLine(
+ (3 + cos(tau * 7 / 8) * 1.5, 3 + sin(tau * 7 / 8) * 1.5),
+ (16 + cos(tau * 7 / 8) * 1.5, 16 + sin(tau * 7 / 8) * 1.5),
+ 1,
+ NUM_VERTICES,
+ ),
+ StrokeRoundLine(
+ (16 + cos(tau * 3 / 8) * 1.5, 16 + sin(tau * 3 / 8) * 1.5),
+ (18, 18),
+ 1,
+ NUM_VERTICES,
+ ),
+ StrokeRoundLine(
+ (16 + cos(tau * 7 / 8) * 1.5, 16 + sin(tau * 7 / 8) * 1.5),
+ (18, 18),
+ 1,
+ NUM_VERTICES,
+ ),
+ StrokeRoundLine((11, 11), (12, 12), 1, NUM_VERTICES),
+ ]
+)
+bluetooth = Shapes(
+ [
+ StrokeSquareLine((10, 2), (15, 6), 1.5),
+ StrokeSquareLine((15, 6), (5, 14), 1.5),
+ StrokeSquareLine((10, 2.75), (10, 18.25), 1.5),
+ StrokeSquareLine((10, 19), (15, 15), 1.5),
+ StrokeSquareLine((15, 15), (5, 7), 1.5),
+ ]
+)
+
+
+def main():
+ shapes = [
+ {
+ "shape": laptop_single,
+ "pos": (100, 100),
+ },
+ {
+ "shape": laptop_double,
+ "pos": (600, 100),
+ },
+ {
+ "shape": laptop_vertical,
+ "pos": (1100, 100),
+ },
+ {
+ "shape": touchscreen,
+ "pos": (100, 600),
+ },
+ {
+ "shape": stylus,
+ "pos": (600, 600),
+ },
+ {
+ "shape": bluetooth,
+ "pos": (1100, 600),
+ },
+ ]
+ surf = pygame.display.set_mode((2000, 1600))
+ running = True
+ dirty = False
+ clock = pygame.time.Clock()
+ while True:
+ for ev in pygame.event.get():
+ if ev.type == pygame.QUIT:
+ running = False
+ break
+ elif ev.type == pygame.WINDOWEXPOSED:
+ dirty = True
+ elif ev.type == pygame.KEYDOWN:
+ if ev.key == pygame.K_ESCAPE:
+ running = False
+ break
+ if not running:
+ break
+ elif dirty:
+ surf.fill("black")
+ for item in shapes:
+ item["shape"].draw(surf, item["pos"], (16, 16), "green")
+ pygame.display.update()
+ dirty = False
+ clock.tick(60)
+
+
+if __name__ == "__main__":
+ main()
-#!/usr/bin/env python3
-
from itertools import chain
from math import atan2, ceil, cos, floor, pi, sin, sqrt
"""
Rotate a rectangle along the direction in which we're drawing.
"""
- def __init__(self, start, end, width):
- super().__init__(list(self.make_line_shape(start, end, width / 2)))
-
- def make_line_shape(self, p1, p2, shape_r):
+ def __init__(self, p1, p2, width):
p_rel = (p1[0] - p2[0], p1[1] - p2[1])
- distance = sqrt(p_rel[0] * p_rel[0] + p_rel[1] * p_rel[1])
- if distance:
- back = (p_rel[0] * shape_r / distance, p_rel[1] * shape_r / distance)
+ twice_distance = 2 * sqrt(p_rel[0] * p_rel[0] + p_rel[1] * p_rel[1])
+ if twice_distance:
+ back = (
+ p_rel[0] * width / twice_distance, p_rel[1] * width / twice_distance
+ )
back_sum, back_diff = back[0] + back[1], back[0] - back[1]
else:
- back_sum, back_diff = -shape_r, shape_r
+ back_sum, back_diff = -width / 2, width / 2
# left is aka: (back[1], -back[0])
# right is aka: (-back[1], back[0])
- yield from (
- (p1[0] + back_sum, p1[1] - back_diff),
- (p1[0] + back_diff, p1[1] + back_sum),
- (p2[0] - back_sum, p2[1] + back_diff),
- (p2[0] - back_diff, p2[1] - back_sum),
+ super().__init__(
+ [
+ (p1[0] + back_sum, p1[1] - back_diff),
+ (p1[0] + back_diff, p1[1] + back_sum),
+ (p2[0] - back_sum, p2[1] + back_diff),
+ (p2[0] - back_diff, p2[1] - back_sum),
+ ]
)
-class StrokeRoundLine(StrokeSquareLine):
+class StrokeRoundLine(Polygon):
"""
Enclose a rectangle fitted between two points with a regular, non-rotated n-gon
that should be visually indistinguishable with a half circle
"""
- def __init__(self, start, end, width, num_vertices):
- self.num_vertices = num_vertices
- super().__init__(start, end, width)
-
- def make_line_shape(self, p1, p2, shape_r):
- p_rel = (p1[0] - p2[0], p1[1] - p2[1])
- distance = sqrt(p_rel[0] * p_rel[0] + p_rel[1] * p_rel[1])
+ def __init__(self, p1, p2, width, num_vertices):
+ radius = width / 2
+ delta = (p1[0] - p2[0], p1[1] - p2[1])
+ distance = sqrt(delta[0] * delta[0] + delta[1] * delta[1])
+ points = []
if distance == 0:
- yield from (
- (p1[0] + cos(a) * shape_r, p1[1] + sin(a) * shape_r)
- for a in (
- (i) * tau / self.num_vertices
- for i in range(self.num_vertices)
- )
+ initial_corner = opposing_corner = num_vertices
+ back = (0, 0)
+ else:
+ initial_corner = ceil(
+ atan2(-delta[0], delta[1]) * num_vertices / tau
)
- return
- angle = atan2(p_rel[1], p_rel[0])
- if angle < 0:
- angle += tau
- # 0 <= angle < tau
- initial_corner = ceil(
- atan2(-p_rel[0], p_rel[1]) * self.num_vertices / tau
- ) % self.num_vertices
- opposing_corner = (
- floor(atan2(p_rel[0], -p_rel[1]) * self.num_vertices / tau) - initial_corner
- ) % self.num_vertices
- shape = [
- (cos(a) * shape_r, sin(a) * shape_r)
- for a in (
- (i + initial_corner) * tau / self.num_vertices
- for i in range(self.num_vertices)
+ opposing_corner = (
+ floor(atan2(delta[0], -delta[1]) * num_vertices / tau) - initial_corner
+ ) % num_vertices
+ back = (delta[0] * radius / distance, delta[1] * radius / distance)
+ points.extend(
+ (
+ (p2[0] + back[1], p2[1] - back[0]),
+ (p1[0] + back[1], p1[1] - back[0]),
+ )
)
- ]
- if distance == 0:
- for vertex in shape:
- yield (p1[0] + vertex[0], p1[1] + vertex[1])
- return
- # left: (cos(angle + tau * 3 / 4) * shape_r, sin(angle + tau * 3 / 4) * shape_r)
- # right: (cos(angle + tau / 4) * shape_r, sin(angle + tau / 4) * shape_r)
- # back = (cos(angle) * shape_r, sin(angle) * shape_r)
- # no need for trig here, though, because we only go ±90°:
- back = (p_rel[0] * shape_r / distance, p_rel[1] * shape_r / distance)
- # left is aka: (back[1], -back[0])
- # right is aka: (-back[1], back[0])
- yield (p2[0] + back[1], p2[1] - back[0])
- yield (p1[0] + back[1], p1[1] - back[0])
p = p1
- for i, vertex in enumerate(shape):
- yield (p[0] + vertex[0], p[1] + vertex[1])
- if i == opposing_corner and p == p1:
- yield (p[0] - back[1], p[1] + back[0])
+ for i in range(num_vertices):
+ angle = (i + initial_corner) * tau / num_vertices
+ points.append((p[0] + cos(angle) * radius, p[1] + sin(angle) * radius))
+ if i == opposing_corner:
+ points.extend(
+ (
+ (p1[0] - back[1], p1[1] + back[0]),
+ (p2[0] - back[1], p2[1] + back[0]),
+ )
+ )
p = p2
- yield (p[0] - back[1], p[1] + back[0])
+ super().__init__(points)
class Shapes(Shape):
yield end_angle
break
yield start_angle + a
-
-
-laptop_single = Shapes(
- [
- Rect((2, 3), (16.4, 1)),
- Rect((2, 4), (1, 8.1)),
- Rect((17.4, 4), (1, 8.1)),
- Rect((2, 12.1), (16.4, 1)),
- Polygon([(2, 13.1), (18.4, 13.1), (22.4, 18.1), (6, 18.1)]),
- ]
-)
-laptop_double = Shapes(
- [
- Rect((2, 3), (16.4, 1)),
- Rect((2, 4), (1, 8.1)),
- Rect((17.4, 4), (1, 8.1)),
- Rect((2, 12.1), (16.4, 1)),
- Polygon([(2, 13.1), (3, 13.1), (6.2, 17.1), (5.2, 17.1)]),
- Polygon([(17.4, 13.1), (18.4, 13.1), (21.6, 17.1), (20.4, 17.1)]),
- Polygon([(5.2, 17.1), (21.6, 17.1), (22.4, 18.1), (6, 18.1)]),
- ]
-)
-laptop_vertical = Shapes(
- [
- Rect((0.5, 1), (17.75, 1)),
- Rect((0.5, 2), (1, 14.4)),
- Rect((9, 2), (1, 14.4)),
- Rect((17.25, 2), (1, 14.4)),
- Rect((0.5, 16.4), (17.75, 1)),
- Polygon([(2, 17.4), (6, 22.4), (20.75, 22.4), (16.75, 17.4)]),
- ]
-)
-FINGER_RADIUS = 1.25
-NUM_VERTICES = 32
-touchscreen = Shapes(
- [
- StrokeCircleSegment((12, 16), 5, 0, tau * 3 / 8, 1, NUM_VERTICES),
- StrokeRoundLine(
- (12 + cos(tau * 3 / 8) * 5, 16 + sin(tau * 3 / 8) * 5),
- (
- 4 + cos(tau * 3 / 8) * FINGER_RADIUS,
- 13 + sin(tau * 3 / 8) * FINGER_RADIUS,
- ),
- 1,
- NUM_VERTICES,
- ),
- StrokeCircleSegment((4, 13), FINGER_RADIUS, tau * 3 / 8, tau * 7 / 8, 1, NUM_VERTICES),
- StrokeRoundLine(
- (
- 4 + cos(tau * 7 / 8) * FINGER_RADIUS,
- 13 + sin(tau * 7 / 8) * FINGER_RADIUS,
- ),
- (7, 13.5),
- 1,
- NUM_VERTICES,
- ),
- StrokeRoundLine((7, 13.5), (7, 6), 1, NUM_VERTICES),
- StrokeCircleSegment((8.25, 6), FINGER_RADIUS, pi, tau, 1, NUM_VERTICES),
- StrokeRoundLine((9.5, 6), (9.5, 11), 1, NUM_VERTICES),
- StrokeCircleSegment(
- (11, 11.5), FINGER_RADIUS, tau * 5 / 8, tau * 7 / 8, 1, NUM_VERTICES
- ),
- StrokeCircleSegment(
- (13.25, 12), FINGER_RADIUS, tau * 5 / 8, tau * 7 / 8, 1, NUM_VERTICES
- ),
- StrokeCircleSegment(
- (15.5, 12.5), FINGER_RADIUS, tau * 5 / 8, tau, 1, NUM_VERTICES
- ),
- StrokeRoundLine((16.75, 12.5), (17, 16), 1, NUM_VERTICES),
- StrokeCircleSegment((8.25, 6), 3, pi, tau, 1, NUM_VERTICES),
- StrokeCircleSegment((8.25, 6), 5, pi, tau, 1, NUM_VERTICES),
- ]
-)
-stylus = Shapes(
- [
- StrokeCircleSegment((3, 3), 1.5, tau * 3 / 8, tau * 7 / 8, 1, NUM_VERTICES),
- StrokeRoundLine(
- (3 + cos(tau * 3 / 8) * 1.5, 3 + sin(tau * 3 / 8) * 1.5),
- (16 + cos(tau * 3 / 8) * 1.5, 16 + sin(tau * 3 / 8) * 1.5),
- 1,
- NUM_VERTICES,
- ),
- StrokeRoundLine(
- (3 + cos(tau * 7 / 8) * 1.5, 3 + sin(tau * 7 / 8) * 1.5),
- (16 + cos(tau * 7 / 8) * 1.5, 16 + sin(tau * 7 / 8) * 1.5),
- 1,
- NUM_VERTICES,
- ),
- StrokeRoundLine(
- (16 + cos(tau * 3 / 8) * 1.5, 16 + sin(tau * 3 / 8) * 1.5),
- (18, 18),
- 1,
- NUM_VERTICES,
- ),
- StrokeRoundLine(
- (16 + cos(tau * 7 / 8) * 1.5, 16 + sin(tau * 7 / 8) * 1.5),
- (18, 18),
- 1,
- NUM_VERTICES,
- ),
- StrokeRoundLine((11, 11), (12, 12), 1, NUM_VERTICES),
- ]
-)
-bluetooth = Shapes(
- [
- StrokeSquareLine((10, 2), (15, 6), 1.5),
- StrokeSquareLine((15, 6), (5, 14), 1.5),
- StrokeSquareLine((10, 2.75), (10, 18.25), 1.5),
- StrokeSquareLine((10, 19), (15, 15), 1.5),
- StrokeSquareLine((15, 15), (5, 7), 1.5),
- ]
-)
-
-
-def main():
- shapes = [
- {
- "shape": laptop_single,
- "pos": (100, 100),
- },
- {
- "shape": laptop_double,
- "pos": (600, 100),
- },
- {
- "shape": laptop_vertical,
- "pos": (1100, 100),
- },
- {
- "shape": touchscreen,
- "pos": (100, 600),
- },
- {
- "shape": stylus,
- "pos": (600, 600),
- },
- {
- "shape": bluetooth,
- "pos": (1100, 600),
- },
- ]
- surf = pygame.display.set_mode((2000, 1600))
- running = True
- dirty = False
- clock = pygame.time.Clock()
- while True:
- for ev in pygame.event.get():
- if ev.type == pygame.QUIT:
- running = False
- break
- elif ev.type == pygame.WINDOWEXPOSED:
- dirty = True
- elif ev.type == pygame.KEYDOWN:
- if ev.key == pygame.K_ESCAPE:
- running = False
- break
- if not running:
- break
- elif dirty:
- surf.fill("black")
- for item in shapes:
- item["shape"].draw(surf, item["pos"], (16, 16), "green")
- pygame.display.update()
- dirty = False
- clock.tick(60)
-
-
-if __name__ == "__main__":
- main()