-
-
Save chrishamant/477a773cc55b6a347678 to your computer and use it in GitHub Desktop.
Collision Avoidance
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import deque | |
from math import sin, cos, pi, atan2, hypot | |
import random | |
import time | |
import wx | |
SIZE = 600 | |
COUNT = 64 | |
SPEED = 100 | |
FOLLOWERS = 4 | |
COLORS = [ | |
wx.RED, | |
] | |
class Bot(object): | |
def __init__(self, position, target): | |
self.position = position | |
self.target = target | |
self.speed = random.random() + 0.5 | |
self.padding = random.random() * 8 + 16 | |
self.history = deque(maxlen=64) | |
def get_position(self, offset): | |
px, py = self.position | |
tx, ty = self.target | |
angle = atan2(ty - py, tx - px) | |
return (px + cos(angle) * offset, py + sin(angle) * offset) | |
def update(self, bots): | |
px, py = self.position | |
tx, ty = self.target | |
angle = atan2(ty - py, tx - px) | |
dx = cos(angle) | |
dy = sin(angle) | |
for bot in bots: | |
if bot == self: | |
continue | |
x, y = bot.position | |
d = hypot(px - x, py - y) ** 2 | |
p = bot.padding ** 2 | |
angle = atan2(py - y, px - x) | |
dx += cos(angle) / d * p | |
dy += sin(angle) / d * p | |
angle = atan2(dy, dx) | |
magnitude = hypot(dx, dy) | |
return angle, magnitude | |
def set_position(self, position): | |
self.position = position | |
if not self.history: | |
self.history.append(self.position) | |
return | |
x, y = self.position | |
px, py = self.history[-1] | |
d = hypot(px - x, py - y) | |
if d >= 10: | |
self.history.append(self.position) | |
class Model(object): | |
def __init__(self, width, height, count): | |
self.width = width | |
self.height = height | |
self.bots = self.create_bots(count) | |
def create_bots(self, count): | |
result = [] | |
for i in range(count): | |
position = self.select_point() | |
target = self.select_point() | |
bot = Bot(position, target) | |
result.append(bot) | |
return result | |
def select_point(self): | |
cx = self.width / 2.0 | |
cy = self.height / 2.0 | |
radius = min(self.width, self.height) * 0.4 | |
angle = random.random() * 2 * pi | |
x = cx + cos(angle) * radius | |
y = cy + sin(angle) * radius | |
return (x, y) | |
def update(self, dt): | |
data = [bot.update(self.bots) for bot in self.bots] | |
for bot, (angle, magnitude) in zip(self.bots, data): | |
speed = min(1, 0.2 + magnitude * 0.8) | |
dx = cos(angle) * dt * SPEED * bot.speed * speed | |
dy = sin(angle) * dt * SPEED * bot.speed * speed | |
px, py = bot.position | |
tx, ty = bot.target | |
bot.set_position((px + dx, py + dy)) | |
if hypot(px - tx, py - ty) < 10: | |
bot.target = self.select_point() | |
for bot in self.bots[-FOLLOWERS:]: | |
bot.target = self.bots[0].get_position(10) | |
class Panel(wx.Panel): | |
def __init__(self, parent): | |
super(Panel, self).__init__(parent) | |
self.model = Model(SIZE, SIZE, COUNT) | |
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) | |
self.Bind(wx.EVT_SIZE, self.on_size) | |
self.Bind(wx.EVT_PAINT, self.on_paint) | |
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down) | |
self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_down) | |
self.timestamp = time.time() | |
self.on_timer() | |
def on_timer(self): | |
now = time.time() | |
dt = now - self.timestamp | |
self.timestamp = now | |
self.model.update(dt) | |
self.Refresh() | |
wx.CallLater(10, self.on_timer) | |
def on_left_down(self, event): | |
self.model.bots[0].target = event.GetPosition() | |
def on_right_down(self, event): | |
width, height = self.GetClientSize() | |
self.model = Model(width, height, COUNT) | |
def on_size(self, event): | |
width, height = self.GetClientSize() | |
self.model = Model(width, height, COUNT) | |
event.Skip() | |
self.Refresh() | |
def on_paint(self, event): | |
n = len(COLORS) | |
dc = wx.AutoBufferedPaintDC(self) | |
dc.SetBackground(wx.BLACK_BRUSH) | |
dc.Clear() | |
dc.SetPen(wx.BLACK_PEN) | |
for index, bot in enumerate(self.model.bots[:n]): | |
dc.SetBrush(wx.Brush(COLORS[index])) | |
for x, y in bot.history: | |
dc.DrawCircle(x, y, 3) | |
dc.SetBrush(wx.BLACK_BRUSH) | |
for index, bot in enumerate(self.model.bots[:n]): | |
dc.SetPen(wx.Pen(COLORS[index])) | |
x, y = bot.target | |
dc.DrawCircle(x, y, 6) | |
for index, bot in enumerate(self.model.bots): | |
dc.SetPen(wx.BLACK_PEN) | |
if index < n: | |
dc.SetBrush(wx.Brush(COLORS[index])) | |
elif index >= COUNT - FOLLOWERS: | |
dc.SetBrush(wx.BLACK_BRUSH) | |
dc.SetPen(wx.WHITE_PEN) | |
else: | |
dc.SetBrush(wx.WHITE_BRUSH) | |
x, y = bot.position | |
dc.DrawCircle(x, y, 6) | |
class Frame(wx.Frame): | |
def __init__(self): | |
super(Frame, self).__init__(None) | |
self.SetTitle('Motion') | |
self.SetClientSize((SIZE, SIZE)) | |
Panel(self) | |
def main(): | |
app = wx.App() | |
frame = Frame() | |
frame.Center() | |
frame.Show() | |
app.MainLoop() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment