Este programa minimal em Mujoco C++ carrega uma cena Mujoco a partir de um arquivo XML e a exibe em uma janela com navegação por mouse. Ele usa GLFW para gerenciamento de janela e tratamento de entrada.
Além da visualização, ele também executa um loop de simulação simples, avançando a física em tempo real. Note que a simulação é muito básica para fins deste exemplo, mas possui sincronização em tempo real embutida.
mujoco_scene_viewer.cpp
// minimal_viewer.cpp
// Compilação: veja as notas abaixo
#include <mujoco/mujoco.h>
#include <GLFW/glfw3.h>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <time.h>
// --- Globais mantidos mínimos para os callbacks do GLFW ---
static mjModel* m = nullptr;
static mjData* d = nullptr;
static mjvCamera cam; // câmera abstrata
static mjvOption opt; // opções de visualização
static mjvScene scn; // cena abstrata
static mjrContext con; // contexto GPU personalizado
static bool button_left = false, button_middle = false, button_right = false;
static double lastx = 0, lasty = 0;
// Sincronização em tempo real: registrar tempos de início da simulação e do relógio de parede
static double sim_start_time = 0.0;
static double realtime_start = 0.0;
// --- Callback de erro do GLFW ---
static void glfw_error_callback(int error, const char* description) {
std::fprintf(stderr, "Erro GLFW %d: %s\n", error, description);
}
// --- Botão do mouse: lembrar quais botões estão pressionados ---
static void mouse_button_callback(GLFWwindow* window, int button, int act, int /*mods*/) {
// Tratamento simples e estável do estado dos botões
if (button == GLFW_MOUSE_BUTTON_LEFT) button_left = (act == GLFW_PRESS) ? true : (act == GLFW_RELEASE ? false : button_left);
if (button == GLFW_MOUSE_BUTTON_MIDDLE) button_middle = (act == GLFW_PRESS) ? true : (act == GLFW_RELEASE ? false : button_middle);
if (button == GLFW_MOUSE_BUTTON_RIGHT) button_right = (act == GLFW_PRESS) ? true : (act == GLFW_RELEASE ? false : button_right);
// Registrar a posição atual do cursor (coordenadas da janela)
glfwGetCursorPos(window, &lastx, &lasty);
}
// --- Movimento do mouse: converter movimento em ação da câmera ---
static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) {
if (!m) return;
// Se nenhum botão do mouse estiver pressionado, apenas atualizar a última posição
if (!button_left && !button_right && !button_middle) {
lastx = xpos;
lasty = ypos;
return;
}
// Calcular movimento em coordenadas de janela, normalizar pela altura do framebuffer (estável entre DPIs)
int fbw = 0, fbh = 1;
glfwGetFramebufferSize(window, &fbw, &fbh);
if (fbh <= 0) fbh = 1;
double dx = (xpos - lastx) / (double)fbh;
double dy = (ypos - lasty) / (double)fbh;
lastx = xpos;
lasty = ypos;
if (button_left) {
// Arrastar com botão esquerdo: rotacionar
mjv_moveCamera(m, mjMOUSE_ROTATE_H, dx, dy, &scn, &cam);
} else if (button_right) {
// Arrastar com botão direito: mover horizontalmente
mjv_moveCamera(m, mjMOUSE_MOVE_H, dx, dy, &scn, &cam);
} else if (button_middle) {
// Arrastar com botão do meio: zoom
mjv_moveCamera(m, mjMOUSE_ZOOM, dx, dy, &scn, &cam);
}
}
// --- Zoom com roda do mouse ---
static void scroll_callback(GLFWwindow* window, double /*xoffset*/, double yoffset) {
if (!m) return;
// Rolar para zoom (fator suave)
mjv_moveCamera(m, mjMOUSE_ZOOM, 0, -0.05 * yoffset, &scn, &cam);
}
// --- Tecla: 'R' para reiniciar ---
static void key_callback(GLFWwindow* window, int key, int scancode, int act, int mods) {
if (act == GLFW_PRESS || act == GLFW_REPEAT) {
if (key == GLFW_KEY_R) {
// Reiniciar estado da simulação e temporizadores de tempo real para que a simulação não avance demais
mj_resetData(m, d);
sim_start_time = (d ? d->time : 0.0);
realtime_start = glfwGetTime();
}
if (key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GLFW_TRUE);
}
}
int main(int argc, char** argv) {
// Uso
if (argc < 2) {
std::printf("Uso: %s modelo.xml\n", argv[0]);
return 1;
}
// (MuJoCo open-source >= 2.2 não tem etapa de ativação de licença)
// Carregar modelo
char error[1024] = {0};
m = mj_loadXML(argv[1], nullptr, error, sizeof(error));
if (!m) {
std::fprintf(stderr, "Não foi possível carregar o modelo: %s\n", error);
return 1;
}
d = mj_makeData(m);
// Inicializar GLFW
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
std::fprintf(stderr, "Não foi possível inicializar o GLFW\n");
return 1;
}
// Criar janela/contexto
glfwWindowHint(GLFW_DOUBLEBUFFER, 1);
GLFWwindow* window = glfwCreateWindow(1200, 900, "MuJoCo Minimal Viewer", nullptr, nullptr);
if (!window) {
std::fprintf(stderr, "Não foi possível criar a janela GLFW\n");
glfwTerminate();
return 1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(0); // desativar vsync
// Definir callbacks
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetCursorPosCallback(window, cursor_pos_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetKeyCallback(window, key_callback);
// Inicializar visualização do MuJoCo
mjv_defaultCamera(&cam);
// Definir ponto de foco da câmera para que a visualização inicial seja centralizada no modelo
cam.lookat[0] = 0.0;
cam.lookat[1] = 0.0;
cam.lookat[2] = 0.5;
mjv_defaultOption(&opt);
mjv_defaultScene(&scn);
mjr_defaultContext(&con);
mjv_makeScene(m, &scn, 2000); // alocar cena
mjr_makeContext(m, &con, mjFONTSCALE_100); // alocar contexto GPU
// Inicializar temporizadores de sincronização em tempo real (iniciar ambos no mesmo tempo de relógio de parede)
sim_start_time = d->time;
realtime_start = glfwGetTime();
// Posicionar câmera para mostrar o modelo inteiro
cam.type = mjCAMERA_FREE;
mj_forward(m, d);
// Loop principal
while (!glfwWindowShouldClose(window)) {
// Avançar a simulação (passo simples aproximado em tempo real)
mj_step(m, d);
// --- Sincronização em tempo real: garantir que a simulação não execute mais rápido que o relógio de parede ---
double sim_elapsed = d->time - sim_start_time; // segundos de simulação desde início/reinício
double real_elapsed = glfwGetTime() - realtime_start; // segundos de relógio de parede desde início/reinício
double sleep_time = sim_elapsed - real_elapsed; // positivo => simulação está adiantada
if (sleep_time > 0.0) {
// Dormir pelo tempo necessário (nanosleep é seguro sem flags de linker de threading)
struct timespec req;
req.tv_sec = (time_t)std::floor(sleep_time);
req.tv_nsec = (long)((sleep_time - req.tv_sec) * 1e9);
if (req.tv_sec < 0) req.tv_sec = 0;
if (req.tv_nsec < 0) req.tv_nsec = 0;
nanosleep(&req, nullptr);
}
// Obter tamanho do framebuffer e definir viewport
int width, height;
glfwGetFramebufferSize(window, &width, &height);
mjrRect viewport = {0, 0, width, height};
// Atualizar cena e renderizar
mjv_updateScene(m, d, &opt, nullptr, &cam, mjCAT_ALL, &scn);
mjr_render(viewport, &scn, &con);
// Trocar buffers e processar eventos
glfwSwapBuffers(window);
glfwPollEvents();
}
// Limpeza
mjr_freeContext(&con);
mjv_freeScene(&scn);
mj_deleteData(d);
mj_deleteModel(m);
glfwTerminate();
return 0;
}Como compilar
Salve isto como CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mujocotest)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(PkgConfig REQUIRED)
pkg_search_module(MUJOCO mujoco)
pkg_search_module(GLFW REQUIRED glfw3)
if(NOT MUJOCO_FOUND)
set(MUJOCO_INCLUDE_DIRS /opt/mujoco/include)
set(MUJOCO_LIBRARIES /opt/mujoco/lib/libmujoco.so)
endif()
add_executable(mujocotest main.cpp)
target_include_directories(mujocotest PRIVATE ${MUJOCO_INCLUDE_DIRS} ${GLFW_INCLUDE_DIRS})
target_link_libraries(mujocotest PRIVATE ${MUJOCO_LIBRARIES} ${GLFW_LIBRARIES})e compile assim:
build_mujoco.sh
cmake .
make -j8Como executar
run_mujoco.sh
./mujocotest ~/mujoco_menagerie/franka_fr3/fr3.xml
Related posts
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow
