Aus der Kategorie „zu doof für Mathematik, aber intelligent genug um sich trotzdem was einfallen zu lassen“: Heute habe ich mir mal wieder ausgiebig meinen alten Quellcode für meine Spacola-Maussteuerung vom August 2010 angesehen und mich daher auch mal wieder ein bisschen mit den mathematischen Grundlagen (Trigonometrie und Vektorarithmetik) auseinandergesetzt. Ein wenig Schmunzeln musste ich über meine unkonventionelle Lösung eines alten Problems, das ich damals hatte.
Man nehme einen Mauszeiger, der irgendwo in einem karthesischen Koordinatensystem liegt. Nun musste ich herausfinden, welchen Winkel der Richtungsvektor Ursprung->Mauszeiger mit der Y-Achse einschließt. Da ich selten mit der Mathematik einer Meinung war, aber dafür umso mehr gewillt, diese Aufgabe von alleine zu lösen, nahm ich mir seinerzeit Papier und Bleistift zur Hand und fing damit an, mir eine Lösung zu überlegen. Und ich fand einen Algorithmus, mit dem ich den Winkel näherungsweise bestimmen konnte.
Aber zunächst zur idealen und simpelsten Lösung:
Man benötigt im Prinzip nur Delta X und Delta Y zwischen den beiden Punkten, also den Ortsvektor. Anschließend teilt man Delta Y durch den Betrag des Vektors und berechnet aus diesem Wert den Arkus-Kosinus. Das Ergebnis muss dann noch mit 360 multipliziert und wieder durch 2 Pi geteilt werden. Schnell und effizient. Ich bin kein Mathegott, aber ein paar Minuten Suchen hat mich auf die richtige Lösung gebracht.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
; html-script: false ] public static int calculateDegrees(int pointX, int pointY){ int deltaX = pointX - centerX; int deltaY = centerY - pointY; double alpha = Math.acos( deltaY / Math.sqrt( Math.pow( deltaX, 2 ) + Math.pow( deltaY, 2 ) ) ); alpha = alpha * 360 / ( Math.PI * 2 ); if(deltaX < 0) alpha = 360.0 - alpha; return (int) alpha; } |
Witzig fand ich dagegen meinen Algorithmus, der folgendermaßen aussieht:
Ich habe beliebig viele Punkte (im Extremfall 360 Stück, in meinem Fall aber nur 40) auf einem Einheitkreis um den Mittelpunkt generiert und von jedem dieser gedachten Punkte den Abstand zur Mauszeigerkoordinate bestimmt. Mein gedanklicher Ansatz war der, dass der Punkt, der den kürzesten Abstand zur Zielkoordinate hat, mehr oder weniger exakt denselben Winkel zur Y-Achse hat. Mit dem Vorteil, dass ich dessen Winkel (da selbst generiert) genau kannte. Anschließend habe ich noch eine merkwürdige Umrechnung vom Bogenmaß ins Gradmaß durchgeführt. Das Ergebnis war zufriedenstellend.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
; html-script: false ]public void calculateDegrees(){ int xPos = Config.SCREEN_WIDTH / 2 + 10; int yPos = Config.SCREEN_HEIGHT / 2; int cnt = 0, diff = 10000, targetCnt = 1000; for (double i = 0; i <= 2 * (Math.PI); i += (2 * (Math.PI)) / 40) { xPos = Config.SCREEN_WIDTH / 2 + (int) (Math.cos(i) * Math.abs(Config.SCREEN_WIDTH / 2 - cursorCoordX)); yPos = Config.SCREEN_HEIGHT / 2 - (int) (Math.sin(i)*(-1) * Math.abs(Config.SCREEN_HEIGHT / 2 - cursorCoordY)); if (Math.abs(cursorCoordX - xPos) + Math.abs(cursorCoordY - yPos) < diff) { diff = Math.abs(cursorCoordX - xPos) + Math.abs(cursorCoordY - yPos); targetCnt = cnt; } cnt++; } int newDegrees = (90+(targetCnt * (360 / 40))) % 360; setDegrees(newDegrees); } |
Für letzteres Codebeispiel schlagt mich bitte nicht. Ich wusste damals schon, dass das nur ein Provisorium ist. Ich weiß noch nicht einmal mehr wofür „targetCnt“ steht.
Zugegeben, meine Lösung ist extrem unperformant und relativ ungenau, aber nichts stärkt das Ego mehr als die Gewissheit, ein Problem selbst und ohne Hilfe bewältigt zu haben, wenn man es sich vorher nicht zugetraut hätte. Wie dem auch sei, heute habe ich meinen provisorischen Algorithmus abgelöst und durch die exakte Berechnung ersetzt.
Am einfachsten wäre Tan(alpha)= xwert/ywert
dann kannste dir den betrag vom ortsvektor sparen
wobei du dann wenn ich mich recht erinnere en wert zwischen -pi/2 und +pi/2 bekommst den du umrechnen musst auf grad (wozu eigentlich?)
Ich hab wohl mit deinem geballten Mathe-Fachwissen nicht gerechnet, als ich den Artikel geschrieben hab. Ich hab mir fast noch gedacht, dass sich meine Berechnung sogar noch weiter vereinfachen lässt.
Ich bin leider schon zu lange aus dem Studium raus um tan(alpha) = xwert/ywert nach alpha aufzulösen. Brauch ich dazu dann den Arctan oder so? Außerdem gab es da nicht das Problem, dass der tan an bestimmten Stellen nicht definiert ist?
Die Berechnung brauche ich, weil ich 32 Sprites von einem Raumschiff habe, die alle in 11-Grad-Schritten um sich selbst gedreht sind. Um nun herauszufinden, welches Sprite ich auf den Bildschirm zeichnen muss, muss ich wissen, welchen Winkel der Mauszeiger zur negativen Y-Achse hat, weil sich das Objekt mit der Maus mitdreht.
Bisher habe ich das unnötig kompliziert gelöst. Inzwischen hat sich die Berechnung schonmal deutlich vereinfacht.
alpha = acrtan(xwert/ywert)
bei tangens muss man immer aufpassen in welchem quadrant man sich befindet da du aber wenn ich das richtig verstehe nur den 1. quadrant verwendest (nur positive x und y Werte) ist das kein Problem.
das „wozu eigentlich“ hat sich eher drauf bezogen warum du auf grad umrechnest und nicht einfach rad weiterverwendest, soviel ich weiss benutzt java für alle Funktionen nur rad, und da mathematisch beide Darstellunsweisen gleichwertig sind erschließt sich mir nicht der Sinn des umrechnens in grad ^^
Doch, alle 4 Quadranten brauche ich. Also können Delta X und Delta Y positiv wie negativ werden.
Es geht nicht darum welches Maß Java intern für seine mathematischen Funktionen verwendet, sondern darum welches Maß ich für meine Zuordnungsfunktion verwende. Die Umrechnung in Grad mache ich erst als letzten Schritt, also wenn ich keine weitere Berechnung mehr brauche. Ist einfach ne persönliche Präferenz.
Dafür habe ich eine simple Zuordnungsvorschrift anhand der Gradzahl:
Sprite 1 = 0-10 Grad
Sprite 2 = 11-21 Grad
Sprite 3 = 22-32 Grad
Sprite 4 etc.
Ich hätte NATÜRLICH auch sagen können
Sprite 1 = 1/2 * PI bis äh … keine Ahnung … 5/18 * PI
Sprite 2 = 1/3 * PI bis (1/4 + X) * PI
…
Sprite X = 0 PI bis -1/5 PI … oder vielleicht auch nicht.
Aber ich glaube mit Gradzahlen gefällt mir das etwas besser.
Wenn du alle 4 Quadranten brauchst dann funktioniert deine Methode nicht ^^ weil yWert/Betrag für 1. und 4. Quadranten den gleichen Wert hat (im 2. und 3. auch) und damit du auch den gleichen Winkel rauskriegst. Auch wenn du „künstlich“ dem Betrag en Vorzeichen verpasst bleibt das Problem das Gleiche. Da musste noch paar if Anweisungen oder switch vorbaun um den Quadranten zu ermitteln (bei der tan Variante ebenfalls)
Genau. Ich vermute du meinst diesen Teil
if(deltaX < 0) alpha = 360.0 - alpha;
Damit wird sichergestellt, dass richtig zwischen Quadrant 1/4 und 2/3 unterschieden wird.
Auf jeden Fall danke für deinen Einblick in die Thematik. Ich schau heute Abend mal ob deine Berechnung mit arctan die Performance vielleicht noch weiter verbessert.
richtig
für arctan müsste das so ausshen:
http://s7.directupload.net/file/d/2926/xpffkmpy_png.htm
stör dich nicht an dem drumrum nur das markierte brauchst du,
komischerweise finde ich schneller was in meinen scripts als bei google
zumindest was Übersichtlichkeit angeht a,b bei dir natürlich x,y
Stimmt, das markierte sieht vertraut aus.
Wobei du wohl meinst a = y und b = x, weil dort b/a geteilt wird, und nicht a/b. Ich seh leider grad nicht ob das ne Rolle spielt, wegen Imaginärteil und Realteil. Sieht irgendwie nach Polarkoordinaten aus oder so.
Mathe verlernt man so schnell, wenn man aufhört zu lernen >_< Ist vor allem deshalb schade, wenn man in der Schule nie was mit Trigonometrie und Vektoren anfangen konnte - und dann braucht man es irgendwann doch.
Ich hab deine Formel mal in Quelltext umgesetzt.
Dabei ist mir aufgefallen, dass die Lösung mit dem arctan wohl auf den ersten Blick einfacher wirken mag, aber durch den Teil mit deltaX / deltaY muss ich jetzt zusätzlich auch gegen Division durch Null prüfen, sonst stürzt das Spiel ab. Mit der anderen Formel steht nie 0 unterm Bruchstrich.
Außerdem scheint meine Umsetzung noch nicht ganz zu stimmen:
double alpha = Math.atan( deltaX / deltaY );
alpha *= 180 / Math.PI;
if(deltaX < 0) alpha = 360.0 - alpha;
Da kommen teilweise falsche Winkel heraus. Ich hab wohl irgendeinen Faktor vergessen.
wie auf dem Zettel hast du 3 Fälle du musst also auch deltay auf ><0 prüfen, das mit der Division durch 0 ist allerdings Katzenbrei, wenn du das noch einbauen musst lohnt der Aufwand vermutlich kaum, ich hatte das ganze nur von der Mathematischen Seite betrachtet ohne mir die Schwierigkeiten bei der Implementierung klar zu machen ^^
if deltaY < 0
alpha = 180-alpha
if deltaX < 0
alpha = 180+alpha
das steht aufm zettel