Lembrando que articulações sempre possuem as suas informações locais e que as informações globais são facilmente calculáveis, nossa tarefa é descobrir como queremos alterar qualquer uma delas para alcançar nosso objetivo.
No caso, nosso objetivo é fazer com que o vetor da articulação aponte para X.
Na primeira parte da imagem, temos o vetor já apontando em direção do X.
Na segunda parte da imagem, o X se encontra a 90º graus do vetor.
Olhando a imagem nós conseguimos saber que para continuar apontando para X temos que rotacionar por 90º, mas como conseguir este ângulo matemáticamente?
Talvez você já tenha notado mas vamos fazer isto usando trigonometria (se prepare que IK é triângulo para tudo que é lado).
Você provavelmente não terá que pensar em nada disso pois muitas game engines já possuem métodos para lidar com isto, por exemplo em Godot podemos encontrar algo como:
Node2D.get_angle_to(global_position)
Essa função retorna o ângulo global que falta para estar em direção ao ponto global.
Se trata de utilizar o mesmo espaço de memória para armazenar um entre diversos tipos.
union Content { int i; float f; char c; }; union Content content;
Nesse exemplo eu declarei um union que pode conter um dos seguintes valores: int, float, char.
Então eu posso escrever qualquer um dos tipos nele:
content.i =10; content.f =5.0; content.c ='a';
O valor final de content vai ser a pois foi o último valor que botamos.
Porém a parte importante é justamente o fato de usarmos o mesmo espaço de memória para armazenar qualquer um destes tipos.
content.c ='a'; printf("%c\n", content.c);// print "a" printf("%c\n", content.i);// print "a"
Quando você declara uma union, o tamanho dela é definido pelo maior tamanho entre os tipos que ela precisa conseguir armazenar.
Tipo
Tamanho
int
4 bytes
float
4 bytes
char
1 byte
No nosso caso o melhor tamanho seria 4 bytes, pois com ele você consegue armazenar o char também.
Nada impede de armazenarmos um char e tentarmos ler aquele espaço da memória como um int.
content.c ='a'; printf("%c\n", content.c);// print "a" printf("%c\n", content.i);// print "a" // O character 'a' é nada mais que o número 97 na memória. printf("%i\n", content.c);// print "97" printf("%i\n", content.i);// print "97"
Resta a nós utilizar corretamente o valor daquele espaço de memória.
Afinal não queremos armazenar um char na union e mais tarde no código tentar utilizar como um int, né?
Em outras lugares enum ligaria apenas identificadores a números, porém em Rust você pode armazenar estrutura/tipos juntos aos enums.
#[derive(Debug)] enumType{ Integer(i32), Floating(f32), Character(char), } let content:Type=Type::Floating(5.0); println!("{:#?}", content);
Isso nos da uma estrutura só que possue a capacidade de fazer o mesmo que tagged union e com menos chance do desenvolvedor cometer um erro.
Por exemplo, não precisamos mais atualizar o tipo armazenado na variável toda vez que alteramos:
// C content.f = 5.0; type = Floating; // Rust let content: Type = Type::Floating(5.0);
Rust é linguagem que preza bastante segurança, então faria sentido tratar como se fosse uma única estrutura para evitar os problemas de union (embora Rust tenha o tipo union).
union content { int i; float f; char c; } content; enumTypes{ Integer, Floating, Character, } type; content.f =5.0; type = Floating; if(type == Floating){ // Fazer algo com float }elseif(type == Character){ // Fazer algo com char }else{ // Fazer algo com int }
#[derive(Debug)] enumType{ Integer(i32), Floating(f32), Character(char), } let content:Type=Type::Floating(5.0); match content { Type::Floating(f)=>// Fazer algo com float Type::Character(c)=>// Fazer algo com char Type::Integer(i)=>// Fazer algo com int }
Minha kinematic favorita por ser a mais simples de calcular, onde tudo se resume a um grande somatório.
Adicionei setas nessa imagem para represetarem as rotações locais das joints (articulações), essa rotação se refere ao quanto aquela joint (articulação) se rotacionou.
Agora adicionei uma circuferência mostrando os ângulos clássicos e podemos usar eles como referência para a rotação global, essa rotação é sempre relacionada à direita (eixo X).
Para começar nosso experimento, podemos rotacionar o end effector (mão) por 90 graus e ver o que podemos concluir.
End effector
Rotação local: 90º
Rotação global: 90º
Joint 2
Rotação local: 0º
Rotação global: 0º
Joint 1
Rotação local: 0º
Rotação global: 0º
Podemos ver que rotacionar um ponto localmente não afeta os anteriores (os pais). Também importante notar que rotação local sempre irá afetar a rotação global daquele ponto.
Mas se tivessemos rotacionado a joint 2 por 90 graus?
End effector
Rotação local: 0º
Rotação global: 90º
Joint 2
Rotação local: 90º
Rotação global: 90º
Joint 1
Rotação local: 0º
Rotação global: 0º
Notamos que rotacionar localmente um ponto, afeta a rotação global dos pontos seguintes pela mesma quantidade.
Se rotacionarmos localmente o end effector por -90 graus?
End effector
Rotação local: -90º
Rotação global: 0º
Joint 2
Rotação local: 90º
Rotação global: 90º
Joint 1
Rotação local: 0º
Rotação global: 0º
Nós conseguimos voltar a rotação global do end effector para 0º pois rotacionar localmente sempre afeta o global do mesmo ponto.
Nesse pequeno experimento já podemos começar a notar uma formula bem simples: Rotação global = Rotação global do ponto anterior + Rotação local
Utilizando o end effector como referência: Rotação global = 90º + (-90º) = 0º
info
No caso do primeiro ponto (joint 1), a rotação global anterior seria 0 graus. Rotação global = 0 + Rotação local
O que aconteceria se rotacionarmos a joint 1 por 90 graus?
End effector
Rotação local: -90º
Rotação global: 90º
Joint 2
Rotação local: 90º
Rotação global: 180º
Joint 1
Rotação local: 90º
Rotação global: 90º
Podemos ver que afetamos a rotação global de todos os pontos seguintes, aumentando eles por 90 graus.
Lembra quando falei que forward kinematics "foca em descobrir o estado da mão, dado que o braço está em certo estado". É exatamente esse somatório que nos ajuda a resolver o problema.
Vamos supor que temos 5 pontos (ponto 1 é a base):
Ponto 5
Rotação local: -90º
Rotação global: ?
Ponto 4
Rotação local: 0º
Rotação global: ?
Ponto 3
Rotação local: 180º
Rotação global: ?
Ponto 2
Rotação local: 0º
Rotação global: ?
Ponto 1
Rotação local: 90º
Rotação global: ?
E que nós queremos descobrir para onde cada ponto está apontando.
Se a gente começar da base, podemos ir calculando a rotação global de cada ponto usando a formula: Rotação global = Rotação global do ponto anterior + Rotação local
Rotação global do ponto 1: 0º + 90º = 90º
Rotação global do ponto 2: 90º + 0º = 90º
Rotação global do ponto 3: 90º + 180º = 270º
Rotação global do ponto 4: 270º + 0º = 270º
Rotação global do ponto 5: 270º + -90º = 180º
note
Isto também deixa bem visível o como a rotação global de um ponto anterior afeta o próximo ponto. Por exemplo:
Somar 90 graus ao ponto 1 faria com que ponto 2 tivesse mais 90 graus...
Ponto 2 com 90 graus a mais faria com que ponto 3 tivesse mais 90 graus...
Ponto 3 com 90 graus a mais faria com que ponto 4 tivesse mais 90 graus...
Se pegarmos um inteiro (unsigned) como 5351, vamos precisar de 2 bytes para armazena-lo (pois seu binário é 00010100 11100111).
Existe duas maneiras famosas de se armazenar eles.
Little-endian: Byte menos significativo primeiro ("little end first"). Big-endian: Byte mais significativo primeiro ("big end first").
Bastou essas duas coisas para eu querer começar este projeto.
Eu não queria fazer a melhor GUI do mundo, eu queria me divertir com Godot ao mesmo tempo que resolvia um incomodo que eu tinha com GUIs.
Abri o Godot, comecei a criar containers, janelas, botões, etc. Dias depois me veio a pergunta:
Como diabos eu vou me comunicar com o Mongo?
Eu vou pegar o texto que o usuário escrever e fazer o que com ele?
Passar para um Node.js?
Eu vou ter que instalar Node.js na máquina da pessoa?
Como eu pego a resposta?
Eu conseguiria usar GDNative?
Como que os outros projetos fazem isto?
Agora eu precisava descobrir como faria isto acontecer.
Robomongo! Também conhecido como Robo 3T.
Um projeto open-source que em 2017 foi adquirido pelos criadores do Studio 3T.
O que importa é que eu tenho um projeto para copiar estudar!
Robo 3T possui dois repositórios:
robomongo
Responsável pela interface gráfica
robomongo-shell
Responsável pela interação com o Mongo.
Importante notar que o segundo é um fork do Mongo oficial, justamente pois nele é incluido um shell interativo para se comunicar com o banco.
Isso faz sentido, já que a interface de linha de comando geralmente é a primeira coisa a ser feita para interagir com bancos. Como mais as pessoas interagiriam com o banco antes de GUI existirem? Poderia ser por código mas seria um baita trabalho cada operação.
Então era isso, tudo que eu tinha que fazer era:
Incluir o shell do Mongo na minha interface gráfica
Eu não tinha ideia de como fazer para incluir o shell na interface gráfica.
Decidi aceitar que passaria como o shell separado para o usuário e dei essa parte como dada.
Eu que não vou reclamar das minhas decisões idiotas no meu projeto pessoal.
Agora tudo que eu tinha que fazer é chamar o shell pelo Godot.
Para isto eu iria precisar usar o método OS.execute().
O problema é que essa função possui dois comportamentos dependendo se blocking for true ou false, porém nenhum dos dois era o que eu buscava.
blocking -> true: Godot vai pausar enquanto a saída não é escrita em output.
Não queremos isto pois uma busca pode resultar em milhares de documentos. Queremos retornar mais documentos conforme o usuário pedir por mais documentos.
blocking -> false: Godot vai continuar executando e o comando irá rodar em um processo separado. Porém não será possível recuperar o output do comando.
Nós queremos receber a saída aos poucos se estivermos falando de pesquisas que resultam em muitos documentos.
Função do Godot não é interativa, como vou usar o shell interativo?
Posso ler input e output usando algum conhecimento do sistema operacional?
Eu vou ter que conhecer bem todos sistemas operacionais?
Vou ter que descobrir como Robo 3T linka o shell?
De todas as maneiras para duas aplicações se comunicarem, eu escolhi a mais simples.
Arquivos... :D
Em vez de quebrar a cabeça para entender como eu poderia reproduzir a interatividade do shell do Mongo, eu poderia apenas fazer com que ambos escrevessem em arquivos quando quisessem se comunicar um com o outro.
O processo em si é bem simples:
Godot executa o shell, passando a query
Shell escreve X documentos em um arquivo
Godot solicita mais documentos
Note que você deve repetir etapa 2 e 3 até acabar os documentos ou Godot mandar parar.
Em outras palavras, eu preciso acoplar o código do usuário ao código do shell.
Note que existem operações que meu shell irá fazer antes e após a execução do código do usuário. Por causa disso, o desenho mostra o código no meio do código shell.
O plano era receber um código simples do usuário, parecido com o do shell do Mongo:
Nas tentativas acima, para obter o resultado desejado eu teria que alterar a string. Porém, isto apresenta grande risco da alteração dar erro pois cada caso pode requer uma alteração diferente.
No final eu seria forçado a ler o código, entende-lo para depois alterar sem grande chance de erros. Mas sabe quem já faz isso de ler e entender código? O compilador.
A compilação de uma linguagem envolve diversas etapas. Uma delas envolve gerar uma árvore sintática abstrata, uma árvore que garante que os objetos estão ligados corretamente.
Podemos usar este conhecimento para compilar parcialmente o código do usuário. Por exemplo, o seguinte código:
for x in db.test.find({}): db.test.update({"_id": x["_id"]},{"code": x["code"]+1}) db.test.find({})
Gera uma árvore com a seguinte raiz:
Note que eu só mostrei o início da árvore pois todo o resto é irrelevante para nós.
Não vamos alterar nada que esteja a fundo do código do usuário, apenas na raiz.
Com esse conhecimento em mão, podemos finalmente alterar o código do usuário!
info
Utilizei um módulo do próprio Python chamado ast para analisar e reestruturar a AST.
Vamos criar um código inútil apenas para usar como exemplo:
# User code doc = db.test.find_one({}) db.test.update_one({},{"valid":True}) db.test.find({})
A idéia é transformar este código em uma função que o shell poderá chamar e receber de volta o valor da última expressão. Em outras palavras, queremos isto:
defcode(): # User code doc = db.test.find_one({}) db.test.update_one({},{"valid":True}) return db.test.find({})
Vamos ao passo a passo de como obter isto:
Análisar o código do usuário com AST
Inserir o conteúdo do módulo dentro de uma função pré montada
Encapsular a última expressão em um return
Apenas fazer isto se for uma expressão que retorna valor
O video seguinte demonstra a transformação que está sendo feita de certa forma.
Em 2019 eu comecei a trabalhar em uma startup que usava banco de dados MongoDB.
Na época mantermos diversas licenças da interface gráfica Studio 3T não era uma boa ideia, não eramos uma empresa lucrativa e o uso dela por pessoa não iria valer a pena.
E por isso comecei a buscar por um interface gráfica que fosse me satisfazer.
Uma alternativa considerada foi Robo 3T/Robomongo mas pelo fato dele ter sido adquirido pelo Studio 3T já não era um bom sinal para mim.
É muito normal ver empresas grandes comprando concorrentes e depois deixando eles aos poucos sumirem, então meu palpite era que Robot 3T não iria receber tantas atualizações e não cresceria como ferramenta.
Hoje em em 2023: Fazendo 6 anos desde que foi adquirido e uma versão de graça do Studio 3T foi lançada para substituir o Robo 3T que foi descontinuado.
Em 2020, QueryAssist foi a ferramenta que eu decidi utilizar sempre para interagir com o Mongo.
Ela possuia uma versão gratuita mas você podia comprar a versão vitalícia e ganhar mais funcionalidades. A versão gratuita era mais que o suficiente para mim e funcionava muito bem.
Infelizmente durou pouco, fui notando a falta de atualização no blog e nos posts do Twitter. Claramente um sinal que a ferramenta iria desaparecer, por causa disso decidi retornar a minha busca por outra interface gráfica antes que está parece de funcionar sem aviso prévio.
Hoje em em 2023: Site deles foi desligado e eles não tem postagens novas desde 2020.
MongoDB Compass parece estar em construção desde 2015, mas na época que eu estava buscando interfaces gráficas era como se ele não existisse para mim. Meu palpite era que não estava divulgando já que não estava pronto para produção 🤔.
Tudo que posso dizer é que quando finalmente descobri a existência dele, a ausência de alguma funcionalidade não me agradou...
Hoje em em 2023: Aposto que hoje em dia já devem ter implementado a funcionalidade que eu queria mas não estou em dia com as ferramentas do Mongo para saber.
Quando MongoDB for VS Code lançou oficialmente para mim era um sinal que minha busca tinha terminado, pois era uma ferramenta desenvolvida pelos mesmos criadores do MongoDB e que era só uma questão de tempo até implementarem diversas funcionalidades legais.
Nesta época eu até parei o desenvolvimento de uma ferramenta que eu estava construindo.
Hoje em em 2023: Parece que a extensão apenas foi criada para fazer o mínimo e não deve evoluir muito.
Na primeira parte nós focamos exclusivamente em tiles que precisavam apenas satisfazer uma relação com os adjacentes. Porém existe casos onde relação com as diagonais também é importante.
Vamos organizar em 3 tipos:
Sides (visto anteriormente)
O foco era casar a adjacente.
Corners (será visto)
O foco vai ser casar duas adjacentes e uma diagonal.
Corners and Sides (será visto)
O foco vai ser resolver corners e sides.
A seguinte imagem desmonstra como cada tipo deve casar:
Tirando isto a lógica principal de Wang tiles permanece, ou seja, não precisamos falar dos mesmos assuntos vistos na primeira parte pois você só precisa adaptar a maneira de casar tiles.
(Wang tiles foi proposto pelo matemático Hao Wang em 1961)
Assuma que teremos um conjunto de tiles onde cada lado está pintado de apenas uma cor. Por exemplo:
Cada lado com uma cor.
Dois lados com a mesma cor.
Todos os lados com a mesma cor.
Dois lados adjacentes com a mesma cor.
Variação das cores já vistas só que em posições diferentes.
note
Por simplicidade mudaremos para duas cores apenas.
A ideia é reutilizar os mesmos tiles quantas vezes quisermos para botar eles lado a lado e formar um plano, porém com as cores laterais do tiles sempre casando. No exemplo seguinte temos 5 tiles e 2 exemplos de planos formados por eles:
Importante notar que:
Tiles não podem se sobrepor.
Tiles não podem ser rotacionados ou refletidos.
Pois não é possível saber se seriam tiles válidos sem conhecer a imagem utilizada neles.
Embora reutilização de tiles para gerar diversos planos/mapas não seja especial, Wang tiles adiciona a lógica de relacionar os tiles entre si. Isto nos permite verificar se um tile é válido numa determinada posição.
Por exemplo, possuindo 2 cores e 4 lados, podemos formar 16 (24) tiles diferentes:
note
Adicionamos um quadrado cinza no centro de cada tile.
Botamos estes tiles na game engine Godot e nela definimos a relação entre os tiles.
Ao começar a pintar tiles dentro da game engine, podemos ver que ela consegue verificar qual tile é válido naquela posição ou se precisa alterar os vizinhos.
Em segundos conseguimos construir planos onde os tiles tem uma conexão entre si.
É importante notar que não há problema criar variações do mesmo tile. Por exemplo:
No momento de preencher por um tile válido naquela posição, a ferramenta iria ter que escolher entre 4 tiles diferentes. Algumas ferramentas como Godot escolheram aleatoriamente:
Na proposta de Wang não se pode rotacionar e refletir tiles pois não existe garantia que a imagem continuará fazendo sentido após rotacionada ou refletida.
Porém como criador dos tiles, somos capazes de deduzir está informação e apenas fazer os tiles necessários. Vamos pegar este conjunto de tiles:
Alguns destes tiles são variações dos anteriores porém rotacionados ou refletidos. Levando isto em conta, podemos minimizar para 6 tiles apenas:
É importante notar que só é possível se conhecermos a imagem. Botando a mesma árvore utilizada anteriormente em um dos tiles, podemos ver o tile perder o sentido quando rotacionado mas não quando refletido:
Todos nossos tiles tem sido com cores, porém as cores apenas servem para representar a relação entre os tiles.
Para deixar isto claro, vamos substituir o desenho dos tiles por desenhos que melhor representem um labirinto. Vamos trocar azul por arbustos e verde por terra:
Utilizando estes tiles com suas rotações/relexões, podemos criar em segundos o nosso labirinto:
note
Este labirinto está com cara de circuitos da placa mãe. 🤔