First SimulationΒΆ

This page creates one dynamic rigid body, steps PhysX, and synchronizes its visual representation.

self.standard_materials = self.create_standard_materials()
self.timing = self.configure_timing(
    ke.SimulationTimingConfig(
        render_hz=60.0,
        physics_hz=120.0,
        fixed_update_hz=60.0,
    )
)
self.set_simulation_hotkeys_enabled(enabled=True)
self.world: ke.sim.KangSimWorld = ke.sim.KangSimWorld(
    num_envs=1,
    sim_dt=self.timing.physics_dt,
    add_ground=True,
)
self.visual: ke.visual.sim.SimWorldVisualizer = ke.visual.sim.SimWorldVisualizer(
    app=self,
    world=self.world,
)

ball_xml = package_asset_path("objects", "ball.xml")
ball_data: ke.asset.ArticulationDesc = self.world.load_mjcf(
    mjcf_path=ball_xml,
)
self.ball: ke.sim.SimRigid = self.world.add_rigid(
    data=ball_data,
    env_id=0,
    obj_id=0,
    name="ball",
    pos=[0.0, 0.0, 1.8],
    density=600.0,
)
self.visual.add(
    sim_handle=self.ball,
    mjcf_path=ball_xml,
    material=self.standard_materials.common,
)

Advance at the fixed simulation rate and display the latest state once per rendered frame:

def fixed_update(self, fixed_dt):
    self.world.advance(fixed_dt)

def pre_render(self):
    self.visual.sync()

Run the complete example:

python ./python/examples/sim_world_minimal.py --width 1280 --height 720
Complete source: sim_world_minimal.py
  1"""Minimal KangSimWorld example.
  2
  3Inspired by Newton's basic shape examples: create a small simulation world,
  4spawn one rigid body, step the world, and sync it to the viewer.
  5"""
  6
  7from __future__ import annotations
  8
  9import argparse
 10from pathlib import Path
 11
 12import kangengine as ke
 13from kangengine import imgui, keys
 14
 15
 16def package_asset_path(*parts: str) -> str:
 17    return str(Path(ke.__file__).resolve().parent / "assets" / Path(*parts))
 18
 19
 20class MinimalSimWorldApp(ke.App):
 21    def setup(self):
 22        self.spawn_pos = [0.0, 0.0, 1.8]
 23        self.timing = self.configure_timing(
 24            ke.SimulationTimingConfig(
 25                render_hz=60.0,
 26                physics_hz=120.0,
 27                fixed_update_hz=60.0,
 28            )
 29        )
 30        self.set_simulation_hotkeys_enabled(True)
 31
 32        self.standard_materials = self.create_standard_materials()
 33        self.add_ground()
 34        self.set_camera_view([3.0, -4.0, 2.2], [0.0, 0.0, 0.7])
 35
 36        self.world = ke.sim.KangSimWorld(
 37            num_envs=1,
 38            sim_dt=self.timing.physics_dt,
 39            add_ground=True,
 40        )
 41        self.visual = ke.visual.sim.SimWorldVisualizer(self, self.world)
 42
 43        self.ball_xml = package_asset_path("objects", "ball.xml")
 44        ball_data = self.world.load_mjcf(self.ball_xml)
 45        self.ball = self.world.add_rigid(
 46            ball_data,
 47            env_id=0,
 48            obj_id=0,
 49            name="ball",
 50            pos=self.spawn_pos,
 51            density=600.0,
 52        )
 53        self.ball_visual = self.visual.add(
 54            self.ball,
 55            self.ball_xml,
 56            path="/ball",
 57            material=self.standard_materials.common,
 58            color=[0.95, 0.2, 0.12, 1.0],
 59        )
 60
 61        self._reset()
 62        print("Minimal KangSimWorld example: one dynamic rigid body")
 63        self.check_error()
 64
 65    def _reset(self):
 66        self.ball.set_root_state(
 67            None,
 68            self.spawn_pos,
 69            [0.0, 0.0, 0.0, 1.0],
 70            linear_velocity=[0.0, 0.0, 0.0],
 71            angular_velocity=[0.0, 0.0, 0.0],
 72        )
 73        self.world.step(substeps=0, apply_commands=False)
 74        self.visual.sync()
 75
 76    def pre_update(self):
 77        if self.was_key_pressed(keys.R):
 78            self._reset()
 79
 80    def fixed_update(self, fixed_dt):
 81        self.world.advance(fixed_dt)
 82
 83    def pre_render(self):
 84        self.visual.sync()
 85        self.check_error()
 86
 87    def render(self):
 88        pos = self.ball.get_root_pos()
 89
 90        imgui.begin("Minimal Sim World")
 91        imgui.text("KangSimWorld + SimWorldVisualizer")
 92        imgui.text("Enter: play/pause    Space: pause/step    R: reset")
 93        imgui.separator()
 94        state = "paused" if self.is_simulation_paused() else "running"
 95        imgui.text(f"State: {state}")
 96        imgui.text(f"Ball root: {pos[0]: .2f}, {pos[1]: .2f}, {pos[2]: .2f}")
 97        imgui.end()
 98
 99    def cleanup(self):
100        if hasattr(self, "world"):
101            self.world.release()
102
103
104def parse_args():
105    parser = argparse.ArgumentParser()
106    parser.add_argument("--width", type=int, default=1920)
107    parser.add_argument("--height", type=int, default=1080)
108    return parser.parse_args()
109
110
111def main():
112    args = parse_args()
113    app = MinimalSimWorldApp()
114    app.initialize(args.width, args.height, False, ke.UpAxis.Z)
115    app.start()
116
117
118if __name__ == "__main__":
119    main()

Expected result: the ball falls onto the ground. Enter toggles play/pause, Space pauses or advances one step, and R resets it.

A rigid-body ball

Next: Fixed Timestep and Rendering or Simulation.