How can I rotate triangle (clock-wise ) with CSGL? - c#

I'm trying to make a clock-wise rotating triangle, but I can not. I made a timer control but the result is the same without the timer. As a result, the below code does not show rotating triangle.
How can i rotate triangle with CSGL?
namespace WinDrawCoordinate
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private float a = 0.0f;
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start();
}
protected void Gosterim()
{
GL.glClear(GL.GL_COLOR_BUFFER_BIT);
GL.glLoadIdentity();
Hesapla();
Ucgen();
}
protected void ayarlar()
{
GL.glClearColor(1, 1, 1, 1);
GL.glShadeModel(GL.GL_FLAT);
}
protected void Hesapla()
{
a += 0.5f;
this.Refresh();
}
protected void Ucgen()
{
GL.glColor3f(0, 1, 1);
GL.glRotatef(a, 0, 0, -1);
GL.glBegin(GL.GL_TRIANGLES);
GL.glVertex2f(-0.2f, -0.2f);
GL.glVertex2f(0.2f, -0.2f);
GL.glVertex2f(0.0f, 0.2f);
GL.glEnd();
}
private void timer1_Tick(object sender, EventArgs e)
{
ayarlar();
Gosterim();
}
}

From the code posted I see a couple things:
You don't have any matrix set-up. While you can use the default matrices you should almost certainly specify your own to ensure the triangle is where you expect. The default matrices should work for you here as they specify an identity modelview and projection.
You don't have a call to swap buffers. You need a call after you're done drawing to swap the front and back buffers so that the triangle is displayed. For CsGL I think there is a SwapBuffer() method you can use.
Don't use a timer to pump your redrawing. Timers work unreliably as they use the windows message pump and they are hard to get good results from. Instead you should use a render loop--a loop that runs the entire time your program runs and just keeps refreshing the screen. You have to make sure you give the operating system time to handle your messages though. A very simple render loop might be:
void RenderLoop() {
while (true) {
SetUpCamera();
CreateGeometry();
SwapBuffers();
Application.DoEvents(); // this lets windows process messages
}
}
note that there are better ways to get a good message loop in C# but this is easy to set up and reason about.

Double check this line:
GL.glRotatef(a, 0, 0, -1);
Shouldn't it be?
GL.glRotatef(a, 0, 0, 1);
Looks like your increment to the rotation angle (a) should be OK....but how fast is the timer "ticking" and firing off ayarlar and Gosterim?
Also you may want to add a check in relation to the rotation angle, if it loops alot and goes over 359.5 (Since you are adding 0.5 each time through), then you are going "full circle" literally

Related

How to create smooth animation movement of shapes in C# windows forms application?

I'm making C# windows forms application in which I want to make smooth animation movement of a black bar over a white background. I tried the following code but the movement is flickering and not achieving smooth rapid movement even on 1 ms interval timer.
I tried the following code :
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
timer1.Interval = 30;
timer1.Tick += Timer1_Tick;
timer1.Enabled = true;
}
int x_position = 0;
int bar_width = 40;
int speed = 1;
private void Timer1_Tick(object sender, EventArgs e)
{
x_position += speed;
if (x_position + bar_width > this.Width) x_position = 0;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
}
The result is not smooth movement at all. I found on another questions something like Invalidate(true) or DoubleBuffered = true; but they didn't solve the issue.
Waiting for help. Thank you
The Winforms API, and the native Win32 window-messaging API on which it's built, is simply not designed for "smooth animation". It will be difficult, if not impossible, to achieve perfectly smooth animation.
That said, much can be done to improve on your attempt above:
Don't use CreateGraphics(). Instead, always draw in response to the Paint event.
Don't use any of the .NET timer implementations to schedule rendering. Instead, draw as quickly as you can, taking into account time between frames to update your object position.
Applying these ideas, here's a version of your code that works much better:
const float speedXPerSecond = 1000f / 30;
const int bar_width = 40;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
}
float x_position = 0;
TimeSpan _lastFrameTime = TimeSpan.Zero;
Stopwatch _frameTimer = Stopwatch.StartNew();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
TimeSpan currentFrameTime = _frameTimer.Elapsed;
float distance = (float)(currentFrameTime - _lastFrameTime).TotalSeconds * speedXPerSecond;
x_position += distance;
while (x_position > this.Width) x_position -= this.Width;
e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
_lastFrameTime = currentFrameTime;
Invalidate();
}
You can apply this general technique to any interactive scenario. A real game will have other elements to this general "render loop", including user input and updating game state (e.g. based on a physics model). These will add overhead to the render loop, and will necessarily reduce frame rate. But for any basic game on any reasonably recent hardware (e.g. built in the last couple of decades), the net frame rate will still be well above that needed for acceptably smooth game-play.
Do note that in the managed .NET/Winforms context, there will always be limits to the success of this approach as compared to using a lower-level API. In particular, without some extra work on your part, garbage collection will interrupt periodically, causing the frame rate to stutter slightly, as well unevenness in thread scheduling and the thread's message queue.
But it's been my experience that people asking this sort of question don't need things to be absolutely perfect. They need "good enough", so that they can move on to learning about other topics involved in game development.

C# low timer interval = slow drawtime?

I'm trying to make a simple pong game but I encountered this problem, each timer1 tick (with the interval set to 1ms) should move of 1 pixel the white paddle rectangle if the 'S' or 'W' button is pressed, this means theorically that the white rectangle inside my 400px in height picturebox should be moving from y = 0 to y = 400 in 0.4 - 0.8 seconds, but apparently it takes more than 4 full seconds.
I understand that timer tick events may be "skipped" if the cpu is already busy or by processing speed problems but I tried to make a snake game way more complex than these 50 lines of code and the speed of the drawn snake was actually accurate with low-time intervals
Why does it takes that much?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
pictureBox1.BackColor = Color.Black;
timer1.Interval = 1;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
PongGame.CheckIfMoving();
PongGame.DrawIt(pictureBox1);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.S)
{ PongGame.movesDown = true; }
if (e.KeyData == Keys.W)
{ PongGame.movesUp = true; }
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.S)
{ PongGame.movesDown = false; }
if (e.KeyData == Keys.W)
{ PongGame.movesUp = false; }
}
}
public class PongGame
{
public static Rectangle paddle1 = new Rectangle(14, 370, 20, 100);
public Rectangle paddle2;
public static bool movesUp;
public static bool movesDown;
public static Graphics Draw;
public static void DrawIt(PictureBox pb1)
{
Draw = pb1.CreateGraphics();
SolidBrush sb = new SolidBrush(Color.White);
Draw.Clear(Color.Black);
Draw.FillRectangle(sb, paddle1);
}
public static void CheckIfMoving()
{
if (movesUp == true)
{
paddle1.Y -= 1;
}
if (movesDown == true)
{
paddle1.Y += 1;
}
}
}
Thanks in advance for the answers (:
First of all, setting the Timer's interval to 1 produces an unnecessary overhead in this case. That's 1000 fps (well, it would be, if you could actually trust the Timer).
In DrawIt:
Draw = pb1.CreateGraphics();
SolidBrush sb = new SolidBrush(Color.White);
This, since DrawIt is called by the Timer's tick, recreates the Graphics, and the Brush, 1000 times, every second. Be careful about what you put in there.
Furtheremore, you shouldn't use the Picturebox's CreateGraphics method. Instead, override its OnPaint method, and call Refresh on it.
You can read more about this on Bob Powell's Website
The timer makes no guarantee that it will fire at the interval that you specify. In fact it can, and does, fire at an interval longer than that which you specify.
Typically the actual interval of a timer will be related to the underlying system and hardware. It's not uncommon for timers on Windows to fire no more frequently than 20Hz.
If you are going to make a timer based game, then use the timer to give your game a pulse, a regular heartbeat. When the timer fires, use the amount of time since the last timer event to update the state. You'll need to use an accurate measure of time. The Stopwatch class should suffice.
You are also using the picture box incorrectly. This is an event driven control. You are expected to paint the scene in a handler for the Paint event of the picture box.

The Bomb doesn't get placed when I want it to

I have a game that I made for Windows phone using XNA/C#, and now I've decided to go and add a Bomb to help the player.
Now I have setup a timer and a bool, so that only 1 bomb can be used per level.
But, as soon as the game opens, the bomb is already there! I don't think the timer is working.
bool canDrawBomb = false;
public static Texture2D bomb;
GameTimer bombTimer = new GameTimer();
protected override void Initialize()
{
// Bomb timer.
bombTimer.UpdateInterval.Add(new TimeSpan(50000));
bombTimer.Update += bombTimer_Update;
bombTimer.Start();
base.Initialize();
}
void bombTimer_Update(object sender, GameTimerEventArgs e)
{
canDrawBomb = true;
bombTimer.Stop();
}
protected override void LoadContent()
{
bomb = Content.Load<Texture2D>("Bomb");
}
protected override void Draw(GameTime gameTime)
{
if (canDrawBomb)
{
// Draw the bomb.
// TESTED: OK. The bomb can draw but not at right time.
spriteBatch.Draw(bomb, new Vector2(), Color.White);
}
}
Now the problem is that even though I have set the bombTimer to ah 50 seconds, it still draws at the very beginning of the game!
How can I fix this? I haev been at this for hours and it's driving me insane. I don't know what I'm doing wrong!
The following line will not change the interval property (because the TimeSpan.Add() method makes a fresh copy, it does not change the existing TimeSpan):
bombTimer.UpdateInterval.Add(new TimeSpan(50000));
just use
bombTimer.UpdateInterval = new TimeSpan(50000);
I suspect that the GameTimer.Update event is fired as soon as GameTimer.Start() is called. Add a counter variable and only set canDrawBomb to false on the second call (and equaly only disable the timer on the second call)
Or use the DispatcherTimer if that is available in XNA, which definitely does not fire on the Start call.

Growing Rectangle

I have a growing rectangle drawn on top of a TableLayoutPanel but when it grows it causes a horrible flicker even with Double Buffer.
I am using e.Graphics.FillRectangle and a global variable that increases the size of the rectangle. I set up a timer to make it grow every 1/10th of a second by 1 pixel. Why does it flicker so much and how can I stop the flicker?
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width-grow)/2, (tableLayoutPanel1.Height-grow)/2, grow,grow);
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Refresh();
}
In order to rule out all other possibilities I removed everything from my program and started from scratch with just a growing rectangle and it still creates this horrible flicker.
Ok, here is the code. You first need to make background buffer Bitmap with the size of your control. After that, you will need to draw everything on the Bitmap, and than draw that bitmap onto the control.
Bitmap backBuffer = null;
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
if (backBuffer == null)
backBuffer = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
Graphics g = Graphics.FromImage(backBuffer);
g.Clear(tableLayoutPanel1.BackColor);
g.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width - grow) / 2, (tableLayoutPanel1.Height - grow) / 2, grow, grow);
e.Graphics.DrawImage(backBuffer, 0, 0, backBuffer.Width, backBuffer.Height);
g.Dispose();
}
private void tableLayoutPanel1_Resize(object sender, EventArgs e)
{
backBuffer = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Invalidate();
}
Note that you will need to create new Bitmap each time you resize the TableLayoutPanel. In addition I suggest using Invalidate() instead of Refresh().
But this will still include some potential flickering. In order to completely avoid flicker, in addition to the previous code, you will need to subclass the TableLayoutPanel and override the OnPaintBackground() method in such a way that base.OnPaintBackground is never called. This way way won't have any flickering at all. The reason why you have the flickering is because the background is redrawn before your Rectangle, any time you draw it. Switch the original TableLayoutPanel class with this BackgroundlessTableLayoutPanel
public class BackgroundlessTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
Most controls have a Paint event where you can implement any custom drawing you need to do. It is also possible to implement your own control where you override the OnPaint method. See the article here.
Both these should give ok results.

creating an advanced metronome in WPF (problems with code created animation and completed event)

Good afternoon,
over the last few weeks I have been working on a project to create an advanced metronome. the metronome is made up of the following things
a swinging arm
a light flash
a collection of dynamically created user controls that represent beats (4 of them that are either on, accented or off).
a usercontrol that displays an LCD numeric display and calculates the number of milliseconds between beats for the selected BPM (60000/BPM=milliseconds)
the user selects a BPM and presses start and the following happens
the arm swings between two angles at a rate of n milliseconds per sweep
the light flashes at the end of each arm sweep
the indicators are created and they flash in sequence (one at the end of each sweep).
now the problem
the Arm and light flash animation are created in code and added to a story board with repeat forever and auto reverse.
the indicators are created in code and need to fire an event at the end of each Arm sweep animation.
So, what I did after much messing around was create a timer that runs at the same pace as the storyboard.
the problem, over 30 seconds the timer and the storyboard go out of sync and therefore the indicators and the arm sweep are not in time (not good for a metronome!!).
I was trying to catch the completed event of the animations and use that as a trigger to stop and restart the timer, this was all I could come up with to keep the two in perfect sync.
the moving out of sync is caused by the storyboard slipping and the fact that the storyboard is invoked with begin on the line before the timer is invoked with .start, this although microseconds I think means that they start impossibly close but not at exactly the same time.
my question,
when I try to bind to the completed event of the animation it never fires. I was under the impression that completed even fires regardless of autoreverse (i.e in between each iteration). is this not the case?
can anyone think of another (more cunning) way to keep the two things in sync.
lastly, I did look to see if I could fire a method from a storyboard (which would of made my life really easy, however it would appear that this cannot be done).
if there are any suggestions I am not precious, I just want to get this finished!!
final point of interest,
the bpm can be adjusted whilst the metronome is running, this is achieved by calculating the millisecond duration on the fly (mouse down of a button) and scale the storyboard by the difference between the current speed and the new speed. obviously the timer running the indicators has to be changed at the same time (using interval).
code below is from my project so far (not the XAML just the C#)
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Threading;
namespace MetronomeLibrary
{
public partial class MetronomeLarge
{
private bool Running;
//Speed and time signature
private int _bpm = 60;
private int _beats = 4;
private int _beatUnit = 4;
private int _currentBeat = 1;
private readonly int _baseSpeed = 60000 / 60;
private readonly DispatcherTimer BeatTimer = new DispatcherTimer();
private Storyboard storyboard = new Storyboard();
public MetronomeLarge()
{
InitializeComponent();
NumericDisplay.Value = BPM;
BeatTimer.Tick += new EventHandler(TimerTick);
SetUpAnimation();
SetUpIndicators();
}
public int Beats
{
get
{
return _beats;
}
set
{
_beats = value;
SetUpIndicators();
}
}
public int BPM
{
get
{
return _bpm;
}
set
{
_bpm = value;
//Scale the story board here
SetSpeedRatio();
}
}
public int BeatUnit
{
get
{
return _beatUnit;
}
set
{
_beatUnit = value;
}
}
private void SetSpeedRatio()
{
//divide the new speed (bpm by the old speed to get the new ratio)
float newMilliseconds = (60000 / BPM);
float newRatio = _baseSpeed / newMilliseconds;
storyboard.SetSpeedRatio(newRatio);
//Set the beat timer according to the beattype (standard is quarter beats for one sweep of the metronome
BeatTimer.Interval = TimeSpan.FromMilliseconds(newMilliseconds);
}
private void TimerTick(object sender, EventArgs e)
{
MetronomeBeat(_currentBeat);
_currentBeat++;
if (_currentBeat > Beats)
{
_currentBeat = 1;
}
}
private void MetronomeBeat(int Beat)
{
//turnoff all indicators
TurnOffAllIndicators();
//Find a control by name
MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[Beat-1];
//illuminate the control
theIndicator.TurnOn();
theIndicator.PlaySound();
}
private void TurnOffAllIndicators()
{
for (int i = 0; i <= gridContainer.Children.Count-1; i++)
{
MetronomeLargeIndicator theIndicator = (MetronomeLargeIndicator)gridContainer.Children[i];
theIndicator.TurnOff();
}
}
private void SetUpIndicators()
{
gridContainer.Children.Clear();
gridContainer.ColumnDefinitions.Clear();
for (int i = 1; i <= _beats; i++)
{
MetronomeLargeIndicator theNewIndicator = new MetronomeLargeIndicator();
ColumnDefinition newCol = new ColumnDefinition() { Width = GridLength.Auto };
gridContainer.ColumnDefinitions.Add(newCol);
gridContainer.Children.Add(theNewIndicator);
theNewIndicator.Name = "Indicator" + i.ToString();
Grid.SetColumn(theNewIndicator, i - 1);
}
}
private void DisplayOverlay_MouseDown(object sender, MouseButtonEventArgs e)
{
ToggleAnimation();
}
private void ToggleAnimation()
{
if (Running)
{
//stop the animation
((Storyboard)Resources["Storyboard"]).Stop() ;
BeatTimer.Stop();
}
else
{
//start the animation
BeatTimer.Start();
((Storyboard)Resources["Storyboard"]).Begin();
SetSpeedRatio();
}
Running = !Running;
}
private void ButtonIncrement_Click(object sender, RoutedEventArgs e)
{
NumericDisplay.Value++;
BPM = NumericDisplay.Value;
}
private void ButtonDecrement_Click(object sender, RoutedEventArgs e)
{
NumericDisplay.Value--;
BPM = NumericDisplay.Value;
}
private void ButtonIncrement_MouseEnter(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button-over.png"))
};
ButtonIncrement.Background = theBrush;
}
private void ButtonIncrement_MouseLeave(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-increment-button.png"))
};
ButtonIncrement.Background = theBrush;
}
private void ButtonDecrement_MouseEnter(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button-over.png"))
};
ButtonDecrement.Background = theBrush;
}
private void ButtonDecrement_MouseLeave(object sender, MouseEventArgs e)
{
ImageBrush theBrush = new ImageBrush()
{
ImageSource = new BitmapImage(new
Uri(#"pack://application:,,,/MetronomeLibrary;component/Images/pad-metronome-decrement-button.png"))
};
ButtonDecrement.Background = theBrush;
}
private void SweepComplete(object sender, EventArgs e)
{
BeatTimer.Stop();
BeatTimer.Start();
}
private void SetUpAnimation()
{
NameScope.SetNameScope(this, new NameScope());
RegisterName(Arm.Name, Arm);
DoubleAnimation animationRotation = new DoubleAnimation()
{
From = -17,
To = 17,
Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds)),
RepeatBehavior = RepeatBehavior.Forever,
AccelerationRatio = 0.3,
DecelerationRatio = 0.3,
AutoReverse = true,
};
Timeline.SetDesiredFrameRate(animationRotation, 90);
MetronomeFlash.Opacity = 0;
DoubleAnimation opacityAnimation = new DoubleAnimation()
{
From = 1.0,
To = 0.0,
AccelerationRatio = 1,
BeginTime = TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds - 0.5),
Duration = new Duration(TimeSpan.FromMilliseconds(100)),
};
Timeline.SetDesiredFrameRate(opacityAnimation, 10);
storyboard.Duration = new Duration(TimeSpan.FromMilliseconds(NumericDisplay.Milliseconds * 2));
storyboard.RepeatBehavior = RepeatBehavior.Forever;
Storyboard.SetTarget(animationRotation, Arm);
Storyboard.SetTargetProperty(animationRotation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
Storyboard.SetTarget(opacityAnimation, MetronomeFlash);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("Opacity"));
storyboard.Children.Add(animationRotation);
storyboard.Children.Add(opacityAnimation);
Resources.Add("Storyboard", storyboard);
}
}
}
This might not be easily implemented with WPF animations. Instead, a good method would be a game loop. A little research should turn up lots of resources about this. The first one that jumped out at me was http://www.nuclex.org/articles/3-basics/5-how-a-game-loop-works.
In your game loop, you would follow one or the other of these basic procedures:
Calculate how much time has elapsed since the last frame.
Move your displays appropriately.
or
Calculate the current time.
Position your displays appropriately.
The advantage of a game loop is that although the timing may drift slightly (depending on what sort of timing you use), all displays will drift by the same amount.
You can prevent clock drift by calculating time by the system clock, which for practical purposes does not drift. Timers do drift, because they do not run by the system clock.
Time sync is a vaster field than you'd think.
I suggest you take a look at Quartz.NET which is renowned for scheduling/timers issues.
Syncing a WPF animation is tricky because Storyboards are not part of the logical tree, therefore you can't bind anything in them.
That's why you can't define dynamic/variable Storyboards in XAML, you have to do it in C# as you did.
I suggest you make 2 Storyboards: one for the tick to the left, the other one to the right.
In between each animation, fire a method to do your calculations/update another part of the UI, but do it in a separate Task so that the timings aren't messed up (a few µs for the calculations make up for quite some time after 30s already!)
Keep in mind that you will need to use Application.Current.Dispatcher from your Task to update the UI.
And lastly, at least set the Task flag TaskCreationOptions.PreferFairness so that Tasks run in the order they were started.
Now since that just gives a hint to the TaskScheduler and doesn't guarantees them to run in order, you may want to use a queueing system instead for full guarantee.
HTH,
Bab.
You could try 2 animations , one for the right swing and one for the left. In the animation complete on each, start the other animation (checking for cancellation flags) and update your indicators (possibly via BeginInvoke on the Dispatcher so you don't interfere with the next animation start.)
I think getting the timer to sync with an animation is difficult - it is a dispatcher based timer which is based on messages - sometimes it can skip a bit of time, ie if you click fast with the mouse a lot I think the animation timer also is dispatcher based, so they will easily get out of sync.
I would suggest to abandon the syncing and let the timer handle it. Can't you let it update a property with notification and let your metronome arm position bind to that?
To get the accelaration/deceleration you just have to use a Sine or Cosine function.

Categories