Rubik's Cube Solver mit 3D-Darstellung

  • C#

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

    @Switcherlapp97
    Gute Arbeit!
    So lob ich mir das, alles schön der Reihe nach solide arbeiten. :)
    Läuft nun schnell und ohne viel RAM.
    Die ganzen Flächen würde ich per Schleifen generieren, per Hand ist das ja ziemlich mühsam :D

    //EDIT:
    e.Graphics.Clear(Color.LightBlue); ist unnötig wenn deine Zeichenfläche schon die gewünschte Farbe hat, schließlich steht das alles im Paint-Event.
    Und der angle-Parameter in den Rotate-Funktionen darf ruhig auch double sein :)
    SᴛᴀʀGᴀᴛᴇ01

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

    Hallo,

    Ich habe nun auch die Unterteilung in 9 Felder pro Fläche fertig gestellt. Ich habe nur die genFaces abgeändert und noch eine kleine Zusatzfunktion zur Vereinfachung geschrieben. Hier mein Ergebnis:


    Code

    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. Face3D[] Faces = 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.Orange),
    16. new Face3D(new Point3D[] { Corners[3], Corners[2], Corners[6], Corners[7] }, Color.White),
    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.Blue),
    19. new Face3D(new Point3D[] { Corners[4], Corners[0], Corners[3], Corners[7] }, Color.Green)
    20. };
    21. List<Face3D> subFaces = new List<Face3D>();
    22. foreach (Face3D f in Faces)
    23. {
    24. int comCo = 0;
    25. double comCoValue = 0;
    26. Point3D[] edges = f.Edges.ToArray<Point3D>();
    27. switch (GetCommonCoordinate(f.Edges))
    28. {
    29. case 0:
    30. comCo = 0;
    31. comCoValue = edges[0].X;
    32. break;
    33. case 1:
    34. comCo = 1;
    35. comCoValue = edges[0].Y;
    36. break;
    37. case 2:
    38. comCo = 2;
    39. comCoValue = edges[0].Z;
    40. break;
    41. }
    42. for (double i = -1; i < 2.0/3; i += 2.0 / 3)
    43. {
    44. for (double j = -1; j < 2.0/3; j += 2.0 / 3)
    45. {
    46. switch (comCo)
    47. {
    48. case 0:
    49. subFaces.Add(new Face3D(new Point3D[] { new Point3D(comCoValue, i + 2.0 / 3, j), new Point3D(comCoValue, i, j), new Point3D(comCoValue, i, j + 2.0 / 3), new Point3D(comCoValue, i + 2.0 / 3, j + 2.0 / 3) }, f.Color));
    50. break;
    51. case 1:
    52. subFaces.Add(new Face3D(new Point3D[] { new Point3D(i + 2.0 / 3, comCoValue, j), new Point3D(i , comCoValue, j), new Point3D(i, comCoValue, j + 2.0 / 3), new Point3D(i + 2.0 / 3, comCoValue, j + 2.0 / 3) }, f.Color));
    53. break;
    54. case 2:
    55. subFaces.Add(new Face3D(new Point3D[] { new Point3D(i + 2.0 / 3, j, comCoValue), new Point3D(i, j, comCoValue), new Point3D(i, j + 2.0 / 3, comCoValue), new Point3D(i + 2.0 / 3, j + 2.0 / 3, comCoValue) }, f.Color));
    56. break;
    57. }
    58. }
    59. }
    60. }
    61. return subFaces;
    62. }
    63. //Die Zusatzmethode
    64. private int GetCommonCoordinate(IEnumerable<Point3D> facepoints)
    65. {
    66. if (facepoints.Where(point => point.X == 1).Count() == 4 || facepoints.Where(point => point.X == -1).Count() == 4) return 0;
    67. if (facepoints.Where(point => point.Y == 1).Count() == 4 || facepoints.Where(point => point.Y == -1).Count() == 4) return 1;
    68. else return 2;
    69. }


    Ich bin mir sicher, dass man den Code noch vereinfachen kann. Ich wäre also froh, wenn ihr meinen Code noch etwas optimieren könntet, denn in Sachen Linq und anderen Code-Verkürzungen, die die Performance des Programms steigern, kenne ich mich einfach noch nicht so gut aus :S

    Danke für die bisherigen Tipps und Tricks
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom
    Ich hätte noch eine Idee:
    Wenn Du später Animationen einfügen willst, sieht man beim Rotieren einer Schicht ja die Hinterseiten der Subwürfel.
    Deswegen und aus Gründen der Übersichtlichkeit würde ich eine neue Klasse einführen: Cube3D.
    Diese beschreibt einfach einen Würfel aus 6 Flächen und wrappt die Rotate- und Project-Methoden.
    Die Rotation um was anderes als den Mittepunkt erfordert auch noch ein bisschen Überlegung, sollte aber einfach machbar sein.
    Vielleicht komm ich dazu das zu implementieren
    SᴛᴀʀGᴀᴛᴇ01
    So, hab meine Idee implementiert.
    Vorteile:
    Du kannst die Schichten tatsächlich animieren
    (weil Würfel können sich um etwas anderes als den Nullpunkt drehen)
    Übersichtlich & Performant dank Linq

    Cube3D
    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 Cube3D
    7. {
    8. public IEnumerable<Face3D> Faces;
    9. public Cube3D(Point3D location, double scale, IEnumerable<Color> colors)
    10. {
    11. Stack<Color> colorStack = new Stack<Color>(colors);
    12. Faces = UniCube.genFaces();
    13. Faces.ToList().ForEach(f =>
    14. {
    15. f.Color = colorStack.Pop(); //color
    16. f.Edges.ToList().ForEach(e =>
    17. {
    18. e.X *= scale; e.Y *= scale; e.Z *= scale; //scale
    19. e.X += location.X; e.Y += location.Y; e.Z += location.Z; //translate
    20. });
    21. });
    22. }
    23. public Cube3D(IEnumerable<Face3D> faces)
    24. {
    25. Faces = faces;
    26. }
    27. public void Rotate(Point3D.RotationType type, double angle, Point3D center)
    28. {
    29. this.Faces.ToList().ForEach(f =>
    30. {
    31. f.Edges.ToList().ForEach(e =>
    32. {
    33. e.X -= center.X; e.Y -= center.Y; e.Z -= center.Z; //translate to center
    34. });
    35. f.Rotate(type, angle); //rotate
    36. f.Edges.ToList().ForEach(e =>
    37. {
    38. e.X += center.X; e.Y += center.Y; e.Z += center.Z; //translate back
    39. });
    40. });
    41. }
    42. public Cube3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    43. {
    44. IEnumerable<Face3D> faces = Faces.Select(f => f.Project(viewWidth, viewHeight, fov, viewDistance));
    45. return new Cube3D(faces);
    46. }
    47. }
    48. static class UniCube
    49. {
    50. public static IEnumerable<Face3D> genFaces()
    51. {
    52. return new Face3D[] {
    53. 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.Black),
    54. 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.Black),
    55. 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.Black),
    56. 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.Black),
    57. 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.Black),
    58. 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.Black)
    59. };
    60. }
    61. }



    Form
    Spoiler anzeigen

    C#-Quellcode

    1. private List<Cube3D> cubes;
    2. private Timer timer;
    3. public Form1()
    4. {
    5. InitializeComponent();
    6. SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    7. SetStyle(ControlStyles.DoubleBuffer, true);
    8. SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    9. SetStyle(ControlStyles.UserPaint, true);
    10. InitCubes();
    11. }
    12. void InitCubes()
    13. {
    14. timer = new Timer();
    15. timer.Interval = 10;
    16. timer.Tick += timer_Tick;
    17. timer.Start();
    18. cubes = new List<Cube3D>();
    19. double ed = ((double)2 / (double)3);
    20. for (int i = -1; i <= 1; i++)
    21. {
    22. for (int j = -1; j <= 1; j++)
    23. {
    24. for (int k = -1; k <= 1; k++)
    25. {
    26. cubes.Add(new Cube3D(new Point3D(ed * i, ed * j, ed * k), ed / 2, new Color[] { Color.Orange, Color.Red, Color.Yellow, Color.White, Color.Blue, Color.Green }));
    27. }
    28. }
    29. }
    30. }
    31. void timer_Tick(object sender, EventArgs e)
    32. {
    33. cubes.ForEach(c => { c.Rotate(Point3D.RotationType.X, 1, new Point3D(0, 0, 0)); c.Rotate(Point3D.RotationType.Y, 1, new Point3D(0, 0, 0)); });
    34. this.Invalidate();
    35. }
    36. private void Form1_Paint(object sender, PaintEventArgs e)
    37. {
    38. e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    39. IEnumerable<Face3D> facesProjected = cubes.Select(c => c.Project(this.ClientRectangle.Width, this.ClientRectangle.Height, 100, 4).Faces).Aggregate((a, b) => a.Concat(b));
    40. facesProjected = facesProjected.OrderBy(p => p.Edges.ElementAt(0).Z).ToArray();
    41. foreach (Face3D face in facesProjected.Reverse())
    42. {
    43. PointF[] parr = face.Edges.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray();
    44. e.Graphics.FillPolygon(new SolidBrush(face.Color), parr);
    45. e.Graphics.DrawPolygon(Pens.Black, parr);
    46. }
    47. }







    //EDIT:
    Rotation der obersten Schicht mit
    Spoiler anzeigen

    C#-Quellcode

    1. Cube3D c = new Cube3D(new Point3D(ed * i, ed * j, ed * k), ed / 2, new Color[] { Color.Orange, Color.Red, Color.Yellow, Color.White, Color.Blue, Color.Green });
    2. if(j==-1)
    3. {
    4. c.Rotate(Point3D.RotationType.Y, 45, new Point3D(0, ed, 0));
    5. }
    6. cubes.Add(c);


    in der Schleife in InitCubes()
    SᴛᴀʀGᴀᴛᴇ01

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

    @RushDen
    Soweit ich weiß kommt es immer auf den Anwendungszweck an. Im Hintergrund werden zwar nur "normale" Methoden ausgeführt, diese sind jedoch hochperformant. Schau dirs doch einfach mal an (ILSpy > System.Core > System.Linq > Enumerable). Außerdem denke ich nicht, dass die Performance hier so eine große Rolle spielt, dass die minimalen Unterschiede effektiv eine Rolle spielen.

    PS: Ich werde mich wahrscheinlich morgen mal damit befassen, was richtig schön optimiertes und mit sehr wenig Code in Betrieb zu nehmendes System zu entwerfen. Hatte sowieso schon vor, irgendwann das 3D-Zeugs an meinen Rubik's Cube Solver dranzuhängen :P. Ich kann dann ja eventuell das fertige "Produkt" in den Sourcecode-Austausch stellen :).

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

    @Switcherlapp97
    Ich habe die Rotate-Methode des Cube3D so umgeschrieben, dass sie eine neue Instanz zurückgibt.
    Warum der Spaß? Ich wollte den gesamten Würfel und gleichzeitig einzelne Schichten Drehen.
    Als erstes müssen die Schichten aus der Nullposition heraus gedreht werden und dann erst der ganze Würfel.
    Um diese Nullposition zu erhalten, habe ich die Methode geändert.
    Außerdem kennt ein Subwürfel nun über ein Flag-System seine Position im Rubik.

    Cube3D
    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 Cube3D
    7. {
    8. public IEnumerable<Face3D> Faces;
    9. [Flags]
    10. public enum RubikPosition
    11. {
    12. None = 0,
    13. TopLayer = 1,
    14. MiddleLayer = 2,
    15. BottomLayer = 4,
    16. FrontSlice = 8,
    17. MiddleSlice = 16,
    18. BackSlice = 32,
    19. LeftSlice = 64,
    20. MiddleSlice_Sides = 128,
    21. RightSlice = 256
    22. }
    23. public RubikPosition Position;
    24. public Cube3D(Point3D location, double scale, IEnumerable<Color> colors, RubikPosition position)
    25. {
    26. Stack<Color> colorStack = new Stack<Color>(colors);
    27. Faces = UniCube.genFaces();
    28. Faces.ToList().ForEach(f => {
    29. f.Color = colorStack.Pop(); //color
    30. f.Edges.ToList().ForEach(e => {
    31. e.X *= scale; e.Y *= scale; e.Z *= scale; //scale
    32. e.X += location.X; e.Y += location.Y; e.Z += location.Z; //translate
    33. });
    34. });
    35. Position = position;
    36. }
    37. public Cube3D(IEnumerable<Face3D> faces, RubikPosition position)
    38. {
    39. Faces = faces;
    40. Position = position;
    41. }
    42. public Cube3D Rotate(Point3D.RotationType type, double angle, Point3D center)
    43. {
    44. //Deep Clone
    45. List<Face3D> faces = new List<Face3D>();
    46. foreach (Face3D f in Faces)
    47. {
    48. List<Point3D> edges = new List<Point3D>();
    49. foreach (Point3D p in f.Edges) { edges.Add(new Point3D(p.X, p.Y, p.Z)); }
    50. Face3D f2 = new Face3D(edges, f.Color);
    51. f2.Edges.ToList().ForEach(e => { e.X -= center.X; e.Y -= center.Y; e.Z -= center.Z; });
    52. f2.Rotate(type, angle);
    53. f2.Edges.ToList().ForEach(e => { e.X += center.X; e.Y += center.Y; e.Z += center.Z; });
    54. faces.Add(f2);
    55. }
    56. return new Cube3D(faces, Position);
    57. }
    58. public Cube3D Project(int viewWidth, int viewHeight, int fov, int viewDistance)
    59. {
    60. return new Cube3D(Faces.Select(f => f.Project(viewWidth, viewHeight, fov, viewDistance)), Position);
    61. }
    62. }
    63. static class UniCube
    64. {
    65. public static IEnumerable<Face3D> genFaces()
    66. {
    67. return new Face3D[] {
    68. 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.Black),
    69. 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.Black),
    70. 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.Black),
    71. 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.Black),
    72. 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.Black),
    73. 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.Black)
    74. };
    75. }
    76. }



    Form
    Spoiler anzeigen

    C#-Quellcode

    1. private Color[] colors = { Color.Orange, Color.Red, Color.Yellow, Color.White, Color.Blue, Color.Green };
    2. private List<Cube3D> cubes;
    3. private List<Cube3D> cubesRender;
    4. private double[] rotation = { 0, 0, 0 };
    5. private double topLayerRotation = 0;
    6. private Timer timer;
    7. public Form1()
    8. {
    9. InitializeComponent();
    10. SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    11. SetStyle(ControlStyles.DoubleBuffer, true);
    12. SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    13. SetStyle(ControlStyles.UserPaint, true);
    14. InitCubes();
    15. }
    16. public void InitCubes()
    17. {
    18. cubesRender = new List<Cube3D>();
    19. cubes = new List<Cube3D>();
    20. double ed = ((double)2 / (double)3);
    21. for (int i = -1; i <= 1; i++)
    22. {
    23. for (int j = -1; j <= 1; j++)
    24. {
    25. for (int k = -1; k <= 1; k++)
    26. {
    27. cubes.Add(new Cube3D(new Point3D(ed * i, ed * j, ed * k), ed / 2, colors, genSideFlags(i, j, k)));
    28. }
    29. }
    30. }
    31. timer = new Timer();
    32. timer.Interval = 10;
    33. timer.Tick += timer_Tick;
    34. timer.Start();
    35. }
    36. private Cube3D.RubikPosition genSideFlags(int i, int j, int k)
    37. {
    38. Cube3D.RubikPosition rp = Cube3D.RubikPosition.None;
    39. switch (i)
    40. {
    41. case -1: rp |= Cube3D.RubikPosition.LeftSlice; break;
    42. case 0: rp |= Cube3D.RubikPosition.MiddleSlice_Sides; break;
    43. case 1: rp |= Cube3D.RubikPosition.RightSlice; break;
    44. }
    45. switch (j)
    46. {
    47. case -1: rp |= Cube3D.RubikPosition.TopLayer; break;
    48. case 0: rp |= Cube3D.RubikPosition.MiddleLayer; break;
    49. case 1: rp |= Cube3D.RubikPosition.BottomLayer; break;
    50. }
    51. switch (k)
    52. {
    53. case -1: rp |= Cube3D.RubikPosition.FrontSlice; break;
    54. case 0: rp |= Cube3D.RubikPosition.MiddleSlice; break;
    55. case 1: rp |= Cube3D.RubikPosition.BackSlice; break;
    56. }
    57. return rp;
    58. }
    59. void timer_Tick(object sender, EventArgs e)
    60. {
    61. cubesRender.Clear();
    62. foreach (Cube3D c in cubes)
    63. {
    64. Cube3D cr = c.Rotate(Point3D.RotationType.X, 0, new Point3D(0, 0, 0));
    65. //Rotate single layers
    66. if (cr.Position.HasFlag(Cube3D.RubikPosition.TopLayer)) cr = cr.Rotate(Point3D.RotationType.Y, topLayerRotation, new Point3D(0, (double)2 / (double)3, 0));
    67. if (cr.Position.HasFlag(Cube3D.RubikPosition.BottomLayer)) cr = cr.Rotate(Point3D.RotationType.Y, -topLayerRotation, new Point3D(0, -(double)2 / (double)3, 0));
    68. //Rotate cube
    69. cr = cr.Rotate(Point3D.RotationType.X, rotation[0], new Point3D(0, 0, 0));
    70. cr = cr.Rotate(Point3D.RotationType.Y, rotation[1], new Point3D(0, 0, 0));
    71. cubesRender.Add(cr);
    72. }
    73. rotation[0] += 1;
    74. rotation[1] += 1;
    75. topLayerRotation += 1;
    76. this.Invalidate();
    77. }
    78. private void Form1_Paint(object sender, PaintEventArgs e)
    79. {
    80. if (cubesRender.Count() > 0)
    81. {
    82. e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    83. IEnumerable<Face3D> facesProjected = cubesRender.Select(c => c.Project(this.ClientRectangle.Width, this.ClientRectangle.Height, 100, 4).Faces).Aggregate((a, b) => a.Concat(b));
    84. facesProjected = facesProjected.OrderBy(p => p.Edges.ElementAt(0).Z).ToArray();
    85. foreach (Face3D face in facesProjected.Reverse())
    86. {
    87. PointF[] parr = face.Edges.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray();
    88. e.Graphics.FillPolygon(new SolidBrush(face.Color), parr);
    89. e.Graphics.DrawPolygon(Pens.Black, parr);
    90. }
    91. }
    92. }



    Das sieht dann so aus:

    (Wär auch n cooler Bildschirmschoner :D)


    Man kann nun also einzelne Schichten UND den ganzen Würfel drehen.
    Ihr dürft gern an dem Code rumwurschteln ^^

    //EDIT:
    Und achja @nafets3646: Kann es sein dass die Z-Rotation rumbuggt?
    SᴛᴀʀGᴀᴛᴇ01

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

    @StarGate01
    Sieht genial aus :thumbsup:.
    Bei welchem Code soll denn die Z-Rotation rumbuggen?

    PS: Ich arbeite noch an meinem Progrämmchen, es kann also noch etwas dauern, bis ich fertig bin.
    @nafets3646

    C#-Quellcode

    1. foreach (Cube3D c in cubes)
    2. {
    3. Cube3D cr = c.Rotate(Point3D.RotationType.Z, rotation[2], new Point3D(0, 0, 0));
    4. cubesRender.Add(cr);
    5. }
    6. rotation[0] += 0;
    7. rotation[1] += 0;
    8. rotation[2] += 1;


    wird zu

    (Ja, _nur_ das! Und es wobbelt so komisch hin und her)

    Erwartetes Verhalten:
    Man sieht die grüne Fläche sich unverzerrt um den Fenstermittelpunkt drehen, der Rest vom Rubik liegt dahinter.

    Ich vermute mal dass es ein ähnlicher Fehler wie der oben war ist bzw. die Trigonometrie mal wieder reinpfuscht :D

    Hier nochmal die verwendete Rotate-Methode:
    Spoiler anzeigen

    C#-Quellcode

    1. public void Rotate(RotationType type, double 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. double yalt = Y;
    10. Y = Y * cosa - Z * sina;
    11. Z = yalt * sina + Z * cosa;
    12. break;
    13. case RotationType.Y:
    14. double xalt = X;
    15. X = Z * sina + X * cosa;
    16. Z = Z * cosa - xalt * sina;
    17. break;
    18. case RotationType.Z:
    19. xalt = X;
    20. X = X * cosa - Y * sina;
    21. Y = xalt * sina - Y * cosa;
    22. break;
    23. }
    24. }



    //EDIT:
    Scheint nur ein kleiner Vorzeichenfehler gewesen zu sein:
    Y = xalt * sina - Y * cosa; wird zu Y = xalt * sina + Y * cosa;
    SᴛᴀʀGᴀᴛᴇ01

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

    Ich hatte Die Idee alles in eine Rubik-Klasse zu packen und die Rotationen der einzelnen Schichten komfortabel nach außen hin zugänglich zu machen.

    Rubik
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Drawing;
    6. using System.Drawing.Drawing2D;
    7. class Rubik
    8. {
    9. private Color[] colors = { Color.Orange, Color.Red, Color.Yellow, Color.White, Color.Blue, Color.Green };
    10. private List<Cube3D> cubes;
    11. private List<Cube3D> cubesRender;
    12. public double[] Rotation = { 0, 0, 0 };
    13. public Dictionary<Cube3D.RubikPosition, double> LayerRotation = new Dictionary<Cube3D.RubikPosition, double>();
    14. public Rubik()
    15. {
    16. cubesRender = new List<Cube3D>();
    17. cubes = new List<Cube3D>();
    18. double ed = ((double)2 / (double)3);
    19. for (int i = -1; i <= 1; i++)
    20. {
    21. for (int j = -1; j <= 1; j++)
    22. {
    23. for (int k = -1; k <= 1; k++)
    24. {
    25. cubes.Add(new Cube3D(new Point3D(ed * i, ed * j, ed * k), ed / 2, colors, genSideFlags(i, j, k)));
    26. }
    27. }
    28. }
    29. foreach (Cube3D.RubikPosition rp in (Cube3D.RubikPosition[])Enum.GetValues(typeof(Cube3D.RubikPosition)))
    30. {
    31. LayerRotation[rp] = 0;
    32. }
    33. }
    34. private Cube3D.RubikPosition genSideFlags(int i, int j, int k)
    35. {
    36. Cube3D.RubikPosition rp = Cube3D.RubikPosition.None;
    37. switch (i)
    38. {
    39. case -1: rp |= Cube3D.RubikPosition.LeftSlice; break;
    40. case 0: rp |= Cube3D.RubikPosition.MiddleSlice_Sides; break;
    41. case 1: rp |= Cube3D.RubikPosition.RightSlice; break;
    42. }
    43. switch (j)
    44. {
    45. case -1: rp |= Cube3D.RubikPosition.TopLayer; break;
    46. case 0: rp |= Cube3D.RubikPosition.MiddleLayer; break;
    47. case 1: rp |= Cube3D.RubikPosition.BottomLayer; break;
    48. }
    49. switch (k)
    50. {
    51. case -1: rp |= Cube3D.RubikPosition.FrontSlice; break;
    52. case 0: rp |= Cube3D.RubikPosition.MiddleSlice; break;
    53. case 1: rp |= Cube3D.RubikPosition.BackSlice; break;
    54. }
    55. return rp;
    56. }
    57. public void Render(Graphics g, Rectangle screen)
    58. {
    59. cubesRender.Clear();
    60. foreach (Cube3D c in cubes)
    61. {
    62. Cube3D cr = c.Rotate(0, 0, new Point3D(0, 0, 0));
    63. double ed = ((double)2 / (double)3);
    64. if (cr.Position.HasFlag(Cube3D.RubikPosition.TopLayer)) cr = cr.Rotate(Point3D.RotationType.Y, LayerRotation[Cube3D.RubikPosition.TopLayer], new Point3D(0, ed, 0));
    65. if (cr.Position.HasFlag(Cube3D.RubikPosition.MiddleLayer)) cr = cr.Rotate(Point3D.RotationType.Y, LayerRotation[Cube3D.RubikPosition.MiddleLayer], new Point3D(0, 0, 0));
    66. if (cr.Position.HasFlag(Cube3D.RubikPosition.BottomLayer)) cr = cr.Rotate(Point3D.RotationType.Y, LayerRotation[Cube3D.RubikPosition.BottomLayer], new Point3D(0, -ed, 0));
    67. if (cr.Position.HasFlag(Cube3D.RubikPosition.FrontSlice)) cr = cr.Rotate(Point3D.RotationType.Z, LayerRotation[Cube3D.RubikPosition.FrontSlice], new Point3D(0, 0, ed));
    68. if (cr.Position.HasFlag(Cube3D.RubikPosition.MiddleSlice)) cr = cr.Rotate(Point3D.RotationType.Z, LayerRotation[Cube3D.RubikPosition.MiddleSlice], new Point3D(0, 0, 0));
    69. if (cr.Position.HasFlag(Cube3D.RubikPosition.BackSlice)) cr = cr.Rotate(Point3D.RotationType.Z, LayerRotation[Cube3D.RubikPosition.BackSlice], new Point3D(0, 0, -ed));
    70. if (cr.Position.HasFlag(Cube3D.RubikPosition.LeftSlice)) cr = cr.Rotate(Point3D.RotationType.X, LayerRotation[Cube3D.RubikPosition.LeftSlice], new Point3D(-ed, 0, 0));
    71. if (cr.Position.HasFlag(Cube3D.RubikPosition.MiddleSlice_Sides)) cr = cr.Rotate(Point3D.RotationType.X, LayerRotation[Cube3D.RubikPosition.MiddleSlice_Sides], new Point3D(0, 0, 0));
    72. if (cr.Position.HasFlag(Cube3D.RubikPosition.RightSlice)) cr = cr.Rotate(Point3D.RotationType.X, LayerRotation[Cube3D.RubikPosition.RightSlice], new Point3D(ed, 0, 0));
    73. cr = cr.Rotate(Point3D.RotationType.X, Rotation[0], new Point3D(0, 0, 0));
    74. cr = cr.Rotate(Point3D.RotationType.Y, Rotation[1], new Point3D(0, 0, 0));
    75. cr = cr.Rotate(Point3D.RotationType.Y, Rotation[2], new Point3D(0, 0, 0));
    76. cubesRender.Add(cr);
    77. }
    78. g.SmoothingMode = SmoothingMode.AntiAlias;
    79. IEnumerable<Face3D> facesProjected = cubesRender.Select(c => c.Project(screen.Width, screen.Height, 100, 4).Faces).Aggregate((a, b) => a.Concat(b));
    80. facesProjected = facesProjected.OrderBy(p => p.Edges.ElementAt(0).Z).ToArray();
    81. foreach (Face3D face in facesProjected.Reverse())
    82. {
    83. PointF[] parr = face.Edges.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray();
    84. g.FillPolygon(new SolidBrush(face.Color), parr);
    85. g.DrawPolygon(Pens.Black, parr);
    86. }
    87. }
    88. }



    Der Code der Form reduziert sich dadurch sehr
    Form
    Spoiler anzeigen

    C#-Quellcode

    1. private Rubik rubikCube;
    2. private Timer timer;
    3. public Form1()
    4. {
    5. InitializeComponent();
    6. SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    7. SetStyle(ControlStyles.DoubleBuffer, true);
    8. SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    9. SetStyle(ControlStyles.UserPaint, true);
    10. rubikCube = new Rubik();
    11. timer = new Timer();
    12. timer.Interval = 10;
    13. timer.Tick += timer_Tick;
    14. timer.Start();
    15. }
    16. void timer_Tick(object sender, EventArgs e)
    17. {
    18. rubikCube.Rotation[0]++;
    19. rubikCube.Rotation[1]++;
    20. rubikCube.LayerRotation[Cube3D.RubikPosition.TopLayer]++;
    21. this.Invalidate();
    22. }
    23. private void Form1_Paint(object sender, PaintEventArgs e)
    24. {
    25. rubikCube.Render(e.Graphics, this.ClientRectangle);
    26. }
    27. }



    Die Rotation ist schön geschachtelt, man braucht nur

    C#-Quellcode

    1. rubikCube.Rotation[0]++;
    2. rubikCube.Rotation[1]++;
    3. rubikCube.LayerRotation[Cube3D.RubikPosition.TopLayer]++;

    Diesen übersichtlichen Code um den ganzen Würfel um die X- und Y-Achse zu drehen und gleichzeitig die oberste Schicht zu drehen.

    Man muss nur aufpassen nicht die Gesetze der Physik zu brechen und unmögliche Drehungen zu vollführen:



    //EDIT:
    Alle bisherigen Klassen sind im Anhang

    //EDIT2:
    Du musst Dich unbedingt darum kümmern dass die Subwürfelflags nach einer Rotation geändert werden, dh. dass die Subwürfel zur neu entstandenen bunten Fläche gehören. Das sollte dann aber die Methode (vllt von der klasse RubikManager oder so) übernehmen, die die Schichten kontrolliert in 90° Schritten dreht, die Rubik-Klasse würde ich so lassen.
    Dateien
    • Code.zip

      (3,92 kB, 58 mal heruntergeladen, zuletzt: )
    SᴛᴀʀGᴀᴛᴇ01

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

    So, mal wieder zu viel Zeit gehabt:

    RubikManager
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Drawing;
    6. using System.Drawing.Drawing2D;
    7. using System.Diagnostics;
    8. class RubikManager
    9. {
    10. public Rubik RubikCube;
    11. private Boolean Rotating;
    12. private double rotationStep;
    13. private Cube3D.RubikPosition rotationLayer;
    14. private int rotationTarget;
    15. public delegate void RotatingFinishedHandler(object sender);
    16. public event RotatingFinishedHandler OnRotatingFinished;
    17. private void BroadcastRotatingFinished()
    18. {
    19. Rotating = false;
    20. if (OnRotatingFinished == null) return;
    21. OnRotatingFinished(this);
    22. }
    23. public RubikManager()
    24. {
    25. RubikCube = new Rubik();
    26. Rotating = false;
    27. }
    28. public void Rotate90(Cube3D.RubikPosition layer, bool direction, int steps)
    29. {
    30. if (!Rotating)
    31. {
    32. Rotating = true;
    33. rotationLayer = layer;
    34. rotationStep = (double)90 / (double)steps;
    35. if (direction) rotationStep *= (-1);
    36. rotationTarget = 90;
    37. if (direction) rotationTarget = -90;
    38. }
    39. }
    40. public void Render(Graphics g, Rectangle screen)
    41. {
    42. RubikCube.Render(g, screen);
    43. if (Rotating)
    44. {
    45. RubikCube.LayerRotation[rotationLayer] += rotationStep;
    46. if ((rotationTarget > 0 && RubikCube.LayerRotation[rotationLayer] >= rotationTarget) || (rotationTarget < 0 && RubikCube.LayerRotation[rotationLayer] <= rotationTarget))
    47. {
    48. RubikCube.LayerRotation[rotationLayer] = rotationTarget;
    49. List<Cube3D> affected = RubikCube.cubes.Where(c => c.Position.HasFlag(rotationLayer)).ToList();
    50. if (rotationLayer == Cube3D.RubikPosition.LeftSlice || rotationLayer == Cube3D.RubikPosition.MiddleSlice_Sides || rotationLayer == Cube3D.RubikPosition.RightSlice)
    51. {
    52. affected.ForEach(c => c.Faces.ToList().ForEach(f => f.Rotate(Point3D.RotationType.X, rotationTarget)));
    53. }
    54. if (rotationLayer == Cube3D.RubikPosition.TopLayer || rotationLayer == Cube3D.RubikPosition.MiddleLayer || rotationLayer == Cube3D.RubikPosition.BottomLayer)
    55. {
    56. affected.ForEach(c => c.Faces.ToList().ForEach(f => f.Rotate(Point3D.RotationType.Y, rotationTarget)));
    57. }
    58. if (rotationLayer == Cube3D.RubikPosition.BackSlice || rotationLayer == Cube3D.RubikPosition.MiddleSlice || rotationLayer == Cube3D.RubikPosition.FrontSlice)
    59. {
    60. affected.ForEach(c => c.Faces.ToList().ForEach(f => f.Rotate(Point3D.RotationType.Z, rotationTarget)));
    61. }
    62. double ed = ((double)2 / (double)3);
    63. for (int i = -1; i <= 1; i++)
    64. {
    65. for (int j = -1; j <= 1; j++)
    66. {
    67. for (int k = -1; k <= 1; k++)
    68. {
    69. RubikCube.cubes.First(c => (Math.Round(c.Faces.Sum(f => f.Edges.Sum(e => e.X)) / 24, 4) == Math.Round(i * ed, 4))
    70. && (Math.Round(c.Faces.Sum(f => f.Edges.Sum(e => e.Y)) / 24, 4) == Math.Round(j * ed, 4))
    71. && (Math.Round(c.Faces.Sum(f => f.Edges.Sum(e => e.Z)) / 24, 4) == Math.Round(k * ed, 4))).Position = RubikCube.genSideFlags(i, j, k); ;
    72. }
    73. }
    74. }
    75. foreach (Cube3D.RubikPosition rp in (Cube3D.RubikPosition[])Enum.GetValues(typeof(Cube3D.RubikPosition))) RubikCube.LayerRotation[rp] = 0;
    76. BroadcastRotatingFinished();
    77. }
    78. }
    79. }
    80. }



    Diese Klasse managed einen Rubik und stellt eine Methode und ein Event zur kontrollierten Animation einer Drehung bereit.
    Außerdem löst sie das Permutationsproblem der Farben.

    Hab mir sogar ne GUI gebastelt:


    //EDIT:
    Das Projekt ist im Anhang
    Dateien
    • CubeGDI3D.zip

      (144,36 kB, 79 mal heruntergeladen, zuletzt: )
    SᴛᴀʀGᴀᴛᴇ01

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

    @StarGate01
    Unglaublich was du hier tolles gebastelt hast :thumbsup:
    Ich hätte nur noch eine kleine Frage. Ich möchte, wenn ich auf einen Button klicke zuerst die rechte Fläche und dann die linke Seite drehen. Ich habe es mal so probiert:

    C#-Quellcode

    1. private void button1_Click(object sender, EventArgs e)
    2. {
    3. comboBox1.Enabled = false;
    4. button1.Enabled = false;
    5. button2.Enabled = false;
    6. rubikManager.Rotate90(Cube3D.RubikPosition.LeftSlice, true, 40);
    7. rubikManager.Rotate90(Cube3D.RubikPosition.RightSlice, false, 40);
    8. }

    Es wird allerdings nur die erste Drehung durchgeführt.

    Ich hoffe das kannst du auch noch verbessern :)
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom
    @Switcherlapp97
    Eine Drehung funktioniert parallel zum Programmablauf.
    Durch den Aufruf der Rotate90-Methode meldest Du eine Rotation nur beim Manager an.
    Ausgeführt wird diese dann stückweise in der Render-Methode.
    Wenn die Drehung fertig ist, wird das Event RotatingFinished ausgelöst.
    Ab diesem Zeitpunkt kannst Du erst eine neue Drehung ausführen.
    Diese System soll verhindern dass der Würfel "kaputtgeht".

    Zu Deinem Problem:
    Bau dir doch einen Stack mit Zügen, den Du dann Stück für Stück abbaust.
    Wenn eine Drehung fertig ist (Event), initiierst Du die nächste usw.

    //EDIT:

    LayerMove

    C#-Quellcode

    1. using System;
    2. namespace CubeGDI3D
    3. {
    4. class LayerMove
    5. {
    6. public Cube3D.RubikPosition Layer;
    7. public Boolean Direction;
    8. public LayerMove(Cube3D.RubikPosition layer, Boolean direction)
    9. {
    10. Layer = layer;
    11. Direction = direction;
    12. }
    13. }
    14. }


    in der Form:

    C#-Quellcode

    1. private Stack<LayerMove> moveStack;

    - in Button1_Click

    C#-Quellcode

    1. comboBox1.Enabled = false;
    2. button1.Enabled = false;
    3. button2.Enabled = false;
    4. moveStack = new Stack<LayerMove>(new LayerMove[] {
    5. new LayerMove(Cube3D.RubikPosition.LeftSlice, true),
    6. new LayerMove(Cube3D.RubikPosition.RightSlice, false)
    7. });
    8. LayerMove nextMove = moveStack.Pop();
    9. rubikManager.Rotate90(nextMove.Layer, nextMove.Direction, 40);

    - in RotatingFinished

    C#-Quellcode

    1. if (moveStack.Count > 0)
    2. {
    3. LayerMove nextMove = moveStack.Pop();
    4. rubikManager.Rotate90(nextMove.Layer, nextMove.Direction, 40);
    5. }
    6. else
    7. {
    8. comboBox1.Enabled = true;
    9. button1.Enabled = true;
    10. button2.Enabled = true;
    11. }
    SᴛᴀʀGᴀᴛᴇ01

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

    @StarGate01
    Vielen Dank für die schnelle Hilfe. Es funktioniert nun so, wie es soll! :thumbsup:

    Nun möchte ich noch gerne etwas Anderes zum Thema Rubiks Cube für meinen Rubiks Cube Solver wissen.
    Sind diese Klassen nur fürs Zeichnen gedacht oder sind auch die Farben, usw. so gespeichert, dass man kein weiteres Speichermodell aufsetzen muss um mit dem Lösen des Würfels zu beginnen?
    Ich hoffe ich habe mich verständlich ausgedrückt ;)

    Liebe Grüße
    Switcherlapp97
    RubiksCubeSolver


    Jetzt im Showroom
    Mhmm.. Du meinst also die Farben direkt zu Anfang zu setzen?
    Das sollte schon gehen... bräuchte aber noch ein bissl Trickserei :)
    Man könnte für jeden Subwürfel ein eigenes Farbarray angeben.
    Man müsste nur wissen welche Fläche von welchen Würfel man anspricht, aber ansonsten, ja.
    Aber ja, die Klassen sind primär nur zum Zeichen gedacht. Zum Lösen gibt die wohl dein Lösungsalgorithmus ein Modell vor denke ich?

    Btw:
    Im Anhang ist nochmal die Implementierung vom Stack (mit GUI).
    Dateien
    • CubeGDI3D.zip

      (155,26 kB, 66 mal heruntergeladen, zuletzt: )
    SᴛᴀʀGᴀᴛᴇ01

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

    Es geht um Folgendes: Mein Programm soll später verdrehte Cubes lösen und die benötigten Schritte ausgeben. Ich habe nun ein Speichermodell für die einzelnen Blöcke und Ebenen aufgesetzt. Aktuell laufen deine Klassen fürs Zeichen unabhängig vom Speichermodell. Also es gibt zum Beispiel eine Rotate-Methode, die die Punkte fürs Zeichnen neu berechnet und projeziert, usw. und es gibt eine Rotate-Methode, die die Blöcke je nach Drehung verschiebt und die Blöcke dann in einer List<Block> speichert. Die erste Möglichkeit verwendet die Klassen von @StarGate01 und die andere Methode läuft mit dem Speichermodell. Ich würde gerne diese zwei Modelle miteinander verbinden, um so den Code zu verkürzen und die Anzahl an Klassen im Projekt zu vermindern. Wäre das möglich? Wenn ja, wie könnte ich anfangen?
    Ich hoffe es ist nun ein bisschen verständlicher.

    Danke
    Switcherlapp97

    EDIT: Man kann bei deiner tollen Testanwendung nur immer im Uhrzeigersinn drehen. Ist das so gewollt?
    RubiksCubeSolver


    Jetzt im Showroom
    Soo, ich hänge mal ein Projekt von mir dran (war mal ein Auftrag, ist ein frühes Stadium, da wars aber schon funktionsfähig und einigermaßen übersichtlich). Vielleicht hilft das ja weiter (@Switcherlapp97: ). Falls ich es heute noch schaffen sollte, das komplett sauber zu verbinden, lad ich das hier auch noch hoch :).
    Dateien
    @Switcherlapp97

    bzgl Edit:
    Ja. Hatte keine Lust noch nen Knopf einzubauen.

    bzgl. Frage:
    Tatsächlich verwenden tust Du nur dem Manager. Der übernimmt dann die ganzen Interna.
    Die Anzahl der Klassen zu Minimieren ist nicht immer günstig. Nicht Wartbarkeit für Kürze opfern.
    Es kommt nun darauf an Was für einen Algorithmus Du verwendest bzw. was der als Eingaben und Ausgaben hat.
    Ich würde das so lösen:
    1. Würfelkonfiguration von Benutzer anfordern
    2. RubikManager die Farben mitteilen und setzen (Das ist der Knackpunkt)
    3. RubikSolver (oder wasauchimmer) mit den (evtl. konvertierten) Eingaben füttern
    4. Die Ausgabe auf Züge umsetzten und in den Zügestack schaufeln.
    5. Den Stack ausführen

    //EDIT
    @nafets3646
    Mit welcher Version von VS ist das gemacht? Ich bekomms mit 2010 nicht geöffnet :(
    SᴛᴀʀGᴀᴛᴇ01