Creating and Using QParticleObject - erayzesen/godot-quarkphysics GitHub Wiki

QParticleObject can be considered the smallest building block of the physics engine. Every QMeshNode type requires particles. They play a key role in the simulation, from collisions to constraints. In most dynamics, you don't need to directly control particle behavior because the particles are moved by their associated dynamics. However, in QSoftBodyNode types, you are left alone with the particles when controlling the object. Additionally, when you want to manipulate QMeshNode objects at runtime or need to create them through code, again, you'll need particles. Particles are crucial for the physics engine.

How is a QParticleObject Created?

QParticleObject instances are not part of the scene tree; they are created via code. You don't need to manually delete them. If Godot finds no references to them, they will be automatically removed.

# Create a particle
var my_particle=QParticleObject.new()
# Add the particle to a QMeshNode
my_mesh.add_particle(my_particle)
# Set particle position (relative to the owner QMeshNode position of the particle)
my_particle.set_position(Vector2(100,200))
# Set particle radius
my_particle.set_radius(5.0)

What is the Difference Between a Particle's Position and Its Global Position?

Each particle you create has a position relative to the position of the QMeshNode it belongs to. These positions are usually set once and rarely changed. They allow the node to remember its initial shape and perform various calculations.

For example:

  • In QRigidBodyNode, particles are repositioned and rotated based on their local position to maintain the correct global transform.

  • In QSoftBodyNode, during shape matching, particles are pushed toward their original positions using these values.

The global position of a particle, however, is its real-time position in world space. It constantly changes as the object moves. Thus, when working with particles, we usually interact with their global positions. To control a particle, we modify its global position. To alter the static shape defined in a QMeshNode, we modify the local position.

Note: For QRigidBodyNode types, changing a particle's global position is useless, as the rigid body dynamics override it every frame. But for QSoftBodyNode, except for some constraints, particles are free — you're in full control.

How Do We Change a Particle's Current Position?

To change a particle’s position, modify its global position. QParticleObject types also include methods like ApplyForce, SetForce, and AddForce, which you'll recognize from QRigidBodyNode.

# Change the particle's current global position
my_particle.set_global_position(Vector2(300, 400))

# Apply an immediate force to the particle's global position
my_particle.apply_force(Vector2.RIGHT * 10.0)

# Set or add a force that will be applied to the particle in the next physics frame
my_particle.set_force(Vector2.UP * 5.0)
my_particle.add_force(Vector2.UP * 1.0)

What Does a Particle’s Radius Mean, and How is It Changed?

Each QParticleObject has a radius. The minimum particle radius is 0.5. In primitive shape-based nodes (QMeshNode types), the radius is set to 0.5. However, for instance, a circle created via QMeshCircleNode is made of a single particle and its radius.

You can:

  • Set radii for all particles from the particle_radius property in the inspector (for regular QMeshNode types),

  • Use QMeshAdvancedNode to define individual radii for each particle (via our editor plugin),

  • Or modify them via code.

Here’s how to change the radii of polygon particles in a QMeshNode:

for i in range(my_mesh.get_polygon_particle_count()) :
	var particle=my_mesh.get_particle_from_polygon(i)
	particle.set_radius(5.0)

Particle Mass

Each QParticleObject has a mass. This is especially relevant for soft body simulations where particles are free. Each particle can have a different mass, affecting behavior.In QSoftBodyNode, by default, all particles inherit the mass defined by the node, but you can override this via code at runtime. For QRigidBodyNode, particle masses are irrelevant — the rigid object has a predefined total mass; particle masses are ignored.

What are Lazy Particles?

As the name suggests, lazy particles are "lazy" when reacting to external forces. Technically, a lazy particle reacts once when colliding with an object and remembers that object. As long as the collision continues, it won't react again. When the collision ends, it forgets the object, and the next time it collides, it reacts again — once.

They were used in our "Classic Platformer" demo for simple water interaction and responsive flags. You can use them for lightweight interactions — plants, decorations, etc.

What does the 'enabled' Setting Do for Particles? What Happens If It's Set to False?

Disabling a particle means:

  • It won't participate in collisions,

  • It won't be affected by gravity or velocity integration,

  • It will remain still unless manually modified.

In the “Classic Platformer” scene, particles of water object that should remain still have enabled = false. Also, the base of flagpoles was disabled to keep the pole standing.

In short, if you're creating a soft body but want certain particles to be pinned, immovable, and ignored in collisions, just set their enabled property to false.

What is previous_global_position for particles?

QuarkPhysics uses Verlet integration.

In simple terms, a particle's velocity is implicitly derived from the difference between its current and previous position. So, previous_global_position is the particle’s position in the previous frame.

You can:

  • Use it to calculate the particle’s current velocity,
var particle_velocity=my_particle.get_global_position()-my_particle.get_previous_global_position()
  • Zero out the velocity by making
my_particle.set_previous_global_position (my_particle.get_global_position() )
  • Set it to a value offset from the current global position to apply velocity manually.
var my_velocity=Vector2.UP*10
# Apply velocity to the particle
my_particle.set_previous_global_position( my_particle.get_global_position()-my_velocity )

You’ll rarely need to touch this, but it’s important to understand the logic behind it in case a situation arises.

What is an Internal Particle?

Particles marked as internal are mainly used for features like passivation_internal_spring in soft body nodes. You don't mark the particles forming the outer shape as internal — it's for the inner ones. Similarly, QSpringObject types can also be marked internal. This can be useful for custom calculations or filtering.

In particular, when the passivation_of_internal_springs feature is enabled in a QSoftBodyNode, all internal QSpringObject and QParticleObject are forced to follow the outer structure. This is especially useful when Area Preserving is active — e.g., for texture preservation. Though often considered non-essential, marking internal particles is a recommended standard when building QMesh objects. You might need to manipulate inner particles separately one day.

All inner particles created through primitive QMeshNode shapes are marked internal by default.