2016-03-10 26 views
5

Ho una matrice 3x3 (startMatrix), che rappresenta la vista effettiva di un'immagine (traslazione, rotazione e scala). Ora creo una nuova matrice (endMatrix) con un identitymatrix, nuova xey coordinate, nuovo angolo e nuova scala come:Animazione rotazionale per combinazione lineare di matrici di trasformazione porta a Zoom-In-Zoom-out

endMatrix = translate(identityMatrix, -x, -y); 
endMatrix = rotate(endMatrix, angle); 
endMatrix = scale(endMatrix, scale); 
endMatrix = translate(endMatrix,(screen.width/2)/scale,screen.height/2)/scale); 

E le funzioni (roba standard)

function scale(m,s) { 
    var n = new Matrix([ 
     [s, 0, 0], 
     [0, s, 0], 
     [0, 0, s] 
    ]); 
    return n.multiply(m); 
} 
function rotate(m, theta) { 
    var n = new Matrix([ 
     [Math.cos(theta), -Math.sin(theta), 0], 
     [Math.sin(theta), Math.cos(theta), 0], 
     [0, 0, 1] 
    ]); 
    return n.multiply(m); 
} 
function translate(m, x, y) { 
    var n = new Matrix([ 
     [1, 0, x], 
     [0, 1, y], 
     [0, 0, 1] 
    ]); 
    return n.multiply(m); 
} 

Dopo che trasformo l'immagine con css transform matrix3d ​​(3d solo per l'accelerazione hardware). Questa trasformazione è animata con requestAnimationFrame.

mio startMatrix è per esempio

enter image description here

And The endMatrix

enter image description here

La combinazione lineare si presenta come:

enter image description here

Da 0 a 1

Il risultato della combinazione lineare di matrici di trasformazione (la posizione dell'immagine risultante) è corretto, il mio problema ora è: Se il nuovo angolo è di circa 180 gradi diverso dall'angolo effettivo, il i valori endMatrix cambiano da positivo a negativo (o viceversa). Questo porta ad un effetto di zoom-in per lo zoom nell'animazione dell'immagine trasformata.

C'è un modo per prevenire questo preferibilmente utilizzando una matrice per la trasformazione?

risposta

4

ci saranno problemi se si interpolare i valori di matrice direttamente, per angoli molto piccoli i inaccuraties non possono essere osservati, ma nel più lungo termine si dovrà affrontare problemi. Anche se normalizzi le matrici, gli angoli maggiori renderanno i problemi visivamente ovvi.

La rotazione 2D è piuttosto semplice, quindi è possibile fare la fine senza l'approccio della matrice di rotazione. Il metodo migliore potrebbe essere utilizzare i quaternioni, ma forse sono più adatti per le trasformazioni 3D.

Le operazioni da effettuare sono:

  1. calcolare la rotazione, la scala e trasformare valori. Se ne hai già, puoi saltare questo passaggio. Potrebbe essere più semplice mantenere questi valori separati per le trasformazioni della matrice 2D.
  2. Poi applly interpolazione a quei valori
  3. costruire una nuova matrice basata sui calcoli

All'inizio dell'animazione si deve calcolare valori da passaggio 1 volta e quindi applicare passaggi 2 e 3 per ogni telaio.

Fase 1: ottenere la rotazione, la scala, trasformare

Assumere il Matrix inizio è S e Matrix finale è E.

I valori di trasformazione sono semplicemente le ultime colonne del, per esempio

var start_tx = S[0][2]; 
var start_ty = S[1][2]; 
var end_tx = E[0][2]; 
var end_ty = E[1][2]; 

la scala per non inclinazione matrice 2D è semplicemente lunghezza sia uno dei vettori di base nello spazio della matrice è spanning, per esempio

// scale is just the length of the rotation matrixes vector 
var startScale = Math.sqrt(S[0][0]*S[0][0] + S[1][0]*S[1][0]); 
var endScale = Math.sqrt(E[0][0]*E[0][0] + E[1][0]*E[1][0]); 

La parte più difficile è ottenere i valori di rotazione per la matrice. La cosa buona è che deve essere calcolato solo una volta per interpolazione.

L'angolo di rotazione per due matrici 2D può essere calcolato in base all'angolo tra i vettori creati dalle colonne delle matrici. Se non c'è rotazione, la prima colonna ha valori (1,0) che rappresentano l'asse x e la seconda colonna ha valori (0,1) che rappresentano l'asse y.

Generalmente posizione asse x per Matrix S è rappresentato da

(S [0] [0], S [0] [1])

e l'asse y è indicando direzione

(S [1] [0], S [1] [1])

e lo stesso per qualsiasi matrice 2D 3x3, come E.

Utilizzando queste informazioni è possibile determinare l'angolo di rotazione tra le due matrici utilizzando solo la matematica vettoriale standard, se si assume che non vi sia una distorsione.

// normalize column vectors 
var s00 = S[0][0]/ startScale; // x-component 
var s01 = S[0][1]/ startScale; // y-component 
var e00 = E[0][0]/ endScale; // x-component 
var e01 = E[0][1]/ endScale; // y-component 
// calculate dot product which is the cos of the angle 
var dp_start = s00*1 + s01*0;  // base rotation, dot prod against x-axis 
var dp_between = s00*e00 + s01*e01; // between matrices 
var startRotation = Math.acos(dp_start); 
var deltaRotation = Math.acos(dp_between); 

// if detect clockwise rotation, the y -comp of x-axis < 0 
if(S[0][1]<0) startRotation = -1*startRotation; 

// for the delta rotation calculate cross product 
var cp_between = s00*e01 - s01*e00; 
if(cp_between<0) deltaRotation = deltaRotation*-1; 

var endRotation = startRotation + deltaRotation; 

Qui il startRotation è calcolato solo da acos del primo valore della matrice. Tuttavia, il primo valore della seconda colonna, che è -sin (angolo) è maggiore di zero, quindi la matrice è stata ruotata in senso orario e l'angolo deve essere negativo. Questo deve essere fatto perché acos fornisce solo valori positivi.

Un altro modo di pensare a ciò sarebbe considerare il prodotto incrociato s00 * e01 - s01 * e00 dove posizione iniziale (s00, s01) è l'asse x, dove s00 == 1 e s01 == 0 e la fine (e00, e01) è la (S [0] [0], S [0] [1]) creando prodotto incrociato

1 * S[0][1] - 0 * S[0][0] 

Quale è S [0] [1]. Se quel valore è negativo, l'asse x è stato girato in senso orario.

Per endRotation, è necessaria la rotazione delta da S a E. Questo può essere calcolato in modo simile dal prodotto punto tra i vettori che la matrice si estende. Allo stesso modo, testiamo il cross-product per vedere se la direzione di rotazione è in senso orario (angolo negativo).

Fase 2: interpolare

Durante animazione ottenere nuovi valori è l'interpolazione banale:

var scale = startScale + t*(endScale-startScale); 
var rotation = startRotation + t*(endRotation-startRotation); 
var tx = start_tx + t*(end_tx-start_tx); 
var ty = start_ty + t*(end_ty-start_ty); 

Passaggio 3 costrutto matrice

Per ogni frame costruire la matrice finale tuo posizionare i valori nella matrice matrice di trasformazione

var cs = Math.cos(rotation); 
var sn = Math.sin(rotation); 
var matrix_values = [[scale*cs, -scale*sn, tx], [scale*sn, scale*cs, ty], [0,0,1]] 

E quindi si dispone di una matrice 2D che è facile da alimentare per qualsiasi acceleratore hardware 3D.

NOTA BENE: alcuni codici sono stati testati, alcuni non sono stati testati, quindi è probabile che si possano trovare errori.

+2

Questa è stata la prima risposta che ci ha dato il suggerimento giusto: la scala di conservazione non può essere eseguita con una sola combinazione lineare. Era anche la soluzione al nostro problema, quindi ti ricompenserò con la generosità. Ma ha portato un nuovo problema: il centro di rotazione è arbitrario e non controllabile. Per gestire questo, abbiamo utilizzato un tweak della soluzione @miks. Ora calcoliamo un centro di rotazione per ogni passo dell'animazione e con questo costruiamo una matrice per ogni t. –

+0

Grazie. Regolare le matrici per preservare il centro di rotazione per i diversi spazi di trasformazione può essere a volte un compito leggermente pungente ... contento che tu l'abbia fatto! –

+0

@JulianHabekost nella mia soluzione ho provato prima a unire tutte le trasformazioni tranne la rotazione in una matrice (in realtà due di esse: una prima e una dopo la rotazione), ma mi sembrava troppo complicata; poi ho modificato la mia risposta. Se fai clic su "modificato ..." vedrai la prima variante. – mik

-1

La matrice di rotazione nell'esempio ha il dominio [-180 °, 180 °]. Gradi superiori a 180 ° sono al di fuori del dominio delle funzioni.

È possibile mappare il tuo rotazione angolo al dominio corretto:

function MapToDomain(theta){ 
/* mapping abitraty rotation-angle to [0,2*PI] */ 
var beta = theta % (2*Math.PI); 

/* mapping [0,2*PI] -> [-PI,PI] */ 
if (beta > (Math.PI/2)) { beta = Math.PI/2-beta; } 

return beta; 
} 

Questa funzione deve essere chiamata prima di valutare i Matrix-elementi nella rotazione funzione:

function rotate(m, theta) { 
var beta = MapToDomain(theta); 
var n = new Matrix([ 
    [Math.cos(beta), -Math.sin(beta), 0], 
    [Math.sin(beta), Math.cos(beta), 0], 
    [0, 0, 1] 
]); 
return n.multiply(m); 
} 

nota: Non sono un programmatore di java-script. Spero che la sintassi sia corretta.

+0

La sintassi è corretta ma non è stata una soluzione al problema. In ogni caso non è possibile una suola con scala di conservazione dell'animazione con combinazione lineare a matrice, come spiegano altre risposte. –

2

L'utilizzo di JavaScript per calcolare le trasformazioni esclude efficacemente una delle funzioni principali dell'accelerazione hardware. L'utilizzo di CSS per le trasformazioni è significativamente più efficiente.

Non è chiaro dalla tua domanda che cosa stai cercando di ottenere con questa tecnica, ma questo approccio potrebbe essere di aiuto. Lo uso per creare array di interpolazione per trasformazioni di matrici.

HTMLElement.prototype.get3dMatrixArray = function() { 
    var st = window.getComputedStyle(this, null), 
     mx = (st.getPropertyValue("transform") || st.getPropertyValue("-o-transform") || st.getPropertyValue("-ms-transform") || st.getPropertyValue("-moz-transform") || st.getPropertyValue("-webkit-transform") || 'none').replace(/\(|\)| |"/g, ''), 
     arr = []; 
    if (mx.indexOf('matrix3d') > -1) { 
     arr = mx.replace('matrix3d', '').split(','); 
    } else if (mx.indexOf('matrix') > -1) { 
     arr = mx.replace('matrix', '').split(','); 
     arr.push(0, 1); 
     [2, 3, 6, 7, 8, 9, 10, 11].map(function (i) { 
      arr.splice(i, 0, 0); 
     }); 
    } else return mx; 
    return arr.map(function (v) { 
     return parseFloat(v) 
    }); 
}; 

HTMLElement.prototype.set3dMatrix = function (mx) { 
    if (Object.prototype.toString.call(mx) === '[object Array]') 
     this.style.webkitTransform = this.style.msTransform = this.style.MozTransform = this.style.OTransform = this.style.transform = 'matrix3d(' + mx.join(",") + ')'; 
    return this; 
}; 

HTMLElement.prototype.matrixTweenArray = function (endEl, steps) { 
    function _tween(b, a, e) { 
     b = b.get3dMatrixArray(); 
     var f = a.get3dMatrixArray(); 
     a = []; 
     for (var c = 1; c < e + 1; c++) { 
      var d = -1; 
      a.push(b.map(function (v) { 
       d++; 
       return v != f[d] ? v - (v - f[d])/e * c : v; 
      })); 
     } 
     return a; 
    } 

    return _tween(this, endEl, steps); 
}; 

HTMLElement.prototype.matrixAnimmate = function (matrixArr) { 
    var that = this, 
     pointer = 0; 

    function _frameloop() { 
     that.set3dMatrix(matrixArr[pointer]); 
     pointer++; 
     pointer === matrixArr.length && (pointer = 0); 
     requestAnimationFrame(_frameloop); 
    } 
    requestAnimationFrame(_frameloop) 
}; 

Per procedere con l'installazione è sufficiente creare un elemento per le posizioni di inizio e fine e l'elemento che si desidera seguire nel percorso di tween.

<div id="start"></div> 
<div id="end"></div> 
<div id="animateMe"></div> 

aggiungere qualsiasi transform regola per #AVVIA e #end usando i CSS. Imposta la regola css display:none se vuoi che siano nascoste.

Quindi è possibile visualizzare il risultato con:

//build transform array - 60 frames 
var mxArr = document.getElementById('start').matrixTweenArray(document.getElementById('end'), 60); 

E chiamare la funzione animate utilizzando:

document.getElementById('animateMe').matrixAnimmate(mxArr); 

Non dimenticate di impostare perspective-origin sui div e perspective sul loro elemento genitore.

https://jsfiddle.net/tnt1/wjunsj36/2/

Spero che questo aiuti)

+1

Ma per quanto posso vedere, questo non risolverà il problema principale della modifica della scala durante la rotazione. – mik

+0

@mik puoi applicare qualsiasi trasformazione css quindi non ci dovrebbero essere problemi) –

+1

Soluzione interessante, ma questa https://jsfiddle.net/s6xjjdmn/ mostra la sua non la soluzione per il mio problema – Alex

3

Quando una rotazione animata conserva la scala, i punti non si spostano lungo le linee rette, ma lungo i cerchi.Di conseguenza, una matrice intermedia non è una combinazione lineare di matrici iniziali e finali. Il modo più semplice per superare questo è calcolare tutte le trasformazioni durante ogni fotogramma di animazione:

scale = startScale*(1-t)+endScale*t; 
transformMatrix = translate(identityMatrix, -startX*(1-t)-endX*t, -startY*(1-t)-endY*t); 
transformMatrix = rotate(transformMatrix, startAngle*(1-t)+endAngle*t); 
transformMatrix = scale(transformMatrix, scale); 
transformMatrix = translate(transformMatrix,screen.width/2/scale,screen.height/2/scale); 
+3

Questa risposta presuppone che sappiate anche iniziare i valori per x, y, angolo e scala. Se conosci solo la matrice, vedi la risposta @Tero Tolonen. – mik

+1

@Teron Tolons ha risolto il nostro problema, quindi gli ho assegnato la taglia. Anche se ha aperto un nuovo problema, la mancanza di controllo del centro di rotazione. Potremmo ricavarne la soluzione da questa risposta, calcolando un "binario" su cui giace il centro di rotazione per ogni t. –

+0

@JulianHabekost In generale non sarebbe possibile indovinare il percorso centrale di rotazione avendo solo le matrici. Una rotazione combinata con la traduzione è in realtà una rotazione attorno a un altro centro, quindi la matrice perde le informazioni sul centro di rotazione originale quando si eseguono le traduzioni. – mik