from pygame.locals import * import pygame from pygame.math import Vector2 import math from math import sin, cos, pi, sqrt, hypot import time def screen2local(x, y=None): # Если аргументом передаётся Vector2 if y is None: y = x.y x = x.x nx = x - WIDTH / 2 ny = HEIGHT / 2 - y return (nx, ny) def local2screen(x, y=None): # Если аргументом передаётся Vector2 if y is None: y = x.y x = x.x nx = x + WIDTH / 2 ny = HEIGHT / 2 - y return (nx, ny) class Ellipse: def __init__(self, a, b): self.a = a self.b = b self.c = sqrt(self.a * self.a - self.b * self.b) self.f1 = (self.c, 0) self.f2 = (-self.c, 0) def draw(self, deltatime, screen, cx, cy): cnt = 0 for i in range(0, int(2 * pi * 100) + 1): cnt += 1 t = i / 100 t1 = t + 1 / 100 # Координаты в локальном пространстве x = self.a * cos(t) y = self.b * sin(t) x1 = self.a * cos(t1) y1 = self.b * sin(t1) start = local2screen(x, y) end = local2screen(x1, y1) pygame.draw.line(screen, WHITE, start, end, 2) pygame.draw.circle(screen, WHITE, local2screen(*self.f1), 3) pygame.draw.circle(screen, WHITE, local2screen(*self.f2), 3) class NiceEllipse: def __init__(self, a, b): self.a = a self.b = b self.time = 0 self.timeout = 700 self.i = 0 self.lines = [] self.c = sqrt(self.a * self.a - self.b * self.b) self.f1 = local2screen(self.c, 0) self.f2 = local2screen(-self.c, 0) self.lastend = None def draw(self, deltatime, screen, cx, cy): for start, end in self.lines: pygame.draw.line(screen, WHITE, start, end, 2) pygame.draw.circle(screen, WHITE, self.f1, 3) pygame.draw.circle(screen, WHITE, self.f2, 3) if self.lastend is not None: pygame.draw.line(screen, RED, self.f1, self.lastend, 2) pygame.draw.line(screen, GREEN, self.f2, self.lastend, 2) if self.i >= int(2 * pi * 100) + 1: self.lastend = None return if self.time > 0: self.time -= deltatime return self.time = self.timeout t = self.i / 100 t1 = t + 1 / 100 self.i += 1 # Координаты в локальном пространстве x = self.a * cos(t) y = self.b * sin(t) x1 = self.a * cos(t1) y1 = self.b * sin(t1) start = local2screen(x, y) end = local2screen(x1, y1) self.lastend = end self.lines.append((start, end)) pygame.draw.line(screen, WHITE, start, end, 2) class ScreenRay: def __init__(self, a: Vector2, b: Vector2, speed: float): self.pos = a self.dir = Vector2(b.x - a.x, b.y - a.y) self.dir.normalize_ip() self.speed = speed def update(self, deltatime): n_pos = self.pos + self.dir * (deltatime * self.speed) scr = Vector2(*local2screen(*tuple(self.pos))) scr_npos = Vector2(*local2screen(*tuple(n_pos))) if scr_npos.x < 0: self.dir = Vector2(-self.dir.x, self.dir.y) n_pos = Vector2(*screen2local(0, scr.y)) elif scr_npos.x > WIDTH: self.dir = Vector2(-self.dir.x, self.dir.y) n_pos = Vector2(*screen2local(WIDTH, scr.y)) elif scr_npos.y < 0: self.dir = Vector2(self.dir.x, -self.dir.y) n_pos = Vector2(*screen2local(scr.x, 0)) elif scr_npos.y > HEIGHT: self.dir = Vector2(self.dir.x, -self.dir.y) n_pos = Vector2(*screen2local(scr.x, HEIGHT)) self.pos = n_pos class EllipseRay: def __init__(self, s: Vector2, e: Vector2, speed: float, el): self.a = el.a self.b = el.b self.pos = e self.dir = Vector2(e.x - s.x, e.y - s.y) self.dir.normalize_ip() self.speed = speed def eps(self, x, y): a = self.a b = self.b tmp = self.ellipse(x, y) e = 0.05 return tmp < e and tmp > e def hit(self): eps = 0.001 npos = self.pos + self.dir tmp = self.ellipse(npos.x, npos.y) if tmp > eps: return self.find_point(self.pos.x, self.pos.y, npos.x, npos.y) else: return None def ellipse(self, x, y): return (x * x) / (self.a * self.a) + (y * y) / (self.b * self.b) - 1 def find_point(self, x0, y0, x1, y1, depth=0): eps = 0.0001 xc = (x0 + x1) / 2 yc = (y0 + y1) / 2 tmp = self.ellipse(xc, yc) if abs(tmp) < eps or hypot(x1 - x0, y1 - y0) < eps: return Vector2(xc, yc) elif tmp >= eps: return self.find_point(x0, y0, xc, yc, depth+1) elif tmp <= -eps: return self.find_point(xc, yc, x1, y1, depth+1) def update(self, deltatime): npos = self.pos + self.dir * (deltatime * self.speed) hit_result = self.hit() if hit_result is None: self.pos = npos return npos = hit_result scr = Vector2(*local2screen(*tuple(self.pos))) scr_npos = Vector2(*local2screen(*tuple(npos))) p0 = npos ######################## # Отражение от эллипса # ######################## if npos.y == 0: if npos.x < 0: self.dir.reflect_ip(Vector2(-1, 0)) else: self.dir.reflect_ip(Vector2(1, 0)) else: t = 1 m = 1 n = -(p0.x * self.b * self.b) / (p0.y * self.a * self.a) p1 = Vector2(p0.x + m * t, p0.y + n * t) kas = Vector2(p1.x - p0.x, p1.y - p0.y) kas.normalize_ip() if kas.y == 0: normal = Vector2(0, -1) else: normal = Vector2(1, -kas.x / kas.y) normal.normalize_ip() self.dir.reflect_ip(normal) ######################## self.pos = npos def draw(self, screen): loc = tuple(self.pos) scr = local2screen(*loc) pygame.draw.circle(screen, RED, scr, 3) class LaserEllipse: def __init__(self, a, b, ray_t=0, ray_end=None): self.a = a self.b = b if ray_end is None: ray_end = Vector2(0, 0) self.ray_end = ray_end self.ray_t = ray_t self.c = sqrt(self.a * self.a - self.b * self.b) self.f1 = Vector2(self.c, 0) self.f2 = Vector2(-self.c, 0) def ellipse(self, x, y): return (x * x) / (self.a * self.a) + (y * y) / (self.b * self.b) - 1 def get_normal(self, p0): if p0.y == 0: if p0.x < 0: normal = Vector2(-1, 0) else: normal = Vector2(1, 0) return normal m = 1 n = -(p0.x * self.b * self.b) / (p0.y * self.a * self.a) p1 = Vector2(p0.x + m, p0.y + n) kas = Vector2(p1.x - p0.x, p1.y - p0.y) kas.normalize_ip() if kas.y == 0: normal = Vector2(0, -1) else: normal = Vector2(1, -kas.x / kas.y) return normal.normalize() def find_point(self, x0, y0, x1, y1, depth=0): eps = 0.0001 xc = (x0 + x1) / 2 yc = (y0 + y1) / 2 tmp = self.ellipse(xc, yc) if abs(tmp) < eps or hypot(x1 - x0, y1 - y0) < eps: return Vector2(xc, yc) elif tmp >= eps: return self.find_point(x0, y0, xc, yc, depth+1) elif tmp <= -eps: return self.find_point(xc, yc, x1, y1, depth+1) def parametric(self, t): return Vector2(self.a * cos(t), self.b * sin(t)) def draw_lasers(self, screen): start = self.parametric(self.ray_t) d = (self.ray_end - start).normalize() end = self.get_laser(start, d) for i in range(50): pygame.draw.line(screen, RED, local2screen(start), local2screen(end), 2) n = self.get_normal(end) start = end d.reflect_ip(n) end = self.get_laser(start, d) def get_laser(self, start: Vector2, direction: Vector2) -> Vector2: tmp = start + direction * ((self.a + self.b) ** 2) end = self.find_point(start.x, start.y, tmp.x, tmp.y) return end def update(self, deltatime, value): # self.ray_t += deltatime / 10 self.ray_t = value * 2 * pi def set_ray_end(self, mousepos): pos = screen2local(*mousepos) if pygame.mouse.get_pressed()[0] and self.ellipse(*pos) < 0: self.ray_end = Vector2(*pos) def draw(self, deltatime, screen, cx, cy): self.draw_lasers(screen) cnt = 0 for i in range(0, int(2 * pi * 100) + 1): cnt += 1 t = i / 100 t1 = t + 1 / 100 # Координаты в локальном пространстве p0 = self.parametric(t) p1 = self.parametric(t1) start = local2screen(p0) end = local2screen(p1) pygame.draw.line(screen, WHITE, start, end, 2) pygame.draw.circle(screen, WHITE, local2screen(self.f1), 3) pygame.draw.circle(screen, WHITE, local2screen(self.f2), 3) ray_start = self.parametric(self.ray_t) start = local2screen(ray_start) d = (self.ray_end - ray_start).normalize() * 30 end = local2screen(ray_start + d) pygame.draw.line(screen, WHITE, start, end, 2) pygame.draw.circle(screen, GREEN, local2screen(self.ray_end), 2) class GUISlider: def __init__(self, rect: Rect, value=0): self.rect = rect self.filled_fg = Color("#2ef6a8") self.filled_bg = Color("#25cc8c") self.empty_fg = Color("#8a8a8a") self.empty_bg = Color("#737373") self.value = value def update(self, mousepos): if not self.rect.collidepoint(mousepos) or \ not pygame.mouse.get_pressed()[0]: return radius = self.rect.h / 2 self.value = (mousepos[0] - self.rect.left - radius) / (self.rect.width - radius * 2) if self.value > 1: self.value = 1 elif self.value < 0: self.value = 0 def draw(self, screen): padding = 3 r = self.rect.height // 2 # Empty background x = self.rect.left + r y = self.rect.top + r pygame.draw.circle(screen, self.empty_bg, (x, y), r) x = self.rect.right - r pygame.draw.circle(screen, self.empty_bg, (x, y), r) lt = (self.rect.left + r, self.rect.top) wh = (self.rect.w - 2 * r, self.rect.h) pygame.draw.rect(screen, self.empty_bg, Rect(lt, wh)) # Empty foreground x = self.rect.left + r y = self.rect.top + r pygame.draw.circle(screen, self.empty_fg, (x, y), r - padding) x = self.rect.right - r pygame.draw.circle(screen, self.empty_fg, (x, y), r - padding) lt = (self.rect.left + r, self.rect.top + padding) wh = (self.rect.w - 2 * r, self.rect.h - padding * 2) pygame.draw.rect(screen, self.empty_fg, Rect(lt, wh)) # Filled background x = self.rect.left + r y = self.rect.top + r pygame.draw.circle(screen, self.filled_bg, (x, y), r) lt = (self.rect.left + r, self.rect.top) wh = ((self.rect.w - 2 * r) * self.value, self.rect.h) pygame.draw.rect(screen, self.filled_bg, Rect(lt, wh)) # Filled foreground x = self.rect.left + r y = self.rect.top + r pygame.draw.circle(screen, self.filled_fg, (x, y), r - padding) lt = (self.rect.left + r, self.rect.top + padding) wh = ((self.rect.w - 2 * r) * self.value, self.rect.h - padding * 2) pygame.draw.rect(screen, self.filled_fg, Rect(lt, wh)) # Circle x = self.rect.left + r + (self.rect.w - 2 * r) * self.value y = self.rect.top + r pygame.draw.circle(screen, WHITE, (x, y), r + padding) pygame.draw.circle(screen, Color("#e7e7e7"), (x, y), r - 4) pygame.init() WIDTH, HEIGHT = SIZE = 800, 600 screen = pygame.display.set_mode(SIZE) clock = pygame.time.Clock() running = True BLACK = Color("black") WHITE = Color("white") GREEN = Color("green") RED = Color("red") font = pygame.font.SysFont("courier", 20) slider = GUISlider(Rect(10, HEIGHT - 30, WIDTH - 120, 20)) k = 2 * pi el = LaserEllipse(300, 200, ray_t=200, ray_end=Vector2(0, 100)) rays = [] # nn = 10 # for i in range(1, nn + 1): # x0, y0 = el.f2 # xr = cos(2 * pi * i / nn) + x0 # yr = sin(2 * pi * i / nn) + y0 # r = EllipseRay(Vector2(x0, y0), Vector2(xr, yr), 70, el) # rays.append(r) while running: screen.fill(BLACK) deltatime = clock.get_time() / 1000 for r in rays: r.update(deltatime) r.draw(screen) el.draw(deltatime, screen, 0, 0) el.update(deltatime, slider.value) slider.draw(screen) text = font.render(f"t = {round(slider.value * k, 2)}", True, WHITE) rect = Rect(0, 0, 0, 0) rect.left = WIDTH - 105 rect.w = text.get_rect().w rect.h = text.get_rect().h rect.centery = HEIGHT - 20 screen.blit(text, rect) # ... pygame.display.flip() for event in pygame.event.get(): if event.type == QUIT: pygame.quit() exit() elif event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() exit() elif event.type in [MOUSEMOTION, MOUSEBUTTONDOWN]: slider.update(event.pos) el.set_ray_end(event.pos) clock.tick(60) # print(clock.get_fps())