Nature2D - 2D Physics Engine for UI Elements

Benchmarks

Here’s some benchmarks (via boatbomber’s Benchmarker plugin) comparing the unoptimized version (v0.5.6) and the current optimized version (v0.5.7). I’ve edited Engine:Start() locally to not connect a RenderStepped event before hand. So if you’re going to benchmark it, be sure to remove the RenderStepped connection and set dt as 1/60.

Also note, that in the benchmarks Engine:Start() has been made a non-yielding function. It is called multiple times by the plugin and not once every frame.

Each benchmark is ran for 2 seconds hence the number of function calls vary for different cases. Each benchmark has 100 data points.

Benchmark 1

Common Information:

  • 50 RigidBodies.
  • Not using Quadtrees.
  • 50% RigidBodies are collidable while the other 50% are non-collidable.
  • All RigidBodies are unanchored.
  • All RigidBodies spawn at the same position.
  • Zero events connected.
Code Used
return {
	ParameterGenerator = function()
		local ReplicatedStorage = game:GetService("ReplicatedStorage")
		local Unoptimized = require(ReplicatedStorage.Original.Nature2D)
		local Optimized = require(ReplicatedStorage.Nature2D)

		local World = Instance.new("ScreenGui")
		local Canvas = Instance.new("Frame")
		Canvas.Name = "Canvas"
		Canvas.Size = UDim2.fromScale(1, 1)
		Canvas.Parent = World

		local function MakeRigidBodies(Engine, num: number)
			local collidable = true

			for i = 1, num do 
				local frame = Instance.new("Frame")
				frame.AnchorPoint = Vector2.new(.5, .5)
				frame.Size = UDim2.fromOffset(50, 50)
				frame.Position = UDim2.fromScale(.5, .5)
				frame.Parent = World.Canvas

				Engine:Create("RigidBody", {
					Mass = 1,
					Object = frame, 
					Collidable = collidable,
					Anchored = false
				})

				collidable = not collidable
			end
		end
		
		local Engine1 = Unoptimized.init(World)
		Engine1:UseQuadtrees(false)
		MakeRigidBodies(Engine1, 50)
		
		local Engine2 = Optimized.init(World)
		Engine2:UseQuadtrees(false)
		MakeRigidBodies(Engine2, 50)
		
		return Engine1, Engine2
	end;

	Functions = {
		["Unoptimized"] = function(Profiler, Engine1, Engine2) 
			Engine1:Start()
		end;

		["Optimized"] = function(Profiler, Engine1, Engine2)
			Engine2:Start()
		end;
	};

}

Results

Unoptimized - 106.9603ms
Optimized - 33.5569ms

Benchmark 2

Common Information:

  • 50 RigidBodies.
  • Uses Quadtrees.
  • 50% RigidBodies are collidable while the other 50% are non-collidable.
  • All RigidBodies are unanchored.
  • All RigidBodies spawn at the same position.
  • Zero events connected.
Code Used
return {
	ParameterGenerator = function()
		local ReplicatedStorage = game:GetService("ReplicatedStorage")
		local Unoptimized = require(ReplicatedStorage.Original.Nature2D)
		local Optimized = require(ReplicatedStorage.Nature2D)

		local World = Instance.new("ScreenGui")
		local Canvas = Instance.new("Frame")
		Canvas.Name = "Canvas"
		Canvas.Size = UDim2.fromScale(1, 1)
		Canvas.Parent = World

		local function MakeRigidBodies(Engine, num: number)
			local collidable = true

			for i = 1, num do 
				local frame = Instance.new("Frame")
				frame.AnchorPoint = Vector2.new(.5, .5)
				frame.Size = UDim2.fromOffset(50, 50)
				frame.Position = UDim2.fromScale(.5, .5)
				frame.Parent = World.Canvas

				Engine:Create("RigidBody", {
					Mass = 1,
					Object = frame, 
					Collidable = collidable,
					Anchored = false
				})

				collidable = not collidable
			end
		end
		
		local Engine1 = Unoptimized.init(World)
		Engine1:UseQuadtrees(true)
		MakeRigidBodies(Engine1, 50)
		
		local Engine2 = Optimized.init(World)
		Engine2:UseQuadtrees(true)
		MakeRigidBodies(Engine2, 50)
		
		return Engine1, Engine2
	end;

	Functions = {
		["Unoptimized"] = function(Profiler, Engine1, Engine2) 
			Engine1:Start()
		end;

		["Optimized"] = function(Profiler, Engine1, Engine2)
			Engine2:Start()
		end;
	};
}

Results

Unoptimized - 70.2497ms
Optimized - 39.1559ms

2 Likes

How did you do the collision detection, this is epic!

Does this support cylinders and triangles and stuff like that tho :smirk:

That’s a huge optimization! Nc.

1 Like

It’s very complicated to make a very simple one, you need to compare the size added to the 2nd frame and compare the position added to the 2nd frame, therefore do the process again but instead of adding it, subtract it instead.

Read the actual post and some of the replies made by the OP because it shows triangles and circles:

2 Likes

Collision detection is handled by the Separating axis theorem. I wrote about it a few months ago here.

My library supports all convex shapes for rigidbodies. It supports concave shapes too but they’ll have a convex hitbox.

1 Like

v0.6.0 - Clone() for Custom Rigid Bodies

  • RigidBody:Clone() now works for custom rigid bodies.
  • Added structure parameter to RigidBody.new() to cache the rigid body’s structure for the future.
  • Remove restrictions from RigidBody:Clone()

Updated Roblox Asset & Github

Updated Documentation

Updated Wally Package - 0.5.7 → 0.6.0


1 Like

Wow, that’s incredible! With that upgrade, I’m sure you’ll get a lot of new users.

can you link the old collisionservice? I wanna take a look at the source code

Hi! Recently I’m working on a game engine using your module. However the current errors that the module shows are very hard to debug.

For example let’s use this error [Nature2D] Received an Invalid Object Property.

In this case it’s super hard to figure it out why did it error, what property caused it.

For most of the warnings, errors should also say what property/object caused it. In this case it could look something like this: [Nature2D] Received an Invalid Object Property; MyExample (string)!

1 Like

This is a very important and needed change, so I’ll get this done in v0.6.1! Thank you for the feedback.

1 Like

v.0.6.1 - Bug fixes, Improvements to Physics Objects and Error Messages

  • “CanTouch” is not longer a valid property for rigid bodies.
  • Fixed bug where Point:KeepInCanvas() won’t calculate collisions accurately.
  • RigidBody.CanvasEdgeTouched event now fires only the moment a rigid body collides with the canvas’ edge and not every rendered frame.
  • Improved exception handling code.
  • Improved error messages for Engine:Create()
    • If an invalid property is specified, the error message will now contain the name of the invalid property you specified for debugging.
    • If properties for completely different physics objects are specified, the error message will now contain the name of the invalid property you specified.
    • If a must-have property is not specified, the error message will now contain the name of that must-have property.
  • RigidBody.Touched event now returns the rigid body’s id as well as the collision information (Collision axis, depth, vertex and edge). This can be beneficial for creating visual effects. For example:

Updated Roblox Asset & Github

Updated Documentation

Updated Wally Package - 0.6.0 → 0.6.1



cc: @james_mc98

4 Likes

v0.6.3 - Implemented Collision and Constraint Iterations

Iterations provide accurate calculations for more rigid and smoother physics. Constraint iterations are applied to Constraint:Constrain() method. Constraint iterations are extremely useful of rod constraints and rope constraints. Constraint iterations do not work on spring constraints.

Collision iterations are used to provide accurate and rigid collision detection and resolution. By default both of these iterations are set to 1. Iterations can be in the range of 1-10 only. Collision iterations can be set only if quadtrees are being used in collision detection.

Keep in mind that the higher the number of iterations the more accurate results. But, having more iterations means you’ll have to sacrifice performance. The lesser the number of iterations, the better performance but we’ll have to sacrifice on accuracy. So be careful where you use them!

Recommended Iteration Amounts:
Constraint Iterations - 3
Collision Iterations - 4


  • Added constraint and collision iterations and their functionality.
  • Added new methods to Engine
    • Engine:SetConstraintIterations(iterations: number)
    • Engine:SetCollisionIterations(iterations: number)
  • Updated RigidBody:Update() to use constraint iterations.
  • Fixed RigidBody.Touched and RigidBody.TouchEnded after adding iterations.
  • Fixed MouseConstraint plugin bug - Stop looping through the rigid bodies if we have already found one to attach to the mouse.

Updated Roblox Asset & Github

Updated Documentation

Updated Wally Package - 0.6.1 → 0.6.2 → 0.6.3


1 Like

Examples of me using iterations for more accurate physics for rigid bodies and constraints.

Here, a simulation of a few boxes. They very rarely clip into each other and act more rigid than before. Running at 3 Collision Iterations and 3 Constraint Iterations.

Similarly, a box suspended from a rope. See how the rope doesn’t let the rigid body go farther than a set distance at all and immediately pulls it in place? Running at 5 Constraint Iterations.

1 Like

Nice job, however will you ever consider making this recommended for (2d) games?

1 Like

Not currently. The library is still under heavy work (been in beta for 3 months). Once I’ve got everything functional + new features, this library will be ready for dedicated 2D games. Right now, it’s just out for you to fiddle around with! If you’re are fine with the features it currently has, then you may consider using it for a larger project.

3 Likes

v0.6.4 - CanRotate Property, UniversalMass and changes to Friction

  • Change default friction and airfriction to 0.1 and 0.02.
  • Add CanRotate property as a valid property.
  • Set CanRotate to true by default.
  • Add new methods to RigidBodies
    • RigidBody:CanRotate(canRotate: boolean) - Determines if a rigid body can rotate after collisions or when forces are applied, extremely useful for creating platformer games, top-down games etc.
  • Restrict RigidBody:CanRotate() for custom rigidbodies
  • Update RigidBody:Update() and Engine:Create() to adhere to CanRotate
  • Add UniversalMass as a valid physical property of the Engine. By default set to 1
    • Engine:SetPhysicalProperty("UniversalMass", 5)

Updated Roblox Asset & Github

Updated Documentation

Updated Wally Package - 0.6.3 → 0.6.4


cc: @james_mc98

1 Like

Incase you’re wondering how rigid bodies act when their CanRotate property is set to false:

If you’re creating platformers, character controllers or top-down view games then this feature should be perfect for you!

2 Likes

Looks incredible! Nice work dude!

1 Like

I’ve updated the documentation site w.r.t the last 4 versions/updates. Edited a few code examples. Edited a few tutorials as well. I also wrote a new and short tutorial which deals with Engine Iterations. You can check that out here: Engine Iterations | Nature2D