Welcome to this documentation! — In this documentation you will learn how to use PyPhyEngine, what my workflow looks like and what my goals for the engine are.
First, we will talk about how the project started and what it was originally meant to be. Then, we will go over to how it's going today. That will answer questions like:
Then, we walk over to the "getting started" section. This is where the coding begins. You'll find code snippets and other helpful resources here along with the API Reference section.
You will find out how you can create PhysicsObjects, enable collision between them, record videos in the program and more.
Please finish this...
Currently I am working on an editor to assist you with creating PhysicsRooms, PhysicsObjects and StaticObjects all within a graphical editor.
The editor would give you a script that you can then insert into any editor and if you run the script it'll work. It was originally meant to assist the user in creating StaticObjects without needing to write down each individual vector, but I then decided on just creating an entire editor for everything.
In the future I'd like to create a 3D physics engine within the limits of MPL.
A normal program utilizing PyPhyEngine usually consists of the following things:
And optionally:
A PhysicsObject (PO) is an object, which moves and is affected by things like gravity or collision with other objects.
A PO consists of an x-position and y-position, which is just the position on the axis where the PO lays on. A PO also has a size which declares, well, the size of the PO. The parameter "grav" indicates gravity and stands for how much gravity is being applied to the PO. The fps or frames per second is the amount of frames that are rendered each second for that object. Bounce stands for how much of the kinetic energy, which the PO has when it hits the ground, will be reversed so the ball bounces on the floor. Bounce is an integer or float between 0 and 1. Friction manipulates the speed of the PO when it's on the floor. Friction is an integer or float between 0 and 1, which declares how much of the velocity on the x-axis will be lost. The general formula for the calculation which will be executed each frame, where the PO is on the floor, is
x_velocity = x_velocity / (friction + 1)
velocity_x and velocity_y indicate how much the PO will move on either the x- or y-axis. Both variables can be integers or floats.
border_x and border_y are a list of integers which declare the barriers where the PO will be.
For example, if border_x = [5, 25] the PO will only move between these two values. Any position outside of that range will be interpreted as a wall.
The same applies to border_y. If border_y = [0, 50] any position on the axis when y <= 0 will act as the floor and any position on the axis when y >= 50 will act as the ceiling of a room.
The color string will define the color of the PO, very simple. The color palette is based on the named colors of MPL.
show_trajectory is a boolean which decides whether or not the trajectory of the PO will be shown.
Finally, last but not least, max_trajectory_iterations declares how many points of the trajectory will be saved before it starts deleting the oldest point. Essentially, the higher the number, the longer the trajectory.
If you want to create a PhysicsObject, you could do this:
ball = PhysicsObject(
x = 5,
y = 35,
size = 100,
grav = 1,
fps = 30,
bounce = 0.8,
friction = 0.2,
velocity_x = 1,
velocity_y = 0.5,
border_x = [0, 20],
border_y = [0, 50],
color = "Blue",
show_trajectory = True,
max_trajectory_iterations = 50
)
This initiates a PhysicsObject object which later can be used to be implemented into the simulation
Next are StaticObjects (SO), which won't move at all. Not the gravity or the collision with other objects moves the SO.
StaticObjects consist of a list of vectors, called matrix, color and fill_color.
matrix is a list of vectors which define the points and lines of the StaticObject. The first list consists of x-positions and the second list of y-positions.
The color defines the color, which is based on the named colors of MPL, of the lines and points of the StaticObject.
Lastly, the fill_color is the color, which will be used as the filling.
To initiate a StaticObject you can do one of the following
# this method uses a list of vectors
box1 = StaticObject(
[
[0, 5, 5, 5, 5, 0, 0, 0], # list of x-positions
[20, 20, 20, 25, 25, 25, 20, 20] # list of y-positions
],
"black",
"black"
)
# this method uses a function to generate a list of vectors for you
rectangle = StaticObject(
utils.generate_rectangle(2, 8, 6, 12), # x1, y1, x2, y2 (corner points)
"red",
"red"
)
A PhysicsRoom consists of an ObjectCollection and a few other settings which we'll get to later. First we have to define a ObjectCollection which can built using a list of PhysicsObjects and/or StaticObjects. The ObjectCollection is a class which is used to store all the PhysicsObjects and StaticObjects in one place as a dictionary. This makes it easier to work with every type of object when rendering the PhysicsRoom. This ObjectCollection will then be passed to the PhysicsRoom.
# here we declare the list of POs, build a ObjectCollection and then render the PhysicsRoom
objects = [ball1, ball2, ball3]
collection = ObjectCollection().build_collection(objects)
PhysicsRoom(
OC = collection,
).render()
When initializing a PhysicsRoom you can pass multiple settings like collision, recording as more.
The collision parameter is a boolean which decides whether or not the objects in the room will collide with each other.
The KEGraphing parameter is also a boolean which decides whether or not there should be another subplot which displays the kinetic energy of each PhysicsObject.
The recording parameter is a boolean too which decides whether or not the simulation should be recorded and saved in the current directory as a .avi file.
Here is an example of how you can use these parameters:
# this script enables collision, recording and KEGraphing
objects = [ball1, ball2, ball3]
collection = ObjectCollection().build_collection(objects)
PhysicsRoom(
OC = collection,
collision = True,
KEGraphing = True,
recording = True
).render()
Heres a video of a simulation created with PyPhyEngine:
Here's the code for the video above:
import pyPhyEngine as phy
import pyPhyEngine.models as models
ball = models.PhysicsObject(
x = 1,
y = 20,
size = 65,
grav = 0.25,
fps = 90,
mass=1,
bounce = 1,
friction = 0.05,
velocity_x = 0.15,
color = "blue",
show_trajectory = True
)
ball2 = models.PhysicsObject(
x = 9,
y = 20,
size = 200,
grav = 0.25,
fps = 90,
bounce = 1,
mass=1,
friction = 0.05,
velocity_x = -0.15,
color = "purple",
show_trajectory = True
)
collection = phy.ObjectCollection().build_collection([ball, ball2])
phy.PhysicsRoom(
OC=collection,
record_video=True,
collision=True
).render()
pyPhyEngine
The pyPhyEngine module is the main module of the engine. It contains the PhysicsRoom class and ObjectCollection class.
The PhysicsRoom class is used to render the simulation, using the ObjectCollection.
The ObjectCollection class is used to store all the PhysicsObjects and StaticObjects in one place as a dictionary.
room = PhysicsRoom(OC: dict, collision: bool = True, KEGraphing: bool = False, tolerance: int | float = 0.25, loop: bool = False, record_video: bool = False, debug: bool = False)
> The OC parameter is the ObjectCollection which contains all the PhysicsObjects and StaticObjects.
> The collision parameter decides whether or not the objects in the room will collide with each other.
> The KEGraphing parameter decides whether or not there should be another subplot which displays the kinetic energy of each PhysicsObject.
> The tolerance parameter is used to calculate the distance between two objects.
> The loop parameter decides whether or not the generation should be looped.
> The record_video parameter decides whether or not the simulation should be recorded and saved in the current directory as a .avi file.
> The debug parameter decides whether or not the debug mode should be enabled.
collection = ObjectCollection().build_collection(*objects: PhysicsObject | StaticObject)# TODO: Explain each function of the ObjectCollection class
keGraphing = KEGraphing(objects: list[PhysicsObject])
> The objects parameter is a list of PhysicsObjects.
> The KEGraphing class will then calculate the kinetic energy of each PhysicsObject and display it in a subplot as a bar graph.
get_kinetic_energy
keGraphing.get_kinetic_energy() -> list
> Returns a list containing the kinetic energy of each PhysicsObject.
get_limit
keGraphing.get_limit() -> int
> Returns the limit.
get_axes
keGraphing.get_axes() -> dict
pyPhyEngine.collider
The pyPhyEngine.collider module contains the Collider class, which detects collisions between objects.
collision = pyPhyEngine.collider(tolerance: int | float = 0.25)
get_objects
collision.get_objects(collection: dict) -> None
extract_current_object
collision.extract_current_object(iteration: int) -> PhysicsObject
check_collides_physicsObject
collision.check_collides_physicsObject(object1: PhysicsObject, physicsObject: PhysicsObject) -> tuple[bool] | PhysicsObject
> Returns a tuple with a boolean and the two colliding PhysicsObjects.
lines_staticObject
collision.lines_staticObject(staticObject: StaticObject) -> list
check_collides_staticObject
collision.check_collides_staticObject(object1: PhysicsObject, staticObject: StaticObject) -> bool
> Returns a boolean depending on whether or not there are any collisions.
check_collides
collision.check_collides(object1: PhysicsObject) -> bool
> It checks this by iterating over each PhysicsObject and StaticObject and then
> calling check_collides_physicsObject and check_collides_staticObject accordingly.
> Returns a boolean depending on whether or not there are any collisions.
calculate_velocities
collision.calculate_velocities(object1: PhysicsObject | StaticObject, object2: PhysicsObject | StaticObject) -> dict[str, dict[str, int | float]]
> Returns a dictionary containing the new velocities of the two objects.
collide
collision.collide(object1: PhysicsObject, object2: PhysicsObject | None) -> None
> You would pass None as the second object if the second object is a StaticObject.
> Thats because StaticObjects don't move.
> Returns None.
pyPhyEngine.generator
The pyPhyEngine.generator module contains the PhysicsRoomGenerator class, which generates a completely random PhysicsRoom.
Note that the PhysicsRoomGenerator is not a PhysicsRoom, but a class which generates a PhysicsRoom. Also, recording and KEGraphing will be disabled by default.
generator = pyPhyEngine.generator.PhysicsRoomGenerator(loop: bool = False, collision: bool = True)
> The loop parameter decides whether or not the generation should be looped.
> The collision parameter decides whether or not the objects in the room will collide with each other.
create_objects
generator.create_objects() -> None
> The objects get appended to the self.PhysicsObjects list.
> Each PhysicsObject gets a random position, size, color, velocity and more.
ObjectCollection
generator.ObjectCollection() -> None
> The ObjectCollection dict will be saved in a variable within the class.
create_room
generator.create_room() -> None
> The PhysicsRoom will be rendered immediately.
pyPhyEngine.models
The pyPhyEngine.models module contains the PhysicsObject and StaticObject classes.
object = pyPhyEngine.models.PhysicsObject(x: int | float, y: int | float, size: int, grav: int | float, fps: int, bounce: int | float, friction: int | float, velocity_x: int | float, velocity_y: int | float, border_x: list[int], border_y: list[int], color: str, show_trajectory: bool, max_trajectory_iterations: int)
> The x and y parameters are the position of the PhysicsObject.
> The size parameter is the size of the PhysicsObject.
> The grav parameter is the gravity which is applied to the PhysicsObject.
> The fps parameter is the frames per second of the PhysicsObject.
> The bounce parameter is the bounce of the PhysicsObject.
> The friction parameter is the friction of the PhysicsObject.
> The velocity_x and velocity_y parameters are the velocities of the PhysicsObject.
> The border_x and border_y parameters are the borders of the PhysicsObject.
> The color parameter is the color of the PhysicsObject.
> The show_trajectory parameter decides whether or not the trajectory of the PhysicsObject should be shown.
> The max_trajectory_iterations parameter is the maximum amount of trajectory points.
tick
PhysicsObject.tick() -> int | dict
> This will usually run each frame of the simulation.
> It validates the new x-, y-position and trajectory points
> Returns the new position of the PhysicsObject.
validate_x_position
PhysicsObject.validate_x_position(x: int) -> int
> Returns the new x-position.
validate_y_position
PhysicsObject.validate_y_position(y: int) -> int
> Returns the new y-position.
validate_trajectory
PhysicsObject.validate_trajectory() -> None
> If the trajectory is too long, it will delete the oldest point.
> The trajectory will be appended to the self.trajectory variable of the class.
get_data_string
PhysicsObject.get_data_string() -> str
> This is usually used for debugging purposes.
object = pyPhyEngine.models.StaticObject(matrix: list[list[int]], color: str = "blue", fill_color: str = "blue")
> The matrix parameter is a list of vectors which define the points and lines of the StaticObject.
> The color parameter is the color of the StaticObject.
> The fill_color parameter is the color of the filling of the StaticObject.
pyPhyEngine.renderer
The pyPhyEngine.renderer module contains the Renderer class, which renders the simulation.
renderer = pyPhyEngine.renderer.Renderer(fps: int = 60, loop: bool = False, collision: bool = True, recorder: bool = False, KEGraphing: bool = False, collection: dict = None)
> The fps parameter is the frames per second of the simulation.
> The loop parameter decides whether or not the simulation should be looped.
> The collision parameter decides whether or not the objects in the room will collide with each other.
> The recorder parameter decides whether or not the simulation should be recorded and saved in the current directory as a .avi file.
> The KEGraphing parameter decides whether or not there should be another subplot which displays the kinetic energy of each PhysicsObject.
> The collection parameter is the ObjectCollection which contains all the PhysicsObjects and StaticObjects.
setup
renderer.setup() -> Figure | Axes
> If recorder is True, it will set up the recorder.
> If KEGraphing is True, it will inititalize 2 subplots instead of 1 and the KEGraphing class
> If collider is True, it will initialize the Collider class.
> Returns the figure and axes.
save_frame
renderer.save_frame() -> None
> This is only used when recording the simulation.
render_object
renderer.render_object(object: PhysicsObject | StaticObject, axis: Axes) -> None
> Returns None.
render_all_objects
renderer.render_all_objects(collection: dict, axis: Axes) -> None
> Returns None.
set_axis_template
renderer.set_axis_template(axis: Axes, room_limits: dict) -> None
> Returns None.
draw_kinetic_energy
renderer.draw_kinetic_energy(axis: Axes) -> None
> Returns None.
initialize_render
renderer.initialize_render(room_limits: dict) -> None
> First it sets the limits of the axis, then renders all objects and finally draws the kinetic energy graph.
> Then, it also checks for collisions and updates the positions of the PhysicsObjects if any collide.
> Returns None.
finish_video
renderer.finish_video() -> None
> Returns None.
pyPhyEngine.utils
The pyPhyEngine.utils module contains the generate_rectangle, generate_circle and extract_physicsObjects functions.
generate_rectangle
rectangle = pyPhyEngine.utils.generate_rectangle(x1: int, y1: int, x2: int, y2: int) -> list[list[int]]
> Returns a list of vectors which define the points and lines of the rectangle.
generate_circle
circle = pyPhyEngine.utils.generate_circle(x: int, y: int, radius: int, max_lines: int) -> list[list[int]]
> The max_lines parameter is the amount of lines which will be generated.
> Returns a list of vectors which define the points and lines of the circle.
extract_physicsObjects
objects = pyPhyEngine.utils.extract_physicsObjects(collection: dict) -> list[PhysicsObject]
> Returns a list of PhysicsObjects.
pyPhyEngine.vectors
The pyPhyEngine.vectors module contains functions which are used to calculate vectors.
Typically they are used in the project whenever we need to find if two objects collide or not.
We use them especially when PhysicsObjects and StaticObjects meet as we have to calculate whether or not the PhysicsObject is on a line of the StaticObject or not.
As of today (20.12.2024) the entire system is very unstable and not very reliable. I am working on a new system which will be more reliable and actually calculate the velocities for colliding objects correctly.
Therefore, the collisions system is not very reliable with StaticObjects.
get_vector_difference
vector = pyPhyEngine.vectors.get_vector_difference(vector1: list[int], vector2: list[int]) -> list[int]
> Returns the difference as a vector.
extract_points
points = pyPhyEngine.vectors.extract_points(line: list[list[int]]) -> list[list[int]]
> Returns a list of points.
neutralize_points
points = pyPhyEngine.vectors.neutralize_points(*points: list[list[int]]) -> list[list[int]]
> Returns a list of points with their absolute values.
point_collides_line
collision = pyPhyEngine.vectors.point_collides_line(point: list[int], line: list[list[int]]) -> bool
> Returns a boolean depending on whether or not there is a collision.
pyPhyEngine.video
The pyPhyEngine.video module contains the VideoRecorder class, which records the simulation as a video.
recorder = pyPhyEngine.video.VideoRecorder(fps: int = 60, video_path: str = '.\\', delete_temp: bool = False)
> The fps parameter is the frames per second of the video.
> The video_path parameter is the path where the video will be saved.
> The delete_temp parameter decides whether or not the temporary frames should be deleted.
delete_tmp
recorder.delete_tmp() -> None
> Returns None.
save_frame
recorder.save_frame(plot: Figure) -> None
> Returns None.
render_video
recorder.render_video() -> None
> Returns None.