Visualization overview
In addition to simulation, ERS also provides functionality to visualize simulation models in both 2D and 3D. The visualization of ERS is not necessarily tied to the simulation: one could use the visualization without performing any simulation.
The coordinate system and units of rotation
Coordinate system
ERS uses a right-handed coordinate system, where:
- X is positive to the right.
- Y is positive into the screen.
- Z is positive up.
We use this coordinate system because it allows one to assign 3D coordinates to a simulation object and using those coordinates in both 2D and 3D. When visualizing a model in 2D, it is assumed it is viewed from above (for example, like a floor plan). Objects are placed along the X- and Y-axis to move them to the right or up respectively. When the same model is visualized in 3D, we add a third dimension for the height of the simulation: the Z-axis.

The ERS coordinate system.
Units of rotation
The units used for rotation in the visualization are turns. One turn is a full rotation, equal to 1 revolution, 360 degrees, or 2π radians.
| Turns | Degrees | Radians |
|---|---|---|
| 0 | 0 | 0 |
| 0.25 | 90 | 0.5π |
| 0.5 | 180 | π |
| 0.75 | 270 | 1.5π |
| 1 | 360 | 2π |
The standard render system
The easiest way to add a visualization to model is using the standard render system.
By adding a RenderComponent along with a TransformComponent to an entity, the visualization of basic shapes in 2D and 3D can be quickly set up.
The RenderComponent can be used to define the shape, color, 2D texture and 3D model of an entity.
When setting the shape, it is used in both 2D and 3D.
The table below shows the different shapes that are available.
| Name | 2D visualization | 3D visualization |
|---|---|---|
| Point | Rectangle of \(10\times10\) pixels, regardless of zoom level | Cube of size \((0.1 \times 0.1 \times 0.1)\) |
| Rectangle | Rectangle | Box |
| Circle | Circle consisting of 32 segments | (Ico)sphere |
| Triangle | Triangle | Square pyramid |
When setting the 2D texture along with a color, the component's color will be mixed with the texture's color by multiplying the color values. To use just the texture's colors, do not set the color in the component, which leaves it to white by default.
To set a custom 3D model with the RenderComponent, set the InstancedModel property of the component.
The color set on the component does not affect the color of the 3D model.
The TransformComponent is always necessary to define the position of an entity.
If no scale or rotation is explicitly given, the scale will be \((1, 1, 1)\) and the entity will not be rotated.
The BoxComponent can optionally be added to define the dimensions of the entity.
For example, to create en entity that is twice as wide as it is tall, the dimensions can be set to \((2, 1, 1)\).
If the transform is now used to make the entity twice as large, the final size of the entity becomes \((4, 2, 2)\).
When no BoxComponent is added, the scale from the transform is used as final size of the entity.
Ers::SubModel& subModel = Ers::Get();
EntityID entity = subModel.CreateEntity("Entity1");
auto* transform = subModel.AddComponent<Ers::TransformComponent>(entity);
transform->SetPosition(30, 0, 0);
auto* render = subModel.AddComponent<Ers::RenderComponent>(entity);
render->Shape = Ers::RenderComponentShape::Rectangle;
render->Color = Ers::Color::FromBytes(0, 0, 255);
auto* box = targetEntity.AddComponent<Ers::BoxComponent>(entity);
box->SetDimensions(20, 10, 10);
SubModel subModel = SubModel.Get();
Entity entity = subModel.CreateEntity("Entity1");
var transform = entity.AddComponent<TransformComponent>();
transform.Value.Position = new Vector3(30, 0, 0);
var render = entity.AddComponent<RenderComponent>();
render.Value.Shape = RenderComponentShape.Rectangle;
render.Value.Color = Color.FromBytes(0, 0, 255);
var box = targetEntity.AddComponent<BoxComponent>();
box.Value.Dimensions = new Vector3(20, 10, 10);

2D and 3D visualization using RenderComponent, TransformComponent, and BoxComponent.
Custom rendering
Instead of using the standard render system, it is also possible to implement custom rendering of entities when you need more complicated visualizations.
To be able to perform custom rendering, you need a RenderContext.
The render context has all available functions that can be used to render.
This includes 2D shapes like lines, triangles, rectangles and images (PNG, BMP, JPEG, etc.);
but also 3D objects like meshes and models.
In the example below, the optional render2D callback of the Ers::Debugger::Run function is being defined.
This callback is automatically called each frame, allowing you to define what is visualized in the ERS Editor.
There is no need to reset the frame, this is done automatically by the Ers::Debugger::Run function.
For 3D rendering, there is a second callback called render3D.
For UI there is a third callback (see the debugger page).
Ers::Color red = Ers::Color::FromBytes(255, 0, 0);
Ers::Color green = Ers::Color::FromFloats(0.0f, 1.0f, 0.0f);
void Render2D(Ers::Debugger& debugger, Ers::Simulator& simulator)
{
Ers::RenderContext renderContext = debugger.GetRenderContext();
// Some 2D rendering
renderContext.DrawLine2D(Ers::Vec2(0, 5), Ers::Vec2(5, 5), 1.0f, red); // 2D red line from (0, 5) to (5, 5)
renderContext.DrawRect2D(Ers::Vec2(3, 3), Ers::Vec2(2, 2), 0.0f, green); // 2D green square with center (3, 3) and size (2, 2)
}
// Somewhere else
Ers::Debugger::Run(modelContainer, Render2D);
In the example below, the optional render2D callback of the Ers.Debugger.Run function is being defined.
This callback is automatically called each frame, allowing you to define what is visualized in the ERS Editor.
There is no need to reset the frame, this is done automatically by the Ers.Debugger.Run function.
For 3D rendering, there is a second callback called render3D.
For UI there is a third callback (see the debugger page).
Color red = Color.FromBytes(255, 0, 0);
Color green = Color.FromFloats(0.0f, 1.0f, 0.0f);
void Render2D(Debugger debugger, Simulator simulator)
{
RenderContext renderContext = debugger.RenderContext;
// Some 2D rendering
renderContext.DrawLine2D(new Vector2(0, 5), new Vector2(5, 5), 1.0f, red); // 2D red line from (0, 5) to (5, 5)
renderContext.DrawRect2D(new Vector2(3, 3), new Vector2(2, 2), 0.0f, green); // 2D green square with center (3, 3) and size (2, 2)
}
// Somewhere else
Debugger.Run(modelContainer, Render2D);
Combining it with simulation objects
To visualize the entities in the simulation, you read the necessary information from the relevant components and call any visualization functions you need.
The example below shows how to use views to iterate over specific entities and visualize them in different ways.
This code would be placed inside the Render2D function we set up earlier.
auto conveyorsView = Ers::Get().GetView<ConveyorComponent, Ers::TransformComponent>();
Ers::Color conveyorsColor = Ers::Color::FromFloats(0.5f, 0.5f, 0.5f, 1.0f);
while (conveyorsView.Next())
{
auto* transform = conveyorsView.GetComponent<Ers::TransformComponent>();
renderContext.DrawRect2D(
transform->GetGlobalPosition().XY,
transform->GetGlobalScale().XY,
transform->GetGlobalRotation().XY,
conveyorColor // All conveyors have the same color
);
}
auto productsView = Ers::Get().GetView<ProductComponent, Ers::TransformComponent>();
while (productsView.Next())
{
auto* product = productsView.GetComponent<ProductComponent>();
auto* transform = productsView.GetComponent<Ers::TransformComponent>();
renderContext.DrawRect2D(
transform->GetGlobalPosition().XY,
transform->GetGlobalScale().XY,
transform->GetGlobalRotation().XY,
product->Color // Use data from component to determine color
);
}
var conveyorsView = SubModel.Get().GetView<ConveyorComponent, TransformComponent>([]);
Color conveyorColor = Color.FromFloats(0.5f, 0.5f, 0.5f, 1.0f);
while (conveyorsView.Next())
{
var transform = conveyorsView.GetComponent<TransformComponent>();
renderContext.DrawRect2D(
transform.Value.GlobalPosition.XY(),
transform.Value.GlobalScale.XY(),
transform.Value.GlobalRotation.XY()
conveyorColor // All conveyors have the same color
);
}
conveyorsView.Dispose();
var productsView = SubModel.Get().GetView<ProductComponent, TransformComponent>([]);
while (productsView.Next())
{
var product = productsView.GetComponent<ProductComponent>();
var transform = productsView.GetComponent<TransformComponent>();
renderContext.DrawRect2D(
transform.Value.GlobalPosition.XY(),
transform.Value.GlobalScale.XY(),
transform.Value.GlobalRotation.XY()
product.Value.Color // Use data from component to determine color
);
}
productsView.Dispose();
A notable distinction between the two views is the usage of the conveyor- and product components.
In the first view, the ConveyorComponent is only used to make sure we only iterate over the conveyors.
In the second view, the ProductComponent is also used to store information relevant to the visualization.
Using the standard render system along with custom rendering
It is possible to use custom rendering along with the standard render system.
This way, it's possible to leave out the RenderComponent for some entities and apply custom rendering,
while still using the RenderComponent on other entities.
void Render2D(Ers::Debugger& debugger, Ers::Simulator& simulator)
{
Ers::SubModel& subModel = Ers::Get();
Ers::RenderContext renderContext = debugger.GetRenderContext();
Ers::RenderSystem::Render2D(subModel, renderContext);
// Custom rendering here
}
void Render3D(Ers::Debugger& debugger, Ers::Simulator& simulator)
{
Ers::SubModel& subModel = Ers::Get();
Ers::RenderContext renderContext = debugger.GetRenderContext();
Ers::RenderSystem::Render3D(subModel, renderContext);
// Custom rendering here
}
// Somewhere else
Ers::Debugger::Run(modelContainer, Render2D, Render3D);
void Render2D(Debugger debugger, Simulator simulator)
{
SubModel subModel = SubModel.Get();
RenderContext renderContext = debugger.RenderContext;
RenderSystem.Render2D(subModel, renderContext);
// Custom rendering here
}
void Render3D(Debugger debugger, Simulator simulator)
{
SubModel subModel = SubModel.Get();
RenderContext renderContext = debugger.RenderContext;
RenderSystem.Render3D(subModel, renderContext);
// Custom rendering here
}
// Somewhere else
Debugger.Run(modelContainer, Render2D, Render3D);