Duas pontes, um desenho.
O professor já tinha atravessado esta mesma ponte antes de mim — com outro rio. O GDChrono é uma GDExtension que liga o Godot ao Project Chrono, uma biblioteca de física de engenharia. Eu construí a minha ligação com o Jolt sem ter visto o código dele, e só depois abri o repositório para conferir.
Por que a coincidência vale como prova
Quem quer usar uma biblioteca de física de fora dentro do Godot encara uma bifurcação logo no primeiro dia. Ela não tem resposta óbvia, e errar custa reescrever tudo.
Caminho B · pegar carona
Reaproveitar o mundo de física que o Godot já mantém e apenas acrescentar nele a peça que falta. É o caminho econômico: não duplica nada, herda tudo que o motor já resolve — colisão da cena, corpos, camadas.
É também o que a maioria tentaria primeiro, e por isso vale registrar que nenhum de nós dois foi por aqui.
Caminho A · trazer o seu mundo
A extensão compila a biblioteca como dependência própria e cria o seu próprio mundo de física, paralelo ao do motor. O Godot fica com desenho e entrada.
Custa manter dois mundos em sincronia e assumir a biblioteca como dependência de build. Em troca, nada fica fora de alcance.
O mesmo esqueleto, lado a lado
Não é "parecido". A classe central das duas extensões tem a mesma forma: um nó do Godot que carrega dentro de si um mundo de física próprio, monta esse mundo no _ready e o avança no _physics_process.
// o dele // o meu
class ChManager class JoltVehicle
: public Node3D { : public Node3D {
void _ready(); void _ready();
void _physics_process(d); void _physics_process(d);
Ch::World world; unique_ptr<PhysicsSystem> sys;
// ↑ ChSystemSMC dele // ↑ PhysicsSystem meu
}; };A classe Ch::World dele (em Chrono/ChWorld.h) guarda um chrono::ChSystemSMC como membro — o mundo de física é dele, não do motor. O ChManager chama world.Init() no _ready e world.Update(delta) no _physics_process. Trocando os nomes, é o meu arquivo.
Os arquivos que registram a extensão no motor — Godot/Register.cpp no dele, src/register_types.cpp no meu — são quase linha a linha o mesmo arquivo: GDREGISTER_CLASS dentro de um InitObject, com MODULE_INITIALIZATION_LEVEL_SCENE. Isso é menos surpreendente (é o padrão da documentação do Godot), mas confirma que estamos usando o mecanismo do mesmo jeito.
Diferença 1 · a cena nasce da física
Esta é a diferença que importa de verdade — as outras duas são engenharia, esta é capacidade.
O meu · fantoches montados à mão
O JoltVehicle procura filhos chamados Wheel0 a Wheel3 e escreve a transform deles a cada passo. A cena é montada no editor, por mim, antes de rodar.
O código sabe de cor que são quatro rodas. Um rover de seis exigiria mexer no C++.
O dele · fantoches construídos pelo código
O CreateMeshes percorre o ChAssembly recursivamente — corpos, links, malhas, e sub-assemblies dentro de sub-assemblies — e cria um nó para cada item que encontrar, carregando o arquivo OBJ de cada um.
A cena não é montada antes: ela é descoberta quando o mundo de física é ligado.
E há um segundo movimento, ainda mais elegante. Cada nó criado é um ChVisualNode, que guarda um weak_ptr para o seu próprio item de física e se sincroniza sozinho no próprio _physics_process:
void ChVisualNode::_physics_process(double delta) {
if (physicsItem.expired()) return;
auto item = physicsItem.lock();
auto frame = item->GetVisualModelFrame();
set_position(ToGodotVec(frame.GetPos()));
set_basis(FrameRotToBasis(frame));
}A sincronia é distribuída: cada nó puxa a sua própria posição. No meu, ela é centralizada — um laço em push_transforms() que precisa conhecer a cena inteira. O dele não precisa conhecer nada; e o weak_ptr ainda faz o nó se desligar sozinho se o corpo de física deixar de existir.
Diferença 2 · um sistema de build só
A entrada anterior descreve o erro que me custou mais tempo na semana: a paridade de ABI. A biblioteca e o meu código precisam ser compilados com o mesmo conjunto de opções, porque essas opções mudam o tamanho das estruturas por dentro. Divergiu, quebra.
Minha solução foi ler o compile_commands.json que o CMake deixa ao compilar a lib e espelhar os flags de lá. Funciona, e é automática. Mas ela só existe porque eu tenho dois sistemas de build: CMake para o Jolt, SCons para a extensão.
O CMakeLists.txt dele traz o godot-cpp para dentro do mesmo projeto — add_subdirectory(ThirdParty/godot-cpp SYSTEM) — junto com o Chrono, o fmt e o tinyobjloader. Com um projeto só, as opções propagam pelos alvos e a divergência não tem como acontecer.
Diferença 3 · o passo desacoplado do quadro
| Como avança o tempo | GdChrono | gdjolt |
|---|---|---|
| Passo por quadro | 0,5 ms (fixo) | 16,7 ms (o delta do Godot) |
Usa o delta recebido? | não — é ignorado | sim, com clamp |
| Tempo simulado × relógio | anda mais devagar | acompanha |
O Ch::World::Update(double dt) dele recebe o dt do Godot e não o usa: chama sys.DoStepDynamics(5e-4), um passo fixo de meio milissegundo, e depois viper->Update(). São 33 vezes menos tempo simulado por quadro do que no meu.
Faz sentido para o problema dele: solo deformável exige passo curto para o cálculo convergir. O preço é que a simulação roda em câmera lenta em relação ao relógio — o que é irrelevante se o objetivo é estudar o comportamento, e fatal se o objetivo é dirigir em tempo real.
O achado que não estava no plano
Eu abri o repositório para conferir arquitetura. O que encontrei dentro do ChWorld.cpp vale mais do que a conferência.
Um rover articulado de verdade
O mundo dele monta um Viper — o rover lunar dos modelos prontos do Chrono, com chassi, braços, juntas e quatro rodas acionadas por ViperDCMotorControl. Não é um corpo só com molas: é um sistema multicorpo, exatamente o que a frente inteira persegue.
Solo deformável com terramecânica
Sobre SCMTerrain, com parâmetros de Bekker e Janosi preenchidos: rigidez do solo, limite de coesão, ângulo de atrito interno, coeficiente de cisalhamento. Com bulldozing ligado — o material deslocado se acumula na borda do sulco.
E domínios ativos por roda
O terreno só é recalculado numa caixa ao redor de cada roda (AddActiveDomain), do tamanho do pneu. É o truque que torna solo deformável viável em tempo de execução: não se recalcula o mapa inteiro, só onde alguém está pisando.
HeightFieldShape::SetHeights(), que permite deformar a geometria do terreno em tempo real. São coisas diferentes: dá para abrir o sulco onde a roda passou, mas a força que o solo devolve — resistência ao afundamento, cisalhamento — teria que ser escrita à mão. As duas frentes de roda–solo e validação passam por essa distinção.O que eu levo desta leitura
Adotar a construção da cena a partir da física
Trocar os nós fixos Wheel0..3 por descoberta em tempo de execução, com cada nó se sincronizando sozinho. É pré-requisito do rover, não melhoria cosmética — enquanto o número de rodas estiver escrito no código, não há rover.
Considerar migrar o build para CMake
Eliminaria a paridade de ABI como classe de problema, e alinharia a estrutura do projeto com a que já existe no grupo.
Tratar o passo como decisão explícita
Medir quanto cálculo cabe em um quadro, e escolher conscientemente entre tempo real e fidelidade — em vez de herdar o passo do Godot por omissão.
Levar a ideia de domínio ativo
Recalcular o terreno só ao redor das rodas é a técnica que torna solo deformável possível. Vale mesmo se a implementação for outra.