Esta é a Parte I de nossa série de três partes sobre física de videogame. Para o restante desta série, consulte:
Parte II: Detecção de colisão para objetos sólidos
Parte III: Simulação de corpo rígido restrito
A simulação de física é um campo dentro da ciência da computação que visa reproduzir fenômenos físicos usando um computador. Em geral, essas simulações aplicam métodos numéricos às teorias existentes para obter resultados o mais próximo possível do que observamos no mundo real. Isso nos permite, como desenvolvedores de jogos avançados , para prever e analisar cuidadosamente como algo se comportaria antes de realmente construí-lo, o que quase sempre é mais simples e barato de fazer.
A gama de aplicações de simulações de física é enorme. Os primeiros computadores já estavam sendo usados para realizar simulações de física - por exemplo, para prever o movimento balístico de projéteis nas forças armadas. É também uma ferramenta essencial na engenharia civil e automotiva, iluminando como certas estruturas se comportariam em eventos como um terremoto ou um acidente de carro. E não para por aí. Podemos simular coisas como astrofísica, relatividade e muitas outras coisas insanas que podemos observar entre as maravilhas da natureza.
Simular física em videogames é muito comum, já que a maioria dos jogos são inspirado por coisas que temos no mundo real . Muitos jogos dependem inteiramente da simulação de física para serem divertidos. Isso significa que esses jogos exigem uma simulação estável que não vai quebrar ou ficar mais lenta, e isso geralmente não é trivial de se conseguir.
Em qualquer jogo, apenas alguns efeitos físicos são de interesse. A dinâmica do corpo rígido - o movimento e a interação de objetos sólidos e inflexíveis - é de longe o tipo mais popular de efeito simulado em jogos. Isso porque a maioria dos objetos com os quais interagimos na vida real são bastante rígidos, e simular corpos rígidos é relativamente simples (embora, como veremos, isso não significa que seja moleza). Alguns outros jogos requerem a simulação de entidades mais complicadas, como corpos deformáveis, fluidos, objetos magnéticos, etc.
Nesta série de tutoriais de física de videogame, a simulação de corpo rígido será explorada, começando com o movimento de corpo rígido simples neste artigo e, em seguida, cobrindo as interações entre corpos por meio de colisões e restrições nos próximos capítulos. As equações mais comuns usadas em motores de física de jogos modernos, como Box2D , Bullet Physics e Chipmunk Physics será apresentado e explicado.
Na física dos videogames, queremos animar objetos na tela e dar-lhes um comportamento físico realista. Isso é alcançado com a animação procedural baseada na física, que é a animação produzida por cálculos numéricos aplicados às leis teóricas da física.
As animações são produzidas exibindo uma sequência de imagens em sucessão, com os objetos movendo-se ligeiramente de uma imagem para a outra. Quando as imagens são exibidas em rápida sucessão, o efeito é um movimento aparentemente suave e contínuo dos objetos. Assim, para animar os objetos em uma simulação de física, precisamos atualizar o estado físico dos objetos (por exemplo, posição e orientação), de acordo com as leis da física várias vezes por segundo, e redesenhar a tela após cada atualização.
o motor de física é o componente de software que realiza a simulação física. Ele recebe uma especificação dos corpos que vão ser simulados, além de alguns parâmetros de configuração, e então a simulação pode ser pisou . Cada etapa avança a simulação em algumas frações de segundo e os resultados podem ser exibidos na tela posteriormente. Observe que os motores de física executam apenas a simulação numérica. O que é feito com os resultados pode depender dos requisitos do jogo. Nem sempre o resultado de cada etapa deseja ser desenhado na tela.
O movimento de corpos rígidos pode ser modelado usando mecânica newtoniana , que se baseia na famosa obra de Isaac Newton Três Leis do Movimento :
Inércia : Se nenhuma força for aplicada em um objeto, sua velocidade (velocidade e direção do movimento) não deve mudar.
Força, massa e aceleração : A força que atua sobre um objeto é igual à massa do objeto multiplicada por sua aceleração (taxa de variação da velocidade). Isso é dado pela fórmula F = mãe .
Ação e Reação : “Para cada ação há uma reação igual e oposta.” Em outras palavras, sempre que um corpo exerce uma força sobre outro, o segundo corpo exerce uma força de mesma magnitude e direção oposta no primeiro.
Com base nessas três leis, podemos fazer um motor de física que é capaz de reproduzir o comportamento dinâmico com o qual estamos tão familiarizados e, assim, criar uma experiência imersiva para o jogador.
O diagrama a seguir mostra uma visão geral de alto nível do procedimento geral de um motor de física:
Para entender como as simulações de física funcionam, é fundamental ter um conhecimento básico de vetores e suas operações. Se você já está familiarizado com a matemática vetorial, continue lendo. Mas se você não estiver, ou quiser se atualizar, reserve um minuto para revisar o apêndice no final deste artigo.
Um bom trampolim para entender a simulação de corpo rígido é começar com as partículas. Simular partículas é mais simples do que simular corpos rígidos, e podemos simular o último usando os mesmos princípios, mas adicionando volume e forma às partículas.
Uma partícula é apenas um ponto no espaço que possui um vetor posição, um vetor velocidade e uma massa. De acordo com a Primeira Lei de Newton, sua velocidade só mudará quando uma força for aplicada sobre ela. Quando seu vetor de velocidade tem um comprimento diferente de zero, sua posição mudará com o tempo.
Para simular um sistema de partículas, precisamos primeiro criar uma matriz de partículas com um estado inicial. Cada partícula deve ter uma massa fixa, uma posição inicial no espaço e uma velocidade inicial. Em seguida, temos que iniciar o loop principal da simulação onde, para cada partícula, temos que calcular a força que está atuando sobre ela, atualizar sua velocidade a partir da aceleração produzida pela força e, em seguida, atualizar sua posição com base na velocidade acabamos de calcular.
A força pode vir de diferentes fontes dependendo do tipo de simulação. Pode ser gravidade, vento ou magnetismo, entre outros - ou pode ser uma combinação dos dois. Pode ser uma força global, como gravidade constante, ou pode ser uma força entre partículas, como atração ou repulsão.
Para fazer a simulação funcionar em um ritmo realista, o intervalo de tempo que “simulamos” deve ser o mesmo que a quantidade real de tempo que passou desde a última etapa da simulação. No entanto, esse intervalo de tempo pode ser aumentado para fazer a simulação rodar mais rápido ou reduzido para rodar em câmera lenta.
Suponha que temos uma partícula com massa m posição p ( t Eu)e velocidade v ( t Eu)em um instante de tempo t Eu. Uma força f ( t Eu)é aplicado naquela partícula naquele momento. A posição e velocidade desta partícula em um momento futuro t i + 1, p ( t i + 1)e v ( t i + 1)respectivamente, pode ser calculado com:
Em termos técnicos, o que estamos fazendo aqui é integrar numericamente uma equação diferencial comum de movimento de uma partícula usando o método de Euler semi-implícito, que é o método que a maioria dos motores de física de jogo usa devido à sua simplicidade e precisão aceitável para pequenos valores de o intervalo de tempo, DT . o Noções básicas de equação diferencial notas de aula do incrível Modelagem Fisicamente Baseada: Princípios e Prática claro, pelos drs. Andy Witkin e David Baraff, é um bom artigo para se ter uma visão mais aprofundada da metodologia numérica deste método.
A seguir, um exemplo de simulação de partículas na linguagem C.
#define NUM_PARTICLES 1 // Two dimensional vector. typedef struct { float x; float y; } Vector2; // Two dimensional particle. typedef struct { Vector2 position; Vector2 velocity; float mass; } Particle; // Global array of particles. Particle particles[NUM_PARTICLES]; // Prints all particles' position to the output. We could instead draw them on screen // in a more interesting application. void PrintParticles() { for (int i = 0; i position.x, particle->position.y); } } // Initializes all particles with random positions, zero velocities and 1kg mass. void InitializeParticles() { for (int i = 0; i mass * -9.81}; } void RunSimulation() { float totalSimulationTime = 10; // The simulation will run for 10 seconds. float currentTime = 0; // This accumulates the time that has passed. float dt = 1; // Each step will take one second. InitializeParticles(); PrintParticles(); while (currentTime mass}; particle->velocity.x += acceleration.x * dt; particle->velocity.y += acceleration.y * dt; particle->position.x += particle->velocity.x * dt; particle->position.y += particle->velocity.y * dt; } PrintParticles(); currentTime += dt; } }
Se você chamar o RunSimulation
função (para uma única partícula), ele imprimirá algo como:
particle[0] (-8.00, 57.00) particle[0] (-8.00, 47.19) particle[0] (-8.00, 27.57) particle[0] (-8.00, -1.86) particle[0] (-8.00, -41.10) particle[0] (-8.00, -90.15) particle[0] (-8.00, -149.01) particle[0] (-8.00, -217.68) particle[0] (-8.00, -296.16) particle[0] (-8.00, -384.45) particle[0] (-8.00, -482.55)
Como você pode ver, a partícula começou em (-8, 57)
posição e, em seguida, sua y
a coordenada começou a cair cada vez mais rápido, porque estava acelerando para baixo sob a força da gravidade.
A animação a seguir oferece uma representação visual de uma sequência de três etapas de uma única partícula sendo simulada:
Inicialmente, em t = 0, a partícula está em p 0. Depois de um passo, ele se move na direção de seu vetor de velocidade v 0estava apontando. Na próxima etapa, uma força f 1é aplicado a ele, e o vetor velocidade começa a mudar como se estivesse sendo puxado na direção do vetor força. Nas próximas duas etapas, o vetor força muda de direção, mas continua puxando a partícula para cima.
PARA corpo rígido é um sólido que não pode deformar. Esses sólidos não existem no mundo real - mesmo os materiais mais duros deformam pelo menos uma quantidade muito pequena quando alguma força é aplicada a eles - mas o corpo rígido é um modelo útil de física para desenvolvedores de jogos que simplifica o estudo da dinâmica de sólidos onde podemos negligenciar as deformações.
Um corpo rígido é como uma extensão de uma partícula porque também tem massa, posição e velocidade. Além disso, ele tem volume e forma, e por isso pode girar. Isso adiciona mais complexidade do que parece, especialmente em três dimensões.
Um corpo rígido gira naturalmente em torno de seu Centro de massa , e a posição de um corpo rígido é considerada a posição de seu centro de massa. Definimos o estado inicial do corpo rígido com seu centro de massa na origem e um ângulo de rotação zero. Sua posição e rotação em qualquer instante de tempo t vai ser um deslocamento do estado inicial.
O centro de massa é o ponto médio da distribuição de massa de um corpo. Se você imaginar que um corpo rígido com massa M é feito de N partículas minúsculas, cada uma com massa m Eue localização r Eudentro do corpo, o centro de massa pode ser calculado como:
Esta fórmula mostra que o centro de massa é a média das posições das partículas ponderadas por sua massa. Se a densidade do corpo for uniforme, o centro de massa é o mesmo que o centro geométrico da forma do corpo, também conhecido como centróide . Os motores de física do jogo geralmente suportam apenas densidade uniforme, então o centro geométrico pode ser usado como o centro de massa.
Corpos rígidos não são feitos de um número finito de partículas discretas, porém, eles são contínuo . Por esse motivo, devemos calculá-lo usando um integrante em vez de uma soma finita, como esta:
Onde r é o vetor posição de cada ponto, e(rho) é uma função que fornece a densidade em cada ponto do corpo. Essencialmente, essa integral faz a mesma coisa que a soma finita, porém faz isso em um volume contínuo V .
Uma vez que um corpo rígido pode girar, temos que apresentar seu angular propriedades, que são análogas às propriedades lineares de uma partícula. Em duas dimensões, um corpo rígido só pode girar em torno do eixo que aponta para fora da tela, portanto, precisamos apenas de um escalar para representar sua orientação. Normalmente usamos radianos (que vão de 0 a 2Pipara um círculo completo) como uma unidade aqui em vez de ângulos (que vão de 0 a 360 para um círculo completo), pois isso simplifica os cálculos.
Para girar, um corpo rígido precisa de alguns velocidade angular , que é um escalar com unidade radianos por segundo e que é comumente representado pela letra grega ω (ómega). No entanto, para ganhar velocidade angular, o corpo precisa receber alguma força rotacional, que chamamos torque , representado pela letra grega τ (tau). Assim, a Segunda Lei de Newton aplicada à rotação é:
Onde uma (alfa) é a aceleração angular e Eu é o momento de inércia .
Para rotações, o momento de inércia é análogo à massa para movimento linear. Ele define o quão difícil é mudar a velocidade angular de um corpo rígido. Em duas dimensões é um escalar e é definido como:
Onde V significa que esta integral deve ser realizada para todos os pontos ao longo do volume corporal, r é o vetor de posição de cada ponto em relação ao eixo de rotação, r 2é na verdade o produto escalar de r consigo mesmo, eé uma função que dá a densidade em cada ponto do corpo.
Por exemplo, o momento de inércia de uma caixa bidimensional com massa m , largura dentro e altura h sobre seu centróide é:
melhor linha de comando para windows
Aqui você pode encontrar uma lista de fórmulas para calcular o momento de inércia para um monte de formas sobre eixos diferentes.
Quando uma força é aplicada a um ponto em um corpo rígido, pode produzir torque. Em duas dimensões, o torque é escalar e o torque τ gerado por uma força f aplicado em um ponto do corpo que tem um vetor de deslocamento r do centro de massa é:
Onde θ (theta) é o menor ângulo entre f e r .
A fórmula anterior é exatamente a fórmula para o comprimento do produto vetorial entre r e f . Assim, em três dimensões podemos fazer isso:
Uma simulação bidimensional pode ser vista como uma simulação tridimensional em que todos os corpos rígidos são finos e planos, e tudo acontece no xy -plane, o que significa que não há movimento no com -eixo. Isso significa que f e r estão sempre no xy avião e então τ sempre terá zero x e Y componentes porque o produto vetorial será sempre perpendicular ao xy -avião. Isso, por sua vez, significa que sempre será paralelo ao com -eixo. Assim, apenas o com componente dos produtos cruzados importa. O que isso leva a que o cálculo do torque em duas dimensões pode ser simplificado para:
É incrível como adicionar apenas mais uma dimensão à simulação torna as coisas consideravelmente mais complicadas. Em três dimensões, a orientação de um corpo rígido deve ser representada por um quaternion , que é uma espécie de vetor de quatro elementos. O momento de inércia é representado por uma matriz 3x3, chamada de tensor de inércia , que não é constante, pois depende da orientação do corpo rígido e, portanto, varia ao longo do tempo à medida que o corpo gira. Para aprender todos os detalhes sobre a simulação de corpo rígido 3D, você pode conferir o excelente Simulação de corpo rígido I - Dinâmica de corpo rígido sem restrição , que também faz parte do Witkin and Baraff’s Modelagem Fisicamente Baseada: Princípios e Prática curso.
O algoritmo de simulação é muito semelhante ao da simulação de partículas. Só precisamos adicionar a forma e as propriedades rotacionais dos corpos rígidos:
#define NUM_RIGID_BODIES 1 // 2D box shape. Physics engines usually have a couple different classes of shapes // such as circles, spheres (3D), cylinders, capsules, polygons, polyhedrons (3D)... typedef struct { float width; float height; float mass; float momentOfInertia; } BoxShape; // Calculates the inertia of a box shape and stores it in the momentOfInertia variable. void CalculateBoxInertia(BoxShape *boxShape) { float m = boxShape->mass; float w = boxShape->width; float h = boxShape->height; boxShape->momentOfInertia = m * (w * w + h * h) / 12; } // Two dimensional rigid body typedef struct { Vector2 position; Vector2 linearVelocity; float angle; float angularVelocity; Vector2 force; float torque; BoxShape shape; } RigidBody; // Global array of rigid bodies. RigidBody rigidBodies[NUM_RIGID_BODIES]; // Prints the position and angle of each body on the output. // We could instead draw them on screen. void PrintRigidBodies() { for (int i = 0; i position.x, rigidBody->position.y, rigidBody->angle); } } // Initializes rigid bodies with random positions and angles and zero linear and angular velocities. // They're all initialized with a box shape of random dimensions. void InitializeRigidBodies() { for (int i = 0; i position = (Vector2){arc4random_uniform(50), arc4random_uniform(50)}; rigidBody->angle = arc4random_uniform(360)/360.f * M_PI * 2; rigidBody->linearVelocity = (Vector2){0, 0}; rigidBody->angularVelocity = 0; BoxShape shape; shape.mass = 10; shape.width = 1 + arc4random_uniform(2); shape.height = 1 + arc4random_uniform(2); CalculateBoxInertia(&shape); rigidBody->shape = shape; } } // Applies a force at a point in the body, inducing some torque. void ComputeForceAndTorque(RigidBody *rigidBody) { Vector2 f = (Vector2){0, 100}; rigidBody->force = f; // r is the 'arm vector' that goes from the center of mass to the point of force application Vector2 r = (Vector2){rigidBody->shape.width / 2, rigidBody->shape.height / 2}; rigidBody->torque = r.x * f.y - r.y * f.x; } void RunRigidBodySimulation() { float totalSimulationTime = 10; // The simulation will run for 10 seconds. float currentTime = 0; // This accumulates the time that has passed. float dt = 1; // Each step will take one second. InitializeRigidBodies(); PrintRigidBodies(); while (currentTime shape.mass, rigidBody->force.y / rigidBody->shape.mass}; rigidBody->linearVelocity.x += linearAcceleration.x * dt; rigidBody->linearVelocity.y += linearAcceleration.y * dt; rigidBody->position.x += rigidBody->linearVelocity.x * dt; rigidBody->position.y += rigidBody->linearVelocity.y * dt; float angularAcceleration = rigidBody->torque / rigidBody->shape.momentOfInertia; rigidBody->angularVelocity += angularAcceleration * dt; rigidBody->angle += rigidBody->angularVelocity * dt; } PrintRigidBodies(); currentTime += dt; } }
Ligando RunRigidBodySimulation
para um único corpo rígido emite sua posição e ângulo, como este:
body[0] p = (36.00, 12.00), a = 0.28 body[0] p = (36.00, 22.00), a = 15.28 body[0] p = (36.00, 42.00), a = 45.28 body[0] p = (36.00, 72.00), a = 90.28 body[0] p = (36.00, 112.00), a = 150.28 body[0] p = (36.00, 162.00), a = 225.28 body[0] p = (36.00, 222.00), a = 315.28 body[0] p = (36.00, 292.00), a = 420.28 body[0] p = (36.00, 372.00), a = 540.28 body[0] p = (36.00, 462.00), a = 675.28 body[0] p = (36.00, 562.00), a = 825.28
Uma vez que uma força ascendente de 100 Newtons está sendo aplicada, apenas oYcoordenar mudanças. A força não está sendo aplicada diretamente no centro de massa e, portanto, induz torque, fazendo com que o ângulo de rotação aumente (rotação no sentido anti-horário).
O resultado gráfico é semelhante à animação de partículas, mas agora temos formas se movendo e girando.
Isto é um irrestrito simulação, porque os corpos podem se mover livremente e não interagem entre si - eles não colidem. Simulações sem colisões são muito chatas e não muito úteis. No próximo capítulo desta série, vamos falar sobre detecção de colisão e como resolver uma colisão depois de detectada.
PARA vetor é uma entidade que tem um comprimento (ou, mais formalmente, um magnitude ) e um direção . Pode ser intuitivamente representado no Sistema de coordenada cartesiana , onde o decompomos em dois componentes, x e Y , em um espaço bidimensional, ou três componentes, x , Y , e com , no espaço tridimensional.
Representamos um vetor com um caractere minúsculo em negrito e seus componentes por um caractere regular idêntico ao subscrito do componente. Por exemplo:
representa um vetor bidimensional. Cada componente é a distância da origem no eixo de coordenadas correspondente.
Os motores de física geralmente têm suas próprias bibliotecas matemáticas leves e altamente otimizadas. o Bullet Physics motor, por exemplo, tem uma biblioteca de matemática desacoplada chamada Matemática Linear que pode ser usado sozinho, sem nenhum dos recursos de simulação de física do Bullet. Possui classes que representam entidades, como vetores e matrizes, e aproveita SIMD instruções, se disponíveis.
Algumas das operações algébricas fundamentais que operam em vetores são revisadas abaixo.
O comprimento, ou magnitude, do operador é representado por|| ||. O comprimento de um vetor pode ser calculado a partir de seus componentes usando o famoso teorema de Pitágoras para triângulos retângulos. Por exemplo, em duas dimensões:
Quando um vetor é negado, o comprimento permanece o mesmo, mas a direção muda para exatamente o oposto. Por exemplo, dado:
a negação é:
Os vetores podem ser adicionados ou subtraídos uns dos outros. Subtrair dois vetores é o mesmo que adicionar um à negação do outro. Nessas operações, simplesmente adicionamos ou subtraímos cada componente:
O vetor resultante pode ser visualizado apontando para o mesmo ponto que os dois vetores originais fariam se estivessem conectados de ponta a ponta:
Quando um vetor é multiplicado por um escalar (para os fins deste tutorial, um escalar é simplesmente qualquer número real), o comprimento do vetor muda nessa quantidade. Se o escalar for negativo, ele também fará com que o vetor aponte na direção oposta.
o produto escalar operador pega dois vetores e produz um número escalar. É definido como:
Ondeilhaé o ângulo entre os dois vetores. Computacionalmente, é muito mais simples computá-lo usando os componentes dos vetores, ou seja:
a maioria dos bancos de dados back-end são capazes de interpretar comandos html.
O valor do produto escalar é equivalente ao comprimento do projeção do vetor para no vetor b , multiplicado pelo comprimento de b . Esta ação também pode ser invertida, medindo o comprimento da projeção de b para para , e multiplicando pelo comprimento de para , produzindo o mesmo resultado. Isso significa que o produto escalar é comutativo - o produto escalar de para com b é o mesmo que o produto escalar de b com para . Uma propriedade útil do produto escalar é que seu valor é zero se os vetores forem ortogonais (o ângulo entre eles é de 90 graus), porquecos 90 = 0.
Em três dimensões, também podemos multiplicar dois vetores para gerar outro vetor usando o produto cruzado operador. É definido como:
e o comprimento do produto vetorial é:
Ondeilhaé o menor ângulo entre para e b . O resultado é um vetor c que é ortogonal (perpendicular) a ambos para e b . E se para e b são paralelos, ou seja, o ângulo entre eles é zero ou 180 graus, c vai ser o vetor zero, porquesin 0 = sin 180 = 0.
Ao contrário do produto escalar, o produto vetorial não é comutativo. A ordem dos elementos no produto vetorial é importante porque:
A direção do resultado pode ser determinada pelo regra da mão direita . Abra sua mão direita e aponte o dedo indicador na mesma direção de para e orientar sua mão de forma que, ao fechar o punho, seus dedos se movam diretamente para b através do menor ângulo entre esses vetores. Agora seu polegar está apontando na direção de para × b .