How to make a L-shaped body in pymunk? - python

I am new to pymunk and I want to make a L-shaped body like this.
I read that it is possible to have two shapes attached to same body but the best I got is this.
The code I used is this:
import pymunk
import pygame
import pymunk.pygame_util
pygame.init()
size = 640, 240
screen = pygame.display.set_mode(size)
draw_options = pymunk.pygame_util.DrawOptions(screen)
space = pymunk.Space()
space.gravity = 0, 90
b0 = space.static_body
segment = pymunk.Segment(b0, (0, 200), (640, 200), 4)
segment.elasticity = 1
body = pymunk.Body(mass=1, moment=10)
body.position = 300, 50
box = pymunk.Poly.create_box(body, (100, 50))
box.elasticity = 0.9
box.friction = 0.8
box2 = pymunk.Poly.create_box(body, (50, 100))
space.add(body, box, box2, segment)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(color="GRAY")
space.debug_draw(draw_options)
pygame.display.update()
space.step(0.01)
pygame.quit()
Is there any way to do make a L-shaped body?
Thanks in advance!

The problem is that both Poly shapes that are created with the shorthand create_box method are created with their center at the position of the body they are attached to. So the two boxes are positioned on top of each other.
To fix this you need to use the more generic Poly constructor. In this way you can pick the positions as you want.
If I just take your picture and write up the coordinates times 10, it would be something like this:
box = pymunk.Poly(body, [(0, 0), (60, 0), (60, 30), (0, 30)])
box.elasticity = 0.9
box.friction = 0.8
box2 = pymunk.Poly(body, [(0, 30), (30, 30), (30, 60), (0, 60)])
box2.elasticity = 0.9
box2.friction = 0.8
However, note that the coordinates are relative to the body they are attached to, and the center of gravity is where the body is positioned. That means that the resulting L shape will behave as if its a hollow shape with a super heavy stone in one corner.
If this is not what you want to can adjust the coordinates, for example like this (I just subtracted 30 from each one. This can probably be tweaked even more depending on what your goal is):
box = pymunk.Poly(body, [(-30, -30), (30, -30), (30, 0), (-30, 0)])
box.elasticity = 0.9
box.friction = 0.8
box2 = pymunk.Poly(body, [(-30, 0), (0, 0), (0, 30), (-30, 30)])
box2.elasticity = 0.9
box2.friction = 0.8
This is the easiest to understand way (at least I think so), but there are two other options available. You can instead give a translate pymunk.Transform as an option to the Poly constructor, with the downside that it is less obvious what the result will be. Or you can move the center of gravity on the body with the center_of_gravity property, with the downside that when you look at the position of the body it will still be at the "corner" of the shape.

Try this
https://pymunk-tutorial.readthedocs.io/en/latest/shape/shape.html#l-shaped-segment
body = pymunk.Body(mass=1, moment=1000)
body.position = (100, 200)
s1 = pymunk.Segment(body, (0, 0), (50, 0), 8)
s1.density = 1
s1.elasticity = 0.999
space.add(body, s1)

Related

Setting colors on a trimesh union mesh according to the original component meshes

I'm working with the trimesh Python library, and I couldn't wrap my head around working with colors from reading the docs (a Spartan API reference) and examples. I'm not even sure if I'm setting the face colors right, if I can modify mesh.visual.face_colors directly or if I should make a ColorVisuals-type object.
But the main question is, can I set the color of different faces of a mesh approximately (I know there can be nasty edge cases) according to it being "overlapping" to faces in the original meshes I had before?
from shapely import Polygon
import trimesh
pts = ((100, 100), (400, 100), (400, 400), (100, 400))
hole = ((150, 150), (350, 150), (350, 350), (150, 350))
p = Polygon(pts, [hole])
mesh = trimesh.creation.extrude_polygon(p, 100)
other = mesh.copy()
other.apply_translation((150, 50, 50))
mesh = mesh.union(other)
# Silly idea (most faces wouldn't be the same) and it doesn't work, I get an error.
# other_v_set = set(other.vertices)
# colors = [([255, 0, 0] if set(mesh.vertices[v] for v in f).issubset(other_v_set)
# else [0, 255, 0]) for f in mesh.faces]
# mesh.visual = trimesh.visual.ColorVisuals(mesh, colors)
mesh.show()

Python Pygame Minesweeper game

I'm trying to make the minesweeper game but I have seen this method to store a grid in a list.
how do I make a list inside a list that looks like my grid, For example:
list =
[[x,y],[x,y][x,y],[x,y][x,y],[x,y]
[x,y],[x,y][x,y],[x,y][x,y],[x,y]
[x,y],[x,y][x,y],[x,y][x,y],[x,y]]
My code now is that all the grid is in one list([] and not [][])
I'm kinda new to programming in python so I'm not good at it. Hoping to get help and to solve this as fast as possible, Thank You all!
My code:
import pygame
from pygame.math import Vector2
import random
pygame.init()
HEIGHT = 800
WIDTH = 800
tile_size = 100
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
class Grid:
def __init__(self):
self.pGrid = [Vector2(0, 0)]
for num in range(0, int(WIDTH/tile_size)):
for num2 in range(0, int(HEIGHT/tile_size)):
if(not(num == 0 and num2 == 0)):
self.pGrid.append(Vector2(num2*tile_size, num*tile_size))
def make_grid(self):
for line in range(0, int(WIDTH/tile_size)):
pygame.draw.line(screen, (255, 255, 255), (0, line*tile_size), (WIDTH, line*tile_size))
pygame.draw.line(screen, (255, 255, 255), (line * tile_size, 0), (line*tile_size, HEIGHT))
grid = Grid()
#def bomb_found():
#bomb = pygame.image.load('Images/Bomb.png')
#bomb = pygame.transform.scale(bomb, (100, 100))
bomb = pygame.image.load('Images/Bomb.png')
bomb = pygame.transform.scale(bomb, (100, 100))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.fill((175, 215, 70))
grid.make_grid()
#bomb_found()
screen.blit(bomb, grid.pGrid[20])
pygame.display.update()
clock.tick(60)
pygame.quit()
You could use list comprehensions for easily creating a nested array of successive integer values.
w, h = 3, 4
[[(i, j) for j in range(h)] for i in range(w)]
#result
[[(0, 0), (0, 1), (0, 2), (0, 3)],
[(1, 0), (1, 1), (1, 2), (1, 3)],
[(2, 0), (2, 1), (2, 2), (2, 3)]]
Create a list for the column before the inner loop and add the Vector2 objects to this list. Add the column list to the grid after the inner loop:
class Grid:
def __init__(self):
self.pGrid = []
for col in range(WIDTH//tile_size):
column = []
for row in range(HEIGHT//tile_size):
column.append(Vector2(col * tile_size, row * tile_size))
self.pGrid.append(column)
The same using List Comprehensions:
class Grid:
def __init__(self):
self.pGrid = [[Vector2(c*tile_size, r*tile_size) for r in range(HEIGHT//tile_size)] for c in range(WIDTH//tile_size)]
The first subscription returns a column and the second subscription returns an item in the column (a row in the column). e.g.:
col = 3 # 4th column
row = 2 # 3rd row
screen.blit(bomb, grid.pGrid[col][row])

How to change the colour on a cv2.rectangle if I "clicked" on this button?

I would like to change the color of my rectangle if i clicked on this selfmade button. Unfortunately, I have managed to change the colour when my index finger points to this button. When I remove my index finger from the button, it returns to its old colour.
How can I implement it so, that when I move my index finger to this button and hold it for a certain second (for exl. 4 seconds), the purple colour of the button should change to green permanently. If I hold it again, my green button should change to purple permanently.
cv2.rectangle(image, (100, 300), (200, 200), (255, 0, 255), cv2.FILLED)
thumb = res.right_hand_landmarks[8]
if ((thumb.x < 0.40 and thumb.x >= 0.30) and
(thumb.y < 0.69 and thumb.y >= 0.64)):
cv2.rectangle(image, (100, 300), (200, 200), (0, 255, 0), cv2.FILLED)
Here, instead of just only depending on the position of the index finger, you can add another condition in the if statement, depending whether the variable (example, ift is either True or False. This might be confusing, but let me explain it with the example:
ift = False
# Keep the var 'False' if the index finger was not in the self-made button for 4 seconds or more, else 'True'
Now, for calculating the time, you can use built-in time module. Here's an example:
from time import time, sleep
val = time()
sleep(1)
print(int(time() - val)) # Converting to integer, as the default is set to 'float'
# OUTPUT
# 1
Here's the documentation to the time module where you can find time.time():
time Documentation
Now, I know for cv2, you can't practically use sleep as it will stop the whole iteration, so here's another work around, for example:
from time import time
var = False
x = time()
y = time()
while True:
if not var:
x = time()
# If the 'var' is False, it will refresh the 'time()' value in every iteration, but not if the value was True.
if int(time() - y) >= 4:
print(int(time() - x), int(time() - y))
break
# OUTPUT
# 0 4
Now, we know how we can calculate time passed using the logic above, let's implement it in your code:
from time import time
# Variables below outside the loop, so that it doesn't reset at every iteration
ift = False # Let's assume 'False' is for 'purple' and 'True' is for 'green'
inside = False # It's to check whether the finger is in or not
t = time() # If the below code (you provided) is in a function, then I prefer adding it in the function before the loop starts
# Everything under is assumed to be inside the loop (assuming you are using the loop)
thumb = res.right_hand_landmarks[8]
if ((thumb.x < 0.40 and thumb.x >= 0.30) and
(thumb.y < 0.69 and thumb.y >= 0.64)):
cv2.rectangle(image, (100, 300), (200, 200), (255, 0, 255), cv2.FILLED)
inside = True
else:
cv2.rectangle(image, (100, 300), (200, 200), (0, 255, 0), cv2.FILLED)
inside = False
t = time() # Refreshes the time, because the finger is no more inside
if inside and int(time() - t) >= 4:
ift = not ift # Changes the value of 'ift' to its opposite, i.e., if the value was False, it will become True and vice versa
if ift:
cv2.rectangle(image, (100, 300), (200, 200), (0, 255, 0), cv2.FILLED)
else:
cv2.rectangle(image, (100, 300), (200, 200), (255, 0, 255), cv2.FILLED)
The code mentioned above might or might not work as it is not tested. But, it is enough to provide you with the information on what logic you can use to solve your issue.
NOTE, that the code above can be reduced as it is expanded and there might be useless lines that can be reduced, but for the sake of explanation, it has been expanded.

How to draw a 1/4 of a circle in Pygame?

I'm using Python 3.7.4 and Pygame 1.9.6. I know about the pygame.draw.arc and pygame.draw.circle. I want to draw a quarter circle like here:
It's the quarter circle of the soccer field.
My attempts:
pygame.draw.arc(screen, (255, 255, 255), (75, 25, 50, 50), 90, 120, 5)
pygame.draw.arc(screen, (255, 255, 255), (75, 25, 50, 50), 80, 100, 5)
Changing the starting angle and ending angle doesn't change a thing it seems.
So it must be the coordinates of drawing the boundaries.
When I change the 75 in this(75, 25, 50, 50) it just makes the circle go to the right more. If the change the 25 in (75, 25, 50, 50) it makes it go up or down more. If I change the first 50 in (75, 25, 50, 50) it makes it wider width bigger or smaller depending on changing the numbers. If I change the second 50 in (75, 25, 50, 50) it makes the height bigger or smaller.
I've just tried decimals in the angles, because the coordinates have to be integers. When I do pygame.draw.arc(screen, (255, 255, 255), (75, 25, 100, 50), 95.5, 100.5, 5) look at the decimals.
I get:
So maybe the key it changing the starting and ending angles I'll keep updating what I find.
I found the right one with help from #bashBedlam in the comments:
pygame.draw.arc(screen, (255, 255, 255), (60, 13, 50, 50), 17, 13, 5)
I changed the coordinates but keep the same starting and ending angle.
Which is pretty much perfect though I do hate the little black dots though.
The screen is 600 x 600
I can't get to right angle or the circle itself. I'm not aloud to use decimals. Only allows integers. So I don't know how to properly make a quarter circle. I appreciate the advice and thanks.
I just wanted to say for people helping me I've created my own soccer field. I drew all the lines out, but then I just turned it into a png so it wouldn't lag me out of loading all 27 lines with the background constantly. Here is the finished result with everyone's help:
The issue with your code is that pygame.draw.arc takes angles in radians as stated in the documentation. Also angles between 90 and 120 would not draw and arc like you need, 90 to -90 will.
import pygame
import math
pygame.init()
d = pygame.display.set_mode((1200, 600))
while True:
pygame.event.get()
d.fill((255, 255, 255))
pygame.draw.arc(d, (0, 0, 0), [900, 300, 100, 100], math.radians(90), math.radians(-90), 5)
pygame.draw.arc(d, (0, 0, 0), [300, 300, 100, 100], math.radians(-90), math.radians(90), 5)
pygame.display.update()
Edit:
So i was looking online for ways for pygame.draw.arc to draw arc without dots without using floating point numbers, but couldn't any, so i wrote this function for you. If you consider this function as not being a part of the code you wrote (like pygame.draw.arc) then technically, you are not using decimal because the decimal is only used inside of the function(finessed the system :)). Here's the function:
def drawArc(display, startAngle, endAngle, distance, pos, color, thickness=1):
if startAngle > endAngle:
theta = endAngle
bigger = startAngle
else:
theta = startAngle
bigger = endAngle
while theta < bigger:
for t in range(thickness):
x = round((cos(radians(theta)) * (distance-t)) + pos[0])
y = round((-sin(radians(theta)) * (distance-t)) + pos[1])
display.set_at((x, y), color)
theta += 0.01
Think of this function like a compass, it draws an arc in-between the angle you specify (in degrees). Argument pos being the centre of the compass and distance being the distance of the arc from the centre. So now just draw the quarter circles in 4 different corners.
import pygame
from math import radians, sin, cos
pygame.init()
d = pygame.display.set_mode((1200, 600))
def drawArc(display, startAngle, endAngle, distance, pos, color, thickness=1):
if startAngle > endAngle:
theta = endAngle
bigger = startAngle
else:
theta = startAngle
bigger = endAngle
while theta < bigger:
for t in range(thickness):
x = round((cos(radians(theta)) * (distance-t)) + pos[0])
y = round((-sin(radians(theta)) * (distance-t)) + pos[1])
display.set_at((x, y), color)
theta += 0.01
while True:
pygame.event.get()
d.fill((255, 255, 255))
drawArc(d, -90, 0, 100, [0, 0], (0, 0, 0), thickness=5)
drawArc(d, 180, 270, 100, [1200, 0], (0, 0, 0), thickness=5)
drawArc(d, 0, 90, 100, [0, 600], (0, 0, 0), thickness=5)
drawArc(d, 180, 90, 100, [1200, 600], (0, 0, 0), thickness=5)
pygame.display.update()
Btw, using this function hinders performance, so i strongly recommend using pygame.draw.arc with floating point numbers if possible.

How to draw shapes in Python/turtle using a list of coordinates

I'm new to Python and have a question. I want to move the turtle to a specific start location, and from there draw a shape. The shape has pre-determined coordinates, so I need to connect the points to make the shape.
I have to make 2 functions such that the following code calls those 2 functions and draws three shapes:
def testPolyLines():
# First square
squareShape = [(50, 0), (50, 50), (0, 50), (0, 0)]
drawPolyLine((200, 200), squareShape)
# Second square
drawPolyLine((-200, 200), squareShape, lineColour="green")
biggerSquareShape = generateSquarePoints(100)
# A triangle
triangleShape = [(200, 0), (100, 100), (0, 0)]
drawPolyLine((100, -100), triangleShape, fillColour="green")
def main():
testPolyLines()
main()
I made the first function to generate points for a square of any size:
def generateSquarePoints(i):
squareShape = [(i, 0), (i, i), (0, i), (0, 0)]
But I get stuck when it comes to actually drawing the shape. I can make the turtle go to the start position, but I don't know how to make it go through a list of points and connect them to form a shape. This is what I have:
def drawPolyLine(start, squareShape, lineColour="black", fillColour = "white"):
pencolor(lineColour)
fillcolor(fillColour)
penup()
goto(start)
pendown()
begin_fill()
goto(squareShape)
end_fill()
This is obviously not right...the part I'm confused about is how to tell the turtle to go to the list of points, and connect them along the way to form the shape. My program now only goes to the start positions but doesn't draw the shape.
I would really appreciate any help or advice! Thanks in advance.
Issues with your code: you can't just goto() the points, you need to adjust them to the starting position, treating the x & y in the point as more of a delta-x, delta-y; generateSquarePoints() needs to return its list of points, not assign it; obviously a for loop is needed as others have mentioned; you need to explicitly draw back to the starting point to close the shape.
Try the following rework of your code to see if it does what you want:
import turtle
def generateSquarePoints(i):
""" generate points for a square of any size """
return [(i, 0), (i, i), (0, i), (0, 0)]
def drawPolyLine(start, points, lineColour="black", fillColour="white"):
""" draw shapes using a list of coordinates """
turtle.pencolor(lineColour)
turtle.fillcolor(fillColour)
turtle.penup()
turtle.goto(start) # make the turtle go to the start position
turtle.pendown()
turtle.begin_fill()
x, y = start
for point in points: # go through a list of (relative) points
dx, dy = point
turtle.goto(x + dx, y + dy)
turtle.goto(start) # connect them to start to form a closed shape
turtle.end_fill()
turtle.penup()
if __name__ == "__main__":
def testPolyLines():
""" test shapes shape drawing functions """
# First square
squareShape = [(50, 0), (50, 50), (0, 50), (0, 0)]
drawPolyLine((200, 200), squareShape)
# Second square
biggerSquareShape = generateSquarePoints(100)
drawPolyLine((-200, 200), biggerSquareShape, lineColour="green")
# A triangle
triangleShape = [(200, 0), (100, 100), (0, 0)]
drawPolyLine((100, -100), triangleShape, fillColour="green")
def main():
testPolyLines()
turtle.done()
main()

Categories