I am attempting to create a program which simulates particles colliding in a 2D box, but each particle is labeled with a random 5 character string name and each collision is tracked in a list along each particle. So after the simulation, I would like a list from each particle listing which particles it has hit. I have forked this great simulation https://github.com/xnx/collision and added the name and history attributes to the particle class. However, whenever I attempt to access .name or .history, my kernel dies. The output says:
Kernel died, restarting
Restarting kernel...
The failure happens in the handle_collisions function (line 197), or whenever I try to access the history or the name, so there must be something wrong in my implementation of the name and history attributes. I have also tried to instantiate name and history in the init_particles function instead of place_particles but that had the same results. I'm not exactly sure how to correctly implement them. Thanks for your help.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation
from itertools import combinations
import random
import string
class Particle:
"""A class representing a two-dimensional particle."""
def __init__(self, x, y, vx, vy, name, history, radius=0.01, styles=None):
"""Initialize the particle's position, velocity, name, history and radius.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Circle patch constructor.
"""
self.r = np.array((x, y))
self.v = np.array((vx, vy))
self.radius = radius
self.mass = self.radius**2
self.styles = styles
if not self.styles:
# Default circle styles
self.styles = {'edgecolor': 'b', 'fill': False}
# For convenience, map the components of the particle's position and
# velocity vector onto the attributes x, y, vx and vy.
#property
def x(self):
return self.r[0]
#x.setter
def x(self, value):
self.r[0] = value
#property
def y(self):
return self.r[1]
#y.setter
def y(self, value):
self.r[1] = value
#property
def vx(self):
return self.v[0]
#vx.setter
def vx(self, value):
self.v[0] = value
#property
def vy(self):
return self.v[1]
#vy.setter
def vy(self, value):
self.v[1] = value
#property
def history(self):
return self.history
#history.setter
def history(self,value):
self.history=value
#property
def name(self):
return self.name
#name.setter
def name(self,value):
self.name=value
def overlaps(self, other):
"""Does the circle of this Particle overlap that of other?"""
return np.hypot(*(self.r - other.r)) < self.radius + other.radius
def draw(self, ax):
"""Add this Particle's Circle patch to the Matplotlib Axes ax."""
circle = Circle(xy=self.r, radius=self.radius, **self.styles)
ax.add_patch(circle)
return circle
def advance(self, dt):
"""Advance the Particle's position forward in time by dt."""
self.r += self.v * dt
class Simulation:
"""A class for a simple hard-circle molecular dynamics simulation.
The simulation is carried out on a square domain: 0 <= x < 1, 0 <= y < 1.
"""
ParticleClass = Particle
def __init__(self, n, radius=0.01, styles=None):
"""Initialize the simulation with n Particles with radii radius.
Each particle is initialized with a 10 letter string name and an empty history.
radius can be a single value or a sequence with n values.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Circle patch constructor when drawing
the Particles.
"""
self.init_particles(n, radius, styles)
self.dt = 0.01
def place_particle(self, rad, styles):
# Choose x, y so that the Particle is entirely inside the
# domain of the simulation.
x, y = rad + (1 - 2*rad) * np.random.random(2)
# Choose a random velocity (within some reasonable range of
# values) for the Particle.
vr = 0.1 * np.sqrt(np.random.random()) + 0.05
vphi = 2*np.pi * np.random.random()
vx, vy = vr * np.cos(vphi), vr * np.sin(vphi)
name = self.assignname
history = []
particle = self.ParticleClass(x, y, vx, vy, name, history, rad, styles)
# Check that the Particle doesn't overlap one that's already
# been placed.
for p2 in self.particles:
if p2.overlaps(particle):
break
else:
self.particles.append(particle)
return True
return False
def assignname(self):
letters = string.ascii_lowercase
name=''.join(random.choice(letters) for i in range(5))
return name
def init_particles(self, n, radius, styles=None):
"""Initialize the n Particles of the simulation.
Positions and velocities are chosen randomly; radius can be a single
value or a sequence with n values.
"""
try:
iterator = iter(radius)
assert n == len(radius)
except TypeError:
# r isn't iterable: turn it into a generator that returns the
# same value n times.
def r_gen(n, radius):
for i in range(n):
yield radius
radius = r_gen(n, radius)
self.n = n
self.particles = []
for i, rad in enumerate(radius):
# Try to find a random initial position for this particle.
while not self.place_particle(rad, styles):
pass
def change_velocities(self, p1, p2):
"""
Particles p1 and p2 have collided elastically: update their
velocities.
"""
m1, m2 = p1.mass, p2.mass
M = m1 + m2
r1, r2 = p1.r, p2.r
d = np.linalg.norm(r1 - r2)**2
v1, v2 = p1.v, p2.v
u1 = v1 - 2*m2 / M * np.dot(v1-v2, r1-r2) / d * (r1 - r2)
u2 = v2 - 2*m1 / M * np.dot(v2-v1, r2-r1) / d * (r2 - r1)
p1.v = u1
p2.v = u2
def handle_collisions(self):
"""Detect and handle any collisions between the Particles.
When two Particles collide, they do so elastically: their velocities
change such that both energy and momentum are conserved.
"""
# We're going to need a sequence of all of the pairs of particles when
# we are detecting collisions. combinations generates pairs of indexes
# into the self.particles list of Particles on the fly.
#particles share history when they collide
pairs = combinations(range(self.n), 2)
for i,j in pairs:
if self.particles[i].overlaps(self.particles[j]):
self.change_velocities(self.particles[i], self.particles[j])
#FAILS HERE
#self.particles[i].history.append(self.particles[j].name)
#self.particles[j].history.append(self.particles[i].name)
def handle_boundary_collisions(self, p):
"""Bounce the particles off the walls elastically."""
if p.x - p.radius < 0:
p.x = p.radius
p.vx = -p.vx
if p.x + p.radius > 1:
p.x = 1-p.radius
p.vx = -p.vx
if p.y - p.radius < 0:
p.y = p.radius
p.vy = -p.vy
if p.y + p.radius > 1:
p.y = 1-p.radius
p.vy = -p.vy
def apply_forces(self):
"""Override this method to accelerate the particles."""
pass
def advance_animation(self):
"""Advance the animation by dt, returning the updated Circles list."""
for i, p in enumerate(self.particles):
p.advance(self.dt)
self.handle_boundary_collisions(p)
self.circles[i].center = p.r
self.handle_collisions()
self.apply_forces()
return self.circles
def advance(self):
"""Advance the animation by dt."""
for i, p in enumerate(self.particles):
p.advance(self.dt)
self.handle_boundary_collisions(p)
self.handle_collisions()
self.apply_forces()
def init(self):
"""Initialize the Matplotlib animation."""
self.circles = []
for particle in self.particles:
self.circles.append(particle.draw(self.ax))
return self.circles
def animate(self, i):
"""The function passed to Matplotlib's FuncAnimation routine."""
self.advance_animation()
return self.circles
def setup_animation(self):
self.fig, self.ax = plt.subplots()
for s in ['top','bottom','left','right']:
self.ax.spines[s].set_linewidth(2)
self.ax.set_aspect('equal', 'box')
self.ax.set_xlim(0, 1)
self.ax.set_ylim(0, 1)
self.ax.xaxis.set_ticks([])
self.ax.yaxis.set_ticks([])
def save_or_show_animation(self, anim, save, filename='collision.mp4'):
if save:
Writer = animation.writers['ffmpeg']
writer = Writer(fps=10, bitrate=1800)
anim.save(filename, writer=writer)
else:
plt.show()
def do_animation(self, save=False, interval=1, filename='collision.mp4'):
"""Set up and carry out the animation of the molecular dynamics.
To save the animation as a MP4 movie, set save=True.
"""
self.setup_animation()
anim = animation.FuncAnimation(self.fig, self.animate,
init_func=self.init, frames=800, interval=interval, blit=True)
self.save_or_show_animation(anim, save, filename)
if __name__ == '__main__':
nparticles = 20
radii = .02
styles = {'edgecolor': 'C0', 'linewidth': 2, 'fill': None}
sim = Simulation(nparticles, radii, styles)
sim.do_animation(save=False)
I see two immediate problems in your code.
First: you did add history as a Particle.__init__ parameter but you never initialize the property itself.
Add something like this:
def __init__(self, x, y, vx, vy, name, history, radius=0.01, styles=None):
self._history = history
And bigger problem: you have an infinite recursion in your #property definition:
#property
def history(self):
return self.history
#history.setter
def history(self,value):
self.history=value
So in your getter you called history you call itself return self.history which will loop itself until program will crash.
Rename internal property to _history:
#property
def history(self):
return self._history
#history.setter
def history(self,value):
self._history=value
Related
I'm trying to solve the tasks (text below), but I have a problem with the second point, i.e. method in it that displays the length of the segment and the positions of the start and end points.-
I don't really know how to write it, Could someone look at the code and give some hints?
Define a Point class with x, y fields and a method displaying the
point's position (eg "point (2,3)").
Then create a class Segment that will inherit from the class point.
Create a method in it that displays the length of the segment and the
positions of the start and end points.
Then define the Triangle class which will contain 3 Points,
automatically determined 3 Sections (walls) of them and included a
method for displaying the surface area of the perimeter.
code:
from math import sqrt, hypot
class Point:
def __init__(self, x_init, y_init):
self.x = x_init
self.y = y_init
def __str__(self):
return "Point(%s,%s)"%(self.x, self.y)
class Segment(Point):
def distance(self): **!-probably a badly written method**
return ((self.x ** 2) + (self.y ** 2)) ** 0.5
def position(self, p): **!-probably a badly written method**
dx = self.x - p.X
dy = self.y - p.Y
return hypot(dx, dy)
class Triangle(Point):
def __init__(self, x, y, z):
Point.__init__(self, x, y)
self.z = z
def __str__(self):
return "Point(x %s ,y %s, z %s )" % (self.x, self.y, self.z)
def __area__(a, b, c):
s = (a + b + c) / 2
return (s * (s - a) * (s - b) * (s - c)) ** 0.5
def __perimeter__(a, b, c):
s = (a + b + c)
return s
Task 2 is just wrong. Segment needs to contain 2 points, so it can't inherit from Point. It should be:
class Segment:
def __init__(self, start, end):
self.start = start
self.end = end
def distance(self):
dist = ((self.start.x - self.end.x) ** 2 + (self.start.y - self.end.y) ** 2) ** 0.5
print(f"distance from {self.start} to {self.end} is {dist}"
So i have here my main program (which i absolutely can't make any changes because this is how our instructor wants it to be run):
from class_point import Point
from class_polygon import Polygon
pt1 = Point(0,0)
pt2 = Point(0,4)
pt3 = Point(3,0)
polygon1 = Polygon([pt1,pt2,pt3]) #yes, the class Polygon will be initialized using a list
print(polygon1.get_perimeter())
So basically, i have two separate files containing the definitions of class Polygon and class Point.
This is my code for class Point which has the function for calculating the distance between two given points:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, second):
x_d = self.x - second.x
y_d = self.y - second.y
return (x_d**2 + y_d**2) **0.5
And this is my code for class Polygon which will use the defined points and the distance function to calculate the perimeter of my polygon:
class Polygon():
def __init__(self, points):
self.points = points
def __len__(self):
len_points = len(self.points)
return len_points
def get_perimeter(self,points,len_points):
perimeter = 0
for i in range(0, len_points):
pt1 = self.points[i]
pt2 = self.points[i+1]
perimeter += pt1.distance(pt2)
if i + 1 == len_points:
perimeter += points[-1].distance(points[0])
else:
continue
return perimeter
But whenever i try to run the code, i get the error:
File "C:/Users/Dust/Desktop/polygon_trial.py", line 8, in <module>
print(polygon1.get_perimeter())
TypeError: get_perimeter() missing 2 required positional arguments: 'points' and 'len_points'
This should work
class Polygon():
def __init__(self, points):
self.points = points
def __len__(self):
return len(self.points)
def get_perimeter(self):
perimeter = 0
for i in range(0, len(self.points)):
pt1 = self.points[i]
pt2 = self.points[i+1]
perimeter += pt1.distance(pt2)
if i + 1 == len(self.points):
perimeter += self.points[-1].distance(self.points[0])
else:
continue
return perimeter
Are the classmethods being used correctly?
I am working on a program to create data input for an 3-D N-body problem. The goal is to create a uniform density sphere with 50000 particles. Each particle class instance must have a mass, position and velocity. The position vector must be in spherical so when the instance of a particle is created it is within a sphere of radius 1. The velocity must be randomized in 3-directions. This will be changed later by adding an orbital velocity. All the data will later be exported into 3 lists masses, position and velocity all in Cartesian coordinates.
I am having trouble creating the particles with such attributes.
The first run of code was:
import math
import numpy as np
class Particle:
def__init__(self,mass,position,velocity):
self.mass = 1/50000
self.position = position
self.velocity=velocity
def position(self):
self.position = (self.r, self.theta, self.phi)
#classmethod
def set_r(cls, r):
cls.r = np.rand.uniform(0,1)
#classmethod
def set_theta(cls, theta):
cls.theta = np.rand.uniform(-(math.pi)/2 ,(math.pi)/2)
#classmethod
def set_phi(cls, phi):
cls.phi = np.rand.uniform(0,2*math.pi)
def velocity(self):
self.velocity = (self.Vx, self.Vy, self.Vz)
#classmethod
def set_Vx(cls, Vx):
cls.Vx = np.rand.uniform(0,0.001)
#classmethod
def set_Vy(cls, Vy):
cls.Vy = np.rand.uniform(0,0.001)
#classmethod
def set_Vz(cls, Vz):
cls.Vz = np.rand.uniform(0,0.001)
After talking to a friend in the CS department the code was edited to:
import math
import numpy as np
class Particle():
def __init__(self,mass,position,velocity):
self.mass = 1/50000
self.position = position[]
self.velocity = velocity[]
#classmethod
def getPosition(cls):
return [cls.r, cls.theta, cls.phi]
#classmethod
def set_r(cls, r):
cls.position[0] = np.rand.uniform(0,1)
#classmethod
def set_theta(cls, theta):
cls.position[1] = np.rand.uniform(-(math.pi)/2 ,(math.pi)/2)
#classmethod
def set_phi(cls, phi):
cls.position[2] = np.rand.uniform(0,2*math.pi)
def getVelocity(cls):
return [cls.Vx, cls.Vy, cls.Vz]
#classmethod
def set_Vx(cls, Vx):
cls.velocity[0] = np.rand.uniform(0,0.001)
#classmethod
def set_Vy(cls, Vy):
cls.velocity[1] = np.rand.uniform(0,0.001)
#classmethod
def set_Vz(cls, Vz):
cls.velocity[2] = np.rand.uniform(0,0.001)
Do I need to define the parts of the vectors in the init and then use a classmethod to create the arrays to be used and changed later?
EDIT 1: The class will be ran trough a for loop to create 50000 particles each with the same mass (normalized to 1/50000), a position vector, and a velocity vector. So exported to a .dat file in a list
If I'm understanding correctly, I don't believe you need classmethods here, rather you want to deal with each Particle individually. If I'm correct, I believe you're looking for a class that each instance knows it's own mass, position and velocity. I made a class that resembles yours but I used namedtuples to represent a position, and velocity.
import math
import numpy as np
from collections import namedtuple
Position = namedtuple('Position', ('r', 'theta', 'phi'))
Velocity = namedtuple('Velocity', ('Vx', 'Vy', 'Vz'))
class Particle():
#With 50,000 instances being created, I suggest using __slots__ if possible.
#This will cut down some on memory consumption.
__slots__ = ('mass', 'position', 'velocity')
def __init__(self, *args, **kwargs):
self.mass = kwargs.get('mass', None)
self.position = kwargs.get('position', None)
self.velocity = kwargs.get('velocity', None)
#Note! This will automatically set random values if any
#of mass, position, velocity are None when initialized
#so this may need to be altered if this is undesired
#this is just a skeleton example and if it works for you it works
if not any((self.mass, self.position, self.velocity)):
self.setup_random()
def setup_random(self):
self.mass = 1/1500
self.position = Position(
r = np.random.uniform(0,1),
theta = np.random.uniform(-(math.pi)/2 ,(math.pi)/2),
phi = np.random.uniform(0,2*math.pi)
)
self.velocity = Velocity(
Vx = np.random.uniform(0,0.001),
Vy = np.random.uniform(0,0.001),
Vz = np.random.uniform(0,0.001)
)
def set_r(self, r):
self.position = self.position.replace(r = r)
def set_theta(self, theta):
self.position = self.position.replace(theta = theta)
def set_phi(self, phi):
self.position = self.position.replace(phi = phi)
def set_Vx(self, Vx):
self.velocity = self.velocity.replace(Vx = Vx)
def set_Vy(self, Vy):
self.velocity = self.velocity.replace(Vy = Vy)
def set_Vz(self, Vz):
self.velocity = self.velocity.replace(Vz = Vz)
def __str__(self):
return('Mass: {}\nPosition: {}\nVelocity: {}'.format(
self.mass,
self.position,
self.velocity))
def __repr__(self):
return('Mass: {}\nPosition: {}\nVelocity: {}'.format(
self.mass,
self.position,
self.velocity))
From here you can make as many particles as needed just using Particle()
p = Particle()
print(p)
And this prints:
Mass: 0.0006666666666666666
Position: Position(r=0.8122849235862195, theta=-0.060787026289457646, phi=3.415049614503205)
Velocity: Velocity(Vx=0.0006988289817776562, Vy=0.00040214068163074246, Vz=0.0003347218438727625)
You can get a value very easily thanks to the namedtuples as well:
print(p.position.r)
#0.8122849235862195
And you can make a particle using pre-defined values like the following:
p = Particle(
mass = 2/5000,
position = Position(r=1, theta = 2, phi = 3),
velocity = Velocity(Vx = 4, Vy = 5, Vz = 6))
print(p)
Results:
Mass: 0.0004
Position: Position(r=1, theta=2, phi=3)
Velocity: Velocity(Vx=4, Vy=5, Vz=6)
You will still need the setter methods to set individual values such as r, theta... as tuples are immutable, although you can set a completely new position easily as well Ex:
#to set an individual value
p.set_r(1)
#setting a whole new position/velocity
p.position = Position(r = 1, theta = 2, phi = 3)
#or
p.position = Position(r = p.position.r, theta = 2, phi = 3)
#as an example
If you want to use a different collection type or anything feel free to do so I just figured namedtuples fit well here.
Edit
To allow for loading and unloading from a data file; you can make to_json and from_json methods.
Let's say your data for one Particle looks like:
d = {'mass': 0.0006666666666666666,
'r': 0.8122849235862195,
'theta': -0.060787026289457646,
'phi': 3.415049614503205,
'Vx': 0.0006988289817776562,
'Vy': 0.00040214068163074246,
'Vz': 0.0003347218438727625
}
Your methods would look like this:
def to_json(self):
json_particle = {'mass': self.mass}
json_particle.update(dict(self.position._asdict()))
json_particle.update(dict(self.velocity._asdict()))
return json_particle
#here we finally use #classmethod
#as we are creating a new instance of the class
#classmethod
def from_json(cls, data):
pos = Position(r = data['r'], theta = data['theta'], phi = data['phi'])
vel = Velocity(Vx = data['Vx'], Vy = data['Vy'], Vz = data['Vz'])
return cls(data['mass'], pos, vel)
First of all, here is a quick graphical description of what I intend to do. I will use Python, but feel free to use pseudo code in your answers.
I have 2 collections of 2D segments, stored as the following: [ [start_point, end_point], [...] ].
For the first step, I have to detect each segment of the blue collection which is colliding with the black collection. For this I use LeMothe's line/line intersection algorithm.
Then comes my first problem: Having a segment AB which intersects with my line in C, I don't know how to determine using code if I have to trim my segment as AC or as CB , ie: I don't know which part of my segment I need to keep and which one I need to remove.
Then for the second step, I have really no ideas how to achieve this.
Any help would be greatly appreciated, so thank you in advance!
The second step is trivial once you figure what to keep and what not, you just need to keep track of the segments you clipped and see where they were originally joined (e.g. assume that the segments are in order and form a connected line).
On the other hand, given that your black line is in fact a line and not a polygon, in your first step, choosing what is "outside" and what is "inside" seems completely arbitrary; is it possible to close that into a polygon? Otherwise, you may need to artificially create two polygons (one for each side of the line) and then do clipping inside those polygons. You could use something like the Cyrus and Beck line clipping algorithm (see this tutorial for an overview: https://www.tutorialspoint.com/computer_graphics/viewing_and_clipping.htm)
Feel free to use any of the code below as a starting point (you have an intersect function and some useful classes). Implements Sutherland and Hodgman.
class Point2(object):
"""Structure for a 2D point"""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __copy__(self):
return self.__class__(self.x, self.y)
copy = __copy__
def __repr__(self):
return 'Point2(%d, %d)' % (self.x, self.y)
def __getitem__(self, key):
return (self.x, self.y)[key]
def __setitem__(self, key, value):
l = [self.x, self.y]
l[key] = value
self.x, self.y = l
def __eq__(self, other):
if isinstance(other, Point2):
return self.x == other.x and \
self.y == other.y
else:
assert hasattr(other, '__len__') and len(other) == 2
return self.x == other[0] and \
self.y == other[1]
def __ne__(self, other):
return not self.__eq__(other)
def __nonzero__(self):
return self.x != 0 or self.y != 0
def __len__(self):
return 2
class Line2(object):
"""Structure for a 2D line"""
def __init__(self,pt1,pt2):
self.pt1,self.pt2=pt1,pt2
def __repr__(self):
return 'Line2(%s, %s)' % (self.pt1, self.pt2)
class Polygon2(object):
def __init__(self,points):
self.points = points
def __repr__(self):
return '[\n %s\n]' % '\n '.join([str(i) for i in self.points])
def lines(self):
lines = []
e = self.points[-1].copy()
for p in self.points:
lines.append(Line2(e,p))
e = p.copy()
return lines
#return [Line2(a,b) for a,b in zip(self.points,self.points[1:]+[self.points[0]])]
def __copy__(self):
return self.__class__(list(self.points))
copy = __copy__
class Renderer(object):
"""Rendering algorithm implementations"""
def __init__(self,world,img,color=1):
self.world,self.img,self.color=world,img,color
def transform(self,s,r,m,n):
"""Homogeneous transformation operations"""
for i in self.world.points():
j = Matrix3.new_translate(m, n)*Matrix3.new_rotate(r)*Matrix3.new_scale(s)*i
i.x,i.y = j.x,j.y
def clip(self,a,b,c,d):
"""Clipping for the world window defined by a,b,c,d"""
self.clip_lines(a, b, c, d)
self.clip_polygons(a, b, c, d)
def shift(self,a,b,c,d):
"""Shift the world window"""
for i in self.world.points():
i.x -= a
i.y -= b
def clip_lines(self,a,b,c,d):
"""Clipping for lines (i.e. open polygons)"""
clipped = []
for i in self.world.lines:
clipped += [self.clip_lines_cohen_sutherland(i.pt1, i.pt2, a, b, c, d)]
self.world.lines = [i for i in clipped if i]
def clip_polygons(self,a,b,c,d):
"""Clipping for polygons"""
polygons = []
for polygon in self.world.polygons:
new_polygon = self.clip_polygon_sutherland_hodgman(polygon, a, b, c, d)
polygons.append(new_polygon)
self.world.polygons = polygons
def clip_polygon_sutherland_hodgman(self,polygon,xmin,ymin,xmax,ymax):
edges = [Line2(Point2(xmax,ymax),Point2(xmin,ymax)), #top
Line2(Point2(xmin,ymax),Point2(xmin,ymin)), #left
Line2(Point2(xmin,ymin),Point2(xmax,ymin)), #bottom
Line2(Point2(xmax,ymin),Point2(xmax,ymax)), #right
]
def is_inside(pt,line):
# uses the determinant of the vectors (AB,AQ), Q(X,Y) is the query
# left is inside
det = (line.pt2.x-line.pt1.x)*(pt.y-line.pt1.y) - (line.pt2.y-line.pt1.y)*(pt.x-line.pt1.x)
return det>=0
def intersect(pt0,pt1,line):
x1,x2,x3,x4 = pt0.x,pt1.x,line.pt1.x,line.pt2.x
y1,y2,y3,y4 = pt0.y,pt1.y,line.pt1.y,line.pt2.y
x = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))
y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4))
return Point2(int(x),int(y))
polygon_new = polygon.copy()
for edge in edges:
polygon_copy = polygon_new.copy()
polygon_new = Polygon2([])
s = polygon_copy.points[-1]
for p in polygon_copy.points:
if is_inside(s,edge) and is_inside(p,edge):
polygon_new.points.append(p)
elif is_inside(s,edge) and not is_inside(p,edge):
polygon_new.points.append(intersect(s,p,edge))
elif not is_inside(s,edge) and not is_inside(p,edge):
pass
else:
polygon_new.points.append(intersect(s,p,edge))
polygon_new.points.append(p)
s = p
return polygon_new
def clip_lines_cohen_sutherland(self,pt0,pt1,xmin,ymin,xmax,ymax):
"""Cohen-Sutherland clipping algorithm for line pt0 to pt1 and clip rectangle with diagonal from (xmin,ymin) to (xmax,ymax)."""
TOP = 1
BOTTOM = 2
RIGHT = 4
LEFT = 8
def ComputeOutCode(pt):
code = 0
if pt.y > ymax: code += TOP
elif pt.y < ymin: code += BOTTOM
if pt.x > xmax: code += RIGHT
elif pt.x < xmin: code += LEFT
return code
accept = False
outcode0, outcode1 = ComputeOutCode(pt0), ComputeOutCode(pt1)
while True:
if outcode0==outcode1==0:
accept=True
break
elif outcode0&outcode1:
accept=False
break
else:
#Failed both tests, so calculate the line segment to clip from an outside point to an intersection with clip edge.
outcodeOut = outcode0 if not outcode0 == 0 else outcode1
if TOP & outcodeOut:
x = pt0.x + (pt1.x - pt0.x) * (ymax - pt0.y) / (pt1.y - pt0.y)
y = ymax
elif BOTTOM & outcodeOut:
x = pt0.x + (pt1.x - pt0.x) * (ymin - pt0.y) / (pt1.y - pt0.y)
y = ymin
elif RIGHT & outcodeOut:
y = pt0.y + (pt1.y - pt0.y) * (xmax - pt0.x) / (pt1.x - pt0.x);
x = xmax;
elif LEFT & outcodeOut:
y = pt0.y + (pt1.y - pt0.y) * (xmin - pt0.x) / (pt1.x - pt0.x);
x = xmin;
if outcodeOut == outcode0:
pt0 = Point2(x,y)
outcode0 = ComputeOutCode(pt0)
else:
pt1 = Point2(x,y)
outcode1 = ComputeOutCode(pt1);
if accept:
return Line2(pt0,pt1)
else:
return False
I think what you'll need to do is find a line from the center of the blue object to the line segment in question. If that new line from the center to the segment AB or BC hits a black line on its way to the blue line segment, then that segment is outside and is trimmed. You would want to check this at a point between A and B or between B and C, so that you don't hit the intersection point.
As for the python aspect, I would recommend defining a line object class with some midpoint attributes and a shape class that's made up of lines with a center attribute, (Actually come to think of it, then a line would count as a shape so you could make line a child class of the shape class and preserve code), that way you can make methods that compare two lines as part of each object.
line_a = Line((4,2),(6,9))
line_b = Line((8,1),(2,10))
line_a.intersects(line.b) #Could return Boolean, or the point of intersection
In my mind that just feels like a really comfortable way to go about this problem since it lets you keep track of what everything's doing.
Points are defined by giving the x and y coordinates. The distance between two points can be calculated by using the distance_from method. Points should have publicly available at least the following properties x, y and distance_from.
Circles are defined by giving the center point and the radius. Circle has the method is_inside that answers if the given point is inside the circle or not. Circles should have publicly available at least the following properties center, radius and is_inside.
The issue here is that i can't initiate my parent and base classes.
Side note: Funtions and classes distance_from, Circle, circle.is_inside only take one p.
import math
class Point():
def __init__(self, xpoint, ypoint):
self.xpoint = xpoint
self.ypoint = ypoint
def distance_from(xpoint, ypoint):
distance = math.sqrt(((p1.xpoint-p2.xpoint)**2)+((p1.ypoint-p2.ypoint)**2))
print(distance)
class Circle(Point):
def __init__(self, xpoint, ypoint, r):
super(Circle, self).__init__(xpoint, ypoint)
self.r = r
def is_inside(xpoint, ypoint):
if self.r > distance:
print("False")
else:
print("True")
p1 = Point(0, 0)
p2 = Point(2, 4)
p1.distance_from(p2)
circle = Circle(p2,4)
circle.is_inside(p1)
Change your coe to this: I added comments to your mistakes
import math
class Point(object): # Add object here
def __init__(self, xpoint, ypoint):
self.xpoint = xpoint
self.ypoint = ypoint
def distance_from(self, xpoint, ypoint): # Missing self here
distance = math.sqrt(((p1.xpoint-p2.xpoint)**2)+((p1.ypoint-p2.ypoint)**2))
print(distance)
class Circle(Point):
def __init__(self, xpoint, ypoint, r):
super(Circle, self).__init__(xpoint, ypoint)
self.r = r
def is_inside(self, point): # Missing self here
if self.r > self.distance_from(point.xpoint, point.ypoint): # You'll have to callculate distance here
print("False")
else:
print("True")
p1 = Point(0, 0)
p2 = Point(2, 4)
p1.distance_from(p1.xpoint, p1.ypoint) # Missing points
c = Circle(p1.xpoint, p1.ypoint, 4) # Missing points
c.is_inside(p2)
If you don't want to use super().init use this:
Point.__init__(self, xpoint, ypoint) # Change super to the superclass name. Add self to the init method
Some notes:
Member functions always have self as the first argument.
Member functions should only access to variables accessible through self or the fuction parameters, never global scope.
import math
class Point(object):
def __init__(self, xpoint, ypoint):
self.xpoint = xpoint
self.ypoint = ypoint
# Here you where missing self and you were accessing p2 that exists in global scope.
def distance_from(self, another_point):
distance = math.sqrt(((self.xpoint-another_point.xpoint)**2)+((self.ypoint-another_point.ypoint)**2))
print(distance)
class Circle(Point):
def __init__(self, xpoint, ypoint, r):
super(Circle, self).__init__(xpoint, ypoint)
self.r = r
def is_inside(self, another_point):
# you forgot to calculate the distance
if self.distance_from(another_point) < self.r:
print("True")
else:
print("False")
p1 = Point(0, 0)
p2 = Point(2, 4)
p1.distance_from(p2)
#create a circle centered at 2,4 with radius 4
circle = Circle(2, 4, 4)
circle.is_inside(p1) # returns "True"
When calling __init__ manually you must provide the self argument.