Rubik's Cube Solver mit 3D-Darstellung

  • C#

Es gibt 85 Antworten in diesem Thema. Der letzte Beitrag () ist von Switcherlapp97.

    @StarGate01
    Meine Rotate-Methode ist in der Point3D Klasse. Hier der Code:

    C#-Quellcode

    1. public Point3D Rotate(RotationType type, int angle)
    2. {
    3. double rad = angle * Math.PI / 180;
    4. double cosa = Math.Cos(rad);
    5. double sina = Math.Sin(rad);
    6. switch (type)
    7. {
    8. case RotationType.X:
    9. return new Point3D(this.X, this.Y * cosa - this.Z * sina, this.Y * sina + this.Z * cosa);
    10. case RotationType.Y:
    11. return new Point3D(this.Z * sina + this.X * cosa, this.Y, this.Z * cosa - this.X * sina);
    12. default:
    13. return new Point3D(this.X * cosa - this.Y * sina, this.X * sina + this.Y * cosa, this.Z);
    14. }
    15. }

    Wie muss ich diese Methode abändern, damit ich den Punkt um den Mittelpunkt des Rubiks Cubes den Punkt drehe?
    Sorry aber ich check das nicht ganz ab :(

    Gruß
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom
    Ich schreib hier mal meine Gedanken hin:
    Ein Rubik-Würfel besteht ja aus 3*3*3 Subwürfeln, wobei der innere niemals sichtbar ist.
    Von den Subwürfeln sind auch nur 1 (Mitte), 2 (Kante) bzw. 3 (Ecke) Flächen zu sehen.
    Jede Fläche ist Quadratisch und daher definiert durch 4 Punkte mit x-, y- und z-Koordinate.
    Nehmen wir mal an ein einfacher Würfel (1*1*1) liegt im Mittelpunkt (0,0,0), so lauten seine Eckkoordinaten:
    (-0.5,0.5,-0.5), (0.5,0.5,-0.5), (0.5,-0.5,-0.5), (-0.5,-0.5,-0.5), (-0.5,0.5,0.5), (0.5,0.5,0.5), (0.5,-0.5,0.5), (-0.5,-0.5,0.5)
    (Wenn ich mich nicht vertippt hab)
    Nun also nur noch Projizieren und die Flächen Rendern.
    Für den kompletten Rubik-Würfel unterteilst du dir deinen Würfel nun in 9 kleine Würfel.
    De facto unterteilst Du also jede Fläche in 9 kleine.
    Nun errechnest du durch simple Addition und Subtraktion von 1/3 die Flächenkoordinaten dieser Subflächen.
    Danach hast du also 9*6 = 54 Flächen. Die Punkte dieser rotiert du dann nach Wunsch einfach um den Mittelpunkt (0,0,0).
    Diese werden dann wie gehabt projiziert und gerendert.
    Der Witz an der Sache ist also den Rubik als Einheitswürfel mit unterteilten Flächen zu betrachten und nicht als 3*3*3 = 27 Subwürfel.
    Irgendwie find ich das Thema ansprechend, vielleicht komm ich dazu mal was zu programmieren.

    Und jetzt mal ganz OT: Warum eigentlich GDI+ und nicht z.B. DirectX? Es ist zwar spannend das alles selber zu machen aber... ^^

    Du erscheinst mir als durchaus fähiger Programmierer, das packst Du sicherlich.
    SᴛᴀʀGᴀᴛᴇ01
    @StarGate01
    So wie du es beschrieben hast, bin ich bei meiner zweiten Variante vorgegangen (ungefähr so). Dort wird das 3D Modell allerdings nicht korrekt dargestellt. Ich wäre froh, wenn du dir das einmal anschauen könntest, denn ich finde den Fehler einfach nicht. Ich glaube, dass dem Code nicht mehr allzu viel fehlt (ich hoffe es zumindestens), aber ich finde einfach den Fehler nicht :(

    Gruß
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom
    Kann es sein, dass du die Punkte für die einzelnen Felder im 2D-Raum verschiebst? Dann wäre der ungewollte Effekt ja logisch, du musst bei deinem aktuellen Ansatz die Punkte nämlich im 3D-Raum verschieben, um die einzelnen Würfel an verschiedenen Stellen zu erstellen.
    Alles der Reihe nach.
    Ich bin mal deinen bisherigen Code für _einen_ Würfel durchgegangen.
    Das Z-Buffering ist... nunja... interessant gelöst.
    Der Code kann erheblich vereinfacht werden wenn man eine zusätzliche Klasse einführt: Face3D.
    Ich hab mal was gebastelt, wo du den Code hoffentlich besser Verstehst.
    Für Subwürfel musst Du nur noch die Flächendefinitionen in genFaces() ergänzen.
    Dein Vorhaben liegt zum Greifen nah!

    Form1.cs
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Drawing.Drawing2D;
    7. using System.Linq;
    8. using System.Text;
    9. using System.Windows.Forms;
    10. namespace CubeGDI3D
    11. {
    12. public partial class Form1 : Form
    13. {
    14. public Form1()
    15. {
    16. InitializeComponent();
    17. SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    18. SetStyle(ControlStyles.DoubleBuffer, true);
    19. SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    20. SetStyle(ControlStyles.UserPaint, true);
    21. }
    22. private Face3D[] faces;
    23. private Face3D[] genFaces()
    24. {
    25. return new Face3D[] {
    26. new Face3D(new Point3D[] { new Point3D(-1, 1, -1), new Point3D(1, 1, -1), new Point3D(1, -1, -1), new Point3D(-1, -1, -1) }, Color.Red),
    27. new Face3D(new Point3D[] { new Point3D(-1, 1, 1), new Point3D(1, 1, 1), new Point3D(1, -1, 1), new Point3D(-1, -1, 1) }, Color.Blue),
    28. new Face3D(new Point3D[] { new Point3D(-1, -1, -1), new Point3D(1, -1, -1), new Point3D(1, -1, 1), new Point3D(-1, -1, 1) }, Color.Green),
    29. new Face3D(new Point3D[] { new Point3D(-1, 1, -1), new Point3D(1, 1, -1), new Point3D(1, 1, 1), new Point3D(-1, 1, 1) }, Color.Yellow),
    30. new Face3D(new Point3D[] { new Point3D(1, 1, 1), new Point3D(1, 1, -1), new Point3D(1, -1, -1), new Point3D(1, -1, 1) }, Color.Purple),
    31. new Face3D(new Point3D[] { new Point3D(-1, 1, 1), new Point3D(-1, 1, -1), new Point3D(-1, -1, -1), new Point3D(-1, -1, 1) }, Color.Brown)
    32. };
    33. }
    34. private void Form1_Load(object sender, EventArgs e)
    35. {
    36. faces = genFaces();
    37. timer1.Start();
    38. }
    39. private void Form1_Paint(object sender, PaintEventArgs e)
    40. {
    41. e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    42. Face3D[] facesProjected = new Face3D[faces.Length];
    43. for (int i = 0; i < faces.Length; i++) facesProjected[i] = faces[i].Project(this.ClientRectangle.Width, this.ClientRectangle.Height, 100, 4);
    44. facesProjected = facesProjected.OrderBy(p => p.Edges[0].Z).ToArray();
    45. for (int i = facesProjected.Length-1; i >= 0; i--)
    46. {
    47. PointF[] parr = Array.ConvertAll<Point3D, PointF>(facesProjected[i].Edges, new Converter<Point3D, PointF>(p => new PointF((float)p.X, (float)p.Y)));
    48. e.Graphics.FillPolygon(new SolidBrush(facesProjected[i].Color), parr);
    49. e.Graphics.DrawPolygon(Pens.Black, parr);
    50. }
    51. }
    52. private void timer1_Tick(object sender, EventArgs e)
    53. {
    54. for (int i = 0; i < faces.Length; i++)
    55. {
    56. faces[i] = faces[i].Rotate(Point3D.RotationType.X, 2);
    57. faces[i] = faces[i].Rotate(Point3D.RotationType.Y, 2);
    58. }
    59. Invalidate();
    60. }
    61. }
    62. }


    Point3D.cs (unverändert)
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. public class Point3D
    3. {
    4. public double X { get; set; }
    5. public double Y { get; set; }
    6. public double Z { get; set; }
    7. public Point3D(double x, double y, double z)
    8. {
    9. this.X = x;
    10. this.Y = y;
    11. this.Z = z;
    12. }
    13. public Point3D Rotate(RotationType type, int angle)
    14. {
    15. double rad = angle * Math.PI / 180;
    16. double cosa = Math.Cos(rad);
    17. double sina = Math.Sin(rad);
    18. switch (type)
    19. {
    20. case RotationType.X:
    21. return new Point3D(this.X, this.Y * cosa - this.Z * sina, this.Y * sina + this.Z * cosa);
    22. case RotationType.Y:
    23. return new Point3D(this.Z * sina + this.X * cosa, this.Y, this.Z * cosa - this.X * sina);
    24. default:
    25. return new Point3D(this.X * cosa - this.Y * sina, this.X * sina + this.Y * cosa, this.Z);
    26. }
    27. }
    28. public Point3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    29. {
    30. double factor = fov / (viewDistance + this.Z);
    31. double Xn = this.X * factor + viewWidth / 2;
    32. double Yn = this.Y * factor + viewHeight / 2;
    33. return new Point3D(Xn, Yn, this.Z);
    34. }
    35. public enum RotationType { X, Y, Z }
    36. }


    Face3D.cs
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Drawing;
    5. using System.Drawing.Drawing2D;
    6. class Face3D
    7. {
    8. public Point3D[] Edges;
    9. public Color Color;
    10. public Face3D(Point3D[] edges, Color color)
    11. {
    12. this.Edges = edges;
    13. this.Color = color;
    14. }
    15. public Face3D Rotate(Point3D.RotationType type, int angle)
    16. {
    17. Point3D[] parr = new Point3D[Edges.Length];
    18. for(int i=0; i<Edges.Length; i++) parr[i] = Edges[i].Rotate(type, angle);
    19. return new Face3D(parr, Color);
    20. }
    21. public Face3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    22. {
    23. Point3D[] parr = new Point3D[Edges.Length];
    24. for (int i = 0; i < Edges.Length; i++) parr[i] = Edges[i].Project(viewWidth, viewHeight, fov, viewDistance);
    25. double mid = parr.Sum(p => p.Z) / parr.Length;
    26. for (int i = 0; i < parr.Length; i++) parr[i].Z = mid;
    27. return new Face3D(parr, Color);
    28. }
    29. }



    Das sieht dann so aus:



    Ich bitte Dich, versteh meinen Code, dann wird dein Vorhaben für dich kein Problem sein.
    Eigentlich ist nur die Face3D-Klasse neu, und die repräsentiert einfach eine Fläche.
    Und nicht vor dem Lambda-Krams zurückschrecken, das spart einfach nur Platz und Schleifen ;)


    Ich hoffe das hilft Dir.


    //EDIT:
    Als Verdeckungsalgorithmus kommt hier Painter's Algorithm zum Einsatz. Er kann in bestimmten Fällen aber fehlschlagen. Eventuell solltest Du später einen richtigen Z-Buffer implementieren.

    //EDIT2:
    Ich habe im Gegensatz zu Deinem alten Code auf einen Indexbuffer verzichtet. Die entstehende Redundanz zu beheben ist auch noch eine Optimierungsmöglichkeit.
    SᴛᴀʀGᴀᴛᴇ01

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „StarGate01“ ()

    Sorry, dass ich hier schon wieder so an euren Codes rumverbessere aber mir steht grad danach. Hoffe, ihr seid nicht böse. Jedenfalls habe ich nochmal etwas in der Form1- und der Face3D-Klasse rumgewerkelt. Rausgekommen ist folgendes:
    Face3D

    C#-Quellcode

    1. class Face3D
    2. {
    3. public IEnumerable<Point3D> Edges { get; set; }
    4. public Color Color;
    5. public Face3D(IEnumerable<Point3D> edges, Color color)
    6. {
    7. this.Edges = edges;
    8. this.Color = color;
    9. }
    10. public Face3D Rotate(Point3D.RotationType type, int angle)
    11. {
    12. IEnumerable<Point3D> parr = Edges.Select(p => p.Rotate(type, angle));
    13. return new Face3D(parr, this.Color);
    14. }
    15. public Face3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    16. {
    17. IEnumerable<Point3D> parr = Edges.Select(p => p.Project(viewWidth, viewHeight, fov, viewDistance));
    18. double mid = parr.Sum(p => p.Z) / parr.Count();
    19. parr = parr.Select(p => new Point3D(p.X, p.Y, mid));
    20. return new Face3D(parr, Color);
    21. }
    22. }

    Form

    C#-Quellcode

    1. public partial class Form1 : Form
    2. {
    3. Timer timer;
    4. private IEnumerable<Face3D> faces;
    5. private IEnumerable<Face3D> genFaces()
    6. {
    7. return new Face3D[] {
    8. new Face3D(new Point3D[] { new Point3D(-1, 1, -1), new Point3D(1, 1, -1), new Point3D(1, -1, -1), new Point3D(-1, -1, -1) }, Color.Red),
    9. new Face3D(new Point3D[] { new Point3D(-1, 1, 1), new Point3D(1, 1, 1), new Point3D(1, -1, 1), new Point3D(-1, -1, 1) }, Color.Blue),
    10. new Face3D(new Point3D[] { new Point3D(-1, -1, -1), new Point3D(1, -1, -1), new Point3D(1, -1, 1), new Point3D(-1, -1, 1) }, Color.Green),
    11. new Face3D(new Point3D[] { new Point3D(-1, 1, -1), new Point3D(1, 1, -1), new Point3D(1, 1, 1), new Point3D(-1, 1, 1) }, Color.Yellow),
    12. new Face3D(new Point3D[] { new Point3D(1, 1, 1), new Point3D(1, 1, -1), new Point3D(1, -1, -1), new Point3D(1, -1, 1) }, Color.Purple),
    13. new Face3D(new Point3D[] { new Point3D(-1, 1, 1), new Point3D(-1, 1, -1), new Point3D(-1, -1, -1), new Point3D(-1, -1, 1) }, Color.Brown)
    14. };
    15. }
    16. public Form1()
    17. {
    18. InitializeComponent();
    19. this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    20. this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    21. timer = new Timer();
    22. timer.Interval = 25;
    23. timer.Tick += timer_Tick;
    24. timer.Start();
    25. faces = genFaces();
    26. }
    27. void timer_Tick(object sender, EventArgs e)
    28. {
    29. faces = faces.Select(f => f.Rotate(Point3D.RotationType.X, 2).Rotate(Point3D.RotationType.Y, 2));
    30. this.Invalidate();
    31. }
    32. private void Form1_Paint(object sender, PaintEventArgs e)
    33. {
    34. e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    35. IEnumerable<Face3D> facesProjected = faces.Select(f => f.Project(this.ClientRectangle.Width, this.ClientRectangle.Height, 100, 4));
    36. facesProjected = facesProjected.OrderBy(p => p.Edges.ElementAt(0).Z).ToArray();
    37. foreach (Face3D face in facesProjected.Reverse()) {
    38. PointF[] parr = face.Edges.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray();
    39. e.Graphics.FillPolygon(new SolidBrush(face.Color), parr);
    40. e.Graphics.DrawPolygon(Pens.Black, parr);
    41. }
    42. }
    43. }

    Falls es noch jemanden interessieren sollte, erwähne ich noch kurz, was ich gemacht habe:
    -Alle Arrays durch IEnumerables ersetzt, wenn möglich und sinnvoll
    -Alle For-Schleifen durch Linq-IEnumerable-Funktionen ersetzt

    MfG Stefan
    @nafets3646: Saubere Arbeit!
    Läuft sogar schneller.

    Und hey, kein Problem, da freu ich mich sogar wenn sich jemand meinem Code annimmt ^^

    Eine Sache habe ich nur auszusetzen:
    genFaces() sollte VOR dem Starten des Timers bzw. dem ersten Durchlauf aufgerufen werden. (Denke ich)
    SᴛᴀʀGᴀᴛᴇ01
    Mal ein anderer Ansatz:
    Wie wärs, wenn du das rumgefrickel mit diesen orthogonalen Projektionen einfach sein lässt, tatsächlich in nem 3D-Raum arbeitest und dann ordentlich nach 2D projizierst?
    Die nötige mathematik kann z.B. diese Lib hier für dich übernehmen: [OpenSource] MathUtils 2.4.2 - erweiterte Mathematikbibliothek
    @StarGate01
    Stimmt, haste Recht. Der Teil, den ich nicht bearbeitet hatte, ist auch nur schnell aus deinem und Switcherlapp seinem Formcode entstanden bzw. zusammenkopiert. Es macht aber insofern nichts aus, da, wenn kein Objekt in der Variable faces ist, alles einfach im Leerlauf läuft ;). Du kannst sogar das genFaces ganz weglassen, Fehler gibts trotztem keine. Es wird halt nur kein Objekt gezeichnet.

    //EDIT:
    @Artentus
    Warum?

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

    @Artentus: wär ich doch auch stark dafür. So sieht der Würfel aus als würde er sich dauernd minimal verziehen. (Was er ja auch tut).
    Ob man eine so mächtige Lib einsetzen will oder es selbst machen will bleibt dann dem TE überlassen denke ich.
    SᴛᴀʀGᴀᴛᴇ01
    Soo kompliziert ist es nun ja wirklich nicht, dass man gleich ne Library dafür verwenden müsste. Die benötigten Klassen sind ja jetzt vorhanden und der Code im Paint-Event ist auch ziemlich kurz. Außerdem muss man jetzt nicht für alles gleich ne Library nehmen. Sonst würde man bei richtig großen Projekten sicher 30 DLLs mitliefern, von denen man die meisten nur für 1 oder 2 Funktionen braucht :D.

    //EDIT:
    Dass der Würfel sich minimal verzieht ist ja auch nich wirklich schlimm, es fällt auch nur auf, wenn man darauf achtet ;).
    Dafür sind Libraries da, dass man diese verwendet. Und er will etwas 3D darstellen, das ist nicht nur was kleines.
    Vorallem wenn man überlegt, was man da mit einem kleinen DirectX Wrapper alles machen kann. Vielleicht will er das ganze später ja etwas, das nicht ganz so plastisch aussieht. Soetwas mit GDI+ zu machen ist schön und gut. Aber da ist die Frage, was will der TE. Will er verstehen was DirectX macht, will er nur das Ergebnis, oder ist die Motivation wirklich alles zu schreiben(Aber dann ist C# ja langweilig :P)
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Beispiel Indexbuffer:
    Spoiler anzeigen

    C#-Quellcode

    1. private IEnumerable<Face3D> genFaces()
    2. {
    3. Point3D[] Corners = new Point3D[] {
    4. new Point3D(-1, 1, -1),
    5. new Point3D(1, 1, -1),
    6. new Point3D(1, -1, -1),
    7. new Point3D(-1, -1, -1),
    8. new Point3D(-1, 1, 1),
    9. new Point3D(1, 1, 1),
    10. new Point3D(1, -1, 1),
    11. new Point3D(-1, -1, 1)
    12. };
    13. return new Face3D[] {
    14. new Face3D(new Point3D[] { Corners[0], Corners[1], Corners[2], Corners[3] }, Color.Red),
    15. new Face3D(new Point3D[] { Corners[4], Corners[5], Corners[6], Corners[7] }, Color.Blue),
    16. new Face3D(new Point3D[] { Corners[3], Corners[2], Corners[6], Corners[7] }, Color.Green),
    17. new Face3D(new Point3D[] { Corners[0], Corners[1], Corners[5], Corners[4] }, Color.Yellow),
    18. new Face3D(new Point3D[] { Corners[5], Corners[1], Corners[2], Corners[6] }, Color.Purple),
    19. new Face3D(new Point3D[] { Corners[4], Corners[0], Corners[3], Corners[7] }, Color.Brown)
    20. };
    21. }

    SᴛᴀʀGᴀᴛᴇ01
    Hallo,

    Ich hatte jetzt erst die Zeit gefunden, um mit euren Tipps mein 3D-Rubiks-Cube-Model zu optimieren und ich habe echt hilfreiche Beiträge von euch erhalten. Echt ein großes Dankeschön an euch alle. Ich habe mit euren Tipps schon sehr viel dazu gelernt. :D

    jvbsl schrieb:

    Aber da ist die Frage, was will der TE. Will er verstehen was DirectX macht, will er nur das Ergebnis, oder ist die Motivation wirklich alles zu schreiben(Aber dann ist C# ja langweilig :P)

    Nachdem ich viele Lösungsverschläge mit GDI bekommen habe, fing mir das Thema an zu gefallen und finde es interessanter und vor allem lehrreicher mein Problem mit GDI zu verwirklichen. Wo ich mit diesem Thread begonnen habe, wollte ich hauptsächlich das Ergebnis, was sich jetzt geändert hat. Ich habe bereits sehr viel dazu gelernt im Bereich projezieren, 3D, etc. Danke für die super Hilfe von euch :!: :D

    StarGate01 schrieb:

    Und achja. Das Programm braucht im Status quo immer mehr CPU. Keine Ahnung warum. Jedenfalls wird es irgendwann zur Schnecke.

    Dieses Problem tritt bei mir auch auf und ich kann mir nicht erklären, wie es dazu kommt. Habt ihr eine Idee, wieso es mit der Zeit anfängt langsamer zu werden? Mein ursprünglicher 3D-Würfel (ohne die Flächenunterteilung), lief übrigens flüssig, obwohl das Projekt mit viel unnötigem Code vollgepackt war ...

    Danke
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom
    Wegen der CPU:
    Das sieht mir stark nach nem Memory-Leak oder ner Endlosschleife aus. Allerdings kann ich im Task-Manager nichts Auffälliges (RAM) feststellen!?
    Vielleicht liegt es an den ganzen Enumerables und Linq-Ausdrücken, aber die sind eigentlich hochperformant.
    @nafets3646: Wenn sich jemand erbarmt und das mal debuggt :D ...Würd mich auch interessieren, ich hab aber im Moment leider keine Zeit.
    Ist bestimmt nur ne Kleinigkeit :)
    SᴛᴀʀGᴀᴛᴇ01

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

    Jo, irgendwas lässt die CPU-Auslastung ziemlich schnell sehr hoch steigen, allerdings nur bis zu einem bestimmten Punkt, dann sinkt sie wieder (bei mir bei 15%).
    Also, es scheint an folgendem Aufruf zu liegen:

    C#-Quellcode

    1. faces = faces.Select(f => f.Rotate(Point3D.RotationType.X, 2).Rotate(Point3D.RotationType.Y, 2));

    Ich schau mal, wie man das verbessern könnte :).

    //EDIT:
    Das Problem liegt eindeutig in der Rotate-Funktion.

    //EDIT 2:
    Das Problem scheint nur aufzutreten, wenn man mit der Rotate-Funktion eine Variable verändert und diese dann auf sich selber zuweist. Beispielcode:

    C#-Quellcode

    1. Face3D TestFace = new Face3D(new Point3D[] { new Point3D(-1, 1, -1), new Point3D(1, 1, -1), new Point3D(1, -1, -1), new Point3D(-1, -1, -1) }, Color.Red);
    2. for (int i = 0; i < 1000000; i++)
    3. {
    4. TestFace = TestFace.Rotate(Point3D.RotationType.X, 2); //Memory-Leak
    5. var Test = TestFace.Rotate(Point3D.RotationType.X, 2); //Kein Memory-Leak
    6. }


    //EDIT 3:
    Ich hab den bzw. die Übeltäter :):
    Die Funktionen Point3D.Rotate und Face3D.Rotate. Geben ja jeweils eine neue Instanz von sich zurück. Hier liegt das Problem, da die "alten" Instanzen im RAM verbleiben und ihn damit zumüllen. Deshalb muss man die Funktionen in Methoden umwandeln:
    Für Face3D

    C#-Quellcode

    1. public void Rotate(Point3D.RotationType type, int angle)
    2. {
    3. this.Edges.ToList().ForEach(p => p.Rotate(type, angle));
    4. }

    Für Point3D

    C#-Quellcode

    1. public void Rotate(RotationType type, int angle)
    2. {
    3. double rad = angle * Math.PI / 180;
    4. double cosa = Math.Cos(rad);
    5. double sina = Math.Sin(rad);
    6. switch (type)
    7. {
    8. case RotationType.X:
    9. this.X = this.X;
    10. this.Y = this.Y * cosa - this.Z * sina;
    11. this.Z = this.Y * sina + this.Z * cosa;
    12. break;
    13. case RotationType.Y:
    14. this.X = this.Z * sina + this.X * cosa;
    15. this.Y = this.Y;
    16. this.Z = this.Z * cosa - this.X * sina;
    17. break;
    18. default:
    19. this.X = this.X * cosa - this.Y * sina;
    20. this.Y = this.X * sina + this.Y * cosa;
    21. this.Z = this.Z;
    22. break;
    23. }
    24. }


    //EDIT 4:
    Es scheint so, als hätte ich irgendwo in den neuen Methoden noch einen Fehler eingebaut, könnte sich das vielleicht noch jemand anschauen? Hab langsam keinen Bock mehr.

    MfG Stefan

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „nafets3646“ ()

    @nafets3646
    Ich habe nun das Problem mit der ansteigenden CPU-Auslastung gelöst :thumbsup: Es hat allerdings recht lange gedauert, bis ich es geschafft habe :)

    Das Problem lag tatsächlich an den alten Instanzen, die den Arbeitsspeicher zumüllten. Als erstes änderte ich die Rotate-Methoden der Point3D und der Face3D Klasse, wie du es in deinem 3.Edit gepostet hast. Im Tick-Event des Timers rief ich dann die Rotate-Methode einer Fläche auf. So der Code:

    C#-Quellcode

    1. faces.ToList().ForEach(f => f.Rotate(Point3D.RotationType.X, 2));

    Als ich dann das Programm startete, wurde der Cube immer länglicher. Hier ein Screenshot:

    Ich denke, dass war auch das Problem von @nafets3646. Schnell wurde mir klar, dass der Fehler in der Rotate-Methode der Point3D-Klasse war. Bis ich dann allerdings den Fehler gefunden habe, ging es doch noch eine ganze Weile. Ich habe so oft den Rechenweg kontrolliert, doch das eigentlich simple Problem ist mir einfach nicht aufgefallen. :D

    C#-Quellcode

    1. case RotationType.X:
    2. Y = Y * cosa - Z * sina;
    3. Z = Y * sina + Z * cosa;

    Die neuen Koordinaten sollen ja aus den ursprünglichen Koordinaten berechnet werden. In dem Code von @nafets3646 wird das Y berechnet, welches allerdings anschließend gleich für die Z-Berechnung verwendet wird. So gehört es dann richtig:

    C#-Quellcode

    1. case RotationType.X:
    2. double yalt = Y;
    3. Y = Y * cosa - Z * sina;
    4. Z = yalt * sina + Z * cosa;

    So jetzt poste ich noch den ganzen Code meiner Anwendung:
    Point3D.cs

    C#-Quellcode

    1. using System;
    2. namespace _3DCube
    3. {
    4. class Point3D
    5. {
    6. public double X { get; set; }
    7. public double Y { get; set; }
    8. public double Z { get; set; }
    9. public Point3D(double x, double y, double z)
    10. {
    11. this.X = x;
    12. this.Y = y;
    13. this.Z = z;
    14. }
    15. public void Rotate(RotationType type, int angle)
    16. {
    17. double rad = angle * Math.PI / 180;
    18. double cosa = Math.Cos(rad);
    19. double sina = Math.Sin(rad);
    20. switch (type)
    21. {
    22. case RotationType.X:
    23. double yalt = Y;
    24. Y = Y * cosa - Z * sina;
    25. Z = yalt * sina + Z * cosa;
    26. break;
    27. case RotationType.Y:
    28. double xalt = X;
    29. X = Z * sina + X * cosa;
    30. Z = Z * cosa - xalt * sina;
    31. break;
    32. case RotationType.Z:
    33. xalt = X;
    34. X = X * cosa - Y * sina;
    35. Y = xalt * sina - Y * cosa;
    36. break;
    37. }
    38. }
    39. public Point3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    40. {
    41. double factor = fov / (viewDistance + this.Z);
    42. double Xn = this.X * factor + viewWidth / 2;
    43. double Yn = this.Y * factor + viewHeight / 2;
    44. return new Point3D(Xn, Yn, this.Z);
    45. }
    46. public enum RotationType { X, Y, Z }
    47. }
    48. }


    Face3D.cs

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Drawing;
    5. using System.Drawing.Drawing2D;
    6. namespace _3DCube
    7. {
    8. class Face3D
    9. {
    10. public IEnumerable<Point3D> Edges { get; set; }
    11. public Color Color;
    12. public Face3D(IEnumerable<Point3D> edges, Color color)
    13. {
    14. this.Edges = edges;
    15. this.Color = color;
    16. }
    17. public void Rotate(Point3D.RotationType type, int angle)
    18. {
    19. this.Edges.ToList().ForEach(p => p.Rotate(type, angle));
    20. }
    21. public Face3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    22. {
    23. IEnumerable<Point3D> parr = Edges.Select(p => p.Project(viewWidth, viewHeight, fov, viewDistance));
    24. double mid = parr.Sum(p => p.Z) / parr.Count();
    25. parr = parr.Select(p => new Point3D(p.X, p.Y, mid));
    26. return new Face3D(parr, Color);
    27. }
    28. }
    29. }


    FormMain.cs

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Drawing.Drawing2D;
    7. using System.Linq;
    8. using System.Text;
    9. using System.Windows.Forms;
    10. namespace _3DCube
    11. {
    12. public partial class FormMain : Form
    13. {
    14. private IEnumerable<Face3D> faces;
    15. private Timer timer;
    16. public FormMain()
    17. {
    18. InitializeComponent();
    19. SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    20. SetStyle(ControlStyles.DoubleBuffer, true);
    21. SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    22. SetStyle(ControlStyles.UserPaint, true);
    23. InitCube();
    24. }
    25. void InitCube()
    26. {
    27. timer = new Timer();
    28. timer.Interval = 25;
    29. timer.Tick += timer_Tick;
    30. timer.Start();
    31. faces = genFaces();
    32. }
    33. void timer_Tick(object sender, EventArgs e)
    34. {
    35. faces.ToList().ForEach(f => { f.Rotate(Point3D.RotationType.X, 2); f.Rotate(Point3D.RotationType.Y,2);});
    36. this.Invalidate();
    37. }
    38. private IEnumerable<Face3D> genFaces()
    39. {
    40. Point3D[] Corners = new Point3D[] {
    41. new Point3D(-1, 1, -1),
    42. new Point3D(1, 1, -1),
    43. new Point3D(1, -1, -1),
    44. new Point3D(-1, -1, -1),
    45. new Point3D(-1, 1, 1),
    46. new Point3D(1, 1, 1),
    47. new Point3D(1, -1, 1),
    48. new Point3D(-1, -1, 1)
    49. };
    50. return new Face3D[] {
    51. new Face3D(new Point3D[] { Corners[0], Corners[1], Corners[2], Corners[3] }, Color.Red),
    52. new Face3D(new Point3D[] { Corners[4], Corners[5], Corners[6], Corners[7] }, Color.Blue),
    53. new Face3D(new Point3D[] { Corners[3], Corners[2], Corners[6], Corners[7] }, Color.Green),
    54. new Face3D(new Point3D[] { Corners[0], Corners[1], Corners[5], Corners[4] }, Color.Yellow),
    55. new Face3D(new Point3D[] { Corners[5], Corners[1], Corners[2], Corners[6] }, Color.Purple),
    56. new Face3D(new Point3D[] { Corners[4], Corners[0], Corners[3], Corners[7] }, Color.Brown)
    57. };
    58. }
    59. private void FormMain_Paint(object sender, PaintEventArgs e)
    60. {
    61. e.Graphics.Clear(Color.LightBlue);
    62. e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    63. IEnumerable<Face3D> facesProjected = faces.Select(f => f.Project(this.ClientRectangle.Width, this.ClientRectangle.Height, 100, 4));
    64. facesProjected = facesProjected.OrderBy(p => p.Edges.ElementAt(0).Z).ToArray();
    65. foreach (Face3D face in facesProjected.Reverse())
    66. {
    67. PointF[] parr = face.Edges.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray();
    68. e.Graphics.FillPolygon(new SolidBrush(face.Color), parr);
    69. e.Graphics.DrawPolygon(Pens.Black, parr);
    70. }
    71. }
    72. }
    73. }


    Jetzt mache ich mach dann an den nächsten Schritt: Die Unterteilung in 9 Felder pro Fläche. Ich weiß, ich wollte das schon früher machen, doch mir war es wichtiger, dass der Würfel sich zuerst flüssig dreht, bevor ich mit einem fehlerhaften Modell weiterarbeite. Ich melde mich wenn ich das dann geschafft habe :)

    Vielen Dank für die Hilfe
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom