Pular para o conteúdo principal
sessão · 7 setfonte · InteractiveDynamics/GdChronocommit · fde5589método · leitura de códigoobjetivo · conferir a decisão
O molde do professor · conferência cega

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.

O resultado da conferência: a decisão grande — a única que seria cara de desfazer — bate. As diferenças estão todas em como a decisão é executada, e nas três ele está à frente. Uma delas não é refinamento: é o que separa simular um carro de simular um rover.
a decisão conferida · Dois mundos, uma cenarepositório · github.com/InteractiveDynamics/GdChrono
01

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 ponto metodológico: se eu tivesse lido o GDChrono antes de decidir, a coincidência não provaria nada — provaria só que sei copiar. Como as duas decisões foram tomadas separadamente, o encontro é evidência de verdade. É a diferença entre "acho que essa é a arquitetura certa" e "duas pessoas resolvendo o mesmo problema sem se falar chegaram nela".
02

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.

GdChrono/Godot/ChManager.h  ×  gdjolt/src/jolt_vehicle.h
// 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.

03

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:

GdChrono/Godot/ChVisualNode.cpp
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.

Por que isso é capacidade e não estilo: é exatamente por causa deste desenho que o GDChrono consegue hospedar um rover articulado inteiro — dezenas de corpos e juntas — sem uma linha de código específica sobre rovers. O meu alcança um veículo de quatro rodas porque eu escrevi "quatro" no código. O dele não precisa saber o que vai simular.
04

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.

A lição não é "ele resolveu melhor o meu problema". É que, na estrutura dele, o problema não existe. A minha solução é correta e automática, mas administra um risco que poderia simplesmente não estar lá. Vale como regra geral: preferir a estrutura que elimina a classe de erro à ferramenta que a detecta.
05

Diferença 3 · o passo desacoplado do quadro

Como avança o tempoGdChronogdjolt
Passo por quadro0,5 ms (fixo)16,7 ms (o delta do Godot)
Usa o delta recebido?não — é ignoradosim, com clamp
Tempo simulado × relógioanda mais devagaracompanha

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.

É uma escolha, não um detalhe. "Tempo real" e "passo estável" puxam para lados opostos, e cada projeto escolhe onde ficar. A minha escolha foi tempo real porque eu queria dirigir; a dele foi estabilidade porque ele quer medir. Saber que existe a escolha é o que essa leitura entregou.
06

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.

o destino da frente já existe rodando, e o código está aberto

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.

é a frente de integração roda–solo, em código, não em plano

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.

a ideia é transferível para qualquer implementação, inclusive no Jolt
O contraste honesto com o Jolt. O Jolt não tem terramecânica — não há Bekker, não há Janosi, não há modelo de solo. Ele tem 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.
07

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.

O saldo: a decisão cara estava certa e não precisa ser desfeita. O que a leitura entregou foi um mapa do que vem depois — e a confirmação de que o caminho já foi percorrido uma vez dentro do grupo, com o resultado disponível para ler.

Fontes