Biquaternions

Se você abriu este artigo, provavelmente já ouviu falar sobre quaternions e talvez até os use em seus projetos. Mas é hora de subir para um nível mais alto - para os biquaternions.

Este artigo fornece conceitos básicos sobre biquaternizações e operações com eles. Para uma melhor compreensão do trabalho com biquaternions, é mostrado um exemplo claro em Javascript usando o Canvas.

Biquaternion


O biquaternion é um número hipercomplexo, com uma dimensão 8. Nos artigos e literatura em inglês, eles são chamados de "quaternion duplo", e na literatura em russo também há os nomes "quaternion dual" ou "quaternion complexo".

A principal diferença dos quaternions é que o quaternion descreve a orientação do objeto no espaço, e o biquaternion também descreve a posição do objeto no espaço.

Biquaternion pode ser representado como dois quaternions:

 w i d o e t i l d e t e x t b f q = b e g i n b m uma t r i x t e x t b f q 1    t e x t b f q 2 e n d b m uma t r i x , 


 t e x t b f q 1 - a parte real, determina a orientação do objeto no espaço;
 textbfq2 - a parte dupla, determina a posição do objeto no espaço.

O biquaternion também é chamado de quaternion complexo, neste caso é representado como um quaternion, cada componente do qual é um número duplo (a não ser confundido com o complexo). Número duplo A=a1+ epsilona2 onde a1 e a2 São números reais e  epsilon - símbolo de Clifford (complexidade), possuindo a propriedade  epsilon2=0 . Não vamos nos aprofundar na matemática, pois estamos mais interessados ​​na parte aplicada; portanto, consideraremos o biquaternion como dois quaternions.

Interpretação geométrica do biquaternion


Por analogia com o quaternion, com o qual você pode definir a orientação do objeto, o biquaternion também pode definir a posição. I.e. O biquaternion define dois valores ao mesmo tempo - a posição e a orientação do objeto no espaço. Se os considerarmos em dinâmica, o biquaternion define duas quantidades - a velocidade linear do movimento e a velocidade angular de rotação do objeto. A figura abaixo mostra o significado geométrico de biquaternion.



Os desenvolvedores de jogos sabem que, para determinar a posição e a orientação de um objeto no espaço do jogo, são utilizadas matrizes de rotação e de deslocamento e, dependendo da ordem em que você as aplica, o resultado da posição final do objeto é diferente. Para aqueles que estão acostumados a dividir o movimento em operações separadas, aceite a regra para trabalhar com biquaterniões: primeiro movemos o objeto e depois o rotacionamos. De fato, você está descrevendo esses dois movimentos com um único número, embora um hipercomplexo complexo.

Características escalares


Considere as principais características escalares. Aqui é necessário prestar atenção ao fato de que eles retornam não números reais comuns, mas duplos.

1. A norma do biquaternion

 | widetilde textbfq |= | textbfq1 |+ epsilon(q10q20+ textbfqT1 textbfq2)



2. Módulo Biquaternion

| widetilde textbfq|=| textbfq1|+ epsilon fracq10q20+ textbfqT1 textbfq2| textbfq1|



Operações básicas


Considere as operações básicas de trabalho com biquaternions. Como você pode ver, eles são muito semelhantes a operações semelhantes com quaternions.

1. Emparelhamento Biquaternion

 widetilde textbfq= beginbmatrix textbfq1 textbfq2 endbmatrix



2. Adição e subtração de biquaterniões

 widetilde textbfq pm widetilde textbfp= beginbmatrix textbfq1 pm textbfp1 textbfq2 pm textbfp2 endbmatrix



A adição e subtração de biquaternions é comutativa (os termos podem ser trocados).

3. Multiplicação do número real por biquaternion

a widetilde textbfq= widetilde textbfqa= beginbmatrixa textbfq1a textbfq2 endbmatrix



4. Multiplicação Biquaternion

 widetilde textbfq otimes widetilde textbfp= beginbmatrix textbfq1 otimes textbfp1 textbfq1 otimas textbfp2+ textbfq2 otimes textbfp1 endbmatrix


A multiplicação de biquaterniões não é comutativa (com uma mudança na ordem dos fatores, o resultado da multiplicação de biquatiões é diferente).

Esta operação é uma das principais quando se trabalha com biquatérnions e possui um significado físico, ou seja, o resultado da multiplicação do biquaternion é a operação de adicionar as rotações e movimentos lineares de dois biquaternions.

5. Batismo reverso

 widetilde textbfq1= frac widetilde textbfq | widetilde textbfq |



Determinação do biquaternio através de ângulos de orientação e vetor de posição


Primeiro, definimos os sistemas de coordenadas nos quais consideraremos a orientação e a posição do objeto no espaço. Isso deve ser feito para especificar a parte real do biquaternion (quaternion de orientação), cuja sequência de rotação afeta o quaternion resultante dos ângulos de orientação. Aqui seremos guiados por ângulos de avião - guinada  psi arremesso  vartheta e rolar  gama .

Defina o sistema de coordenadas da base. Imagine que você está parado na superfície da Terra e olhando na direção do norte.

Ponto O o - a origem do sistema de coordenadas, localizado no ponto de origem do objeto.
O eixo O o Y g - é direcionado verticalmente para cima e é oposto à direção do vetor de gravidade.
O eixo O o X g - é direcionado para o norte, ao longo da tangente do meridiano local.
Eixo O o Z g - complementa o sistema para a direita e é direcionado para a direita, em direção ao leste.

O segundo sistema de coordenadas está conectado. Imagine, por exemplo, um avião ou outro objeto.
Ponto O - a origem do sistema de coordenadas, via de regra, está localizada no ponto do centro de massa do objeto.
Eixo OY - direcionado verticalmente para cima e perpendicular ao plano horizontal do objeto.
Axis OX - direcionado para o ponto frontal do objeto.
Eixo OZ - complementa o sistema à direita.

A posição do objeto no espaço é determinada pelo vetor de raio da origem (ponto O ) do sistema de coordenadas associado em relação ao sistema de coordenadas da base fixa. A orientação do sistema de coordenadas associado em relação à base é determinada por três turnos sucessivos:

ângulo da guinada  psi - rotação em torno do eixo OY ,
ângulo de inclinação  vartheta - rotação em torno do eixo OZ ,
ângulo de rolagem  gama - rotação ao redor do eixo OX .

Para a determinação inicial do biquaternion, você deve especificar as partes real e dupla do biquaternion. A orientação e a posição do objeto são definidas em relação a um determinado sistema de coordenadas base usando ângulos de orientação  psi, vartheta, gama e vetor de posição do centro de massa r=(rx,ry,rz)T .

A parte real  textbfq1 pode ser definido usando a fórmula:

\ textbf {q} _1 = \ begin {bmatrix} \ cos \ frac {\ psi} {2} \ cos \ frac {\ vartheta} {2} \ cos \ frac {\ gamma} {2} e - & \ sin \ frac {\ psi} {2} \ sin \ frac {\ vartheta} {2} \ sin \ frac {\ gamma} {2} \\ \ cos \ frac {\ psi} {2} \ cos \ frac { \ vartheta} {2} \ sin \ frac {\ gama} {2} & + & \ sin \ frac {\ psi} {2} \ sin \ frac {\ vartheta} {2} \ cos \ frac {\ gamma} {2} \\ \ cos \ frac {\ psi} {2} \ sin \ frac {\ vartheta} {2} \ sin \ frac {\ gamma} {2} e + & \ sin \ frac {\ psi} { 2} \ cos \ frac {\ vartheta} {2} \ cos \ frac {\ gamma} {2} \\ \ cos \ frac {\ psi} {2} \ sin \ frac {\ vartheta} {2} \ cos \ frac {\ gamma} {2} e - & \ sin \ frac {\ psi} {2} \ cos \ frac {\ vartheta} {2} \ sin \ frac {\ gamma} {2} \ end {bmatrix}


Observe que, se você tiver uma sequência de rotação diferente, as expressões também serão diferentes.

Parte dupla  textbfq2 definido pela expressão:

 textbfq2= frac12 textbfr otimes textbfq1



Cálculo dos ângulos de orientação e vetor de posição do biquatérion Transformação inversa


Os ângulos de orientação podem ser calculados a partir da parte real do biquíni.  textbfq1 :

 psi= arctan frac2(q0q2q1q3)q20+q21q22q23


 vartheta= arcsin(2(q1q2+q0q3))


 gamma= arctan frac2(q0q1q2q3)q20q21+q22q23



A posição do objeto é determinada pela expressão:

 textbfr=2 textbfq2 otimes textbfq11


o resultado é um vetor em forma de quaternário  textbfr=(0,rx,ry,rz)T

Gire e mova o biquaternion de vetor


Uma das grandes propriedades dos biquatérnions é a rotação e o movimento de um vetor de um sistema de coordenadas para outro. Seja O o X g Y g Z g um sistema de coordenadas de base fixo e OXYZ seja o sistema de coordenadas conectado do objeto. Em seguida, a orientação e a posição do objeto em relação ao sistema de coordenadas base podem ser especificadas pelo biquaternion  widetilde textbfq . Se um vetor for especificado  textbfr em um sistema de coordenadas conectado, você pode obter um vetor  textbfr0 no sistema de coordenadas base usando a fórmula:

 textbfr0= widetilde textbfq otimes textbfr otimes widetilde textbfq1


e de volta:

 textbfr= widetilde textbfq1 otimes textbfr0 otimes widetilde textbfq


onde  textbfr É um vetor em forma de biquíni,  textbfr=(1,0,0,0,0,0,rx,ry,rz)

Biblioteca JavaScript Biquaternion


Todas as operações acima com biquaternions são implementadas na biblioteca javascript, dependendo das suas tarefas, elas podem ser implementadas em outras linguagens de programação. As principais funções do trabalho com biquaternions:
FunçãoDescrição do produto
DualQuaternion.dqCorpo biquaternário como uma matriz de 8 números
DualQuaternion(dq0, dq1, dq2, dq3, dq4, dq5, dq6, dq7)Um construtor que define um biquíni, especificando todos os oito números
DualQuaternion.fromEulerVector(psi, theta, gamma, v)Obter biquaternion definindo a orientação do objeto com ângulos de Euler e o vetor de posição do objeto
DualQuaternion.getEulerVector()Obter ângulos de Euler e vetor de posição do biquaternion
DualQuaternion.getVector()Obter vetor de posição de biquaternion
DualQuaternion.getReal()Obter a parte real do biquaternion (determina a orientação do objeto no espaço)
DualQuaternion.getDual()Obter a parte dupla do biquaternion (determina a posição do objeto no espaço)
DualQuaternion.norm()Obter a norma biquaternion como um número duplo
DualQuaternion.mod()Obtenha o módulo biquaternion como um número duplo
DualQuaternion.conjugate()Obter o biquaternion conjugado
DualQuaternion.inverse()Obter Biquaternion reverso
DualQuaternion.mul(DQ2)Multiplicação biquaternion
DualQuaternion.toString()Converter biquaternion em uma cadeia de caracteres, por exemplo, para saída no console de depuração

Arquivo dual_quaternion.js
 /** * * Author 2017, Akhramovich A. Sergey (akhramovichsa@gmail.com) * see https://github.com/infusion/Quaternion.js */ // 'use strict'; /** * Dual Quaternion constructor * * @constructor * @returns {DualQuaternion} */ function DualQuaternion(dq0, dq1, dq2, dq3, dq4, dq5, dq6, dq7) { if (dq0 === undefined) { this.dq = [1, 0, 0, 0, 0, 0, 0, 0]; } else { this.dq = [dq0, dq1, dq2, dq3, dq4, dq5, dq6, dq7]; } return this; }; //         DualQuaternion['fromEulerVector'] = function(psi, theta, gamma, v) { var q_real = new Quaternion.fromEuler(psi, theta, gamma); var q_v = new Quaternion(0, v[0], v[1], v[2]); var q_dual = q_v.mul(q_real); return new DualQuaternion( q_real.q[0], q_real.q[1], q_real.q[2], q_real.q[3], q_dual.q[0]*0.5, q_dual.q[1]*0.5, q_dual.q[2]*0.5, q_dual.q[3]*0.5); }; DualQuaternion.prototype = { 'dq': [1, 0, 0, 0, 0, 0, 0, 0], /** *    (psi, theta, gamma)      */ 'getEulerVector': function() { var euler_angles = this.getReal().getEuler(); var q_dual = this.getDual(); var q_dual_2 = new Quaternion(2.0*q_dual.q[0], 2.0*q_dual.q[1], 2.0*q_dual.q[2], 2.0*q_dual.q[3]); var q_vector = q_dual_2.mul(this.getReal().conjugate()); return [euler_angles[0], euler_angles[1], euler_angles[2], q_vector.q[1], q_vector.q[2], q_vector.q[3]]; }, /** *       */ 'getVector': function() { var euler_vector = this.getEulerVector(); return [euler_vector[3], euler_vector[4], euler_vector[5]]; }, /** *     * @returns {Quaternion} */ 'getReal': function() { return new Quaternion(this.dq[0], this.dq[1], this.dq[2], this.dq[3]); }, /** *     * @returns {Quaternion} */ 'getDual': function() { return new Quaternion(this.dq[4], this.dq[5], this.dq[6], this.dq[7]); }, /** *   * !   ! */ 'norm': function() { return [Math.pow(this.dq[0], 2) + Math.pow(this.dq[1], 2) + Math.pow(this.dq[2], 2) + Math.pow(this.dq[3], 2), this.dq[0]*this.dq[4] + this.dq[1]*this.dq[5] + this.dq[2]*this.dq[6] + this.dq[3]*this.dq[7]]; }, /** *   * !   ! */ 'mod': function() { var q_real_mod = Math.sqrt(Math.pow(this.dq[0], 2) + Math.pow(this.dq[1], 2) + Math.pow(this.dq[2], 2) + Math.pow(this.dq[3], 2)); return [q_real_mod, (this.dq[0]*this.dq[4] + this.dq[1]*this.dq[5] + this.dq[2]*this.dq[6] + this.dq[3]*this.dq[7])/q_real_mod]; }, /** *   * DQ' := (dq0, -dq1, -dq2, -dq3, dq4, -dq5, -dq6, -dq7) */ 'conjugate': function() { return new DualQuaternion(this.dq[0], -this.dq[1], -this.dq[2], -this.dq[3], this.dq[4], -this.dq[5], -this.dq[6], -this.dq[7]); }, //    'inverse': function() { var q_real_norm = new Quaternion(this.dq[0], this.dq[1], this.dq[2], this.dq[3]).norm(); var dq_norm_inv = [q_real_norm, - (this.dq[0]*this.dq[4] + this.dq[1]*this.dq[5] + this.dq[2]*this.dq[6] + this.dq[3]*this.dq[7])/q_real_norm]; var dq_conj = this.conjugate(); //      return new DualQuaternion( dq_norm_inv[0] * dq_conj.dq[0], dq_norm_inv[0] * dq_conj.dq[1], dq_norm_inv[0] * dq_conj.dq[2], dq_norm_inv[0] * dq_conj.dq[3], dq_norm_inv[0] * dq_conj.dq[4] + dq_norm_inv[1] * dq_conj.dq[0], dq_norm_inv[0] * dq_conj.dq[5] + dq_norm_inv[1] * dq_conj.dq[1], dq_norm_inv[0] * dq_conj.dq[6] + dq_norm_inv[1] * dq_conj.dq[2], dq_norm_inv[0] * dq_conj.dq[7] + dq_norm_inv[1] * dq_conj.dq[3]); }, /** *   * q1_real*q2_real, q1_real*q2_dual + q1_dual*q2_real */ 'mul': function(DQ2) { var q1_real = this.getReal(); var q1_dual = this.getDual(); var q2_real = DQ2.getReal(); var q2_dual = DQ2.getDual(); var q_res_real = q1_real.mul(q2_real); var q_res_dual_1 = q1_real.mul(q2_dual); var q_res_dual_2 = q1_dual.mul(q2_real); return new DualQuaternion( q_res_real.q[0], q_res_real.q[1], q_res_real.q[2], q_res_real.q[3], q_res_dual_1.q[0] + q_res_dual_2.q[0], q_res_dual_1.q[1] + q_res_dual_2.q[1], q_res_dual_1.q[2] + q_res_dual_2.q[2], q_res_dual_1.q[3] + q_res_dual_2.q[3]); }, /** *    */ 'transformVector': function (v) { var dq_res = this.mul(new DualQuaternion(1, 0, 0, 0, 0, v[0], v[1], v[2])).mul(this.conjugate()); return [dq_res.dq[5], dq_res.dq[6], dq_res.dq[7]]; }, /** *   ,   */ 'toString': function() { return '[' + this.dq[0].toString() + ', ' + this.dq[1].toString() + ', ' + this.dq[2].toString() + ', ' + this.dq[3].toString() + ', ' + this.dq[4].toString() + ', ' + this.dq[5].toString() + ', ' + this.dq[6].toString() + ', ' + this.dq[7].toString() + ']'; } } /* // TEST: var dq1 = new DualQuaternion.fromEulerVector(0 * Math.PI/180.0, 0 * Math.PI/180, 0 * Math.PI/180, [10, 20, 30]); console.log(dq1.toString()); console.log('getEulerVector = ', dq1.getEulerVector()); console.log('norm = ', dq1.norm()); console.log('mod = ', dq1.mod()); console.log('conjugate = ', dq1.conjugate().dq); console.log('inverse = ', dq1.inverse().dq); var dq2 = new DualQuaternion.fromEulerVector(0 * Math.PI/180.0, 0 * Math.PI/180, 0 * Math.PI/180, [10, 0, 0]); console.log('mul = ', dq1.mul(dq2).dq); console.log('transformVector ??? = ', dq1.transformVector([0, 0, 0])); */ 


Um exemplo de trabalho com biquaternions


Para uma melhor compreensão dos conceitos básicos do uso de biquaternions como exemplo, considere um pequeno jogo. A área retangular é definida - o mapa. Um navio flutuando em um mapa com uma pistola rotativa montada no mapa. Aqui é necessário levar em consideração que, para o navio, o sistema de coordenadas básicas é o sistema de coordenadas do mapa, e para a arma o sistema de coordenadas básicas é o navio. Todos os objetos são desenhados no sistema de coordenadas do mapa e aqui será interessante ver como é possível ir do sistema de coordenadas da pistola para o sistema de coordenadas do mapa usando a propriedade de multiplicação do biquaternion. O movimento do navio é controlado pelas teclas W, A, S, D. A direção da arma é definida pelo cursor do mouse.



O navio e a arma são descritos por duas classes: Ship e Gun . No construtor da classe de navio, sua forma na forma de pontos de biquíni, a orientação inicial e a posição no mapa na forma de this.dq_pos biquaternion são this.dq_pos .

Também são fornecidos incrementos de biquaternidade para o controle do navio. Ao se mover para frente e para trás (teclas W, S), apenas a parte dupla do biquíni é alterada e, ao controlar a esquerda e a direita (teclas A, D), a parte real e dupla do biquaternion muda, o que define o ângulo de rotação.

 function Ship(ctx, v) { this.ctx = ctx; this.dq_pos = new DualQuaternion.fromEulerVector(0*Math.PI/180, 0, 0, v); //   this.dq_forward_left = new DualQuaternion.fromEulerVector(0, 0, 0, [ 15, 0, -10]); this.dq_forward_right = new DualQuaternion.fromEulerVector(0, 0, 0, [ 15, 0, 10]); this.dq_backward_left = new DualQuaternion.fromEulerVector(0, 0, 0, [-15, 0, -10]); this.dq_backward_right = new DualQuaternion.fromEulerVector(0, 0, 0, [-15, 0, 10]); this.dq_forward_forward = new DualQuaternion.fromEulerVector(0, 0, 0, [ 30, 0, 0]); //      this.dq_dx_left = new DualQuaternion.fromEulerVector( 1*Math.PI/180, 0, 0, [0, 0, 0]); this.dq_dx_right = new DualQuaternion.fromEulerVector(-1*Math.PI/180, 0, 0, [0, 0, 0]); this.dq_dx_forward = new DualQuaternion.fromEulerVector(0, 0, 0, [ 1, 0, 0]); this.dq_dx_backward = new DualQuaternion.fromEulerVector(0, 0, 0, [-1, 0, 0]); return this; }; 

Na própria classe, há apenas uma função para renderizar a Ship.draw() . Preste atenção à aplicação da operação de biquatérion de multiplicar cada ponto do navio pelo biquatérion da posição e orientação atual do navio.

 Ship.prototype = { 'ctx': 0, 'dq_pos': new DualQuaternion.fromEulerVector(0, 0, 0, 0, 0, 0), /** *   */ 'draw': function() { //         v_pos = this.dq_pos.getVector(); v_forward_left = this.dq_pos.mul(this.dq_forward_left).getVector(); v_forward_right = this.dq_pos.mul(this.dq_forward_right).getVector(); v_backward_left = this.dq_pos.mul(this.dq_backward_left).getVector(); v_backward_right = this.dq_pos.mul(this.dq_backward_right).getVector(); v_forward_forward = this.dq_pos.mul(this.dq_forward_forward).getVector(); //   ctx.beginPath(); ctx.moveTo(v_backward_left[0], v_backward_left[2]); ctx.lineTo(v_forward_left[0], v_forward_left[2]); ctx.lineTo(v_forward_left[0], v_forward_left[2]); ctx.lineTo(v_forward_forward[0], v_forward_forward[2]); ctx.lineTo(v_forward_right[0], v_forward_right[2]); ctx.lineTo(v_backward_right[0], v_backward_right[2]); ctx.lineTo(v_backward_left[0], v_backward_left[2]); ctx.stroke(); ctx.closePath(); } }; 

No construtor da classe de armas, sua forma na forma de pontos de biquíni é especificada. A arma será exibida como uma linha. A orientação e posição iniciais no navio são definidas pelo this.dq_pos biquaternion. Além disso, a ligação ao navio em que está instalado também é definida. A pistola no navio só pode girar, então o biquaternion aumenta quando o controle da arma muda apenas a parte real do biquaternion, que define o ângulo de rotação. Neste exemplo, o implemento é guiado pelo cursor do mouse, de modo que a rotação da pistola ocorrerá instantaneamente.

 function Gun(ctx, ship, v) { this.ctx = ctx; this.ship = ship; //     this.dq_pos = new DualQuaternion.fromEulerVector(0, 0, 0, v); //   this.dq_forward = new DualQuaternion.fromEulerVector(0, 0, 0, [20, 0, 0]); this.dq_backward = new DualQuaternion.fromEulerVector(0, 0, 0, [ 0, 0, 0]); //     this.dq_dx_left = new DualQuaternion.fromEulerVector( 1*Math.PI/180, 0, 0, [0, 0, 0]); this.dq_dx_right = new DualQuaternion.fromEulerVector(-1*Math.PI/180, 0, 0, [0, 0, 0]); return this; }; 

Na classe gun, apenas uma função de renderizar Ship.draw() também Ship.draw() implementada. A arma é exibida como uma linha, que é definida por dois pontos this.dq_backward e this.dq_forward . Para determinar as coordenadas dos pontos da pistola, é utilizada a operação de multiplicação do biquaternion.

 Gun.prototype = { 'ctx': 0, 'ship': 0, 'dq_pos': new DualQuaternion.fromEulerVector(0, 0, 0, [0, 0, 0]), /** *   */ 'draw': function() { //     v_pos = this.ship.dq_pos.getVector(); v_forward = this.ship.dq_pos.mul(this.dq_backward).mul(this.dq_forward).getVector(); v_backward = this.ship.dq_pos.mul(this.dq_backward).getVector(); //   ctx.beginPath(); ctx.moveTo(v_backward[0], v_backward[2]); ctx.lineTo(v_forward[0], v_forward[2]); ctx.stroke(); ctx.closePath(); } }; 

O processamento de controle de navios e armas é implementado por meio de eventos. Quatro variáveis leftPressed, upPressed, rightPressed, downPressed , que são processadas no loop principal do programa, são responsáveis ​​por pressionar e liberar as teclas de controle do navio.

 leftPressed = false; rightPressed = false; upPressed = false; downPressed = false; dq_mouse_pos = new DualQuaternion.fromEulerVector(0, 0, 0, [0, 0, 0]); document.addEventListener("keydown", keyDownHandler, false); document.addEventListener("keyup", keyUpHandler, false); document.addEventListener("mousemove", mouseMoveHandler, false); //     function keyDownHandler(e) { if (e.keyCode == 37 || e.keyCode == 65 || e.keyCode == 97) { leftPressed = true; } //  A else if (e.keyCode == 38 || e.keyCode == 87 || e.keyCode == 119) { upPressed = true; } //  W else if (e.keyCode == 39 || e.keyCode == 68 || e.keyCode == 100) { rightPressed = true; } //  D else if (e.keyCode == 40 || e.keyCode == 83 || e.keyCode == 115) { downPressed = true; } //  S } //     function keyUpHandler(e) { if (e.keyCode == 37 || e.keyCode == 65 || e.keyCode == 97) { leftPressed = false; } //  A else if (e.keyCode == 38 || e.keyCode == 87 || e.keyCode == 119) { upPressed = false; } //  W else if (e.keyCode == 39 || e.keyCode == 68 || e.keyCode == 100) { rightPressed = false; } //  D else if (e.keyCode == 40 || e.keyCode == 83 || e.keyCode == 115) { downPressed = false; } //  S } 

Uma das funções mais interessantes, do ponto de vista do uso de operações de biquíni, é controlar a arma do navio na direção do ponteiro do mouse. Primeiro, as coordenadas do ponteiro do mouse são determinadas no dq_mouse_pos biquaternion. Em seguida, o biquatião da posição do mouse em relação ao navio é calculado usando a multiplicação do biquatino. O biquatião do navio é retirado do dq_mouse_pos_about_ship = ship_1.dq_pos.inverse().mul(dq_mouse_pos); do mouse dq_mouse_pos_about_ship = ship_1.dq_pos.inverse().mul(dq_mouse_pos);
(Nota: as operações de multiplicação sequencial de biquíni são lidas da direita para a esquerda). E, finalmente, o ângulo entre os vetores da ferramenta e o mouse é determinado. O ponto inicial da arma gun_1.dq_backward recebe o valor recebido.

 function mouseMoveHandler(e) { var relativeX = e.clientX - canvas.offsetLeft; var relativeY = e.clientY - canvas.offsetTop; //           if (relativeX > 0 && relativeX < canvas.width && relativeY > 0 && relativeY < canvas.height) { //    dq_mouse_pos = new DualQuaternion.fromEulerVector(0, 0, 0, [relativeX, 0, relativeY]); //      //  .       //     // DQ_ship^(-1) * DQ_mouse dq_mouse_pos_about_ship = ship_1.dq_pos.inverse().mul(dq_mouse_pos); //       q_gun_mouse = new Quaternion.fromBetweenVectors(gun_1.dq_forward.getVector(), dq_mouse_pos_about_ship.getVector()); dq_gun_mouse = new DualQuaternion(q_gun_mouse.q[0], q_gun_mouse.q[1], q_gun_mouse.q[2], q_gun_mouse.q[3], 0, 0, 0, 0); gun_1.dq_backward = dq_gun_mouse; // console.log(dq_gun_mouse.getEulerVector()); // console.log(relativeX + ' ' + relativeY + ' ' + gun_1.dq_forward.toString()); } } 

No corpo principal do programa, os objetos do navio e das armas ship_1 e gun_1 , as informações de depuração são exibidas e o processamento de controle do navio é realizado.

 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ship_1 = new Ship(ctx, [100, 0, 100]); gun_1 = new Gun(ctx, ship_1, [0, 0, 0]); function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ship_1.draw(); gun_1.draw(); // Debug info ship_euler_vector = ship_1.dq_pos.getEulerVector(); ship_euler_vector[0] = ship_euler_vector[0]*180/Math.PI; ship_euler_vector[1] = ship_euler_vector[1]*180/Math.PI; ship_euler_vector[2] = ship_euler_vector[2]*180/Math.PI; ship_euler_vector = ship_euler_vector.map(function(each_element){ return each_element.toFixed(2); }); ship_dq = ship_1.dq_pos.dq.map(function(each_element){ return each_element.toFixed(2); }); gun_dq = ship_1.dq_pos.mul(gun_1.dq_backward).dq.map(function(each_element){ return each_element.toFixed(2); }); ctx.font = "8pt Courier"; ctx.fillText("Ship: " + ship_dq + " | psi, theta, gamma, vector:" + ship_euler_vector, 10, 20); ctx.fillText("Gun: " + gun_dq, 10, 40); //   if (leftPressed) { ship_1.dq_pos = ship_1.dq_pos.mul(ship_1.dq_dx_left); } if (rightPressed) { ship_1.dq_pos = ship_1.dq_pos.mul(ship_1.dq_dx_right); } if (upPressed) { ship_1.dq_pos = ship_1.dq_pos.mul(ship_1.dq_dx_forward); } if (downPressed) { ship_1.dq_pos = ship_1.dq_pos.mul(ship_1.dq_dx_backward); } requestAnimationFrame(draw); } draw(); 

O link para o arquivo contém o código completo das bibliotecas para trabalhar com quaternions e biquaternions, o próprio script do programa e o arquivo index.html, que pode ser aberto localmente no navegador para executar o exemplo acima.

Um exemplo de trabalho com biquaternions

Conclusão


Você pode ter uma pergunta: por que usar um aparato matemático tão complexo quando se pode conviver com ferramentas padrão para mover e girar objetos? Uma das principais vantagens é que a forma de escrever biquaternion é mais computacionalmente eficiente, pois todas as operações com biquaternions após a expansão das expressões são lineares. Este vídeo, Skinning geométrico com mistura aproximada de quaternário duplo, mostra o quão mais eficientes são os cálculos do biquaternion do que outros métodos.

Eu peguei principalmente informações sobre o uso de biquaternions de fontes inglesas.
Da literatura doméstica, posso aconselhar dois livros:

  1. Chelnokov Yuri Nikolaevich. Modelos e métodos de Quaternion e biquaternion de mecânica dos sólidos e suas aplicações. Geometria e cinemática do movimento. - trabalho teórico monumental.
  2. Gordeev Vadim Nikolaevich. Quaternions e biquaternions com aplicações em geometria e mecânica. - É escrito em uma linguagem mais compreensível e mostra aplicações nas tarefas de modelar estruturas espaciais curvas.

Source: https://habr.com/ru/post/pt436210/


All Articles