I'm trying to implement SVG path calculations in Python, but I'm running into problems with Arc curves.
I think the problem is in the conversion from end point to center parameterization, but I can't find the problem. You can find notes on how to implement it in section F6.5 of the SVG specifications. I've also looked at implementations in other languages and I can't see what they do different either.
My Arc object implementation is here:
class Arc(object):
def __init__(self, start, radius, rotation, arc, sweep, end):
"""radius is complex, rotation is in degrees,
large and sweep are 1 or 0 (True/False also work)"""
self.start = start
self.radius = radius
self.rotation = rotation
self.arc = bool(arc)
self.sweep = bool(sweep)
self.end = end
self._parameterize()
def _parameterize(self):
# Conversion from endpoint to center parameterization
# http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
cosr = cos(radians(self.rotation))
sinr = sin(radians(self.rotation))
dx = (self.start.real - self.end.real) / 2
dy = (self.start.imag - self.end.imag) / 2
x1prim = cosr * dx + sinr * dy
x1prim_sq = x1prim * x1prim
y1prim = -sinr * dx + cosr * dy
y1prim_sq = y1prim * y1prim
rx = self.radius.real
rx_sq = rx * rx
ry = self.radius.imag
ry_sq = ry * ry
# Correct out of range radii
radius_check = (x1prim_sq / rx_sq) + (y1prim_sq / ry_sq)
if radius_check > 1:
rx *= sqrt(radius_check)
ry *= sqrt(radius_check)
rx_sq = rx * rx
ry_sq = ry * ry
t1 = rx_sq * y1prim_sq
t2 = ry_sq * x1prim_sq
c = sqrt((rx_sq * ry_sq - t1 - t2) / (t1 + t2))
if self.arc == self.sweep:
c = -c
cxprim = c * rx * y1prim / ry
cyprim = -c * ry * x1prim / rx
self.center = complex((cosr * cxprim - sinr * cyprim) +
((self.start.real + self.end.real) / 2),
(sinr * cxprim + cosr * cyprim) +
((self.start.imag + self.end.imag) / 2))
ux = (x1prim - cxprim) / rx
uy = (y1prim - cyprim) / ry
vx = (-x1prim - cxprim) / rx
vy = (-y1prim - cyprim) / ry
n = sqrt(ux * ux + uy * uy)
p = ux
theta = degrees(acos(p / n))
if uy > 0:
theta = -theta
self.theta = theta % 360
n = sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))
p = ux * vx + uy * vy
if p == 0:
delta = degrees(acos(0))
else:
delta = degrees(acos(p / n))
if (ux * vy - uy * vx) < 0:
delta = -delta
self.delta = delta % 360
if not self.sweep:
self.delta -= 360
def point(self, pos):
if self.arc == self.sweep:
angle = radians(self.theta - (self.delta * pos))
else:
angle = radians(self.delta + (self.delta * pos))
x = sin(angle) * self.radius.real + self.center.real
y = cos(angle) * self.radius.imag + self.center.imag
return complex(x, y)
You can test this with the following code that will draw the curves with the Turtle module. (The raw_input() at the end is just to that the screen doesn't disappear when the program exits).
arc1 = Arc(0j, 100+50j, 0, 0, 0, 100+50j)
arc2 = Arc(0j, 100+50j, 0, 1, 0, 100+50j)
arc3 = Arc(0j, 100+50j, 0, 0, 1, 100+50j)
arc4 = Arc(0j, 100+50j, 0, 1, 1, 100+50j)
import turtle
t = turtle.Turtle()
t.penup()
t.goto(0, 0)
t.dot(5, 'red')
t.write('Start')
t.goto(100, 50)
t.dot(5, 'red')
t.write('End')
t.pencolor = t.color('blue')
for arc in (arc1, arc2, arc3, arc4):
t.penup()
p = arc.point(0)
t.goto(p.real, p.imag)
t.pendown()
for x in range(1,101):
p = arc.point(x*0.01)
t.goto(p.real, p.imag)
raw_input()
The issue:
Each of these four arcs drawn should draw from the Start point to the End point. However, they are drawn from the wrong points. Two curves go from end to start, and two goes from 100,-50 to 0,0 instead of from 0,0 to 100, 50.
Part of the problem is that the implementation notes give you the formula from how to do the conversion form endpoints to center, but doesn't explain what it does geometrically, so I'm not all clear on what each step does. An explanation of that would also be helpful.
I think I have found some errors in your code:
theta = degrees(acos(p / n))
if uy > 0:
theta = -theta
self.theta = theta % 360
The condition uy > 0 is wrong, correct is uy < 0 (the directed angle from (1, 0) to (ux, uy) is negative if uy < 0):
theta = degrees(acos(p / n))
if uy < 0:
theta = -theta
self.theta = theta % 360
Then
if self.arc == self.sweep:
angle = radians(self.theta - (self.delta * pos))
else:
angle = radians(self.delta + (self.delta * pos))
The distinction is not necessary here, the sweep and arc parameters are already accounted for in theta and delta. This can be simplified to:
angle = radians(self.theta + (self.delta * pos))
And finally
x = sin(angle) * self.radius.real + self.center.real
y = cos(angle) * self.radius.imag + self.center.imag
Here sin and cos are mixed up, correct is
x = cos(angle) * self.radius.real + self.center.real
y = sin(angle) * self.radius.imag + self.center.imag
After these modifications, the program runs as expected.
EDIT: There is one more problem. The point method does not account for a possible rotation parameter. The following version should be correct:
def point(self, pos):
angle = radians(self.theta + (self.delta * pos))
cosr = cos(radians(self.rotation))
sinr = sin(radians(self.rotation))
x = cosr * cos(angle) * self.radius.real - sinr * sin(angle) * self.radius.imag + self.center.real
y = sinr * cos(angle) * self.radius.real + cosr * sin(angle) * self.radius.imag + self.center.imag
return complex(x, y)
(See formula F.6.3.1 in the SVG specification.)
Maybe you could have a look at links below, there seems to be a step-by-step guide on how to compute the arcs (see computeArc()):
http://svn.apache.org/repos/asf/xmlgraphics/batik/branches/svg11/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java
or
http://java.net/projects/svgsalamander/sources/svn/content/trunk/svg-core/src/main/java/com/kitfox/svg/pathcmd/Arc.java
Related
I'am working on a simulation of the Coulomb's Law with pygame, but I have a problem. When I start the Simulation an set two Protons, they attract each other. But there is more!
When I set an electron and a proton, they both start to "travel" with a constant distance to each other in the same direction.
Here is my code:
import pygame
import numpy as np
import math
pygame.init()
# window-settings
WIDTH, HEIGHT = 800, 800
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Electric field simulation")
# constants
Pi = np.pi
epsilon0 = 8.85 * (10**(-12))
q = 1.6 * (10**-19)
# colors
WHITE = (255,255,255)
BLACK = (0,0,0)
BLUE = (100, 149, 237)
RED = (188, 39, 50)
class Charge:
TIMESTEP = 50000
def __init__(self, x, y, radius, color, q):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.q = q
self.x_vel = 0
self.y_vel = 0
def draw(self, win):
pygame.draw.circle(win, self.color, (self.x, self.y), self.radius)
def attraction(self, other):
other_x, other_y = other.x, other.y
distance_x = other_x - self.x
distance_y = other_y - self.y
distance = math.sqrt(distance_x ** 2 + distance_y ** 2)
force = (1 / (4 * Pi * epsilon0)) * self.q * other.q / (distance ** 2)
theta = math.atan2(distance_y, distance_x)
force_x = math.cos(theta) * force
force_y = math.sin(theta) * force
return force_x, force_y
def update_position(self, charges):
total_fx = total_fy = 0
for charge in charges:
if self == charge:
continue
fx, fy = self.attraction(charge)
total_fx += fx
total_fy += fy
self.x_vel += total_fx / self.q * self.TIMESTEP
self.y_vel += total_fy / self.q * self.TIMESTEP
self.x += self.x_vel * self.TIMESTEP
self.y += self.y_vel * self.TIMESTEP
def main():
run = True
clock = pygame.time.Clock()
proton = Charge(200, 200, 10, RED, q)
electron = Charge(300, 200, 10, BLUE, -q)
charges = [electron, proton]
while run:
clock.tick(60)
WIN.fill(WHITE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN: # To set charges
if event.button == 1: # Sets positive charge
charges.append(Charge(pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1], 10, RED, q))
if event.button == 3: # Sets negitive charge
charges.append(Charge(pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1], 10, BLUE, -q))
for charge in charges:
charge.update_position(charges)
charge.draw(WIN)
pygame.display.update()
pygame.quit()
if __name__ == "__main__":
main()
I think the problem is the attraction-function, but I don't know how to solve it.
At the moment I have not the possibility to run your code but have you mixed up the direction of your force?
At least Wikipedia calculates r1-r2
Annother source of error might be the sin, cos etc. functions have you checked for your example that the calculated values are correct?
If I understand your code correct you update all positions one after eachother and apply the changes right away. Dependend on your application you might want to
Calculate all forces
Update all Values
Otherwise you have a multiple states of your system in just one time step.
Okay I solved the problem by calculating the direction of the force. With this I calculate at first the the force but return the absolute and then calculate the direction and speed.
Here is the code of my changes (I just changed something in the updating and attracting function):
def attraction(self, other):
other_x, other_y = other.x, other.y
distance_x = self.x - other_x
distance_y = self.y - other_y
distance = math.sqrt(distance_x ** 2 + distance_y ** 2)
if self.q != other.q and distance < 5:
return 0,0
force = (1 / (4 * Pi * epsilon0)) * self.q * other.q / (distance ** 2)
force_x = distance_x / distance*np.abs(force)
force_y = distance_y / distance*np.abs(force)
return force_x, force_y
def update_position(self, charges):
total_fx = total_fy = 0
for charge in charges:
if self == charge:
continue
fx, fy = self.attraction(charge)
total_fx += fx
total_fy += fy
self.x_vel += total_fx / self.q * self.TIMESTEP
self.y_vel += total_fy / self.q * self.TIMESTEP
if self != charge and self.q < 0 and self.q != charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [x / np.sqrt((x ** 2) + (y ** 2)),y / np.sqrt((x ** 2) + (y ** 2))]
elif self != charge and self.q > 0 and self.q != charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [-x / np.sqrt((x ** 2) + (y ** 2)), -y / np.sqrt((x ** 2) + (y ** 2))]
elif self != charge and self.q < 0 and self.q == charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [-x / np.sqrt((x ** 2) + (y ** 2)), -y / np.sqrt((x ** 2) + (y ** 2))]
elif self != charge and self.q > 0 and self.q == charge.q:
x = abs(self.x - charge.x)
y = abs(self.y - charge.y)
self.direction = [x / np.sqrt((x ** 2) + (y ** 2)), y / np.sqrt((x ** 2) + (y ** 2))]
self.x += self.x_vel * self.TIMESTEP * self.direction[0]
self.y += self.y_vel * self.TIMESTEP * self.direction[1]
I know this code isn't that beautiful, but at first it works.
So I have a pan-tilt system with an airbrush on top, the pressure is quite strong so that I can place the robot at a distance of at least 1.5 meters. I currently have normalized coordinates XY that I can visualize on my camera like this
Now I want to translate those coordinates to a real canvas and allow the pan-tilt to point towards them and eventually spray. The two servos have 0 to 180 degrees but the airbrush is positioned on top of the tilt at 90. So if we consider that the pan and tilt it's at 90 the airbrush points perpendicularly to the real canvas. I am following along with this answer https://stackoverflow.com/a/44269392/13475623
lx = (2 * canvasXpoint / canvasW - 1) * np.tan(fovX / 2)
ly = (-2 * canvasYpoint / canvasH + 1) * np.tan(fovY / 2)
lz = 100
tx = np.cos(pan) * np.cos(tilt) * lx - np.cos(tilt) * np.sin(pan) * ly - np.sin(tilt) * lz
ty = np.sin(pan) * lx + np.cos(pan) * ly
tz = np.cos(pan) * np.sin(tilt) * lx - np.sin(pan) * np.sin(tilt) * ly + np.cos(tilt) * lz
tilt = abs(np.arctan2(tz, tx) )*180 /np.pi
pan = abs(np.arcsin(ty / np.sqrt(tx*tx + ty*ty + tz*tz))) *180 /np.pi
he specifically ask to use fovx and fovy, but i have no idea how to place the, is fovx and fovy the same as the centre of the canvas plus z? which gives the robot position?
this is the entire code:
import numpy as np
import random
import cv2
rect = (0,0,0,0)
startPoint = False
endPoint = False
def on_mouse(event,x,y,flags,params):
global rect,startPoint,endPoint
# get mouse click
if event == cv2.EVENT_LBUTTONDOWN:
if startPoint == True and endPoint == True:
startPoint = False
endPoint = False
rect = (0, 0, 0, 0)
if startPoint == False:
rect = (x, y, 0, 0)
startPoint = True
elif endPoint == False:
rect = (rect[0], rect[1], x, y)
endPoint = True
cap = cv2.VideoCapture(0)
waitTime = 50
#Reading the first frame
(grabbed, frame) = cap.read()
# create a numpy array with coordinates, dtype= np.uint32
points = np.array([
[0.3791454386035252, 0.5089704263689607], [0.4983802415059109, 0.4865878212776629], [0.4191061040406586, 0.4890729258496474], [0.48898375092596835, 0.6904554156787046], [0.41117320428962, 0.6855686449973655], [0.48969027909831686, 0.8806483247709954], [0.4096722346480175, 0.8725103831012889], [0.45146556567120294, 0.216198952126905], [0.6304876750748412, 0.1994776546413951], [0.6406976694235704, 0.1861724655606558], [0.6199918357274865, 0.18561325370105788], [0.6525936779272056, 0.201758477474465], [0.6013198509477334, 0.20041966221830415], [0.6683290543094758, 0.29699362669473495], [0.5645238852104717, 0.3113999818240313], [0.6545654774178274, 0.49620430200480303], [0.5898070573107588, 0.49659117464889346], [0.6592482998457356, 0.6834740545963035], [0.5840631897032319, 0.6828527784533074], [0.6408640096147972, 0.8299668209407426], [0.5829181988101784, 0.8173392725052692], [0.6197806290284397, 0.30050890733295843], [0.8252923243905792, 0.23409826375167195], [0.835683753646597, 0.2185883280832016], [0.8131540844750428, 0.21904862499113367], [0.8506741192799976, 0.2279991219170517], [0.7959142481709739, 0.22725381616179272], [0.8733570624656342, 0.3256920048853457], [0.7652207837892534, 0.3239122878098148], [0.893097550288673, 0.44273291363944955], [0.7346131146711571, 0.4430594635999311], [0.902709244982588, 0.5343829401117663], [0.8520378940615836, 0.543215423861057], [0.7842126810888624, 0.5430821914771806], [0.8496391467917583, 0.7170072127563635], [0.7934480818135997, 0.7157067918591926], [0.8415470663986131, 0.8790693270711738], [0.7969306654944098, 0.8786970205344115], [0.8191112469834433, 0.32444646417244244], [0.4544294400182521, 0.10802826838116084], [0.4652589441860643, 0.09470838455219986], [0.44184697991125976, 0.09401847354478254], [0.4784184639521475, 0.1113126386155105], [0.42270482157448985, 0.10977393520172159], [0.5101597581790689, 0.21719483055184013], [0.39370939342390643, 0.21645334444157344], [0.3703281257159549, 0.34746637604116004]], np.float64)
while(cap.isOpened()):
(grabbed, frame) = cap.read()
cv2.namedWindow('frame')
cv2.setMouseCallback('frame', on_mouse)
panarr=[]
tiltarr=[]
#drawing rectangle
if startPoint == True:
cv2.circle(frame, (rect[0], rect[1]), 2,(255, 0, 255), 2)
if startPoint == True and endPoint == True:
cv2.rectangle(frame, (rect[0], rect[1]), (rect[2], rect[3]), (255, 0, 255), 2)
w = rect[2] - rect[0]
h = rect[3] - rect[1]
canvasW = 120
canvasH = 90
distanceZ = 100
#position machine
screenXCenter = (rect[0] + rect[2]) / 2
screenYCenter = (rect[1] + rect[3]) / 2
pan = tilt = 90
servoXcentrepoint = canvasW / 2
servoYcentrepoint = canvasH / 2
# fov
fovX = np.arctan((canvasW * canvasH )/distanceZ)
for x, y in points:
screenX = (x * w) + rect[0] #- differencesqrx
screenY = (y * h) + rect[1] #- differencesqry
cv2.circle(frame,(int(screenXCenter),int(screenYCenter)),2,(255, 255, 0),2)
cv2.circle(frame,(int(screenX),int(screenY)),2,(255, 45, 250),2)
canvasXpoint = (x * canvasW)
canvasYpoint = (y * canvasH)
# dx = canvasXpoint - servoXcentrepoint
# dy = canvasYpoint - servoYcentrepoint
# pan = abs(np.arctan((distanceZ/dx))) * 180/np.pi
# tilt = abs(np.arctan(distanceZ/dy)) * 180/np.pi
lx = (2 * canvasXpoint / canvasW - 1) * np.tan(servoXcentrepoint / 2)
ly = (-2 * canvasYpoint / canvasH + 1) * np.tan(servoYcentrepoint / 2)
lz = 10
tx = np.cos(pan) * np.cos(tilt) * lx - np.cos(tilt) * np.sin(pan) * ly - np.sin(tilt) * lz
ty = np.sin(pan) * lx + np.cos(pan) * ly
tz = np.cos(pan) * np.sin(tilt) * lx - np.sin(pan) * np.sin(tilt) * ly + np.cos(tilt) * lz
tilt = abs(np.arctan2(tz, tx) )*180 /np.pi
pan = abs(np.arcsin(ty / np.sqrt(tx*tx + ty*ty + tz*tz))) *180 /np.pi
tiltarr.append(int(tilt))
panarr.append(int(pan))
# a = [x,y]
cv2.imshow('frame',frame)
if cv2.waitKey(1)==ord('q'):
break
print(tiltarr)
print(panarr)
cap.release()
cv2.destroyAllWindows()
The ultimate goal is to determine the angle for the pan and tilt based on each point
I need to draw sine and cosine waves on a coordinate system exactly like in this picture. I did all the work well except I failed to represent dashed and curved line with pygame. I have smth similar to what I need, but how can I make it curved? Or how can I improve this to make it like pygame.draw.lines, not pygame.draw.line?
import pygame
import math
class Point:
# constructed using a normal tupple
def __init__(self, point_t = (0,0)):
self.x = float(point_t[0])
self.y = float(point_t[1])
# define all useful operators
def __add__(self, other):
return Point((self.x + other.x, self.y + other.y))
def __sub__(self, other):
return Point((self.x - other.x, self.y - other.y))
def __mul__(self, scalar):
return Point((self.x*scalar, self.y*scalar))
def __div__(self, scalar):
return Point((self.x/scalar, self.y/scalar))
def __len__(self):
return int(math.sqrt(self.x**2 + self.y**2))
# get back values in original tuple format
def get(self):
return (self.x, self.y)
def draw_dashed_line(surf, color, start_pos, end_pos, width=1, dash_length=4):
origin = Point(start_pos)
target = Point(end_pos)
displacement = target - origin
length = len(displacement)
slope = displacement.__div__(length)
for index in range(0, int(length/dash_length), 2):
start = origin + (slope * index * dash_length)
end = origin + (slope * (index + 1) * dash_length)
pygame.draw.line(surf, color, start.get(), end.get(), width)
pygame.init()
screen = pygame.display.set_mode((400, 300))
done = False
while not done:
draw_dashed_line(screen,(0,255,0),(0,0),(110,110))
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.display.flip()
Write a function that operates similar as pygame.draw.line() but draws a dashed straight line. The function has an additional argument prev_line_len which indicates where the line segment is within a consecutive curve. Compute the Euclidean distance between the points and the Unit vector that points from the beginning of the line segment to its end. Distribute the strokes along the line:
def draw_dashed_line(surf, color, p1, p2, prev_line_len, dash_length=8):
dx, dy = p2[0]-p1[0], p2[1]-p1[1]
if dx == 0 and dy == 0:
return
dist = math.hypot(dx, dy)
dx /= dist
dy /= dist
step = dash_length*2
start = (int(prev_line_len) // step) * step
end = (int(prev_line_len + dist) // step + 1) * step
for i in range(start, end, dash_length*2):
s = max(0, start - prev_line_len)
e = min(start - prev_line_len + dash_length, dist)
if s < e:
ps = p1[0] + dx * s, p1[1] + dy * s
pe = p1[0] + dx * e, p1[1] + dy * e
pygame.draw.line(surf, color, pe, ps
Write another function that behaves similarly to pygame.draw.lines(), but uses the former function (draw_dashed_line) to draw the dashed curve. Calculate the length from the beginning of the curve to the beginning of each line segment and pass it to the function:
def draw_dashed_lines(surf, color, points, dash_length=8):
line_len = 0
for i in range(1, len(points)):
p1, p2 = points[i-1], points[i]
dist = math.hypot(p2[0]-p1[0], p2[1]-p1[1])
draw_dashed_line(surf, color, p1, p2, line_len, dash_length)
line_len += dist
Minimal example:
repl.it/#Rabbid76/DashedLine
import pygame
import math
def draw_dashed_line(surf, color, p1, p2, prev_line_len, dash_length=8):
dx, dy = p2[0]-p1[0], p2[1]-p1[1]
if dx == 0 and dy == 0:
return
dist = math.hypot(dx, dy)
dx /= dist
dy /= dist
step = dash_length*2
start = (int(prev_line_len) // step) * step
end = (int(prev_line_len + dist) // step + 1) * step
for i in range(start, end, dash_length*2):
s = max(0, start - prev_line_len)
e = min(start - prev_line_len + dash_length, dist)
if s < e:
ps = p1[0] + dx * s, p1[1] + dy * s
pe = p1[0] + dx * e, p1[1] + dy * e
pygame.draw.line(surf, color, pe, ps)
def draw_dashed_lines(surf, color, points, dash_length=8):
line_len = 0
for i in range(1, len(points)):
p1, p2 = points[i-1], points[i]
dist = math.hypot(p2[0]-p1[0], p2[1]-p1[1])
draw_dashed_line(surf, color, p1, p2, line_len, dash_length)
line_len += dist
pygame.init()
screen = pygame.display.set_mode((400, 300))
done = False
line = [(i, 150 + math.sin(math.radians(i*2)) * 100) for i in range(400)]
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
draw_dashed_lines(screen, (255, 255, 255), line)
pygame.display.flip()
I don't want the code (I really do want the code), but can someone explain to me how I can create the diagonal line to see if there's a gap? I know we have to use vectors, but I don't know how to do that using python
So, using the logic of Separating Axis Theorem that if you cant draw a line in between 2 squares then they are overlapping and colliding. I made something close, its not perfect but its close, I also haven't accounted for rotation of squares but if you find a way to find the vertices/corners of the square, then this could easily work. The way i did it was that i turned the squares into lines and drew a line directly in the middle of the squares and at the normal of the line in between the squares, its a bit confusing but it makes sense once you see it. I then used line intersecting maths to find if they intersect.
import pygame
from pygame.locals import *
from pygame import Vector2
pygame.init()
screen = pygame.display.set_mode((500,500))
#check if 2 lines are intersecting
def LineIntersect(line1, line2):
#the math is from wikipedia
x1 = line1[0].x
y1 = line1[0].y
x2 = line1[1].x
y2 = line1[1].y
x3 = line2[0].x
y3 = line2[0].y
x4 = line2[1].x
y4 = line2[1].y
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if den == 0:
return
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
if t > 0 and t < 1 and u > 0 and u < 1:
pt = Vector2()
pt.x = x1 + t * (x2 - x1)
pt.y = y1 + t * (y2 - y1)
return pt
return
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#get the lines that make up the square, the outline/perameter
def Lines(self):
lines = []
lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
return lines
#draw a line inbetween the 2 squares
def DrawLineInBetween():
#draw a line between the 2 squares, get gradient
#to avoid divide by zero
if abs(sqr1.x - sqr2.x) == 0:
gradient = "infinity"
else:
#rise over run
#left - right = run
left = sqr1 if sqr1.x < sqr2.x else sqr2
right = sqr1 if left == sqr2 else sqr2
gradient = ((left.y - right.y)/abs(sqr1.x - sqr2.x))
#print("gradient:",gradient)
#get the middle point between the centers of the squares
middle = (max(sqr1.x + sqr1.w//2, sqr2.x + sqr2.w//2) - abs(sqr1.x - sqr2.x)//2,
max(sqr1.y + sqr1.w//2, sqr2.y + sqr2.w//2) - abs(sqr1.y - sqr2.y)//2)
#to avoid divide by 0
if gradient == 0:
point1 = Vector2(middle[0], middle[1] + 100)
point2 = Vector2(middle[0], middle[1] - 100)
elif gradient == "infinity":
point1 = Vector2(middle[0] - 100, middle[1])
point2 = Vector2(middle[0] + 100, middle[1])
else:
#get normal of line
gradient = -1/gradient
#print("normal:",gradient)
point1 = Vector2(middle[0] + 100, middle[1] + int(-100 * gradient))
point2 = Vector2(middle[0] - 100, middle[1] + int(100 * gradient))
#print(point1)
#print(point2)
#print(middle)
pygame.draw.line(screen,(0,255,0),point1,point2,1)
line = (point1, point2)
return line
sqr1 = square(100,100,50)
sqr2 = square(200,100,50)
Clock = pygame.time.Clock()
running = True
key = ""
while running:
screen.fill((0,0,0))
sqr1.Draw(outline=True)
sqr2.Draw()
line = DrawLineInBetween()
for sqr_line in sqr1.Lines():
pt = LineIntersect(line,sqr_line)
if pt:
pygame.draw.circle(screen,(0,255,255),(int(pt.x),int(pt.y)),5)
if key == "s":
sqr1.y += 1
elif key == "w":
sqr1.y -= 1
if key == "d":
sqr1.x += 1
if key == "a":
sqr1.x -= 1
pygame.display.update()
Clock.tick(60)
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
running = False
if e.type == MOUSEBUTTONDOWN:
print(e.pos)
if e.type == KEYDOWN:
key = e.unicode
if e.type == KEYUP:
key = ""
doing rotating squares:
added rotation variable in square class, i used this answer to find the corners of the square, then once i have the corners, used the line intersetion.
Here is new class:
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
self.rotation_angle = 0
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
#this used the normal coordinate of an unrotated square to find new coordinates of rotated sqaure
def GetCorner(self,tempX,tempY):
angle = math.radians(self.rotation_angle)
rotatedX = tempX*math.cos(angle) - tempY*math.sin(angle);
rotatedY = tempX*math.sin(angle) + tempY*math.cos(angle);
x = rotatedX + self.centerx;
y = rotatedY + self.centery;
return Vector2(x,y)
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#new lines method, only changed to GetCorner()
def Lines(self):
lines = []
top_left = self.GetCorner(self.x - self.centerx, self.y - self.centery)
top_right = self.GetCorner(self.x + self.w - self.centerx, self.y - self.centery)
bottom_left = self.GetCorner(self.x - self.centerx, self.y + self.w - self.centery)
bottom_right = self.GetCorner(self.x + self.w - self.centerx, self.y + self.w - self.centery)
lines.append((top_left,top_right))
lines.append((top_left,bottom_left))
lines.append((bottom_right,top_right))
lines.append((bottom_right,bottom_left))
return lines
#chnaged to this as rotation rotates around center, so need to update both x and centerx
def Move(self,x =None, y = None):
if x:
self.x += x
self.centerx += x
if y:
self.y += y
self.centery += y
#get the lines that make up the square, the outline/perameter
#def Lines(self):
#lines = []
#lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
#return lines
I can not seem draw lines between the old pixel and the new pixel making a smooth Bezier curve. I have the Bezier curve, but it is only made up of pixels alone. Also, when I click on the canvas to update the Bezier curve I can't get the old one to delete. I have tried canvas.delete(line) and also canvas.delete(tk.ALL), but this gets rid of everything and i need to be able to keep my click_points or the where the ovals where drawn. Any help would be greatly appreciated.
import tkinter as tk
#Screen creation.
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
SIZE = 4
**# Creates the canvas.**
root_window = tk.Tk()
canvas = tk.Canvas(root_window, width = SCREEN_WIDTH, height = SCREEN_HEIGHT)
canvas.pack()
**#Plots each pixel**
def plot_pixel(x0, y0):
line = canvas.create_line(x0, y0, x0 + 1, y0)
**#Pre: Takes a list of points.
#Post: Recursively iterates through the list to find the
# linear interpolation (x, y)**
def draw_curve(points, t):
if len(points) == 1:
plot_pixel(points[0][0], points[0][1])
else:
newpoints = []
for i in range(0, len(points) - 1):
# old_point_x = (1 - t) * points[i - 1][0] + t * points[i][0]
# old_point_y = (1 - t) * points[i - 1][1] + t * points[i][1]
x = (1 - t) * points[i][0] + t * points[i + 1][0]
y = (1 - t) * points[i][1] + t * points[i + 1][1]
newpoints.append((x, y))
draw_curve(newpoints, t)
Recursion call to draw_curve while t <= 1.
def draw(points):
t = 0
while t <= 1:
draw_curve(points, t)
t += 0.01
#Mouse B-1 bound, for every click draw an oval and append to
#list -- click_points.
click_points = []
def click(event):
click_points.append((event.x, event.y))
oval = canvas.create_oval(event.x - SIZE,
event.y - SIZE,
event.x + SIZE,
event.y + SIZE,
fill = "red")
draw(click_points)
canvas.bind("<Button-1>", click)
root_window.mainloop()