[OpenSource] MathUtils 2.4.3 - erweiterte Mathematikbibliothek

    • Release
    • Open Source

    Es gibt 104 Antworten in diesem Thema. Der letzte Beitrag () ist von Artentus.

      [OpenSource] MathUtils 2.4.3 - erweiterte Mathematikbibliothek

      Beschreibung:
      Gleich vorneweg: diese Bibliothek habe ich geschrieben, um meine Fähigkeiten in C# und meinen allgemeinen Codestil zu verbessern. Sie hat zwar auch einen praktischen Nutzen, jedoch gehe ich nicht davon aus, dass das in großen Projekten produktiv eingesetzt werden wird (weder von mir, noch von jemand anderem). Deswegen habe ich es OpenSource gemacht, da der Code wohl der interessante Teil an der Sache ist.
      Die Bibliothek enthält Klassen, die für die Lösung kompliziertere mathematischer Probleme geschrieben sind. Unter anderem sind das Matrizen und Vektoren (wohl der Hauptteil der Lib).
      Es liegt auch eine kleine Testanwendung bei, die demonstriert, was damit alles möglich ist: es wird ein 3D-Würfel, der sich um seine drei Achsen dreht und dabei seine Größe ändert, mit GDI+ gezeichnet.

      Update 2.0: Ab jetzt wird auch zweidimensionale Geometrie unterstützt.
      Update 2.1: Ab jetzt werden auch Geradengleichungen unterstützt.
      Update 2.2: Ab jetzt können lineare Gleichungssysteme gelöst werden
      Update 2.3: Ab jetzt auch mit Formelparser

      Wenn jemand noch Vorschläge hat, was noch dringend in so einer Bibliothek rein müsste, kann er sie gerne äußern, ich werde dann schauen, ob ich es hinbekomme.

      Ich werde übrigens kein Tutorial hier rein schreiben, wie man das ganze Zeug benutzt, da ich das selbst wahrscheinlich nicht besonders gut erklären könnte, und außerdem weil das definitiv zu lange für einen Post im Forum wäre. Einen kurzen Überblick könnt ihr euch aber hier und hier verschaffen.

      Klassendiagramm:



      Changelog:

      Version 2.4
      • Vektoren sind nun leichter in der Handhabung
      • neue Matrix3x3-Klasse für 2D-Transformationen hinzugefügt
      • neue Rational-Klasse

      Version 2.4.1
      • Testprojekt hinzugefügt
      • kleine Bugfixes und Verbesserungen


      Verwendete Programmiersprache und IDE:
      Visual C# (IDE: Visual Studio 2012)

      Systemanforderungen:
      .NET Framework 4

      Download:
      vb-paradise.de/index.php/Attac…19811a97458c00c73251fc176 (83,5 kB)
      vb-paradise.de/index.php/Attac…19811a97458c00c73251fc176 (594,3 kB)

      Source:
      GitHub

      Lizenz/Weitergabe:
      OpenSource
      Dateien
      • MathUtils.zip

        (83,64 kB, 334 mal heruntergeladen, zuletzt: )
      • MathUtils_Solution.zip

        (704,75 kB, 381 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 19 mal editiert, zuletzt von „Artentus“ ()

      Da mir der Beispielcode für meine Verhältnisse wirklich zu ranzig war, habe ich die Testanwendung nochmal komplett neu geschrieben. Ich hab auch gleich noch Vollbildmodus und FPS-Counter eingebaut. Den Code habe ich ebenfalls mit dem neuen ausgetauscht. Jetzt wird glaube ich auch leichter ersichtlich, wie man die Lib benutzt.
      @Artentus
      Also das Klassendiagramm gibt ja schonmal einen schönen Überblick über viele Funktionen :thumbsup: Tolle Arbeit

      Wenn du nichts dagegen hast werde ich mal in einem Parser diese Klassen verwenden damit der besser mit Matrizen und Vektoren arbeiten kann. Vor allem die Unterstützung für Operatoren wird die Arbeit wahrscheinlich erleichtern :thumbup: (Danach kann ich auch nochmal ein Feedback mit Kritik abgeben :D )

      Hast du vor noch weiter hierdran zu arbeiten oder ist das jetzt die finale Version?
      Implizite Typenkonvertierungen von Vector2 zu Size und Point wären vielleicht auch ganz hilfreich. Generell finde ich implizite Konvertierungen besser als Methoden wie ToPoint, ToSize etc.
      So Methoden würde ich nur bei nicht so offensichtlichen Dingen einbauen, wie von Size -> Point oder andersherum.


      MfG :)
      @Telcrome
      Danke für dein Lob.
      Wenn ich noch Sachen finde, die dazu passen, dann werde ich das sicherlich auch noch mit einbauen. Möglicherweise erweitere ich das ganze auch noch um Geometrie (also Polygonkollision usw. , hab da schon nen Source in VB rumfliegen).

      @Trudi
      Laut den Framework Designgiudlines sollte man einen impliziten Konvertierungsoperator nur einsetzen, wenn der Zieltyp alle Funktionalität des Ausgangstyps besitzt. Das heißt als, von Point nach Vector2 wäre in Ordnung, anders rum aber nicht. Ich werde mal schauen, was ich da tun kann, es fehlen sowieso noch ein paar Sachen, wie mir aufgefallen ist.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()

      @Artentus: Überleg mal, im Sinne von DateTime und TimeSpan zwischen
      Ortsvektoren
      und
      Richtungsvektoren
      zu unterscheiden. Das ist sowas wie Option Explicit für Vektoren bzw. deren überladene Vektoren.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Hi
      das System ist eigentlich losgelöst von System.Drawing. Insofern wäre es für eine Schicht sinnvoll, Point, etc. manuell nochmal zu definieren und dann halt [Vector] + [Vector] = [Vector] und [Point] + [Vector] = [Point]. Modelliere btw. die Matrix-Klasse doch einfach als Interface IMatrix (und dann Implementierung z.B. per Extension).
      Ansonsten siehts übrigens ganz nice aus.

      Gruß
      ~blaze~

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

      @RodFromGermany
      Wie sollte ich das deiner Meinung nach denn am besten umsetzen?
      Ich hab das weggelassen, weil ich diese Unterscheidung nicht ordentlich realisieren konnte. Es ist ja eigentlich nur eine Unterscheidung nach Einsatzzweck, die Mathematik bleibt die gleiche.

      @~blaze~
      Das ist ne gute Idee, wird nur viel Schreibarbeit.
      Aber wenn ich die Matrix als Interface deklariere, dann hab ich doch keine Möglichkeit den Inhalt zu speichern, oder? Das würde doch dann bedeuten, ich müsste dann gleich noch ne Standardimplementation mitliefern: IMatrix<--Matrix Sorry, wenn ich da jetzt komplett daneben liege, ich hab immer noch nicht so richtig raus, wie man mit Interfaces arbeitet.

      @all
      Ich hab gerade übrigens nochmal die komplette Sache mit der Projektion von 3D -> 2D neu gemacht (war schlecht implementiert), gibt also demnächst nen Update.
      Jep, aber du könntest Structures verwenden statt Matrizen.

      VB.NET-Quellcode

      1. Public Interface IMatrix
      2. ReadOnly Property Rows As Integer
      3. ReadOnly Property Columns As Integer
      4. Default Property Value(ByVal row As Integer, ByVal column As Integer) As Object
      5. End Interface
      6. Public Interface IMatrix(Of T)
      7. Inherits IMatrix
      8. Default Shadows Property Value(ByVal row As Integer, ByVal column As Integer) As T
      9. End Interface

      So z.B. könnte das aussehen. Dann halt konkrete Typen implementieren für IMatrix(Of Single) und 4x4-Matrix. Ist halt etwas overengineered, aber dann kannst du über Extensions eben die konkreten Funktionen wie Invert, etc. implementieren. Vector wäre dann nat. analog. Structures sind bei sowas halt praktischer als Klassen. Die Implementierung einer Matrix-Klasse mit dynamischer Anzahl von Rows und Columns wär' nat. trotzdem sinnvoll.

      Gruß
      ~blaze~
      Ok, danke für die Hilfe. Ich werde das dann mal in Angriff nehmen.

      Edit:
      @~blaze~
      Ok, ich hab das gerad mal versucht und hatte aber Probleme damit, dass das Interface generisch ist. Viele der Operationen sind halt nur mit Zahlentypen machbar, deswegen kann ich keine Generischen Extensions dafür schreiben. Gibts da was, das ich tun kann?

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()

      Artentus schrieb:

      Wie sollte ich das deiner Meinung nach denn am besten umsetzen?
      Grob-Plan:

      VB.NET-Quellcode

      1. Public MustInherit Class Vector
      2. Dim x As Double
      3. Dim y As Double
      4. Dim z As Double
      5. End Class
      6. Public Class OrtsVector
      7. Inherits Vector
      8. ' erlaubte Operationen überladen
      9. ' Addition, Multiplikation, Division nicht nicht überladen
      10. 'RichtungsVector = OrtsVector - OrtsVector
      11. End Class
      12. Public Class RichtungsVector
      13. Inherits Vector
      14. ' erlaubte Operationen überladen
      15. End Class
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Ich hatte es so gedacht:

      VB.NET-Quellcode

      1. Public Interface IMatrix
      2. ReadOnly Property Rows As Integer
      3. ReadOnly Property Columns As Integer
      4. Default Property Element(ByVal row As Integer, ByVal column As Integer) As Object
      5. ReadOnly Property ElementType As Type
      6. End Interface
      7. Public Interface IMatrix(Of T)
      8. Inherits IMatrix
      9. Default Shadows Property Value(ByVal row As Integer, ByVal column As Integer) As T
      10. End Interface
      11. Public Module MatrixExtensions
      12. Public Function IsIdentity(ByVal value As IMatrix(Of Single)) As Boolean
      13. Dim rows As Integer = value.Rows, columns As Integer = value.Columns
      14. If rows <> columns Then Return False
      15. For i As Integer = 0 To rows - 1
      16. For j As Integer = 0 To columns - 1
      17. If value(i, j) <> If(i = j, 1, 0) Then Return False
      18. Next
      19. Next
      20. Return True
      21. End Function
      22. End Module
      23. Public Structure Matrix2x2
      24. Implements IMatrix(Of Single)
      25. Public M11, M12, M21, M22 As Single 'oder eben als Array oder in c# mit einem fixed-array float[4] in der struct (allerdings wohl eher private)
      26. Public ReadOnly Property Columns As Integer Implements IMatrix.Columns
      27. Get
      28. Return 2
      29. End Get
      30. End Property
      31. Private Overloads Property ElementObj(ByVal row As Integer, ByVal column As Integer) As Object Implements IMatrix.Element
      32. Get
      33. Return Element(row, column)
      34. End Get
      35. Set(ByVal value As Object)
      36. Element(row, column) = DirectCast(value, Single)
      37. End Set
      38. End Property
      39. Private ReadOnly Property ElementType As System.Type Implements IMatrix.ElementType
      40. Get
      41. Return GetType(Single)
      42. End Get
      43. End Property
      44. Public ReadOnly Property Rows As Integer Implements IMatrix.Rows
      45. Get
      46. Return 2
      47. End Get
      48. End Property
      49. Default Public Overloads Property Element(ByVal row As Integer, ByVal column As Integer) As Single Implements IMatrix(Of Single).Value
      50. Get
      51. If row < 0 OrElse row >= 2 Then Throw New ArgumentOutOfRangeException("row")
      52. If column < 0 OrElse column >= 2 Then Throw New ArgumentOutOfRangeException("column")
      53. If row = 0 Then
      54. If column = 0 Then
      55. Return M11
      56. Else
      57. Return M12
      58. End If
      59. Else
      60. If column = 0 Then
      61. Return M21
      62. Else
      63. Return M22
      64. End If
      65. End If
      66. End Get
      67. Set(ByVal value As Single)
      68. If row = 0 Then
      69. If column = 0 Then
      70. M11 = value
      71. Else
      72. M12 = value
      73. End If
      74. Else
      75. If column = 0 Then
      76. M21 = value
      77. Else
      78. M22 = value
      79. End If
      80. End If
      81. End Set
      82. End Property
      83. End Structure


      Gruß
      ~blaze~
      So würde es gehen und so werde ich es jetzt auch machen. Aber dann frage ich mich, wofür das generische Interface überhaupt da sein soll, wenn man die Extensions eh nur für einen bestimmten Typ schreibt. IEnumerable zeichnet ja z.B. aus, dass es für jeden Typ die gleichen Funktionen bietet, nicht nur für die Grunddatentypen.
      Im Prinzip genügt eine Matrix-Klasse sicher, aber wenn man Matrix2x2 oder über homogene Koordinaten eine 3x3-Matrix, etc. z.B. für Rendering verwendet, sind halt doch relativ grobe Unterschiede zwischen Klassen und Strukturen zu erkennen. Insbesondere müssen die dann nicht geklont werden, wenn man auf den konkreten Typ ausweicht. Allerdings kann man über das Interface eben auch nicht-statische Typen zur Verfügung stellen.

      Aso, ganz vergessen: du kannst auch für die restlichen Typen einfach IConvertible verwenden und in den ursprünglichen Elementtyp zurückkonvertieren.

      VB.NET-Quellcode

      1. Public Module MatrixExtensions
      2. Public Function IsIdentity(ByVal value As IMatrix(Of Single)) As Boolean
      3. '...
      4. End Function
      5. Public Function IsIdentity(ByVal value As IMatrix) As Boolean
      6. If GetType(IConvertible).IsAssignableFrom(value.ElementType) Then Throw New ArgumentException("Matrix elements do not provide an implementation of IConvertible.")
      7. Dim rows As Integer = value.Rows, columns As Integer = value.Columns
      8. Dim c As System.Globalization.CultureInfo = System.Globalization.CultureInfo.CurrentCulture
      9. If rows <> columns Then Return False
      10. For i As Integer = 0 To rows - 1
      11. For j As Integer = 0 To columns - 1
      12. If DirectCast(value(i, j), IConvertible).ToDouble(c) <> If(i = j, 1, 0) Then Return False
      13. Next
      14. Next
      15. Return True
      16. End Function
      17. End Module


      Gruß
      ~blaze~
      Hatte bei beiden btw. das Extension-Attribut vergessen. Bei meinem Code ist's so, dass IMatrix(Of T) von IMatrix erbt, welches eben den ElementType zurückgibt. Für bekannte Matrixtypen (IMatrix(Of Single), IMatrix(Of Double)) kann die Implementierung halt konkretisiert werden. Ansonsten wird überprüft, ob der ElementType IConvertable implementiert. Wenn das der Fall ist, können Elemente IConvertible zugewiesen werden - daher die Abfrage auf IsAssignable (die ganzen primitiven Datentypen implementieren IConvertible), wird eben eine Double-Konvertierung vorgenommen (der 0-Vergleich ist übrigens hier nicht ganz sinnvoll, da kein Epsilon verwendet wird, sondern 0 und 1).

      Gruß
      ~blaze~
      Das war mir schon klar, aber ich bekomme wegen diesen Interfaces manche Sachen nicht mehr hin, da mir ja jetzt die Basisklasse Matrix fehlt, und ich somit keine Matrizen von beliebiger Größe mehr erstellen kann. Mir nützen die ganzen Extensions nicht, wenn ich nur Interfaces habe, aber keine Klasse, die die Eigenschaften auch tatsächlich implementiert. Und wenn ich eine solche hinzufüge und sie von IMatrix erben lasse, dann kann ich mir das Interface auch gleich wieder sparen, da man dann wieder von der klasse ableiten kann.
      Ich hab gerade echt schon Kopfschmerzen, weil ich mir Gedanken darüber gemacht habe, wie ich dieses Problem lösen könnte, bin aber zu keinem anderen Ergebnis gekommen, als alles mit Klassen zu lösen (also so, wie ich es jetzt habe). Meine Kenntnisse sind vielleicht einfach noch nicht groß genug, um was komplexeres zu verstehen.
      Das wäre dann eine IMatrix(Of Double), die die Rows und Columns im Konstruktor übergeben bekommt und nur die Properties nach außen reicht. Diese Matrix ist dann aber eine Klasse und keine Structure. Das gleiche Phänomen tritt übrigens bei einigen Leuten auf, die mit mir schon mal ein Projekt durchgeführt haben... :P

      Gruß
      ~blaze~
      So wollte ich es zunächst auch machen, aber dann bekomme ich das Problem, dass ich nach bestimmten Operation, die in Extensions ausgeführt werden, danach nicht mehr die Art Matrix rausbekomme, die ich eingebe, sondern immer eine Instanz dieser Klasse. Außerdem kann ich dann keine Operatoren mehr verwenden, weil die Structures nicht von der Matrix-klasse erben und ich somit für jede Structure eine eigene Überladung schreiben müsste.
      Vielleicht lässt sich das auch ganz einfach lösen, und ich unterliege nur nem inneren Zwang, alles immer in einer möglichst niedrigen Ebene der Vererbunghirarchie implementieren zu wollen, obwohl die Sachen eigentlich in abgeleitete Klassen gehören.
      Ich lös' das Problem dann meist so, dass zwei Klassen noch mal Methoden anbieten, die das nicht auf abstrakter Ebene lösen, sondern auf diskreter, also z.B. für Matrix2x2 eine Überladung mit Function Multiply(Matrix2x2, Matrix2x) As Matrix2x2 und bei der Extension/statischen Methode in einer zusätzlichen Klasse eben eine Überladung auf IMatrix, IMatrix o.ä.

      Gruß
      ~blaze~