WOWCube Docs logo
WOWCube Docs
Mission Control
Section Shortcuts
APIExamplesSourceWOWConnectChangelog
Filters
SDK and language defaults persist in cookies.
SDK version
Navigation Tree
Collapsed by default, focused on the active path.
Made byMcKay Seamons
GitHub
  1. Home
  2. Docs
  3. Examples
  4. Scrambling
Mission NodeSDK 6.1C++topologyProject Included

Scrambling

Virtual topology In previous examples, we looked at what the topology is and how to work with it. The CubiOS operating system provides information about the ...

Examples / SDK 6.1 / C++ / topology

Virtual topology

In previous examples, we looked at what the topology is and how to work with it. The CubiOS operating system provides information about the relative positions of modules and their screens, and this is what we call “topology”. However, working with real, that is, physical topology is not always convenient. Quite often tasks arise that require, for example, preliminary disordering of the topology to begin the game process. For example, if the goal of the application is to solve a cube, then the cube must initially be disordered.

The process of disordering a cube to a state that can subsequently be guaranteed to be completely ordered is what we call scrambling.

In order to de-order the cube without changing the physical arrangement of the modules, a virtual topology is used. In essence, a virtual topology is a copy of the real topology made at a certain point in time. This copy is stored in application memory and allows you to perform face twists without actually interacting with the cube. In other words, virtual topology makes it possible to take the original topology of a cube and randomize it in a controlled way.

To simplify working with virtual topology, the Cubios::Scramble class is used (see documentation).

This class provides a number of functions that allow you to apply single or multiple twists to a virtual topology. In addition, this class provides the ability to receive callbacks called before and after applying the twist.

Generalized steps

So what steps do you need to take to get it working?

Step 1: Determine what information will be stored in the virtual topology.

The fact is, a virtual topology can be a repository of not only actual information about module and screen numbers, but also any other data associated with the topology. These could be, for example, resource identifiers or game level fragments or something else.

Step 2: Create a class inherited from Cubios::Scramble to implement callback handlers

Step 3: Populate the virtual topology with the original data, for example, make a copy of the current physical topology.

Step 4: Perform the required number of twists to the virtual topology.

Step 5: Use the result.

The example

So, let's look at an example that uses a virtual topology to scramble an initially assembled cube by tapping on one of its faces.

The first thing to do is to describe our virtual topology and what exactly we will store in it. For this we will use an array

Scrambling
CPP
1mapping_element_t value[MODULES_MAX][SCREENS_MAX]
2
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

of structures

Scrambling
CPP
1struct mapping_element_t
2{
3 Cubios::TOPOLOGY_place_t place;
4 int resourceID;
5 unsigned char angle;
6};
7
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

As you can see, the array contains information about 8 modules (MODULES_MAX) of 3 screens (SCREENS_MAX) each. Moreover, in addition to the standard Cubios::TOPOLOGY_place_t values, we will additionally store the resource identifier and the corresponding rotation angle. This is needed to draw a cube map.

The next step will be to write a class inherited from Cubios::Scramble, whose task will be to apply transformations (twists) to our custom virtual topology, as well as process events.

Let's take a look at this class.

The header file looks like this:

Scrambling
CPP
1class MyScramble : public Cubios::Scramble
2{
3public:
4 MyScramble() : Scramble(){};
5 ~MyScramble(){};
6 virtual void on_Twist(const Cubios::TOPOLOGY_twistInfo_t twist) override;
7 virtual void on_BeforeTwist() override;
8 virtual void on_MappingChanged(uint8_t moduleTo, uint8_t screenTo, uint8_t moduleFrom, uint8_t screenFrom) override;
9
10 void SetMapping(mapping_t *data);
11 mapping_t GetMapping();
12
13private:
14 mapping_t mapping;
15 mapping_t mappingTemp;
16};
17
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

Everything is more or less clear here: there are three overridden callback functions and two methods for accessing virtual topology data. But in addition, there are 2 private variables mapping and mappingTemp, both of type mapping_t.

It is clear that one of them is needed to store the virtual topology, but why the second?

The answer is quite simple: two copies are needed because we need to know the state of the virtual topology both before and after applying the twist. In other words, we need to know “how it was” and “how it is now" !

Let's look at the source code below:

Scrambling
CPP
1void MyScramble::on_BeforeTwist()
2{
3 memcpy(&this->mappingTemp, &this->mapping, sizeof(this->mapping));
4};
5
6void MyScramble::on_Twist(const Cubios::TOPOLOGY_twistInfo_t twist)
7{
8 LOG_i("Twist screen = %d direction = %d", twist.screen, twist.direction);
9};
10
11void MyScramble::on_MappingChanged(uint8_t moduleTo, uint8_t screenTo, uint8_t moduleFrom, uint8_t screenFrom)
12{
13 this->mapping.value[moduleTo][screenTo].place.face = this->mappingTemp.value[moduleFrom][screenFrom].place.face;
14 this->mapping.value[moduleTo][screenTo].place.position = this->mappingTemp.value[moduleFrom][screenFrom].place.position;
15 this->mapping.value[moduleTo][screenTo].resourceID = this->mappingTemp.value[moduleFrom][screenFrom].resourceID;
16 this->mapping.value[moduleTo][screenTo].angle = this->mappingTemp.value[moduleFrom][screenFrom].angle;
17};
18
19void MyScramble::SetMapping(mapping_t *data)
20{
21 memcpy(&this->mapping, data, sizeof(this->mapping));
22};
23
24mapping_t MyScramble::GetMapping()
25{
26 return this->mapping;
27};
28
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.
  • on_BeforeTwist callback is called before a transformation (a twist) is applied to the virtual torology. This means that if we want to remember the previous state of the topology, we just need to make a copy of the current state. That's what we do.

  • After the twist is performed, the on_MappingChanged callback is called first. This callback makes it possible to find out exactly how the modules and screens were rearranged after applying the twist. And this means that in the body of this callback we must implement data exchange between the cells of our virtual topology array.

  • The last callback on_Twist is called at the very end and signals the completion of scrambling of the virtual topology. In this example, we simply output a message to the log, but in real applications this callback can be used differently.

Now let's move on to the main application.

First we declare the necessary variables:

Scrambling
CPP
1#include "MyScramble.h"
2...
3class ScrambleApp: public Cubios::Application
4{
5...
6private:
7 MyScramble *scramble;
8 mapping_t mapping;
9
10 Cubios::NetworkMessage *network;
11 int angles[4];
12}
13
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.
  • scramble is our virtual topology handling class

  • mapping is an instnace of virtual topology array the application uses for rendering

    ScrambleApp::ScrambleApp():angles{0,90,180,270} { }

    void ScrambleApp::InitializeResources() { for (uint8_t module = 0; module < MODULES_MAX; ++module) { for (uint8_t screen = 0; screen < SCREENS_MAX; ++screen) { Cubios::TOPOLOGY_place_t place; TOPOLOGY_getPlace(module,screen,Cubios::TOPOLOGY_orientation_mode_t::ORIENTATION_MODE_MENU, &this->mapping.value[module][screen].place); this->mapping.value[module][screen].place.face = Cubios::TOPOLOGY_getPlaceOrientation(this->mapping.value[module][screen].place.face, this->mapping.value[module][screen].place.position);

    Scrambling
    CPP
    1 switch(this->mapping.value[module][screen].place.face)
    2 {
    3 case Cubios::TOPOLOGY_orientation_t::ORIENTATION_UP: //0
    4 {
    5 switch(this->mapping.value[module][screen].place.position)
    6 {
    7 case 0: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("up3.png"); this->mapping.value[module][screen].angle = 0; break;
    8 case 1: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("up4.png"); this->mapping.value[module][screen].angle = 3; break;
    9 case 2: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("up1.png"); this->mapping.value[module][screen].angle = 2; break;
    10 case 3: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("up2.png"); this->mapping.value[module][screen].angle = 1; break;
    11 }
    12 }
    13 break;
    14 case Cubios::TOPOLOGY_orientation_t::ORIENTATION_DOWN: //1
    15 switch(this->mapping.value[module][screen].place.position)
    16 {
    17 case 0: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("down4.png"); this->mapping.value[module][screen].angle = 3; break;
    18 case 1: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("down1.png"); this->mapping.value[module][screen].angle = 2; break;
    19 case 2: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("down2.png"); this->mapping.value[module][screen].angle = 1; break;
    20 case 3: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("down3.png"); this->mapping.value[module][screen].angle = 0; break;
    21 }
    22 break;
    23 case Cubios::TOPOLOGY_orientation_t::ORIENTATION_FRONT: //2
    24 switch(this->mapping.value[module][screen].place.position)
    25 {
    26 case 0: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("front2.png"); this->mapping.value[module][screen].angle = 1; break;
    27 case 1: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("front3.png"); this->mapping.value[module][screen].angle = 0; break;
    28 case 2: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("front4.png"); this->mapping.value[module][screen].angle = 3; break;
    29 case 3: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("front1.png"); this->mapping.value[module][screen].angle = 2; break;
    30 }
    31 break;
    32 case Cubios::TOPOLOGY_orientation_t::ORIENTATION_BACK: //3
    33 switch(this->mapping.value[module][screen].place.position)
    34 {
    35 case 0: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("back3.png"); this->mapping.value[module][screen].angle = 0; break;
    36 case 1: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("back4.png"); this->mapping.value[module][screen].angle = 3; break;
    37 case 2: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("back1.png"); this->mapping.value[module][screen].angle = 2; break;
    38 case 3: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("back2.png"); this->mapping.value[module][screen].angle = 1; break;
    39 }
    40 break;
    41 case Cubios::TOPOLOGY_orientation_t::ORIENTATION_LEFT: //4
    42 switch(this->mapping.value[module][screen].place.position)
    43 {
    44 case 0: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("left4.png"); this->mapping.value[module][screen].angle = 3; break;
    45 case 1: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("left1.png"); this->mapping.value[module][screen].angle = 2; break;
    46 case 2: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("left2.png"); this->mapping.value[module][screen].angle = 1; break;
    47 case 3: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("left3.png"); this->mapping.value[module][screen].angle = 0; break;
    48 }
    49 break;
    50 case Cubios::TOPOLOGY_orientation_t::ORIENTATION_RIGHT: //5
    51 switch(this->mapping.value[module][screen].place.position)
    52 {
    53 case 0: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("right1.png"); this->mapping.value[module][screen].angle = 2; break;
    54 case 1: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("right2.png"); this->mapping.value[module][screen].angle = 1; break;
    55 case 2: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("right3.png"); this->mapping.value[module][screen].angle = 0; break;
    56 case 3: this->mapping.value[module][screen].resourceID = Cubios::GFX_getAssetId("right4.png"); this->mapping.value[module][screen].angle = 3; break;
    57 }
    58 break;
    59 }
    60 }
    61 }
    62
    63 this->scramble = new MyScramble();
    64 this->network = new Cubios::NetworkMessage();
    65
    Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

    }

How does it all work?

Well, in this function we do the following: we take the current physical topology and orientation of the cube by iterating through all modules and screens, save this topology in the mapping variable, simultaneously supplementing it with custom information about resource identifiers and angles, and at the end we create the objects we need.

In this way, we initialize our virtual mapping with an initial state identical to the state of the physical cube.

Next, we can proceed directly to rendering.

Scrambling
CPP
1void ScrambleApp::on_Render(std::array<Cubios::Screen, 3>& screens)
2{
3 for (auto it = screens.begin(); it != screens.end(); ++it)
4 {
5 Cubios::GFX_setRenderTarget(it->ID());
6
7 Cubios::GFX_drawImage(this->mapping.value[this->Module][it->ID()].resourceID, 120,120,0xFF, Cubios::Gfx::Colors::black, 100, 100, this->angles[this->mapping.value[this->Module][it->ID()].angle] ,Cubios::GFX_mirror_t::MIRROR_BLANK);
8
9 ...
10
11 Cubios::GFX_render();
12 }
13}
14
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

As you can see, the rendering process consists of simply selecting the desired resource and the corresponding rotation angle from the virtual topology data array.

The next stage is to make sure that when you tap the cube, the virtual topology will scramble.

Let's look at the on_Tap function:

Scrambling
CPP
1void ScrambleApp::on_Tap(uint32_t count)
2{
3 if (IsMasterModule())
4 {
5 int number_of_twists_to_scramble = 5;
6
7 this->scramble->SetMapping(&this->mapping);
8
9 this->scramble->StartScramble(number_of_twists_to_scramble);
10 ...
11 this->mapping = this->scramble->GetMapping();
12 }
13}
14
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.
  • First of all, we must pass the current state of our virtual topology to the scramble object.

  • Next, we, in fact, can apply twists the required number of times - five times in this case.

  • And in the end, we get the transformed virtual topology from the scramble object back into the application.

But that's not all.

We have one small but important point left - how to ensure synchronization of virtual torology changes between modules? After all, it is absolutely obvious that mapping a virtual topology onto a real one will work correctly only in the case when all cube modules operate with identical data, right? And if suddenly this data “diverges,” then we will immediately get visual rendering artifacts.

There are many ways to ensure data synchronization between modules - some more optimal, others less so. But all of them, one way or another, come down to the idea of transferring data from the “master” module (or modules) to the “slave” modules. That is, the master module is responsible for generating data, and the slave modules are only consumers of this data, but do not participate in the generation.

This approach is also used in our application.

The calculation of virtual topology transformations is carried out on the 0th module, which constantly sends data to neighboring modules:

Scrambling
CPP
1void ScrambleApp::on_Tick(uint32_t currentTime, uint32_t deltaTime)
2{
3 if (this->IsMasterModule())
4 {
5 this->network->Reset(true);
6
7 for(int m=0;m<MODULES_MAX;m++)
8 for(int s=0;s<SCREENS_MAX;s++)
9 {
10 this->network->WriteInt(this->mapping.value[m][s].place.face,5);
11 }
12 this->SendNetworkMessage(0, network);
13
14 this->network->Reset(true);
15 for(int m=0;m<MODULES_MAX;m++)
16 for(int s=0;s<SCREENS_MAX;s++)
17 {
18 this->network->WriteInt(this->mapping.value[m][s].place.position,4);
19 this->network->WriteInt(this->mapping.value[m][s].angle,4);
20 }
21 this->SendNetworkMessage(1, network);
22
23 this->network->Reset(true);
24 for(int m=0;m<MODULES_MAX;m++)
25 for(int s=0;s<SCREENS_MAX;s++)
26 {
27 this->network->WriteInt(this->mapping.value[m][s].resourceID,8);
28 }
29 this->SendNetworkMessage(2, network);
30 }
31}
32
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

That is, as soon as the virtual topology on the 0th module has changed, all neighbors will immediately know about it.

Scrambling
CPP
1void ScrambleApp::on_Message(uint32_t type, uint8_t* pkt, u32_t size)
2{
3 if (IsMasterModule())
4 return;
5
6 this->network->SetData(pkt, size);
7
8 for(int m=0;m<MODULES_MAX;m++)
9 for(int s=0;s<SCREENS_MAX;s++)
10 {
11 if(type==0) this->mapping.value[m][s].place.face = this->network->ReadInt(5);
12 if(type==1) {this->mapping.value[m][s].place.position = this->network->ReadInt(4);this->mapping.value[m][s].angle = this->network->ReadInt(4);}
13 if(type==2) this->mapping.value[m][s].resourceID = this->network->ReadInt(8);
14 }
15}
16
Wrapped for easier reading. Turn wrap off to inspect exact line lengths.

And thus we will get an application that renders a cubemap and scrambles it when we tap the cube.

Good job!

Context Rail

Project files

MyScramble.cpp
project/src/MyScramble.cpp
MyScramble.h
project/src/MyScramble.h
ScrambleApp.cpp
project/src/ScrambleApp.cpp
ScrambleApp.h
project/src/ScrambleApp.h
wowcubeapp-build.json
project/wowcubeapp-build.json
Jump Grid

On This Page

Virtual topologyGeneralized stepsThe example
Context Rail

Related nodes

info.json
Examples / SDK 6.1 / C++ / topology / Scrambling
MyScramble.cpp
Examples / SDK 6.1 / C++ / topology / Scrambling / project / src
MyScramble.h
Examples / SDK 6.1 / C++ / topology / Scrambling / project / src
ScrambleApp.cpp
Examples / SDK 6.1 / C++ / topology / Scrambling / project / src
Previous Node
wowcubeapp-build.json
Examples / SDK 6.1 / C++ / topology / Cube Mapping / project
Next Node
info.json
Examples / SDK 6.1 / C++ / topology / Scrambling