Kurvorna Math och ActionScript Gradienter och Normaler

Vi har tacklat ritningskurvor, och hitta deras kvadratiska och kubiska rötter, samt praktiska applikationer för att använda kvadratiska rötter inom spel. Nu, som lovat, ser vi på ansökningar för att hitta kubisk rötter, såväl som kurvornas gradienter och normaler, som att göra föremål studsa av böjda ytor. Nu går vi!


Exempel

Låt oss ta en titt en praktisk användning av denna matte:

I denna demonstration studsar "skeppet" av kanterna på SWF och kurvan. Den gula pricken representerar närmaste punkt på fartyget som ligger på kurvan. Du kan justera kurvens form genom att dra de röda prickarna och justera fartygets rörelse med piltangenterna.


Steg 1: Kortaste avstånd till en kurva

Låt oss ta hänsyn till scenariot där en punkt ligger nära en kvadratisk kurva. Hur beräknar du det kortaste avståndet mellan punkten och kurvan?

Tja, låt oss börja med Pythagoras teorem.

\ [
Låt \ the \ point \ be \ (x_p, \ y_p) \\
och \ call \ the \ nearest \ point \ på \ the \ curve \ (x_c, \ y_c) \\
Sedan:\\
z ^ 2 = x ^ 2 + y ^ 2 \\
z ^ 2 = (x_c-x_p) ^ 2 + (y_c-y_p) ^ 2 \\
Givet \ y_c = ax_c ^ 2 + bx_c + c, \\
z ^ 2 = (x_c-x_p) ^ 2 + [(ax_c ^ 2 + bx_c + c) -y_p] ^ 2
\]

Du kan se att vi har ersatt \ (y_c \) med den kvadratiska ekvationen. I ett ögonblick kan vi se att den högsta kraften är 4. Således har vi en fjärdegrads ekvation. Allt vi behöver göra är att hitta en minsta punkt i denna ekvation för att ge oss det kortaste avståndet från en punkt till en kvadratisk kurva.

Men före det måste vi förstå gradienter på en kurva ...


Steg 2: Gradient av en kurva

Innan vi tittar på problemet med att minimera en kvartslikning, låt oss försöka förstå gradienter av en kurva. En rak linje har bara en gradient. Men en kvadratisk kurves gradient beror på vilken punkt på kurvan vi refererar till. Kolla in Flash-presentationen nedan:

Dra de röda prickarna runt för att ändra den kvadratiska kurvan. Du kan också spela med skjutreglaget för att ändra positionen för den blå punkten längs x. När den blå punkten ändras, så kommer lutningen att dras.


Steg 3: Gradient Through Calculus

Det är här där kalkylen kommer att vara till nytta. Du kanske har gissat att differentiera en kvadratisk ekvation skulle ge dig kurvens gradient.

\ [
f (x) = ax ^ 2 + bx + c \\
\ frac df (x) dx = 2ax + b
\]

Så \ \ frac df (x) dx \) är gradienten för en kvadratisk kurva, och den är beroende av \ (x \) koordinaten. Jo, bra vi har en metod att hantera detta: diff1 (x: Number) kommer att returnera värdet efter en enda differentiering.

För att dra gradienten behöver vi en ekvation som representerar raden, \ (y = mx + c \). Koordinaten för den blå punkten \ ((x_p, y_p) \) kommer att ersättas med \ (x \) och \ (y \), och gradienten för linjen som hittas genom differentiering kommer att gå in i \ (m \). Således kan y-avsnitten av rad, \ (c \) beräknas genom något algebraarbete.

Kolla in AS3:

 var x: Number = s.value var y: Number = kvadratisk_ekvation.fx_of (s.value) point.x = x; point.y = y; / ** * y = mx + c; * c = y - mx; <== use this to find c */ var m:Number = quadratic_equation.diff1(x); var c:Number = y - m * x; graphics.clear(); graphics.lineStyle(1, 0xff0000); graphics.moveTo(0, c); graphics.lineTo(stage.stageWidth, m * stage.stageWidth + c);

Steg 4: Koordinatsystem

Tänk alltid på den inverterade y-axeln för Flash-koordinatutrymmet som visas i bilden nedan. Vid första anblicken kan diagrammet till höger verka som en negativ gradient - men på grund av den inverterade y-axeln är det faktiskt en positiv gradient.

Detsamma gäller för minimipunkten som anges nedan. På grund av den inverterade y-axeln ser minimipunktet i kartesiskt koordinatutrymme (vid (0,0)) ut som ett maximalt i koordinatutrymme för Flash. Men genom att hänvisa till ursprungsorten i Flash-koordinatutrymmet i förhållande till den kvadratiska kurvan är det faktiskt en lägsta punkt.


Steg 5: Ändringsgrad för Gradient

Låt oss nu säga att jag är intresserad av att hitta den lägsta punkten på en kurva - hur går jag vidare? Kolla in bilden nedan (båda figurerna är i samma koordinatutrymme).

För att få lägsta punkten kommer vi bara att jämföra \ (\ frac df (x) dx = 0 \), eftersom vi per definition söker efter punkten där gradienten är noll. Men som visat ovan visar det sig att den maximala punkten på en kurva också uppfyller detta tillstånd. Så hur diskriminerar vi mellan dessa två fall?

Låt oss försöka differentiera den andra graden. Det ger oss graden av förändring av gradienten.

\ [
\ frac df (x) dx = 2ax + b \\
\ frac df ^ 2 (x) dx ^ 2 = 2a
\]

Jag kommer att förklara med hänvisning till bilden nedan (ritad i kartesiska koordinatutrymmet). Vi ser att när vi ökar längs x-axeln ändras gradienten från negativ till positiv. Så bör förändringsgraden vara en positiv värde.

Vi kan också se att när \ (\ frac df ^ 2 (x) dx ^ 2 \) är positiv är det en minsta punkt på kurvan. Omvänt om kursen är negativ är en maximal punkt närvarande.


Steg 6: Tillbaka till problemet

Nu är vi redo att lösa problemet som presenteras i steg 1. Återkalla kvartslikningen (där högsta graden är 4) vi anlände till:

\ [
z ^ 2 = (x_c-x_p) ^ 2 + [(ax_c ^ 2 + bx_c + c) -y_p] ^ 2
\]

Samma kvartslikning, ritad

Kom ihåg att vi är intresserade av att hitta minsta punkt på denna kurva, eftersom motsvarande punkt på den ursprungliga kvadratiska kurvan kommer att vara den punkt som ligger vid det minsta avståndet från den röda pricken.

Så, låt oss skilja den kvartära funktionen för att komma till gradienten av denna kurva och jämföra sedan gradienten för denna kvartsfunktion till noll. Du ser att gradienten faktiskt är en kubisk funktion. Jag ska hänvisa intresserade läsare till Wolframs sida; för denna handledning kommer jag bara att plocka resultatet av deras algebraövningar:

\ [
\ Frac d (z ^ 2) dx =
2 (x_c-x_p) + 2 (ax_c ^ 2 + bx_c + c - y_p) (2ax_c + b) \\
(x ^ c) ^ 2 + (b ^ 2 + 2ac-2ay_p + 1) (x_c) + (bc-by_p- x_p) \\
Ekvivalera \ gradient \ till \ 0 \\
\ Frac d (z ^ 2) dx = 0 \\
2a ^ 2 (x_c) ^ 3 + 3ab (x_c) ^ 2 + (b ^ 2 + 2AC-2ay_p + 1) (x_c) + (bc-by_p-x_p) = 0 \\
Jämför \ med \ cubic \ ekvation \\
Ax ^ 3 + Bx ^ 2 + Cx + D = 0 \\
A = 2a ^ 2 \\
B = 3ab \\
C = b ^ 2 + 2AC-2ay_p + 1 \\
D = bc-by_p-x_p
\]

Lös för rötterna till denna (ganska röriga) kubikfunktion och vi kommer fram till koordinaterna för de tre blå punkterna som anges ovan.

Nästa, hur filtrerar vi våra resultat för minsta poäng? Minns från föregående steg att en minsta punkt har en förändringshastighet som är positiv. För att få denna förändringshastighet, differentiera den kubiska funktionen som representerar gradienten. Om förändringshastigheten för den givna blåpunkten är positiv är det en av minsta poäng. Att få de Minsta poäng, den som vi är intresserade av, välj punkten med högsta förändringsgrad.


Steg 7: Prov av produktionen

Så här är ett exempel på genomförandet av den förklaring som beskrivs ovan. Du kan dra de röda prickarna runt för att anpassa din kvadratiska kurva. Den blå pricken kan också släpas. När du flyttar den blå punkten kommer den gula enheten att flyttas så att avståndet mellan de blå och gula punkterna blir minimalt bland alla punkter på kurvan.

När du interagerar med Flash-presentationen kan det finnas tider där tre gula prickar visas på en gång. Två av dessa, bleknade ut, hänvisar till rötterna som erhållits från beräkningen men avvisas eftersom de inte är närmaste punkter på kurvan till den blå pricken.


Steg 8: Implementering av ActionScript

Så här är ActionScript-implementeringen av ovanstående. Du kan hitta hela skriptet i Demo2.as.

Först av allt måste vi rita den kvadratiska kurvan. Observera att matrisen m2 kommer att hänvisas till för ytterligare beräkning.

 privat funktion redraw_quadratic_curve (): 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));  graphics.clear(); graphics.lineStyle(1); graphics.drawPath(cmd, coord); 

Och här är den som implementerar det matematiska konceptet som förklaras. c1 avser en punkt som är slumpmässigt placerad på scenen.

 privat funktion recalculate_distance (): void var a: Number = m2.n11; var b: Number = m2.n21; var c: Number = m2.n31; / * f (x) = Axe ^ 3 + Bx ^ 2 + Cx + D * / var A: Nummer = 2 * a * a var B: Nummer = 3 * b * a var C: Nummer = b * b + 2 * c * a - 2 * a * c1.y +1 var D: Nummer = c * b - b * c1.y - c1.x quartic_gradient = nytt EqCubic (); quartic_gradient.define (A, B, C, D); quartic_gradient.calcRoots (); rötter = quartic_gradient.roots_R; var vald: Nummer = rötter [0]; om ! isNaN (rötter [1]) &&! isNaN (rötter [2])) // beräkna gradient och graden av gradient av alla reella rötter var quartic_rate: Vector. = ny vektor.; för (var i: int = 0; i < roots.length; i++)  if (!isNaN(roots[i])) quartic_rate.push(quartic_gradient.diff1(roots[i])); else roots.splice(i, 1);  //select the root that will produce the shortest distance for (var j:int = 1; j < roots.length; j++)  //the rate that corresponds with the root must be the highest positive value //because that will correspond with the minimum point if (quartic_rate[j] > quartic_rate [j - 1]) selected = roots [j];  // placera de extra rötterna i demo position_extras ();  else // ta bort de extra rötterna i demo kill_extras ();  intersec_points [0] .x = valda intersec_points [0] .y = quadratic_equation.fx_of (vald); 

Steg 9: Exempel: Kollisionsdetektion

Låt oss använda detta koncept för att upptäcka överlappningen mellan en cirkel och en kurva.

Tanken är enkel: Om avståndet mellan den blå punkten och den gula punkten är mindre än den blå punktens radie, har vi en kollision. Kolla in demon nedan. De interaktiva objekten är de röda prickarna (för att styra kurvan) och den blå pricken. Om den blå pricken kolliderar med kurvan kommer den att blekna ut lite.


Steg 10: Implementering av ActionScript

Tja, koden är ganska enkel. Kolla in hela källan i CollisionDetection.as.

 graphics.moveTo (intersec_points [0] .x, intersec_points [0] .y); graphics.lineTo (c1.x, c1.y); var avstånd: Number = Math2.Pythagoras (intersec_points [0] .x, intersec_points [0] .y, c1.x, c1.y) om < c1.radius) c1.alpha = 0.5; else c1.alpha = 1.0; t.text = distance.toPrecision(3);

Steg 11: studsar bort kurvan

Så nu när vi vet när kollision kommer att inträffa, låt oss försöka programmera ett kollisionssvar. Vad sägs om att studsa bort ytan? Kolla in Flash-presentationen nedan.

Du kan se skeppet (triangelform), omges av en cirkel (genomskinlig blå). När cirkeln kolliderar med kurvan kommer skeppet att studsa av ytan.


Steg 12: Styra fartyget

Här är ActionScript för att styra skeppet.

 offentlig funktion CollisionDetection2 () / ** * Instantiation of ship & its blue-ish circular area * / ship = new Triangle (); addChild (fartyg); ship.x = Math.random () * stadium.stageWidth; ship.y = stadium.stageHeight * 0,8; c1 = ny cirkel (0x0000ff, 15); addChild (c1); c1.alpha = 0.2; / ** * fartygets hastighet * / velo = ny vektor2D (0, -1); updateShip (); stage.addEventListener (KeyboardEvent.KEY_DOWN, handleKey); stage.addEventListener (KeyboardEvent.KEY_UP, handleKey); stage.addEventListener (Event.EXIT_FRAME, handleEnterFrame); / ** * Kurvan och beräkningarna * / quadratic_equation = new EqQuadratic (); curve_points = ny vektor.; befolka (kurvpoäng, 0xff0000, 3); intersec_points = ny vektor.; fylla i (intersec_points, 0xffff00, 3, false); redraw_quadratic_curve ();  privatfunktionshandbokKey (e: KeyboardEvent): void if (e.type == "keyDown") if (e.keyCode == Keyboard.UP) isUp = true; annars om (e.keyCode == Keyboard.DOWN) isDown = true; om (e.keyCode == Keyboard.LEFT) isLeft = true; annars om (e.keyCode == Keyboard.RIGHT) isRight = true;  om (e.type == "keyUp") om (e.keyCode == Keyboard.UP) isUp = false; annars om (e.keyCode == Keyboard.DOWN) isDown = false; om (e.keyCode == Keyboard.LEFT) isLeft = false; annars om (e.keyCode == Keyboard.RIGHT) isRight = false;  privata funktionshandtagEnterFrame (e: Event): void / ** * Styr storleksordningen * / if (isUp) velo.setMagnitude (Math.min (velo.getMagnitude () + 0.2, 3)); annars om (isDown) velo.setMagnitude (Math.max (velo.getMagnitude () - 0.2, 1)); / ** * Kontrollera riktningen * / om (isRight) velo.setAngle (velo.getAngle () + 0.03); annars om (isLeft) velo.setAngle (velo.getAngle () - 0.03); recalculate_distance (); om (avstånd < c1.radius) bounce(); updateShip();  /** * Update ship's position, orientation and it's area (the blue-ish circle) */ private function updateShip():void  ship.x += velo.x; ship.y += velo.y; ship.rotation = Math2.degreeOf(velo.getAngle()); c1.x = ship.x; c1.y = ship.y; if (ship.x > stage.stageWidth || ship.x < 0) velo.x *= -1; if (ship.y > stage.stageHeight || ship.y < 0) velo.y *= -1; 

Du kan se att tangentbordskontrollerna uppdaterar flaggor för att ange huruvida knapparna vänster, upp, höger eller ner trycks ned. Dessa flaggor kommer att fångas av Enterframe Event Handler och uppdatera fartygets storlek och riktning.


Steg 13: Beräkning av reflektionsvektorn

Jag har redan täckt vektorkalkylen av reflektionsvektorn i detta inlägg. Här ska jag bara täcka hur man får den normala vektorn från gradienten.

\ [
\ Frac df (x) dx = gradient \\
rad \ gradient = \ frac y x \\
Antag \ gradient = 0.5 \\
y = 0,5 \\
x = 1 \\
Vector \ of \ left \ normal =
\ begin bmatrix -1 \\ 0.5 \ end bmatrix \\
Vector \ of \ right \ normal =
\ begin bmatrix 1 \\ - 0.5 \ end bmatrix
\]


Steg 14: Implementering av ActionScript

Så kommer ActionScript nedan att genomföra det matematiska konceptet som förklaras i föregående steg. Kolla in de markerade linjerna:

 privat funktion studsa (): void var gradient: Number = quadratic_equation.diff1 (intersec_points [0] .x); var grad_vec: Vector2D = ny vektor2D (1, gradient); var left_norm: Vector2D = grad_vec.getNormal (false); var right_norm: Vector2D = grad_vec.getNormal (); var selected_vec: Vector2D; om (velo.dotProduct (left_norm)> 0) selected_vec = left_norm annars selected_vec = right_norm var selected_unit: Vector2D = chosen_vec.normalise (); var proj: Number = velo.dotProdukt (selected_unit); chosen_unit.scale (-2 * proj); velo = velo.add (selected_unit); 

Slutsats

Tja, tack för din tid! Om du har hittat det här användbart, eller har några frågor, lämna några kommentarer.