borismassesa / double_pendulum-simulation

0 stars 1 forks source link

Double Pendulum Simulation in Godot Engine

Overview

https://github.com/user-attachments/assets/e98fc45c-3c76-4549-be5b-7af3167424c9

Features

Table of Contents

  1. Installation
  2. Project Structure
  3. Usage
  4. Customization
  5. Controls
  6. Scripts Overview
  7. setup_scene.gd
  8. pendulum_system.gd
  9. Troubleshooting
  10. Assertins and Error Handling

Installation

Steps:

  1. Clone the Repository git clone https://github.com/yourusername/double-pendulum-godot.git
  2. Open the Project in Godot
    • Launch Godot Engine.
    • Click on "Import".
    • Navigate to the cloned repository folder.
    • Select the project.godot file.
    • Click "Import & Edit".
  3. Run the Project
    • Press the Play button or press F5 to start the simulation.

Project Structure:

double-pendulum-godot/
        ├── assets/
        │   └── double_pendulum_screenshot.png
        ├── scenes/
        │   ├── Main.tscn
        │   └── PendulumSystem.tscn
        ├── scripts/
        │   ├── setup_scene.gd
        │   └── pendulum_system.gd
        ├── project.godot
        └── README.md

Node Hierarchy:

Usage:

  1. Running the Simulation
    • Open the project in Godot Engine.
    • Press F5 or click the Play button to run the simulation.
    • The double pendulum will start swinging based on the initial parameters.
  2. Observing the Simulation
    • Adjust your camera to view the pendulum from the side for the best perspective.
    • Watch the pendulum's motion and observe the chaotic behavior typical of a double pendulum.

Customization:

Pendulum Properties:

Initial Conditions:

How to Customize:

  1. Open pendulum_system.gd
    • Navigate to scripts/pendulum_system.gd in the Godot Editor.
  2. Modify Exported Variables
    • Adjust the variables under the "Pendulum Properties" and "Initial Conditions" export groups.
  3. Save and Run
    • Save your changes and run the project to see the effects.

Controls:

Scripts Overview:

Responsibilities:

Key Functions:

Example Snippet:

    func create_rod(rod_node: Node3D, rod_length: float):
        var rod_mesh_instance = MeshInstance3D.new()
        rod_node.add_child(rod_mesh_instance)
        rod_mesh_instance.name = "RodMesh"
        var cylinder = CylinderMesh.new()
        cylinder.height = rod_length
        cylinder.top_radius = 0.05
        cylinder.bottom_radius = 0.05
        rod_mesh_instance.mesh = cylinder
        rod_mesh_instance.transform.origin.y = -rod_length / 2

pendulum_system.gd

Responsibilities

Key Functions:

Troubleshooting and Debugging Guide

This section outlines common problems encountered during the development of the double pendulum simulation, their causes, and applied solutions.

1. Simulation Not Updating at 60 FPS

Problem

The pendulum simulation was not updating as expected; the motion was either non-existent or incorrect.

Cause

The _physics_process(delta) method was missing from the pendulum_system.gd script. This method is essential for updating the simulation at the default physics tick rate of 60 times per second.

Effects

Solution

Added the _physics_process(delta) Method:

func _physics_process(delta):
    if pause_simulation:
        return

    calculate_physics(delta)
    update_positions()

    if debug_mode:
        debug_state()

Debugging Steps

  1. Verified that _physics_process(delta) was not present
  2. Added the method and observed that the simulation began updating correctly
  3. Confirmed the update rate by printing the physics frames or using the debugger

2. Incorrect Node Hierarchy Affecting Transformations

Problem

The pendulum arms were not rotating or moving correctly because the node hierarchy did not reflect the necessary parent-child relationships.

Cause

The node hierarchy was not correctly established, so transformations and rotations did not propagate as expected.

Effects

Solution

Ensured Correct Hierarchy:

func create_pendulum():
    # Create Arm1
    arm1 = Node3D.new()
    arm1.name = "Arm1"
    pivot.add_child(arm1)
    arm1.position = Vector3.ZERO

    # Create Arm2
    arm2 = Node3D.new()
    arm2.name = "Arm2"
    arm1.add_child(arm2)
    arm2.position = Vector3(0, -rod_length_1, 0)  # Position at end of Arm1

    # Create the rods and masses
    create_rod(arm1, rod_length_1)
    create_rod(arm2, rod_length_2)
    create_mass(arm1, rod_length_1, "Mass1")
    create_mass(arm2, rod_length_2, "Mass2")

3. Git Conflicts When Pushing Changes

Problem

Errors occurred when pushing local commits to the remote repository due to divergent branches.

Cause

The local and remote main branches had diverged; the remote branch contained commits that were not in the local branch.

Solution

  1. Merged Remote Changes into Local Branch:
    git pull origin main --no-rebase
  2. Resolved any merge conflicts that arose
  3. Pushed Local Changes:
    git pull origin main

4. Nodes Not Referenced Correctly

Problem

Errors occurred due to nodes not being found or referenced correctly in the scripts.

Solution

Updated node references to use correct paths:

@onready var mass1 = $Pivot/Arm1/Mass1

Debugging Steps

  1. Check scene tree for correct node names and hierarchy
  2. Use print statements to verify node references
  3. Update node paths in scripts accordingly

Assertions and Error Handling

To enhance the reliability and robustness of our double pendulum simulation, we have implemented strategic assert function calls within the GDScript code. These assertions serve as internal checks to verify that certain conditions hold true at specific points in the code.

Purpose of Assertions

Implementation Details

1. Verifying Node Initialization

We ensure that all essential nodes are correctly initialized and not null to prevent null reference errors during execution.

func _ready():
    # Assert that critical nodes are not null
    assert(pivot != null, "Pivot node is not initialized.")
    assert(arm1 != null, "Arm1 node is not initialized.")
    assert(arm2 != null, "Arm2 node is not initialized.")
    assert(mass1 != null, "Mass1 node is not initialized.")
    assert(mass2 != null, "Mass2 node is not initialized.")

2. Validating Input Parameters

We validate that exported variables and input parameters have acceptable values:

func _ready():
    # Assert that rod lengths are positive
    assert(rod_length_1 > 0, "rod_length_1 must be greater than zero.")
    assert(rod_length_2 > 0, "rod_length_2 must be greater than zero.")

    # Assert that masses are positive
    assert(mass_1 > 0, "mass_1 must be greater than zero.")
    assert(mass_2 > 0, "mass_2 must be greater than zero.")

3. Preventing Division by Zero in Physics Calculations

func calculate_physics(delta):
    # Calculate denominators
    var den1 = l1 * (2.0 * m1 + m2 - m2 * cos(2.0 * (theta1 - theta2)))
    var den2 = l2 * (2.0 * m1 + m2 - m2 * cos(2.0 * (theta1 - theta2)))

    # Assert that denominators are not zero
    assert(abs(den1) > 0.000001, "Denominator den1 is too close to zero.")
    assert(abs(den2) > 0.000001, "Denominator den2 is too close to zero.")

Testing the Assertions

  1. Trigger an Assertion Failure:

    • Set an invalid value (e.g., rod_length_1 = 0)
    • Run the simulation
  2. Observe the Error Message:

    • Simulation halts with clear error message
    • Example: "Assertion failed: rod_length_1 must be greater than zero."
  3. Correct the Value:

    • Restore valid value
    • Verify simulation runs without errors

Benefits of Using Assertions

Important Considerations

Development vs. Production

Appropriate Usage