This post is the second post in my miniseries to OGRE graphics library and today we learn how to handle moving object in a scene and GUI integration.

Content

Cube rain

We are going to create scene with “falling” cubes from top of the scene to its bottom. The idea is to create some cubes at the beginning, let them fall and recreate those that reached the ground so it looks like rain of cubes.

cube rain scene screenshot

Basic concept implementation

We can start the same way as in case of previous camera sample and deriving ogre_app from ApplicatonContext, InputListener and this time also from RenderTargetListener.

RenderTargetListener allows us to receive notification before viewport update in which case preViewportUpdate() member function is called. Our custom implementation will handle GUI, but we will go through GUI handling later in this article.

Let’s start with constructor where we initialize pool of 300 cubes stored in _cubes vector this way

_cubes.resize(_cube_count);
for (cube_object & cube : _cubes)
    cube = new_cube();

_cube_nodes.resize(_cube_count);

we also need one scene node instance for each cube in scene stored in _cube_notes vector, that is why _cube_nodes.resize() is called.

Let’s now focuse on setup_scene() member function, where at the beginning we create lights and camera controller pretty much the same way as in camera sample. Then in a for loop we create node for each cube and store it in _cube_nodes via iterator whis way

auto cube_nodes_it = begin(_cube_nodes);

for (cube_object & cube : _cubes) {
	*cube_nodes_it = create_cube_node(scene, cube);
		++cube_nodes_it;

	*cube_nodes_it = node;  // save node for later update
	++cube_nodes_it;
}

Helper member function create_cube_node() implementation is pretty straightforward. It creates cube entity and associate it with a scene node after scaling so not all cubes looks the same, this way

SceneNode * ogre_app::create_cube_node(SceneManager & scene, cube_object const & cube) {
	Entity * cube_model = scene.createEntity(SceneManager::PT_CUBE);
	cube_model->setMaterialName("cube_color");  // see media/cube.material

	SceneNode * nd = scene.getRootSceneNode()->createChildSceneNode(cube.position);
	Real model_scale = 0.2 * (2.0 / cube_model->getBoundingBox().getSize().x);
	Real cube_scale = model_scale * cube.scale;
	nd->setScale(cube_scale, cube_scale, cube_scale);
	nd->attachObject(cube_model);

	return nd;
}

At the end of setup_scene() we create xyz axis at (0,0,0) position.

That was pretty much the setup and after that we ends up with a bunch of cubes in scene, but these cubes are not yet moving. To create movement we need periodically update every cube’s position in a update() member function.

Class ApplicationContext offers frameStarted() function which can be used as basis for our update() this way

bool ogre_app::frameStarted(FrameEvent const & evt) {
	duration<double> dt{evt.timeSinceLastFrame};
	update(dt);
	return ApplicationContext::frameStarted(evt);
}

and finally in update() implementation we update every cube’s position this way

constexpr Real fall_speed = 3;
constexpr Real fall_off_threshold = -10.0;

auto cube_node_it = begin(_cube_nodes);
for (cube_object & cube : _cubes) {
	cube.position.y -= fall_speed * (2.0 - cube.scale) * dt.count();
	if (cube.position.y < fall_off_threshold)
		cube = new_cube();

	(*cube_node_it)->setPosition(cube.position);  // update scene position
	++cube_node_it;
}

where new cube y position is calculated by y -= fall_speed * (2.0 - cube.scale) * dt formula where 2.0 - cube.scale adds little bit variation so not all cubes are falling the same way.

GUI integration

We would like to change number of falling cubes in our scene so some GUI integration comes handy and luckylly for us OGRE already integrate an awesome Dear ImGui library. All we need to do in ogre_app is to override preViewportUpdate() member function from RenderTargetListener this way

void ogre_app::preViewportUpdate(RenderTargetViewportEvent const & evt) {
	if (!evt.source->getOverlaysEnabled())
		return;

	ImGuiOverlay::NewFrame();

	update_gui();
}

and in update_gui() member function we create iteger slider this way

void ogre_app::update_gui() {
	ImGui::Begin("Info");  // begin window

	ImGui::SliderInt("Number of cubes", &_cube_count, 100, 1500);

	ImGui::End();  // end window

	ImGui::Render();
}

where number of cubes is stored as _cube_count private member variable. Then we need to handle keyboard and mouse inputs with ImGuiListener instance stored as _imgui_listener member variable. We can then use InputListenerChain class to chain input handling for ImGuiInputListener and CameraMan instances at the end of setup() member function this way

_imgui_listener = make_unique<ImGuiInputListener>();
_input_listeners = InputListenerChain({_imgui_listener.get(), _cameraman.get()});

Keyboard and mouse InputListener overrides in ogre_app looks this way

bool ogre_app::keyReleased(KeyboardEvent const & evt) {
	return _input_listeners.keyReleased(evt);
}

tip: watch out implementations for keyPressed(), mouseMoved(), mousePressed() and mouseReleased() in cube_rain.cpp file

Changing number of cubes

We heve made small simplification during update() description and skipped handling number of cubes option change via slider widget.

There are two scenarios there, we can eigther end up with less cubes in the scene or with more cubes in the scene after using the slider widget. In both cases we resize _cubes vector with

int prev_cube_count = size(_cubes);
_cubes.resize(_cube_count);

and then in case we end up with less cubes we remove additional cube nodes from scene graph whis way

if (_cube_count < prev_cube_count) {
	for_each(begin(_cube_nodes) + _cube_count, end(_cube_nodes),
		[&root](SceneNode * nd){root.removeChild(nd);});

	_cube_nodes.resize(_cube_count);
}

In case we end up with more cubes, we need to add additional cubes to scene graph this way

else if (_cube_count > prev_cube_count) {
	_cubes.resize(_cube_count);
	_cube_nodes.resize(_cube_count);

	for (int i = 0; i < _cube_count - prev_cube_count; ++i) {
		cube_object & cube = _cubes[prev_cube_count + i];
		cube = new_cube();

		_cube_nodes[prev_cube_count + i] = create_cube_node(*_scene, cube);
	}
}