Trigonometrie-Trick

Höchstwahrscheinlich kennen Sie die folgenden VerhÀltnisse aus der Schule:


 sin( alpha+ beta)= sin alpha times cos beta+ cos alpha times sin beta cos( alpha+ beta)= cos alpha times cos beta− sin alpha times sin beta


Als Sie diese Formel zum ersten Mal in Ihrer Kindheit kennen lernten, war Ihr erstes GefĂŒhl höchstwahrscheinlich Schmerz, da diese Formel in Erinnerung bleiben muss. Dies ist sehr schlecht, da Sie sich diese Formel nicht merken mĂŒssen - sie wird angezeigt, wenn Sie das Dreieck auf Papier drehen. TatsĂ€chlich mache ich dasselbe, wenn ich diese Formel aufschreibe. Diese Interpretation wird in der Mitte dieses Artikels ersichtlich. Aber jetzt, um den ganzen Spaß fĂŒr spĂ€ter zu lassen und den Moment zu verschieben, in dem Sie „Eureka!“ Sagen, ĂŒberlegen wir uns, warum wir ĂŒberhaupt ĂŒber diese Formel nachdenken sollten.



Eintrag


Die trigonometrischen Funktionen sin() und cos() in der Computergrafik wahrscheinlich am beliebtesten, da sie die Grundlage fĂŒr die parametrische Beschreibung einer runden Form bilden. Zu den Orten ihrer möglichen Anwendung gehören die Erzeugung von Kreisen oder volumetrischen Rotationsobjekten bei der Berechnung der Fourier-Transformation, die prozedurale Erzeugung von Wellen auf der Wasserebene, Generatoren fĂŒr einen Software-Klangsynthesizer und so weiter. In all diesen FĂ€llen werden sin() und cos() innerhalb der Schleife aufgerufen, wie hier:


 for(int n=0; n < num; n++) { const float t = 2.0f*PI*(float)n/(float)num; const float s = sinf(t); const float c = cosf(t); // do something with s and c ... } 

Wir beginnen, den Zyklus schrittweise umzuschreiben (siehe den folgenden Code), so dass wir uns leichter vorstellen können, dass bei der Iteration n dieses Zyklus mit der Phase t die nĂ€chste Iteration n+1 sin() und cos() fĂŒr t+f berĂŒcksichtigt. Mit anderen Worten, sin(t) und cos(t) werden fĂŒr uns gezĂ€hlt und wir mĂŒssen sin(t+f) und cos(t+f) :


 const float f = 2.0f*PI/(float)num; const float t = 0.0f; for( int n=0; n < num; n++ ) { const float s = sinf(t); const float c = cosf(t); // do something with s and c ... t += f; } 

Es spielt keine Rolle, wie wir t und wie hoch sein Wertebereich ist (im obigen Beispiel - [0;2 pi]) Das einzige, was uns stört, ist, dass es eine Schleife gibt, die stĂ€ndig sin() und cos() mit einem Parameter aufruft, der in konstanten Schritten zunimmt (in diesem Fall,  frac2 pi textnum) In diesem Artikel geht es darum, wie Sie diesen Code fĂŒr die Geschwindigkeit so optimieren können, dass dieselben Berechnungen ĂŒberhaupt durchgefĂŒhrt werden können, ohne die Funktionen sin() oder cos() (in der inneren Schleife) und sogar die schnellere Funktion sincos() verwenden.


Wenn Sie sich jedoch die erste Formel im Artikel ansehen, werden wir sehen, ob f= alphaund t= betawir können dies umschreiben als


 sin(t+f) = sin(f)*cos(t) + cos(f)*sin(t) cos(t+f) = cos(f)*cos(t) - sin(f)*sin(t) 

oder mit anderen Worten


 new_s = sin(f)*old_c + cos(f)*old_s new_c = cos(f)*old_c - sin(f)*old_s 

Da f eine Konstante ist, sind auch sin(f) und cos(f) . Nennen Sie sie a bzw. b :


 new_s = b*old_c + a*old_s new_c = a*old_c - b*old_s 

Dieser Ausdruck kann direkt in unserem Code verwendet werden, und dann erhalten wir eine Schleife, in der teure (und tatsÀchlich keine) trigonometrische Funktionen aufgerufen werden!


 const float f = 2.0f*PI/(float)num; const float a = cosf(f); const float b = sinf(f); float s = 0.0f; float c = 1.0f; for( int n=0; n < num; n++ ) { // do something with s and c ... const float ns = b*c + a*s; const float nc = a*c - b*s; c = nc; s = ns; } 

Interpretation


Bisher haben wir blind mit Mathe gespielt und nicht verstanden, was wirklich passiert. Schreiben wir die innere Schleife folgendermaßen um:


sn+1=sna+cnbcn+1=cna−snb


Einige von Ihnen haben vielleicht bemerkt, dass dies eine Formel zum Drehen eines Objekts im zweidimensionalen Raum ist. Wenn Sie dies immer noch nicht verstehen, hilft Ihnen möglicherweise die Matrixform.


\ left (\ begin {matrix} s_ {n + 1} \\ c_ {n + 1} \ end {matrix} \ right) = \ left (\ begin {matrix} a & b \\ -b & a \ end {matrix} \ right) \ cdot \ left (\ begin {matrix} s_ {n} \\ c_ {n} \ end {matrix} \ right)


TatsĂ€chlich können sin(t) und cos(t) zu einem Vektor der LĂ€nge 1 gruppiert und als Punkt auf dem Bildschirm gezeichnet werden. Nennen Sie diesen Vektor x . Dann x = \ {\ sin \ beta, \ cos \ beta \} . Die Vektorform des Ausdrucks ist also xn+1=Rxnwo R = \ left (\ begin {matrix} a & b \\ - b & a \ end {matrix} \ right) . Wir sehen, dass unsere Schleife bei jeder Iteration eine kleine zweidimensionale Drehung ausfĂŒhrt, so dass sich x wĂ€hrend der AusfĂŒhrung des Zyklus in einem Kreis dreht. Jede Iteration x dreht sich um  frac2 pi textnumBogenmaß.
Also im Grunde


 sin( alpha+ beta)= sin alpha times cos beta+ cos alpha times sin beta cos( alpha+ beta)= cos alpha times cos beta− sin alpha times sin beta


Dies ist die Punktbewegungsformel x = \ {\ cos \ alpha, \ sin \ alpha \} Umfang in Schritten von  betaBogenmaß. Dazu konstruieren wir eine von zwei Rotationsachsen, zum Beispiel u = \ {\ cos \ beta, \ sin \ beta \} . Die erste Komponente der Rotation ist die Projektion xauf u. Als xund unormalisiert (haben eine LĂ€nge von 1), ist die Projektion ihr Skalarprodukt. Deshalb s=x cdotu= sin alpha cdot cos beta+ cos alpha cdot sin betaund natĂŒrlich ist die zweite Komponente die Antiprojektion, die durch Projizieren auf die senkrechte Achse gefunden werden kann. v. Wir können diesen Vektor erstellen, indem wir die Koordinaten erweitern uund Ă€ndern Sie das Vorzeichen an der ersten Koordinate in das Gegenteil: c=x cdotv= cos alpha cdot cos beta+ sin alpha cdot sin beta


Anmerkungen


Normalerweise sollten Sie in der Lage sein, diese winzigen Rotationen immer wieder auszufĂŒhren. TatsĂ€chlich, | R | = \ left | \ begin {matrix} a & b \\ - b & a \ end {matrix} \ right | = a ^ 2 + b ^ 2 = \ sin ^ 2 \ alpha + \ cos ^ 2 \ alpha = 1 , was bedeutet, dass die Matrix RvergrĂ¶ĂŸert oder verkleinert nicht den Raum, auf den es angewendet wird, was bedeutet, dass xwird sich in einem perfekten Kreis bewegen. Da Computer jedoch nicht genau sind, xbewegt sich spiralförmig und fĂ€llt schließlich mit dem Mittelpunkt des Rotationskreises zusammen. Ich hatte solche Probleme nicht, aber ich denke, dass sie mit sehr großer num , d.h. kleine Drehwinkel.


Beispiel


In Kindercrasher , der 4096-Byte-Demo von 2008 (Screenshot auf KDPV), pulsiert eine Gruppe von Kugeln zur Musik. DafĂŒr habe ich die Fourier-Transformation des Klangs gezĂ€hlt. Es gibt Algorithmen, die dies in Echtzeit tun, beispielsweise FFT . Mein Code sollte jedoch in ein paar Kilobyte passen, und ich entschied mich fĂŒr den anderen Weg. Anstatt FFT zu implementieren, habe ich DFT durch seine einfache Definition geschrieben. Sie können es auf Wikipedia ĂŒberprĂŒfen.


Xk= sumn=0N−1xne− frac2 piiNkn quadk=0,1, ldots,N−1


Meine Funktion verwendet auch einen 16-Bit-Stereo-Audiopuffer x und gibt die ersten 128 Frequenzen des Klangspektrums von y . Sehen Sie, wie die innere Schleife organisiert ist, die 4096-mal ausgefĂŒhrt wird: kein einziger Aufruf der Funktionen sin() oder cos() , obwohl dies in anderen Implementierungen der Fall sein wird.


 #include <math.h> void iqDFT12( float *y, const short *x ) { for( int i=0; i<128; i++ ) { const float wi = (float)i*(2.0f*3.1415927f/4096.0f); const float sii = sinf( wi ); const float coi = cosf( wi ); float co = 1.0f; float si = 0.0f; float acco = 0.0f; float acsi = 0.0f; for( int j=0; j<4096; j++ ) { const float f = (float)(x[2*j+0]+x[2*j+1]); const float oco = co; acco += co*f; co = co*coi - si*sii; acsi += si*f; si = si*coi + oco*sii; } y[i] = sqrtf(acco*acco+acsi*acsi)*(1.0f/32767.0f); } } 

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


All Articles