kopia lustrzana https://github.com/villares/sketch-a-day
274 wiersze
7.8 KiB
Python
274 wiersze
7.8 KiB
Python
# -*- coding: UTF-8 -*-
|
|
"""
|
|
From github.com/villares/villares/line_geometry.py
|
|
|
|
2020-09-25
|
|
2020-10-15 Fixed "line_instersection" typo, added dist() & removed TOLERANCE
|
|
2020-10-17 Added point_in_screen(), renamed poly() -> draw_poly()
|
|
2020-10-19 Fixed line_intersection typo, again :/, cleaned up stuff
|
|
"""
|
|
from __future__ import division
|
|
|
|
class Line():
|
|
|
|
def __init__(self, a, b):
|
|
self.a = PVector(*a)
|
|
self.b = PVector(*b)
|
|
|
|
def __getitem__(self, i):
|
|
return (self.a, self.b)[i]
|
|
|
|
def dist(self):
|
|
return PVector.dist(self.a, self.b)
|
|
|
|
def plot(self):
|
|
line(self.a.x, self.a.y, self.b.x, self.b.y)
|
|
|
|
draw = plot
|
|
|
|
def lerp(self, other, t):
|
|
a = PVector.lerp(self.a, other.a, t)
|
|
b = PVector.lerp(self.b, other.b, t)
|
|
return Line(a, b)
|
|
|
|
def intersect(self, other):
|
|
return line_intersect(self, other)
|
|
|
|
def contains_point(self, x, y, tolerance=0.1):
|
|
return point_over_line(x, y,
|
|
self[0][0], self[0][1],
|
|
self[1][0], self[1][1],
|
|
tolerance)
|
|
|
|
point_over = contains_point
|
|
|
|
def point_colinear(self, x, y, tolerance=EPSILON):
|
|
return points_are_colinear(x, y,
|
|
self[0][0], self[0][1],
|
|
self[1][0], self[1][1],
|
|
tolerance)
|
|
|
|
def line_intersect(line_a, line_b):
|
|
"""
|
|
code adapted from Bernardo Fontes
|
|
https://github.com/berinhard/sketches/
|
|
"""
|
|
x1, y1 = line_a.a.x, line_a.a.y
|
|
x2, y2 = line_a.b.x, line_a.b.y
|
|
x3, y3 = line_b.a.x, line_b.a.y
|
|
x4, y4 = line_b.b.x, line_b.b.y
|
|
try:
|
|
uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / \
|
|
((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
|
|
uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / \
|
|
((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
|
|
except ZeroDivisionError:
|
|
return
|
|
if not(0 <= uA <= 1 and 0 <= uB <= 1):
|
|
return
|
|
x = line_a.a.x + uA * (line_a.b.x - line_a.a.x)
|
|
y = line_a.a.y + uA * (line_a.b.y - line_a.a.y)
|
|
return PVector(x, y)
|
|
|
|
def point_over_line(px, py, lax, lay, lbx, lby,
|
|
tolerance=0.1):
|
|
"""
|
|
Check if point is over line using the sum of
|
|
the distances from the point to the line ends
|
|
(the result has to be near equal for True).
|
|
"""
|
|
ab = dist(lax, lay, lbx, lby)
|
|
pa = dist(lax, lay, px, py)
|
|
pb = dist(px, py, lbx, lby)
|
|
return (pa + pb) <= ab + tolerance
|
|
|
|
def points_are_colinear(ax, ay, bx, by, cx, cy,
|
|
tolerance=EPSILON):
|
|
"""
|
|
Test for colinearity by calculating the area
|
|
of a triangle formed by the 3 points.
|
|
"""
|
|
area = triangle_area((ax, ay), (bx, by), (cx, cy))
|
|
return abs(area) < tolerance
|
|
|
|
def triangle_area(a, b, c):
|
|
area = (a[0] * (b[1] - c[1]) +
|
|
b[0] * (c[1] - a[1]) +
|
|
c[0] * (a[1] - b[1]))
|
|
return area
|
|
|
|
# class Poly():
|
|
|
|
# def __init__(iterable):
|
|
# self.__points = [p for p in iterable]
|
|
|
|
# def __iter__(self):
|
|
# return iter(self.__points)
|
|
|
|
# def plot(self):
|
|
# poly(self)
|
|
|
|
# draw = poly
|
|
|
|
|
|
def draw_poly(points, holes=None, closed=True):
|
|
"""
|
|
Aceita como pontos sequencias de tuplas, lista ou vetores com (x, y) ou (x, y, z).
|
|
Note que `holes` espera uma sequencias de sequencias ou uma única sequencia de
|
|
pontos. Por default faz um polígono fechado.
|
|
"""
|
|
|
|
def depth(seq):
|
|
"""
|
|
usada para checar se temos um furo ou vários
|
|
devolve 2 para um só furo, 3 para vários furos
|
|
"""
|
|
if (isinstance(seq, list) or
|
|
isinstance(seq, tuple) or
|
|
isinstance(seq, PVector)):
|
|
return 1 + max(depth(item) for item in seq)
|
|
else:
|
|
return 0
|
|
|
|
beginShape() # inicia o PShape
|
|
for p in points:
|
|
if len(p) == 2 or p[2] == 0:
|
|
vertex(p[0], p[1])
|
|
else:
|
|
vertex(*p) # desempacota pontos em 3d
|
|
# tratamento dos furos, se houver
|
|
holes = holes or [] # equivale a: holes if holes else []
|
|
if holes and depth(holes) == 2: # sequência única de pontos
|
|
holes = (holes,) # envolve em um tupla
|
|
for hole in holes: # para cada furo
|
|
beginContour() # inicia o furo
|
|
for p in hole:
|
|
if len(p) == 2 or p[2] == 0:
|
|
vertex(p[0], p[1])
|
|
else:
|
|
vertex(*p)
|
|
endContour() # final e um furo
|
|
# encerra o PShape
|
|
if closed:
|
|
endShape(CLOSE)
|
|
else:
|
|
endShape()
|
|
|
|
poly = draw_poly
|
|
|
|
def edges_as_sets(poly_points):
|
|
"""
|
|
Return a frozenset of poly edges as frozensets of 2 points.
|
|
"""
|
|
return frozenset(frozenset(edge) for edge in edges(poly_points))
|
|
|
|
def edges(poly_points):
|
|
"""
|
|
Return a list of edges (tuples containing pairs of points)
|
|
for a list of points that represent a closed polygon
|
|
"""
|
|
return pairwise(poly_points) + [(poly_points[-1], poly_points[0])]
|
|
|
|
def pairwise(iterable):
|
|
from itertools import tee
|
|
"s -> (s0, s1), (s1, s2), (s2, s3), ..."
|
|
a, b = tee(iterable)
|
|
next(b, None)
|
|
return zip(a, b)
|
|
|
|
|
|
def min_max(points):
|
|
"""
|
|
Return two PVectors with the most extreme coordinates,
|
|
resulting in "bounding box" corners.
|
|
"""
|
|
points = iter(points)
|
|
try:
|
|
p = points.next()
|
|
min_x, min_y = max_x, max_y = p[0], p[1]
|
|
except StopIteration:
|
|
raise ValueError, "min_max requires at least one point"
|
|
for p in points:
|
|
if p[0] < min_x:
|
|
min_x = p[0]
|
|
elif p[0] > max_x:
|
|
max_x = p[0]
|
|
if p[1] < min_y:
|
|
min_y = p[1]
|
|
elif p[1] > max_y:
|
|
max_y = p[1]
|
|
return (PVector(min_x, min_y),
|
|
PVector(max_x, max_y))
|
|
|
|
def par_hatch(points, divisions, *sides):
|
|
vectors = [PVector(p[0], p[1]) for p in points]
|
|
lines = []
|
|
if not sides:
|
|
sides = [0]
|
|
for s in sides:
|
|
a, b = vectors[-1 + s], vectors[+0 + s]
|
|
d, c = vectors[-2 + s], vectors[-3 + s]
|
|
for i in range(1, divisions):
|
|
s0 = PVector.lerp(a, b, i / float(divisions))
|
|
s1 = PVector.lerp(d, c, i / float(divisions))
|
|
lines.append(Line(s0, s1))
|
|
return lines
|
|
|
|
def is_poly_self_intersecting(poly_points):
|
|
ed = edges(poly_points)
|
|
intersect = False
|
|
for a, b in ed[::-1]:
|
|
for c, d in ed[2::]:
|
|
# test only non consecutive edges
|
|
if (a != c) and (b != c) and (a != d):
|
|
if line_intersect(Line(a, b), Line(c, d)):
|
|
intersect = True
|
|
break
|
|
return intersect
|
|
|
|
def point_inside_poly(x, y, poly_points):
|
|
min_, max_ = min_max(poly_points)
|
|
if x < min_.x or y < min_.y or x > max_.x or y > max_.y:
|
|
return False
|
|
|
|
a = PVector(x, min_.y)
|
|
b = PVector(x, max_.y)
|
|
v_lines = inter_lines(Line(a, b), poly_points)
|
|
if not v_lines:
|
|
return False
|
|
|
|
a = PVector(min_.x, y)
|
|
b = PVector(max_.x, y)
|
|
h_lines = inter_lines(Line(a, b), poly_points)
|
|
if not h_lines:
|
|
return False
|
|
|
|
for v in v_lines:
|
|
for h in h_lines:
|
|
if line_intersect(v, h):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def inter_lines(L, poly_points):
|
|
inter_points = []
|
|
for a, b in edges(poly_points):
|
|
inter = line_intersect(Line(a, b), L)
|
|
if inter:
|
|
inter_points.append(inter)
|
|
if not inter_points:
|
|
return []
|
|
inter_lines = []
|
|
if len(inter_points) > 1:
|
|
inter_points.sort()
|
|
pairs = zip(inter_points[::2], inter_points[1::2])
|
|
for a, b in pairs:
|
|
if b:
|
|
inter_lines.append(Line(PVector(a.x, a.y),
|
|
PVector(b.x, b.y)))
|
|
return inter_lines
|
|
|
|
def point_in_screen(p):
|
|
return 0 <= p[0] <= width and 0 <= p[1] <= height
|