locked
How to draw Rotated Rhombus? RRS feed

  • Question

  • I'm making a program in C# that will rotate rhombs made out of "+" but I don't know how to draw them. I tried to type Drawing method myself but I'm stuck, I just don't know how to draw it anymore. I'd like to keep the code as simple as possible, without any bitmaps or graphics. If someone would come up with Drawing method I would be greatful.

    Punkt = Point   

    class romb    {       

    private punkt p_up { get; set; }      

    private punkt p_left { get; set; }        

    private punkt p_right { get; set; }        

    private punkt p_down { get; set; }  

        

    private void MoveOneFrame()      {       

    punkt pivot = new punkt(((this.p_left.Get_x() + this.p_right.Get_x()) / 2), ((this.p_left.Get_y() + this.p_right.Get_y()) / 2));       

    this.p_up = Rotate(p_up, pivot, 8);       

    this.p_down = Rotate(p_down, pivot, 8);       

    this.p_left = Rotate(p_left, pivot, 8);       

    this.p_right = Rotate(p_right, pivot, 8);     

    }     

    private static punkt Rotate(punkt point, punkt pivot, double angleDegree)      {                   

    double angle = angleDegree * Math.PI / 180;       

    double cos = Math.Cos(angle);       

    double sin = Math.Sin(angle);       

    int dx = point.Get_x() - pivot.Get_x();       

    int dy = point.Get_y() - pivot.Get_y();       

    double x = cos * dx - sin * dy + pivot.Get_x();       

    double y = sin * dx + cos * dy + pivot.Get_y();       

    punkt rotated = new punkt((int)Math.Round(x), (int)Math.Round(y));       

    return rotated;     

    }   

    }




    Output: - And every rhombus should rotate itself around pivots(their centers)

     
          +
         + +
        +   +
       +     +
      +       +
     +         +
    +           +        +
     +         +        + +
      +       +        +   +
       +     +        +     +
        +   +        +       +
         + +        +         +
          +        +           +
          +         +         +
         + +         +       +
        +   +         +     +
       +     +         +   +
      +       +         + +
     +         +         +      
    +           +   
     +         +
      +       +
       +     +
        +   +
         + +
          +       


    • Edited by Dreik100 Thursday, January 21, 2016 8:00 AM format fixing
    Thursday, January 21, 2016 7:56 AM

Answers

  • If you look at the DrawLine method that I posted it is using Winforms but the 2 commented out lines represent 2 other algorithms for drawing lines that would work on the console. The Graphics object isn't needed/supported in a console but include a reference to System.Drawing and everything else should compile.

    The only missing code is for drawing a pixel. For that replace it with the code you already had.

    private void DrawPixel ( Pen p, int x, int y )
    {
       Console.SetCursorPosition(x, y);
       Console.Write('+');
    }

    The rest of the code for the rhombus remains unchanged. But both algorithms for drawing a line don't work if the line is horizontal because X doesn't change.  I made a change to the IncrementalLine algorithm to try to take that into account, try this instead in your console code. For my sample it causes the rhombus to remain intact while rotating.

    //If the X axis is changing more than the Y then use it
    //as the basis for the increment
    if (Math.Abs(p2.GetX() - p1.GetX()) >= Math.Abs(p2.GetY() - p1.GetY()))
    { 
        if (p1.GetX() > p2.GetX())
        {
            var temp = p1;
            p1 = p2;
            p2 = temp;
        };
    
        int dx = p2.GetX() - p1.GetX();
        int dy = p2.GetY() - p1.GetY();
    
        var slope = dy / (double)dx;
    
        double y = p1.GetY();
        for (var x = p1.GetX(); x <= p2.GetX(); ++x)
        {
            DrawPixel(x, (int)Math.Round(y));
            y += slope;
        };
    } else
    {
        if (p1.GetY() > p2.GetY())
        {
            var temp = p1;
            p1 = p2;
            p2 = temp;
        };
    
        int dx = p2.GetX() - p1.GetX();
        int dy = p2.GetY() - p1.GetY();
    
        var slope = dx / (double)dy;
    
        double x = p1.GetX();
        for (var y = p1.GetY(); y <= p2.GetY(); ++y)
        {
            DrawPixel((int)Math.Round(x), y);
            x += slope;
        };
    };

    Sunday, January 24, 2016 1:17 AM

All replies

  • This looks like a homework assignment.  We don't write code for homework assignments. You mentioned that you had drawing code but it didn't work.  Please post that code so we can provide some direction.

    Michael Taylor
    http://blogs.msmvps.com/p3net

    Thursday, January 21, 2016 10:05 PM
  • Agreed. Typical homework assignment.
    Friday, January 22, 2016 12:01 AM
  • Here is the whole program to download https://drive.google.com/open?id=0B8dXAYWDB0eSUFd5RHdCVUZPQWc

    And its not Homework :P Just something what I'm tring to do.
    • Edited by Dreik100 Friday, January 22, 2016 1:27 PM
    Friday, January 22, 2016 1:26 PM
  • Your code is trying to do too much at once. The Rhombus code should be focused on defining a rhombus and drawing it on the screen.  A separate component should be responsible for moving it because ultimately a move is simply redrawing the rhombus with modified coordinates. To avoid aliasing you need to remove the old drawing first. In a more advanced drawing program you'd simply draw the rhombus again with the background color set as the foreground color but for this simple program clearing the screen works just as well.  Once you have 1 rhombus rotating on an interval, adding more becomes simple.

    Looking at your code the drawing of the rhombus is correct.  So the next step is to get it to rotate. You already have a static method for that but I would make it an instance method that takes the degrees and updates the existing points based upon the results of the calculation. At this point you can now draw and rotate a rhombus.

    To get the animation that you want you'll need a separate component because the rhombus should only be focused on drawing and manipulating itself. To animate you can create a separate type or simply put the code in the main program. It should be the main program that is responsible for drawing and determining when (and how much) each rhombus rotates.  I noticed you are using a task and that would certainly work but tasks are generally for doing async work that eventually ends. Since your program will continue indefinitely a timer is probably a better choice.

    static List<Rhombus> s_shapes = new List<Rhombus>();
    
        static void Main(string[] args)
        {
            Console.CursorVisible = false;
    
            int x = 15;
            int y = 9;
            s_shapes.Add(new Rhombus(new Point(x, y-7), new Point(x-7, y), new Point(x+7, y), new Point(x, y+7)));
    
            foreach (var shape in s_shapes)
                shape.Draw();
    
            //Start a timer
            var timer = new System.Timers.Timer(5000);
            timer.Elapsed += RotateShapes;
            timer.Start();
    
            Console.ReadLine();
        }                
    
        static void RotateShapes ( object sender, EventArgs e )
        {
            Console.Clear();
    
            foreach (var shape in s_shapes)
            {
                shape.Rotate(90);
                shape.Draw();
            };
        }
    This also sets you up later to add new shapes without having to update your code (beyond moving the core shape members into an interface or something.

    Friday, January 22, 2016 4:00 PM
  • Thank you very much, but how do I change the Rotate method now?
    Friday, January 22, 2016 6:49 PM
  • Your rotate method should simply calculate the new endpoints based upon the degrees.  You had an algorithm already but I didn't look to see if it is valid or not. Ultimately you're solving a triangle for each point. Given the rotation angle you already know the length of 1 side of the triangle so you can solve for the others. This will give you the offset for the existing point (at least in my mind). Such a question is better suited for other forums however.  Just as an example I hard coded a 90 degree rotation.

    public void Rotate ( double angleDegree )
    {
        var temp = p_up;
        p_up = p_left;
        p_left = p_down;
        p_down = p_right;
        p_right = temp;
    
        drawn = false;
    }

    In your Draw method you should probably not bother to calculate the points on the rhombus and then store them. Simply recalculate the points every time you need to draw again. So by having your Rotate method update the points then when the rhombus is drawn again it will be rotated. If you really want to store the points between draws then you need to make sure you change your drawn flag back to false after the rotate call.  In the above rotation code I used it broke when trying to draw the rotated value. 

    To be honest, drawing shapes in a console window isn't trivial. If possible you might consider doing this as a Winforms app which would easily allow you to draw a line between 2 points. In a console app you'll have to calculate the line that should be drawn and that isn't trivial. You end up having to interpolate where to put each point to build up the final line. Alternatively you could trying using Win32 but it is still a hack.

    As a final note, the timer I posted is a victim of a race condition. If the timer handler doesn't finish within the allotted time it will run again. Since you're using instance fields inside your drawing code you will end up stomping over the values while you're trying to use them causing crashes.  If you don't want to look into Winforms then you'll need to modify the timer code to not be reentrant. There are different ways of doing this but a simple locking variable or stopping and restarting the timer should suffice.

    Friday, January 22, 2016 7:19 PM
  • The problem with your original code is with the rotation logic. I'll need a little bit of time to look at it.

    Friday, January 22, 2016 9:42 PM
  • I took a look at your code and it mostly appears correct but you combined the transforms with the rotations making it hard to confirm. I didn't really understand what line algorithm you were using either so I tried the midpoint line algorithm instead. The results were consistently the same and reveals what the likely issue is - precision.

    The console window is not at a high enough resolution to generate the pixels such that the shape remains the same.  The results are that as you rotate around the pixels get generated together resulting in lines rather than keeping the shape. This is because there aren't enough pixels in the console window to proper rotate the shape. If you were to have a higher resolution window then you wouldn't have as much overlap and the rotation would be correct. I dumped the coordinates to the debugger after each rotation and it shows that some points only change slightly because of the rounding issue.  As the rotation comes back to 360 degrees the original shape returns.  I suspect that if you were to use this code on a higher resolution window then it would probably be correct but I'm not aware of a way to do this to the console window in .NET.

    Saturday, January 23, 2016 2:25 AM
  • Forgot to mention I did adjust your code a little. I modified it to calculate the center when the shape was created rather than each time it was drawn. The center shouldn't change if you are rotating it around the center. I also had it recalculate the 4 points each time rather than storing them in an array.

    The other issue is that the coordinate system of the console is top to bottom, left to right so the y axis is wrong for the rotation formula (which assumes the left, bottom is 0). If you adjust for this the rotation may work correctly in the y axis. I believe that may be why it is collapsing as it gets closer to 180 degrees.  Again, using the console for this kind of stuff is not common so I'm rusty on the conversion to solve for this.

    Saturday, January 23, 2016 2:58 AM
  • I'm very grateful for time you have spent on it. But I got one more little request. Could you post or upload the code you have done for it? I'd like to check it and see the differences. This could be a great opportunity to learn something.
    Saturday, January 23, 2016 10:30 AM
  • I looked some more at your code and I think the problem goes back to how the lines are being drawn. My code had the exact same issue as the angle approached 45 degrees. The problem is that at 45 some of the points are on the same x axis. In both the line algorithm I use and the one you provided rely on X to change in order to draw pixels at this point. Since X is the same you get disappearing lines because the loop is never entered for these points.

    I'm providing the basic code I used to test. It is actually a Winforms app but if you switch the DrawLine function from using Graphics.DrawLine to the other algorithms you'll see the behavior you're seeing in the console app.

    class Rhombus
    {
        public int Width { get; set; }
    
        public Point Top { get; set; }
        public Point Left { get; set; }
        public Point Right { get; set; }
        public Point Bottom { get; set; }
    
        private Point p_center;
    
    
        public Rhombus ( Point up, Point left, Point right, Point down )
        {
            Top = up;
            Left = left;
            Right = right;
            Bottom = down;
    
            p_center = new Point((left.X + right.X) / 2, (up.Y + down.Y) / 2);
        }
    
    
        public void Draw ( Pen pen, Graphics g )
        {
            DrawLine(pen, g, Top, Right);
            DrawLine(pen, g, Right, Bottom);
            DrawLine(pen, g, Bottom, Left);
            DrawLine(pen, g, Left, Top);
        }
    
        private void DrawPixel ( Graphics g, Pen p, int x, int y )
        {
            Debug.WriteLine($"({x},{y})");
    
            g.FillRectangle(new SolidBrush(p.Color), x, y, 1, 1);
        }
    
        private void DrawLine ( Pen pen, Graphics g, Point p1, Point p2 )
        {
            Debug.WriteLine($"({p1.X},{p1.Y})-({p2.X},{p2.Y})");
    
            //MidpointLine(pen, g, p1, p2);
            //IncrementalLine(pen, g, p1, p2);
            g.DrawLine(pen, p1, p2);
        }
    
        private void MidpointLine ( Pen pen, Graphics g, Point p1, Point p2 )
        {
            if (p1.X > p2.X)
            {
                var temp = p1;
                p1 = p2;
                p2 = temp;
            };
    
            var dx = p2.X - p1.X;
            var dy = p2.Y - p1.Y;
            var d = dy * 2 - dx;
    
            var incrE = dy * 2;
            var incrNE = (dy - dx) * 2;
    
            var x = p1.X;
            var y = p1.Y;
            DrawPixel(g, pen, x, y);
    
            while (x < p2.X)
            {
                if (d <= 0)
                {
                    d += incrE;
                    ++x;
                } else
                {
                    d += incrNE;
                    ++x;
                    ++y;
                };
                DrawPixel(g, pen, x, y);
            };
        }
    
        private void IncrementalLine ( Pen pen, Graphics g, Point p1, Point p2 )
        {
            if (p1.X > p2.X)
            {
                var temp = p1;
                p1 = p2;
                p2 = temp;
            };
    
            int dx = p2.X - p1.X;
            int dy = p2.Y - p1.Y;
    
            var slope = dy / (double)dx;
                            
            double y = p1.Y;
            for (var x = p1.X; x <= p2.X; ++x)
            {
                DrawPixel(g, pen, x, (int)Math.Round(y));
                y += slope;
            };
        }
    
        public void Rotate ( double angle )
        {
            Top = Rotate(Top, p_center, angle);
            Right = Rotate(Right, p_center, angle);
            Bottom = Rotate(Bottom, p_center, angle);
            Left = Rotate(Left, p_center, angle);
        }
    
        private static Point Rotate(Point point, Point center, double angleDegree)
        {
            double angle = angleDegree * (Math.PI / 180);
            double cos = Math.Cos(angle);
            double sin = Math.Sin(angle);
            int dx = point.X - center.X;
            int dy = point.Y - center.Y;
            double x = (cos * dx) - (sin * dy) + center.X;
            double y = (sin * dx) + (cos * dy) + center.Y;            
               
            return new Point((int)Math.Round(x), (int)Math.Round(y));
        }
    }

    Here's the form code that was being used.

    public partial class Form1 : Form
    {
        public Form1 ()
        {
            InitializeComponent();
        }
    
        protected override void OnPaint ( PaintEventArgs e )
        {
            base.OnPaint(e);
                            
            foreach (var shape in m_shapes)
                shape.Draw(new Pen(new SolidBrush(ForeColor)), e.Graphics);
        }
    
        private void Form1_Load ( object sender, EventArgs e )
        {
            int x = 50;
            int y = 90;
                            
            m_shapes.Add(new Rhombus(new Point(x, y - 20), new Point(x - 20, y), new Point(x + 20, y), new Point(x, y + 20)));
    
            timer1.Enabled = true;
        }
    
        private void timer1_Tick ( object sender, EventArgs e )
        {
            foreach (var shape in m_shapes)
            {
                shape.Rotate(15);
            };
    
            Invalidate();
        }
    
        private List<Rhombus> m_shapes = new List<Rhombus>();
    }

    Saturday, January 23, 2016 6:33 PM
  • I really would like to see the console code you were talking about because I don't even know what algorithm I could use to swap for DrawLine
    Saturday, January 23, 2016 8:13 PM
  • If you look at the DrawLine method that I posted it is using Winforms but the 2 commented out lines represent 2 other algorithms for drawing lines that would work on the console. The Graphics object isn't needed/supported in a console but include a reference to System.Drawing and everything else should compile.

    The only missing code is for drawing a pixel. For that replace it with the code you already had.

    private void DrawPixel ( Pen p, int x, int y )
    {
       Console.SetCursorPosition(x, y);
       Console.Write('+');
    }

    The rest of the code for the rhombus remains unchanged. But both algorithms for drawing a line don't work if the line is horizontal because X doesn't change.  I made a change to the IncrementalLine algorithm to try to take that into account, try this instead in your console code. For my sample it causes the rhombus to remain intact while rotating.

    //If the X axis is changing more than the Y then use it
    //as the basis for the increment
    if (Math.Abs(p2.GetX() - p1.GetX()) >= Math.Abs(p2.GetY() - p1.GetY()))
    { 
        if (p1.GetX() > p2.GetX())
        {
            var temp = p1;
            p1 = p2;
            p2 = temp;
        };
    
        int dx = p2.GetX() - p1.GetX();
        int dy = p2.GetY() - p1.GetY();
    
        var slope = dy / (double)dx;
    
        double y = p1.GetY();
        for (var x = p1.GetX(); x <= p2.GetX(); ++x)
        {
            DrawPixel(x, (int)Math.Round(y));
            y += slope;
        };
    } else
    {
        if (p1.GetY() > p2.GetY())
        {
            var temp = p1;
            p1 = p2;
            p2 = temp;
        };
    
        int dx = p2.GetX() - p1.GetX();
        int dy = p2.GetY() - p1.GetY();
    
        var slope = dx / (double)dy;
    
        double x = p1.GetX();
        for (var y = p1.GetY(); y <= p2.GetY(); ++y)
        {
            DrawPixel((int)Math.Round(x), y);
            x += slope;
        };
    };

    Sunday, January 24, 2016 1:17 AM