Matematiken och ActionScript of Curves Roots

I den första handledningen av denna serie tittade vi på ritningskurvor med hjälp av ekvationer och AS3. Nu ska vi ta itu med att lösa dessa ekvationer för att hitta roten av en kurva - det vill säga de platser där kurvan korsar en viss rak linje. Vi kan använda detta för att förutse kollisioner med böjda ytor, och för att undvika "tunneling" i Flash-spel.


Steg 1: Kvadratiska rötter

Först, tid för lite snabb mathrevision. I den här handledningen accepterar vi bara och tillämpar de metoder vi använder, men intresserade läsare kan hänvisa till Wikipedias sida om kvadratiska ekvationer för information om de matematiska avledningarna.

Så \ (f (x) \) är en kvadratisk funktion. Om \ (f (x) \) motsvarar 0, kan \ (x \) erhållas med följande formel:

\ [Givet \ f (x) \ = \ ax ^ 2 + bx + c, \ \]
\ [f (x) \ = \ 0, \ x = \ frac -b \ pm \ sqrt b ^ 2 - 4ac 2a \]

\ (b ^ 2 - 4ac \) kallas diskriminantanalys av formeln. Om diskriminanten är negativ kommer kvadratroten av diskriminanten att producera imaginära rötter, som vi inte kan plotta. Omvänt, om diskrimineringen är positiv, kommer du att ha riktiga antal rötter och du kommer att kunna plotta dem på skärmen.


Steg 2: Visualisering av kvadratiska rötter

Så vad är rötterna? Tja i vårt sammanhang är de inget mer än korsningspunkter mellan den kvadratiska kurvan och en linje. Antag att vi är intresserade av att hitta skärningspunkten i följande ekvationssätt:

\ (
f (x) \ = \ ax ^ 2 + bx + c \\
g (x) \ = \ 0
\)

Detta är ett typiskt scenario för att leta efter korsningspunkten mellan en kvadratisk kurva och x-axeln (eftersom x-axeln är linjen där y == 0). Eftersom kryssningspunkten delas delas av \ (f (x) \) och \ (g (x) \), kan vi konstatera att \ (f (x) = g (x) \) för värdena av x som vi letar efter.

Det är då en trivial operation där du bara ersätter funktionerna och sedan tillämpar formeln från steg 1 för att få rötterna. Nu finns det flera möjligheter som vi kan förutse som visas nedan.

(Som du kan se, betyder "imaginära rötter" för våra syften att kurvan inte någonsin korsar x-axeln.)

Låt oss nu överväga fallet där \ (g (x) \) är mer än bara en vardaglig horisontell linje. Låt oss säga att det är en sned linje, \ (g (x) \ = \ mx \ + \ d \). Nu när vi jämställer båda funktionerna måste vi göra en liten precalculation innan formeln kan tillämpas effektivt.

\ [
ax ^ 2 \ + \ bx + c \ = \ mx \ + \ d \\
ax ^ 2 \ + \ (\ b \ - m) \ x + (c \ - \ d) \ = \ 0
\]

Jag har inkluderat en interaktiv Flash-presentation nedan, så gärna dra de röda och blåa punkterna. Gula prickar indikerar korsningspunkterna. Du kan behöva placera kurvan och linjen för att korsa varandra för att de gula prickarna ska visas.


Steg 3: Plottar detta med ActionScript

Det fullständiga manuskriptet finns i Demo1.as; här ska jag bara förklara ett viktigt utdrag av koden. Låt oss titta på AS3 för att rita kurvan och linjen:

 privata funktionen redraw (): void var cmd: Vector. = ny vektor.; var koordinat: vektor. = ny vektor.; // redraw kurva; m1 = ny Matrix3d ​​(curve_points [0] .x * curve_points [0] .x, curve_points [0] .x, 1, 0, curve_points [1] .x * curve_points [1] .x, curve_points [1] .x , 1, 0, curve_points [2] .x * curve_points [2] .x, curve_points [2] .x, 1, 0, 0,0,0,1); m2 = ny Matrix3d ​​(curve_points [0] .y, 0, 0, 0, curvepoints [1] .y, 0, 0, 0, curvepoints [2] .y, 0, 0, 0, 0,0,0, 1) m1.invert (); m2.append (m1); quadratic_equation.define (m2.n11, m2.n21, m2.n31); för (var i: int = 0; i < stage.stageWidth; i+=2)  if (i == 0) cmd.push(1); else cmd.push(2); coord.push(i, quadratic_equation.fx_of(i));  //draw line n1 = new Matrix(); n1.a = line_points[0].x; n1.c = 1; n1.b = line_points[1].x; n1.d = 1; n2 = new Matrix(); n2.a = line_points[0].y; n2.c = 0; n2.b = line_points[1].y; n2.d = 0; n1.invert(); n2.concat(n1); var x:Number = stage.stageWidth //y = mx + c cmd.push(1); coord.push(0, n2.a * 0 + n2.b); cmd.push(2); coord.push(x, n2.a * x + n2.b); graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); 

Huvuddelen av ActionScript för att rita kurvan från rad 80 ~ 104 lånas i stor utsträckning från den tidigare handledningen, så jag ska bara förklara lite om koden för att dra en linje.

I Flash-presentationen ovan finns det två interaktiva blå prickar. Var och en av dessa har koordinater, och med båda prickarna bildas en linje. Eftersom båda punkterna ligger på samma linje delar de en gemensam sluttning och y-avlyssning för att bilda en generell linje ekvation:

\ [
y \ = \ mx \ + \ d, \\
m \ = \ sluttning, \ d \ = \ y-intercept
\]

Vi kan använda en liten algebra för att lösa de två okända, \ (m \) och \ (d \). Med tanke på koordinaterna för de två blå prickarna som \ ((x_1, \ y_1) \) och \ ((x_2, \ y_2) \):

[latex]
y_1 = mx_1 + d \\
y_2 = mx_2 + d \\
[/latex]

[latex]
\ begin bmatrix y_1 \\ y_2 \ end bmatrix =
\ begin bmatrix x_1 & 1 \\ x_2 & 1 \ end bmatrix
\ begin bmatrix m \\ d \ end bmatrix \\
[/latex]

[latex]
\ begin bmatrix x_1 och 1 \\ x_2 & 1 \ end bmatrix ^ - 1
\ begin bmatrix y_1 \\ y_2 \ end bmatrix =
\ begin bmatrix x_1 och 1 \\ x_2 & 1 \ end bmatrix ^ - 1
\ begin bmatrix x_1 & 1 \\ x_2 & 1 \ end bmatrix
\ begin bmatrix m \\ d \ end bmatrix \\
[/latex]

[latex]
\ begin bmatrix x_1 och 1 \\ x_2 & 1 \ end bmatrix ^ - 1
\ begin bmatrix y_1 \\ y_2 \ end bmatrix =
jag
\ begin bmatrix m \\ d \ end bmatrix
[/latex]

(Observera att en matris med superskriven -1 hänvisar till invers av den matrisen.)

Används här, beräknas \ (m \) och \ (d \). Vi kan nu rita linjen genom att gå med i koordinaterna \ ((0, y_3) \) och \ ((stage.stageWidth, y_4) \). Hur hittar du \ (y_3 \) och \ (y_4 \)? Tja nu när \ (m \), \ (x \) och \ (d \) är kända kan vi helt enkelt sätta alla dessa värden i linjen allmänna ekvationen,

\ (y \ = \ mx \ + \ d \)

... för att få dem \ (y \) s.


Steg 4: Beräkna kvadratiska rötterna

För att beräkna positionen för skärningspunkterna ska vi använda formeln från steg 1. Detta görs i EqQuadratic.as som funktionerna nedan:

 / ** Endast skrivskyddad * Diskriminering av ekvation * / Offentlig funktion får diskriminator (): Nummer // B * B-4 * A * C retur _B * _B - 4 * _A * _C;  / ** * Utför beräkning för att få rötter * / allmän funktion calcRoots (): void var disk: Number = this.discriminant // hantera imaginära rötter om (skiva < 0)  disc *= -1; var component_real:Number = -_B / (2 * _A); var component_imaginary:Number = Math.sqrt(disc) / (2 * _A); _root_i[0] = (component_real + "+ i" + component_imaginary).toString(); _root_i[1] = (component_real + "- i" + component_imaginary).toString();  //handle real roots else  var sqrt:Number = Math.sqrt(disc); _root_R[0] = ( -_B + sqrt) / (2 * _A); _root_R[1] = ( -_B - sqrt) / (2 * _A);  

Ytterligare detaljer om EqQuadratic.as:

Fungera Typ Input Parameter Funktionalitet
EqQuadratic Metod Noll Klasskonstruktor
definiera Metod Koefficienter a, b och c av kvadratisk ekvation Instansiera koefficientvärdena
fx_of Metod Värdet av x Returnerar \ (f (x) \) av given \ (x \) inmatning.
calcRoots Metod Noll Utför beräkning för att få kvadratisk rot
diff1 Metod \ (x \) koordinat för första graders differentiering Differentierad \ (f (x) \) av given \ (x \) vid första graden.
diff2 Metod \ (x \) koordinat för andra graders differentiering Differentierad \ (f (x) \) av given \ (x \) vid andra graden.
diskriminera Fastighet, läs bara Noll Returnerar värdet av diskriminant, \ (b ^ 2 - 4ac \)
roots_R Fastighet, läs bara Noll Returnerar en talvektor för rötter av reellt tal. Elementen är NaN om inga verkliga rötter finns.
roots_i Fastighet, läs bara Noll Returnerar en strängvektor för rötter av imaginärt tal. Elementen är null om inga imaginära rötter finns.

Steg 5: Plottar detta med ActionScript

Ett exempel på att använda detta EqQuadratic.as är i Demo1.as. Efter initieringen av EqQuadratic, Vi ska använda den för att beräkna rötterna. Sedan, efter att ha validerat förekomsten av riktiga rötter, använder vi dem för att plotta de gula prickarna.

Nu refererar rötterna till endast \ (x \) komponenten i koordinaterna. För att få \ (y \) s, gissa vad? Återigen sätter vi värdena på \ (m \), \ (d \) (beräknad tidigare i steg 3) och \ (x \) (från rötterna) till linjens generella ekvation, \ (y \ = \ mx \ + \ d \). Kolla in motsvarande kod i rad 135 och 136.

 privat funktion omberäkning_reposition (): void quadratic_equation.define (m2.n11, m2.n21 - n2.a, m2.n31 - n2.b); quadratic_equation.calcRoots (); varrötter: vektor. = quadratic_equation.roots_R; om (! isNaN (rötter [0]) &&! isNaN (rötter [1])) intersec_points [0] .x = rötter [0]; intersec_points [0] .y = n2.a * rötter [0] + n2.b intersec_points [1] .x = rötter [1]; intersec_points [1] .y = n2.a * rötter [1] + n2.b annars intersec_points [0] .x = -100; intersec_points [0] .y = -100; intersec_points [1] .x = -100; intersec_points [1] .y = -100; 

Steg 6: Kubiska rötter

Kubiska rötter, inte överraskande, är korsningspunkterna mellan a kubisk kurva och en linje. Men en kubisk kurva är lite annorlunda än en kvadratisk kurva, och i detta avseende kan möjligheterna för var korsningar ligga att vara olika.

Bilden nedan visar en kubisk kurva som skär med x-axeln:

Återigen, här är en liten Flash-presentation för att du ska experimentera med. Röda och blåa prickar kan släpas medan de gula bara visar skärningspunkterna.


Steg 7: Allmän formel för kubiska rötter

Den allmänna formeln för att hitta en kubisk kurva upptäcktes av Cardano. Även om jag är ledsen att utarbeta detaljerna, pekar jag bara på intresserade läsare på följande länkar:

  • Wikipedia
  • Wolfram och
  • En annan användarvänlig PDF-fil.

Hur som helst, EqCubic.as klassen implementerar denna formel för att lösa rötter av kubiska funktioner tillsammans med andra matematiska verktygsfunktioner. Generellt alla attribut och metoder för EqCubic.as följ beskrivningen som framlagt i steg 4, eftersom båda klasserna EqQuadratic.as och EqCubic.as implementera ett gemensamt gränssnitt, IEquation.as, med undantag för uppgifterna nedan.

Fungera Skillnad
definiera Totalt fyra koefficienter (a, b, c, d) för inmatning för kubisk ekvation; bara tre för kvadratisk ekvation.
roots_R, root_i Summan av reella och imaginära rötter är tre för en kubisk ekvation, men två för en kvadratisk ekvation.

Steg 8: Plottar detta med ActionScript

Här är ActionScript-implementeringen för Flash-presentationen från steg 5. Den fullständiga koden är i Demo3.as.

 privata funktionen redraw (): void var cmd: Vector. = ny vektor.; var koordinat: vektor. = ny vektor.; // redraw kurva; m1 = ny Matrix3d ​​(curve_points [0] .x * curve_points [0] .x * curve_points [0] .x, curve_points [0] .x * curve_points [0] .x, curve_points [0] .x, 1, curve_points [1] .x * curve_points [1] .x * curve_points [1] .x, curve_points [1] .x * curve_points [1] .x, curve_points [1] .x, 1, curve_points [2] .x * curve_points [2] .x * curve_points [2] .x, curve_points [2] .x * curve_points [2] .x, curve_points [2] .x, 1, curve_points [3] .x * curve_points [3] .x * curve_points [3] .x, curve_points [3] .x * curve_points [3] .x, curve_points [3] .x, 1); m2 = ny Matrix3d ​​(curve_points [0] .y, 0, 0, 0, curve_points [1] .y, 0, 0, 0, curve_points [2] .y, 0, 0, 0, curve_points [3] .y , 0, 0, 0) m1.invert (); m2.append (m1); cubic_equation.define (m2.n11, m2.n21, m2.n31, m2.n41); för (var i: int = 0; i < stage.stageWidth; i+=2)  if (i == 0) cmd.push(1); else cmd.push(2); coord.push(i, cubic_equation.fx_of(i));  //draw line n1 = new Matrix(); n1.a = line_points[0].x; n1.c = 1; n1.b = line_points[1].x; n1.d = 1; n2 = new Matrix(); n2.a = line_points[0].y; n2.c = 0; n2.b = line_points[1].y; n2.d = 0; n1.invert(); n2.concat(n1); var x:Number = stage.stageWidth //y = mx + c cmd.push(1); coord.push(0, n2.a * 0 + n2.b); cmd.push(2); coord.push(x, n2.a * x + n2.b); graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); 

Återigen är ActionScript-kommandon för att rita en kubikkurva exakt densamma som förklaras i min tidigare artikel medan ActionScript-kommandon för att rita linjen redan förklaras i steg 3 i den här.

Låt oss nu fortsätta att beräkna och placera de kubiska rötterna:

 privat funktion omberäkning_reposition (): void cubic_equation.define (m2.n11, m2.n21, m2.n31 - n2.a, m2.n41 - n2.b); cubic_equation.calcRoots (); varrötter: vektor. = cubic_equation.roots_R; för (var i: int = 0; i < roots.length; i++)  if (!isNaN(roots[i]))  intersec_points[i].x = roots[i]; intersec_points[i].y = n2.a * roots[i] + n2.b  else  intersec_points[i].x = -100; intersec_points[i].y = -100;   

Efter instantiating cubic_equation i konstruktören fortsätter vi att definiera dess koefficienter, beräkna rötterna och lagra rötterna i en variabel.

En liten anteckning på rötterna: det finns högst tre riktiga rötter för en kubisk ekvation, men inte alla riktiga rötter är närvarande i alla situationer, eftersom vissa rötter kan vara imaginära. Så vad händer när det bara finns en riktig rot, till exempel? Tja, en av de rötter som kallas från cubic_equation.roots_R kommer att vara ett riktigt tal, medan alla de andra kommer inte att vara ett nummer (NaN). Kolla in den markerade ActionScript för detta.


Steg 9: Förutsägande där ett objekt kommer att kollidera med böjd yta

En bra tillämpning av beräkning av rötter utskjuter en kollisionspunkt på krökt yta, som visas nedan. Använd vänster och höger piltangenter för att styra det rörliga skeppet och tryck på för att accelerera. Du kommer märka att kollisionspunkter som skulle ha hänt i det förflutna är något dämpade.


Steg 10: Implementering

Tanken liknar den i min handledning om att förutsäga kollisionspunkter. I stället för att kollidera med en rak linje använder vi nu en böjd linje. Låt oss kolla koden.

Biten nedan kallas varje ram:

 privatfunktionsuppdatering (e: Event): void // Styrning åt vänster och höger om (kontroll == 1) velo = velo.rotate (Math2.radianOf (-5)); annars om (kontroll == 2) velo = velo.rotate (Math2.radianOf (5)); // manipulera hastighet var currVelo: Number = velo.getMagnitude (); om (ökning == 0) currVelo - = 0,5; currVelo = Math.max (currVelo, 1); // lägre bunden för hastighet annars om (ökning == 1) currVelo + = 0.5; currVelo = Math.min (currVelo, 5); // övre gränsen för hastighet velo.setMagnitude (currVelo); // uppdateringshastighet ship.x + = velo.x; ship.y + = velo.y; ship.rotation = Math2.degreeOf (velo.getAngle ()); // reflektera när fartyget är borta om (ship.x <0 || ship.x > stage.stageWidth) velo.x * = -1; om (ship.y <0 || ship.y > stage.stageHeight) velo.y * = -1; rita om(); omberäkna (); 

Kärnkoden ligger i rita om och omberäkna. Låt oss först se vad som finns i rita om. Det är samma som vi hade använt i tidigare demos. En liten anteckning om att rita linjen. Vi såg i tidigare demos att två prickar behövs för att rita få ekvationen. Tja, här har vi bara ett skepp. Så för att få den andra punkten, lägg bara fartygets hastighet till sin nuvarande position. Jag har markerat koden för enkelhets skull.

 privata funktionen redraw (): void var cmd: Vector. = ny vektor.; var koordinat: vektor. = ny vektor.; // redraw kurva; m1 = ny Matrix3d ​​(w1.x * w1.x, w1.x, 1, 0, w2.x * w2.x, w2.x, 1, 0, w3.x * w3.x, w3.x, 1 , O, 0,0,0,1); m2 = ny Matrix3d ​​(w1.y, 0, 0, 0, w2.y, 0, 0, 0, w3.y, 0, 0, 0, 0.0,0,1) m1.invert (); m2.append (m1); quadratic_equation.define (m2.n11, m2.n21, m2.n31); minX = Math.min (w1.x, w2.x, w3.x); maxX = Math.max (w1.x, w2.x, w3.x); för (var i: int = minX; jag < maxX; i+=2)  if (i == minX) cmd.push(1); else cmd.push(2); coord.push(i, quadratic_equation.fx_of(i));  n1 = new Matrix(); n1.a = ship.x; n1.c = 1; n1.b = ship.x + velo.x; n1.d = 1; n2 = new Matrix(); n2.a = ship.y; n2.c = 0; n2.b = ship.y + velo.y; n2.d = 0; n1.invert(); n2.concat(n1); var x:Number = stage.stageWidth //y = mx + c cmd.push(1); coord.push(0, n2.a * 0 + n2.b); cmd.push(2); coord.push(x, n2.a * x + n2.b); graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); 

Nu för omberäkna, Jag har gjort en liten vektorberäkning för att kontrollera om punkten ligger bakom eller framför skeppet. Kolla in den markerade koden:

 privat funktion omräknas (): void quadratic_equation.define (m2.n11, m2.n21 - n2.a, m2.n31 - n2.b); quadratic_equation.calcRoots (); varrötter: vektor. = quadratic_equation.roots_R; för (var i: int = 0; i < roots.length; i++)  var reposition:Sprite = getChildByName("c" + i) as Sprite //conditions: //real root, value of x within the range if (!isNaN(roots[i]) && roots[i] > minX && rötter [i] < maxX)  reposition.x = roots[i]; reposition.y = n2.a * roots[i] + n2.b; //discriminating between future and already happened collision point var vec:Vector2D = new Vector2D(reposition.x - ship.x, reposition.y - ship.y); if (velo.dotProduct(vec) < 0) reposition.alpha = 0.4; else reposition.alpha = 1  else  reposition.x = -100; reposition.y = -100;   

Steg 11: Tidsbaserad kollisionsdetektion

En annan bra applikation är inte lika uppenbar som den första. För att utföra en mer exakt kollisionsdetektering, istället för att basera vår slutsats på avståndet mellan två objekt, kommer vi att se deras dags att påverka. Varför? Eftersom "tunneling" kan hända om vi använder distansbaserad kollisionsdetektering:

Överväg en kollisionsdetekteringsalgoritm som bygger på avstånd för två cirklar. Av de fyra visade ramarna upptäckte endast ram 2.15 framgångsrikt kollision mellan två cirklar. Varför? Eftersom det aktuella avståndet mellan de grå och röda cirkelcentralerna är mindre än summan av båda cirklarnas radier.

(Läsare som är intresserade av mer information om detta ämne kan hänvisa till den här artikeln.)

\ [avstånd \ mellan \ cirklar \

Problemet orsakas av hur Flash fortsätter med en diskret ram i taget, vilket innebär att endast ramar 1, 2 och 3 kommer att fångas med framgång, och inte stunderna mellan dessa ögonblicksbilder. Nu kolliderades inte de grå och röda cirklarna i dessa ramar enligt en distansbaserad beräkning, så de röda cirkeltunnlarna genom den gråa!

För att åtgärda detta behöver vi ett sätt att se kollisionen som inträffade mellan ramar 2 och 3. Vi måste beräkna tiden för att påverka mellan två cirklar. När vi exempelvis kontrollerat att tiden för att påverka är mindre än 1 ram vid ram 2, innebär det att en gång Flash fortsätter 1 ram framåtkollision eller ens tunnling har definitivt kommit.

\ [if \ time \ to \ impact, \ t, \ uppfyller \ 0 \

Frågan är hur vi beräknar den här tiden?


Steg 12: Förutberäkningar

Jag ska försöka visa min metod så enkelt som möjligt.

Med tanke på scenariot ovan är de två grå och röda cirklarna för närvarande placerade i \ ((x_ grå, \ y_ grå) \) och \ ((x_ röd, \ y_ röd) \). De rör sig vid \ (v_ gray \) och \ (v_ red \) och ställs in på en kollisionsväg. Vi är intresserade av att beräkna den tid som tagits, \ (t \) för att de ska nå positionerna \ ((x '_ grå, \ y' _ grå) \) och \ , \ y '_ röd) \), indikerad av de genomskinliga grå och röda cirklarna, där kollisionen inträffade.

\ [
displacement_ future = displacement_ present + hastighet * tid \\
x '_ grå = x_ grå + v_ gray_x * t \ ... (ekv. \ 1) \\
y '_ grå = y_ grå + v_ gray_y * t \ ... (ekv. \ 2) \\
x '_ röd = x_ röd + v_ red_x * t \ ... (ekv. \ 3) \\
y 'röd = y_ röd + v_ red_y * t \ ... (ekv. \ 4)
\]

Observera att jag har härledt de horisontella och vertikala komponenterna i \ (v_ gray \) till \ (v_ gray_x \) och \ (v_ gray_y \). Samma gäller med den röda cirkelns hastighet; kolla in denna snabba tips för att lära känna hur dessa komponenter är härledda.

Vi saknar fortfarande ett förhållande att binda alla dessa ekvationer tillsammans. Låt oss se bilden nedan.

Det andra förhållandet går tillbaka till Pythagoras. När båda cirklarna möts är avståndet mellan båda centra exakt \ (rad_ gray \) plus \ (rad_ red \).

\ [
Pythagoras \ Theorem, \ z ^ 2 = x ^ 2 + y ^ 2 \\
(Rad_ grå + rad_ röd) ^ 2 = (x '_ grå -x' _ röd) ^ 2 + (y '_ grå -y' _ röd) ^ 2 \ ... (ekv. \ 5) \\
\]

Här ersätter du ekvationerna 1 ~ 4 i ekvation 5. Jag förstår det ganska skrämmande matematiskt, så jag har separerat det i steg 13. Tänk på att hoppa över det för att komma fram till resultatet i steg 14.


Steg 13 (Valfritt): Matematisk Rigor

Först fastställer vi följande identiteter.

\ [
Identitet,\\
(a + b) ^ 2 = a ^ 2 + 2ab + b ^ 2 \ ... (id. \ 1) \\
(a-b) ^ 2 = a ^ 2-2ab + b ^ 2 \ ... (id. \ 2) \\
\]

Tänk alltid att alla matematiska symboler representerar en konstant förutom tid, \ (t \), vilket är ämnet av intresse.

\ (x_ gray, \ v_ gray_x, \ y_ red, \) och så vidare definieras alla i scenariot.

Därefter försöker vi bryta vårt problem på sikt efter sikt:

\ [
(Rad_ grå + rad_ röd) ^ 2 = (x '_ grå -x' _ röd) ^ 2 + (y '_ grå -y' _ röd) ^ 2 \ \
Betrakta \ term \ (x '_ grå -x' _ röd) ^ 2 \ och \ använda \ id. \ 2 \\
(x '_ grå -x' _ röd) ^ 2 = (x '_ grå) ^ 2-2 (x' _ grå) (x '_ röd) + _ röd) ^ 2 \\
\]
\ [
Tänk på \ term \ (x '_ grå) ^ 2 \\
(X '_ grå) ^ 2 \\
= (x_ grå + v_ gray_x * t) ^ 2, \ använd \ id. \ 1 \\
= (X_ grå) ^ 2 + 2 (X_ grå) (V_ gray_x * t) + (V_ gray_x * t) ^ 2
\]
\ [
Betrakta \ term \ -2 (x '_ grå) (x' _ röd) \\
-2 (x '_ grå) (x' _ röd) \\
= -2 (X_ grå + V_ gray_x * t) (X_ röd + V_ red_x * t) \\
= -2 [(X_ grå) (X_ röd) + (X_ grå) (V_ red_x * t) + (V_ gray_x * t) (X_ röd) + (V_ gray_x * t) (V_ red_x * t)] \\
= -2 (X_ grå) (X_ röd) - 2 (X_ grå) (V_ red_x * t) -2 (V_ gray_x * t) (X_ röd) - 2 ( V_ gray_x * t) (V_ red_x * t)
\]
\ [
Tänk på \ term \ (x '_ red) ^ 2 \\
(X '_ röd) ^ 2 \\
= (x_ röd + v_ red_x * t) ^ 2, \ använd \ id. \ 1 \\
= (X_ röd) ^ 2 + 2 (X_ röd) (V_ red_x * t) + (V_ red_x * t) ^ 2
\]

Nu kan vi snabbt se den högsta effekten av \ (t \) är 2. Så vi har oss själva en kvadratisk ekvation. Låt oss samla alla koefficienter som bidrar med dessa tre villkor enligt deras befogenheter.

\ (T ^ 2 \) \ (T \) \ (T ^ 0 = 1 \)
\ ((V_ gray_x) ^ 2 \) \ (2 (X_ grå) (V_ gray_x) \) \ ((X_ grå) ^ 2 \)
\ (- 2 (V_ gray_x) (V_ red_x) \) \ (- 2 (X_ grå) (V_ red_x) - 2 (V_ gray_x) (X_ röd) \) \ (- 2 (X_ grå) (X_ röd) \)
\ ((V_ red_x) ^ 2 \) \ (2 (X_ röd) (V_ red_x) \) \ ((X_ röd) ^ 2 \)

Låt oss analysera koefficienterna med \ (t ^ 2 \) och \ (t ^ 0 \).

\ [
(v_ gray_x) ^ 2-2 (v_ gray_x) (v_ red_x) + (v_ red_x) ^ 2, \ återkalla \ id. \ 2 \\
(v_ gray_x) ^ 2-2 (v_ gray_x) (v_ red_x) + (v_ red_x) ^ 2 = (v_ gray_x -v_ red_x) ^ 2
\]
\ [
(x_ grå) ^ 2-2 (x_ grå) (x_ röd) + (x_ röd) ^ 2, \ återkalla \ id. \ 2 \\
(x_ grå) ^ 2-2 (x_ grå) (x_ röd) + (x_ röd) ^ 2 = (x_ grå -x_ röd) ^ 2
\]

Och det för \ (t \).

\ [
Förenkla\\
a = (x_ grå), \ b = (v_ gray_x) \\
c = (v_ red_x), \ d = (x_ röd) \\
2ab-2AC-2bd + 2dc \\
= 2 [ab-ac-bd + dc] \\
= 2 [a (b-c) -d (b-c)] \\
= 2 [(b-c) (a-d)] \\
Resubstitute \\
2 [(b-c) (a-d)] = 2 (v_ gray_x -v_ red_x) (x_ grå -x_ röd)
\]

Låt oss sammanfatta i termen av \ ((x '_ grå -x' _ röd) ^ 2 \)

\ [
(X '_ grå -x' _ röd) ^ 2 \\
= v_ gray_x -v_ red_x) ^ 2 * t ^ 2 + 2 (v_ gray_x -v_ red_x) (x_ grå -x_ röd) * t + (x_ grå -x_ röd) ^ 2
\]

Observera att detta endast gäller för en term i \ (ekv. \ 5 \). Vi måste utföra samma process för en annan term \ ((y '_ grå -y' _ röd) ^ 2 \). Eftersom de har samma algebraiska form, bör resultatet också vara detsamma.
\ [
(Y '_ grå -y' _ röd) ^ 2 \\
= v_ gray_y -v_ red_y) ^ 2 * t ^ 2 + 2 (v_ gray_y -v_ red_y) (y_ grå -y_ röd) * t + (y_ grå -y_ röd) ^ 2
\]

Således efter omplacering i termer av \ (t \), \ (ekv. \ 5 \) bör följande vara följande.

\ [
(Rad_ grå + rad_ röd) ^ 2 = (x '_ grå -x' _ röd) ^ 2 + (y '_ grå -y' _ röd) ^ 2 \ \
p = V_ gray_x -v_ red_x \\
q = X_ grå -x_ röd \\
r = V_ gray_y -v_ red_y \\
s = Y_ grå -y_ röd \\
(p ^ 2 + r ^ 2) * t ^ 2 + 2 (pq + rs) * t + (q ^ 2 + s ^ 2- (rad_ grå + rad_ röd) ^ 2) = 0
\]


Steg 14: Resultatet

Så från föregående steg, genom rigorös algebra, kom vi fram till följande formel:

\ [
p = V_ gray_x -v_ red_x \\
q = X_ grå -x_ röd \\
r = V_ gray_y -v_ red_y \\
s = Y_ grå -y_ röd \\
(p ^ 2 + r ^ 2) * t ^ 2 + 2 (pq + rs) * t + (q ^ 2 + s ^ 2- (rad_ grå + rad_ röd) ^ 2) = 0
\]

Nu är det här en stor kvadratisk formel. Vi försöker gruppera koefficienterna i det som accepteras av EqQuadratic. Jämför de två formerna:

\ [
yx ^ 2 + bx + c = 0 \\
(p ^ 2 + r ^ 2) * t ^ 2 + 2 (pq + rs) * t + (q ^ 2 + s ^ 2- (rad_ grå + rad_ röd) ^ 2) = 0 \\
a = p ^ 2 + r ^ 2) \\
b = 2 (pq + rs) \\
c = (q ^ 2 + s ^ 2- (rad_ grå + rad_ röd) ^ 2)
\]


Steg 15: Provimplementering

Så här är en Flash-presentation för att visa idén. Du kommer att se två partiklar på scenen, en grå och den andra röda. Båda är anslutna till en pil, vilket anger hastighetens storlek och riktning.

  • Klicka på "Nästa" för att framsteg en ram i tid.
  • Klicka på knappen "Tillbaka" för att återgå en ram i tid.

För att ändra partikelhastigheten trycker du på:

  • "Upp" och "Ner" för att öka respektive minska hastighetens storlek.
  • "Vänster" och "Rätt" för att rotera hastigheten.
  • "N" för att ändra vilken cirkel du kontrollerar.

Slutligen, för att växla pilarnas synlighet, tryck på "V"


Steg 16: En anteckning på kvadratiska rötterna

Det finns två rötter till den kvadratiska ekvationen. I det här sammanhanget är vi intresserade av de verkliga rötterna. Men om de två partiklarna inte är inställda på kollisionsbanan (båda vägarna är parallella med varandra), så kommer imaginära rötter att produceras istället för riktiga rötter. I det här fallet kommer båda äkta rötterna att förbli NaN.

Om båda partiklarna är inställda på en kollisionsväg kommer vi att få två riktiga rötter. Men vad representerar dessa två rötter?

Minns i steg 12 att vi använde Pythagoras teorem för att binda \ (x '_ grå, \ y' _ grå) \) och \ ((x '_ röd, \ ) \) tillsammans i en ekvation. Tja, det finns två situationer där avståndet mellan två cirkelcentraler är exakt summan av båda radierna: en före kollision och en efter kollision. Ta en titt på den här bilden:

Så vilken väljer vi? Självklart det första eftersom vi inte är intresserade av förekomsten efter kollision. Så vi borde alltid välja det mindre värdet av båda rötterna och utvärdera det. Om värdet är positivt och mindre än 1 kommer en kollision att hända under nästa ram. Om värdet är negativt hände kollisionen tidigare.


Steg 17: ActionScript förklaras

Låt oss titta på ActionScript implementerat för detta exempel. Först, variablerna.

 // c1 är den grå cirkeln // c2 är den röda cirkeln privat var c1: Cirkel, c2: Cirkel; // v1 är den grå cirkelns hastighet // v2 är den röda cirkelns hastighet privata var v1: Vector2D, v2: Vector2D, växla: Boolean = true, usingV1: Boolean = true; // tri1 kommer att bilda pilhuvudet i v1 // tri2 kommer att bilda pilhuvudet för v2 private var tri1: Triangle, Tri2: Triangle; privat var container: Sprite; privat var eq: EqQuadratic;

Då beräkningen av rötter. Du kanske vill kryssa igenom följande ActionScript med variablerna ovan.

 var p: Nummer = v1.x - v2.x; var q: Number = c1.x - c2.x; var r: Number = v1.y - v2.y; var s: Number = c1.y - c2.y; var a: Number = p * p + r * r; var b: Number = 2 * (p * q + r * s); var c: Nummer = q * q + s * s - (c1.radius + c2.radius) * (c1.radius + c2.radius); eq.define (a, b, c); eq.calcRoots (); varrötter: vektor. = eq.roots_R;

Så här tolkar du rötterna:

 // om inga riktiga rötter finns tillgängliga, så är de inte på kollisionsbanan om (isNaN (rötter [0]) && isNaN (rötter [1])) t.text = "Partiklar inte på kollisionsvägen."  annat var tid: Nummer = Math.min (rötter [0], rötter [1]) var int_time: int = tid * 1000; tid = int_tid / 1000; t.text = "Ramar för påverkan:" + time.toString () + "\ n"; om (tid> 1) t.appendText ("Partiklar stänger ...") annars om (tid> 0 && tid < 1) t.appendText("Particles WILL collide next frame!") else if (time < 0) t.appendText("Collision had already happened."); 

Slutsats

Så nu har vi studerat kvadratiska och kubiska rötter i ActionScript, samt att titta på två exempel på hur vi kan använda de kvadratiska rötterna.

Tack för att du tog dig tid att läsa den här handledningen. Lämna en kommentar om du ser andra tillämpningar av kvadratiska rötter (eller några fel!)