File: Boing.cs

package info (click to toggle)
taoframework 2.1.svn20090801-15.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 14,312 kB
  • sloc: cs: 113,093; makefile: 908; sh: 107; ansic: 7
file content (670 lines) | stat: -rw-r--r-- 25,849 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
#region License
/*
MIT License
Copyright 2003-2004 Randy Ridge
http://www.taoframework.com
All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion License

#region Original Credits/License
/*
Copyright (c) 2002-2004 Marcus Geelnard

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would
   be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not
   be misrepresented as being the original software.

3. This notice may not be removed or altered from any source
   distribution.

Marcus Geelnard
marcus.geelnard at home.se
*/

/*****************************************************************************
 * Title:   GLBoing
 * Desc:    Tribute to Amiga Boing.
 * Author:  Jim Brooks  <gfx@jimbrooks.org>
 *          Original Amiga authors were R.J. Mical and Dale Luck.
 *          GLFW conversion by Marcus Geelnard
 * Notes:   - 360' = 2*PI [radian]
 *
 *          - Distances between objects are created by doing a relative
 *            Z translations.
 *
 *          - Although OpenGL enticingly supports alpha-blending,
 *            the shadow of the original Boing didn't affect the color
 *            of the grid.
 *
 *          - [Marcus] Changed timing scheme from interval driven to frame-
 *            time based animation steps (which results in much smoother
 *            movement)
 *
 * History of Amiga Boing:
 *
 * Boing was demonstrated on the prototype Amiga (codenamed "Lorraine") in
 * 1985. According to legend, it was written ad-hoc in one night by
 * R. J. Mical and Dale Luck. Because the bouncing ball animation was so fast
 * and smooth, attendees did not believe the Amiga prototype was really doing
 * the rendering. Suspecting a trick, they began looking around the booth for
 * a hidden computer or VCR.
 *****************************************************************************/
#endregion Original Credits/License

using System;
using Tao.Glfw;
using Tao.OpenGl;

namespace GlfwExamples {
    #region Class Documentation
    /// <summary>
    ///     This is a small test application for GLFW.
    /// </summary>
    /// <remarks>
    ///     Tribute to Amiga's Boing.
    /// </remarks>
    #endregion Class Documentation
    public sealed class Boing {
        // --- Fields ---
        #region Private Constants
        private const float PI = 3.1415926535897932384626433832795f;
        private const int RAND_MAX = 4095;
        private const float RADIUS = 70;
        // 22.5 makes 8 bands like original Boing
        private const float STEP_LONGITUDE = 22.5f;
        private const float STEP_LATITUDE = 22.5f;
        private const float DISTANCE_BALL = RADIUS * 2 + RADIUS * 0.1f;
        // Distance from viewer to middle of buing area
        private const float VIEW_SCENE_DISTANCE = DISTANCE_BALL * 3 + 200;
        // Length (width) of grid
        private const float GRID_SIZE = (RADIUS * 4.5f);
        private const float BOUNCE_HEIGHT = (RADIUS * 2.1f);
        private const float BOUNCE_WIDTH = (RADIUS * 2.1f);
        // Maximum allowed delta time per physics iteration
        private const float MAX_DELTA_TIME = 0.02f;
        // Animation speed (50.0 mimics the original GLUT demo speed)
        private const float ANIMATION_SPEED = 50;
        private const float SHADOW_OFFSET_X = -20;
        private const float SHADOW_OFFSET_Y = 10;
        private const float SHADOW_OFFSET_Z = 0;
        private const float WALL_LEFT_OFFSET = 0;
        private const float WALL_RIGHT_OFFSET = 5f;
        #endregion Private Constants

        #region Private Enums
        #region DrawType
        /// <summary>
        ///     Draw type.
        /// </summary>
        private enum DrawType {
            /// <summary>
            ///     Draw ball.
            /// </summary>
            DrawBall,
            /// <summary>
            ///     Draw ball's shadow.
            /// </summary>
            DrawBallShadow
        }
        #endregion DrawType
        #endregion Private Enums

        #region Private Structs
        #region Vertex
        /// <summary>
        ///     Vertex structure.
        /// </summary>
        private struct Vertex {
            /// <summary>
            ///     X coordinate.
            /// </summary>
            public float X;
            /// <summary>
            ///     Y coordinate.
            /// </summary>
            public float Y;
            /// <summary>
            ///     Z coordinate.
            /// </summary>
            public float Z;
        }
        #endregion Vertex
        #endregion Private Structs

        #region Private Static Fields
        private static Random rand = new Random();
        private static double currentTime, oldTime, timeDifference;
        private static float yRotationDegree = 0;
        private static float yRotationDegreeIncrement = 2;
        private static float ballX = -RADIUS;
        private static float ballY = -RADIUS;
        private static float ballXIncrement = 1;
        private static float ballYIncrement = 2;
        private static DrawType drawType;
        private static bool isColor;
        #endregion Private Static Fields

        // --- Application Entry Point ---
        #region Run()
        /// <summary>
        ///     The main entry point for the application.
        /// </summary>
        [STAThread]
        public static void Run() {
            bool isRunning = true;

            // Initialise GLFW
            Glfw.glfwInit();
            if(Glfw.glfwOpenWindow(400, 400, 0, 0, 0, 0, 16, 0, Glfw.GLFW_WINDOW) == Gl.GL_FALSE) {
                Glfw.glfwTerminate();
                return;
            }
            Glfw.glfwSetWindowTitle("Boing (classic Amiga demo)");
            Glfw.glfwSetWindowSizeCallback(new Glfw.GLFWwindowsizefun(Reshape));
            Glfw.glfwEnable(Glfw.GLFW_STICKY_KEYS);
            Glfw.glfwSwapInterval(1);
            // TODO: Glfw.glfwSetTime(0.0);

            Init();

            // Main loop
            while(isRunning) {
                // Timing
                currentTime = Glfw.glfwGetTime();
                timeDifference = currentTime - oldTime;
                oldTime = currentTime;

                // Draw one frame
                Display();

                // Swap buffers
                Glfw.glfwSwapBuffers();

                // Check if we are still running
                isRunning = ((Glfw.glfwGetKey(Glfw.GLFW_KEY_ESC) == Glfw.GLFW_RELEASE) &&
                    Glfw.glfwGetWindowParam(Glfw.GLFW_OPENED) == Gl.GL_TRUE);
            }

            // Close OpenGL window and terminate GLFW
            Glfw.glfwTerminate();
        }
        #endregion Run()

        // --- Private Static Methods ---
        #region BounceBall(double timeDifference)
        /// <summary>
        ///     Bounces the ball.
        /// </summary>
        /// <param name="timeDifference">
        ///     The time difference.
        /// </param>
        private static void BounceBall(double timeDifference) {
            float sign;
            float degree;

            // Bounce on walls
            if(ballX > (BOUNCE_WIDTH / 2 + WALL_RIGHT_OFFSET)) {
                ballXIncrement = -0.5f - 0.75f * (float) rand.NextDouble() / (float) RAND_MAX;
                yRotationDegreeIncrement = -yRotationDegreeIncrement;
            }

            if(ballX < -(BOUNCE_HEIGHT / 2 + WALL_LEFT_OFFSET)) {
                ballXIncrement = 0.5f + 0.75f * (float) rand.NextDouble() / (float) RAND_MAX;
                yRotationDegreeIncrement = -yRotationDegreeIncrement;
            }

            // Bounce on floor / ceiling
            if(ballY > BOUNCE_HEIGHT / 2) {
                ballYIncrement = -0.75f - 1.0f * (float) rand.NextDouble() / (float) RAND_MAX;
            }

            if(ballY < -BOUNCE_HEIGHT / 2 * 0.85f) {
                ballYIncrement = 0.75f + 1 * (float) rand.NextDouble() / (float) RAND_MAX;
            }

            // Update ball position
            ballX += (float) (ballXIncrement * (timeDifference * ANIMATION_SPEED));
            ballY += (float) (ballYIncrement * (timeDifference * ANIMATION_SPEED));

            // Simulate the effects of gravity on Y movement
            if(ballYIncrement < 0) {
                sign = -1;
            }
            else {
                sign = 1;
            }

            degree = (ballY + BOUNCE_HEIGHT / 2) * 90 / BOUNCE_HEIGHT;

            if(degree > 80) {
                degree = 80;
            }
            else if(degree < 10) {
                degree = 10;
            }

            ballYIncrement = sign * 4 * (float) SinDegree(degree);
        }
        #endregion BounceBall(double timeDifference)

        #region double CosDegree(double degree)
        /// <summary>
        ///     360' Cos().
        /// </summary>
        /// <param name="degree">
        ///     The degree.
        /// </param>
        /// <returns>
        ///     The Cos.
        /// </returns>
        private static double CosDegree(double degree) {
            return Math.Cos(DegreeToRadian(degree));
        }
        #endregion double CosDegree(double degree)

        #region CrossProduct(Vertex a, Vertex b, Vertex c, out Vertex normal)
        /// <summary>
        ///     Computes a cross product for a vector normal.
        /// </summary>
        /// <param name="a">
        ///     First vector.
        /// </param>
        /// <param name="b">
        ///     Second vector.
        /// </param>
        /// <param name="c">
        ///     Third vector.
        /// </param>
        /// <param name="normal">
        ///     The normalized vector.
        /// </param>
        private static void CrossProduct(Vertex a, Vertex b, Vertex c, out Vertex normal) {
            float u1, u2, u3;
            float v1, v2, v3;

            u1 = b.X - a.X;
            u2 = b.Y - a.Y;
            u3 = b.Y - a.Z;

            v1 = c.X - a.X;
            v2 = c.Y - a.Y;
            v3 = c.Z - a.Z;

            normal.X = u2 * v3 - v2 * v3;
            normal.Y = u3 * v1 - v3 * u1;
            normal.Z = u1 * v2 - v1 * u2;
        }
        #endregion CrossProduct(Vertex a, Vertex b, Vertex c, out Vertex normal)

        #region double DegreeToRadian(double degree)
        /// <summary>
        ///     Converts a degree to a radian.
        /// </summary>
        /// <param name="degree">
        ///     The degree.
        /// </param>
        /// <returns>
        ///     The radian.
        /// </returns>
        private static double DegreeToRadian(double degree) {
            return degree / 360 * (2 * PI);
        }
        #endregion double DegreeToRadian(double degree)

        #region Display()
        /// <summary>
        ///     Draws a frame.
        /// </summary>
        private static void Display() {
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
            Gl.glPushMatrix();
                drawType = DrawType.DrawBallShadow;
                DrawBoingBall();
                DrawGrid();
                drawType = DrawType.DrawBall;
                DrawBoingBall();
            Gl.glPopMatrix();
            Gl.glFlush();
        }
        #endregion Display()

        #region DrawBoingBall()
        /// <summary>
        ///     Draws the Boing ball.
        /// </summary>
        /// <remarks>
        ///     The Boing ball is sphere in which each facet is a rectangle.  Facet colors
        ///     alternate between red and white.  The ball is built by stacking latitudinal
        ///     circles.  Each circle is composed of a widely-separated set of points, so
        ///     that each facet is noticably large.
        /// </remarks>
        private static void DrawBoingBall() {
            float degreeOfLongitude;
            double timeDifferenceTotal, timeDifferenceTemp;

            Gl.glPushMatrix();
                Gl.glMatrixMode(Gl.GL_MODELVIEW);

                // Another relative Z translation to separate objects
                Gl.glTranslatef(0, 0, (float) DISTANCE_BALL);

                // Update ball position and rotation (iterate if necessary)
                timeDifferenceTotal = timeDifference;
                while(timeDifferenceTotal > 0.0) {
                    timeDifferenceTemp = timeDifferenceTotal > MAX_DELTA_TIME ? MAX_DELTA_TIME :
                        timeDifferenceTotal;
                    timeDifferenceTotal -= timeDifferenceTemp;
                    BounceBall(timeDifferenceTemp);
                    yRotationDegree = TruncateDegree((float) (yRotationDegree + yRotationDegreeIncrement *
                        (timeDifferenceTemp * ANIMATION_SPEED)));
                }

                // Set ball position
                Gl.glTranslatef(ballX, ballY, 0);

                // Offset the shadow
                if(drawType == DrawType.DrawBallShadow) {
                    Gl.glTranslatef(SHADOW_OFFSET_X, SHADOW_OFFSET_Y, SHADOW_OFFSET_Z);
                }

                // Tilt the ball
                Gl.glRotatef(-20, 0, 0, 1);

                // Continually rotate ball around Y axis
                Gl.glRotatef(yRotationDegree, 0, 1, 0);

                // Set OpenGL state for Boing ball
                Gl.glCullFace(Gl.GL_FRONT);
                Gl.glEnable(Gl.GL_CULL_FACE);
                Gl.glEnable(Gl.GL_NORMALIZE);

                // Build a faceted latitude slice of the Boing ball,  stepping same-sized vertical
                // bands of the sphere
                for(degreeOfLongitude = 0; degreeOfLongitude < 180;
                    degreeOfLongitude += STEP_LONGITUDE) {
                    // Draw a latitude circle at this longitude
                    DrawBoingBallBand(degreeOfLongitude, degreeOfLongitude + STEP_LONGITUDE);
                }
            Gl.glPopMatrix();
        }
        #endregion DrawBoingBall()

        #region DrawBoingBallBand(float low, float high)
        /// <summary>
        ///     Drawa a faceted latitude band of the Boing ball.
        /// </summary>
        /// <param name="low">
        ///     The low longitude of the slice.
        /// </param>
        /// <param name="high">
        ///     The high longitude of the slice.
        /// </param>
        private static void DrawBoingBallBand(float low, float high) {
            // "ne" means north-east, so on
            Vertex neVertex;
            Vertex nwVertex;
            Vertex seVertex;
            Vertex swVertex;
            Vertex normalVertex;
            float degreeLatitude;

            // Iterate thru the points of a latitude circle.  A latitude circle is a 2D set of
            // X, Z points
            for(degreeLatitude = 0; degreeLatitude <= (360 - STEP_LATITUDE);
                degreeLatitude += STEP_LATITUDE) {
            // Color this polygon with red or white
                if(isColor) {
                    Gl.glColor3f(0.8f, 0.1f, 0.1f);
                }
                else {
                    Gl.glColor3f(0.95f, 0.95f, 0.95f);
                }

                #if BOING_DEBUG
                if(degreeLatitude >= 180) {
                    if(isColor) {
                        Gl.glColor3f(0.1f, 0.8f, 0.1f);
                    }
                    else {
                        Gl.glColor3f(0.5f, 0.5f, 0.95f);
                    }
                }
                #endif

                isColor = !isColor;

                // Change color if drawing shadow
                if(drawType == DrawType.DrawBallShadow) {
                    Gl.glColor3f(0.35f, 0.35f, 0.35f);
                }

                // Assign each Y
                neVertex.Y = nwVertex.Y = (float) CosDegree(high) * RADIUS;
                swVertex.Y = seVertex.Y = (float) CosDegree(low) * RADIUS;

                // Assign each X, Z with Sin, Cos values scaled by latitude radius indexed by
                // longitude.  E.G. longitude = 0 and longitude = 180 are at the poles, so zero
                // scale is Sin(longitude), while longitude = 90 (Sin(90) = 1) is at equator
                neVertex.X = (float) CosDegree(degreeLatitude) * (float) (RADIUS * SinDegree(low + STEP_LONGITUDE));
                seVertex.X = (float) CosDegree(degreeLatitude) * (float) (RADIUS * SinDegree(low));
                nwVertex.X = (float) CosDegree(degreeLatitude + STEP_LATITUDE) * (float) (RADIUS * SinDegree(low + STEP_LONGITUDE));
                swVertex.X = (float) CosDegree(degreeLatitude + STEP_LATITUDE) * (float) (RADIUS * SinDegree(low));

                neVertex.Z = (float) SinDegree(degreeLatitude) * (float) (RADIUS * SinDegree(low + STEP_LONGITUDE));
                seVertex.Z = (float) SinDegree(degreeLatitude) * (float) (RADIUS * SinDegree(low));
                nwVertex.Z = (float) SinDegree(degreeLatitude + STEP_LATITUDE) * (float) (RADIUS * SinDegree(low + STEP_LONGITUDE));
                swVertex.Z = (float) SinDegree(degreeLatitude + STEP_LATITUDE) * (float) (RADIUS * SinDegree(low));

                //  Draw the facet
                Gl.glBegin(Gl.GL_POLYGON);
                    CrossProduct(neVertex, nwVertex, swVertex, out normalVertex);
                    Gl.glNormal3f(normalVertex.X, normalVertex.Y, normalVertex.Z);
                    Gl.glVertex3f(neVertex.X, neVertex.Y, neVertex.Z);
                    Gl.glVertex3f(nwVertex.X, nwVertex.Y, nwVertex.Z);
                    Gl.glVertex3f(swVertex.X, swVertex.Y, swVertex.Z);
                    Gl.glVertex3f(seVertex.X, seVertex.Y, seVertex.Z);
                Gl.glEnd();

                #if BOING_DEBUG
                Console.WriteLine("-----------------------------------------------------------");
                Console.WriteLine("lat = {0}  low = {1}  high = {2}", degreeLatitude, low, high);
                Console.WriteLine( "vert_ne  x = %.8f  y = %.8f  z = %.8f \n", vert_ne.x, vert_ne.y, vert_ne.z );
                Console.WriteLine( "vert_nw  x = %.8f  y = %.8f  z = %.8f \n", vert_nw.x, vert_nw.y, vert_nw.z );
                Console.WriteLine( "vert_se  x = %.8f  y = %.8f  z = %.8f \n", vert_se.x, vert_se.y, vert_se.z );
                Console.WriteLine( "vert_sw  x = %.8f  y = %.8f  z = %.8f \n", vert_sw.x, vert_sw.y, vert_sw.z );
                #endif
            }

            // Toggle color so that next band will opposite red/white colors than this one
            isColor = !isColor;
        }
        #endregion DrawBoingBallBand(float low, float high)

        #region DrawGrid()
        /// <summary>
        ///     Draws the purple grid of lines behind the Boing ball.
        /// </summary>
        private static void DrawGrid() {
            int row, column;
            int rowTotal = 12;              // must be divisible by 2
            int columnTotal = rowTotal;     // must be same as rowTotal
            float widthLine = 2;            // should be divisible by 2
            float sizeCell = (float) GRID_SIZE / rowTotal;
            float z_offset = -40;
            float xl, xr;
            float yt, yb;

            Gl.glPushMatrix();
                Gl.glDisable(Gl.GL_CULL_FACE);

                // Another relative Z translation to separate objects
                Gl.glTranslatef(0, 0, DISTANCE_BALL);

                // Draw vertical lines (as skinny 3D rectangles)
                for(column = 0; column <= columnTotal; column++) {
                    // Compute coordinatess of line
                    xl = (float) -GRID_SIZE / 2 + column * sizeCell;
                    xr = xl + widthLine;

                    yt =  (float) GRID_SIZE / 2;
                    yb = (float) -GRID_SIZE / 2 - widthLine;

                    Gl.glBegin(Gl.GL_POLYGON);
                        Gl.glColor3f(0.6f, 0.1f, 0.6f);     // purple
                        Gl.glVertex3f(xr, yt, z_offset);    // NE
                        Gl.glVertex3f(xl, yt, z_offset);    // NW
                        Gl.glVertex3f(xl, yb, z_offset);    // SW
                        Gl.glVertex3f(xr, yb, z_offset);    // SE
                    Gl.glEnd();
                }

                // Draw horizontal lines (as skinny 3D rectangles)
                for(row = 0; row <= rowTotal; row++) {
                    // Compute coordinates of line
                    yt = (float) GRID_SIZE / 2 - row * sizeCell;
                    yb = yt - widthLine;

                    xl = (float) -GRID_SIZE / 2;
                    xr = (float) GRID_SIZE / 2 + widthLine;

                    Gl.glBegin(Gl.GL_POLYGON);
                        Gl.glColor3f(0.6f, 0.1f, 0.6f);     // purple
                        Gl.glVertex3f(xr, yt, z_offset);    // NE
                        Gl.glVertex3f(xl, yt, z_offset);    // NW
                        Gl.glVertex3f(xl, yb, z_offset);    // SW
                        Gl.glVertex3f(xr, yb, z_offset);    // SE
                    Gl.glEnd();
                }
            Gl.glPopMatrix();
        }
        #endregion DrawGrid()

        #region Init()
        /// <summary>
        ///     Initializes some GL settings.
        /// </summary>
        private static void Init() {
            // Clear background
            Gl.glClearColor(0.55f, 0.55f, 0.55f, 0.0f);
            // Flat shading
            Gl.glShadeModel(Gl.GL_FLAT);
        }
        #endregion Init()

        #region double PerspectiveAngle(double size, double distance)
        /// <summary>
        ///     Calculates the angle to be passed to <see cref="Glu.gluPerspective" /> so that
        ///     a scene is visible.
        /// </summary>
        /// <param name="size">
        ///     The size of the segment when the angle is intersected at
        ///     <paramref name="distance" />.
        /// </param>
        /// <param name="distance">
        ///     The distance from viewpoint to the scene.
        /// </param>
        /// <returns></returns>
        private static double PerspectiveAngle(double size, double distance) {
            double radianTheta = 2 * Math.Atan2(size / 2, distance);
            return (double) (180 * radianTheta) / PI;
        }
        #endregion double PerspectiveAngle(double size, double distance)

        #region double SinDegree(double degree)
        /// <summary>
        ///     360' Sin().
        /// </summary>
        /// <param name="degree">
        ///     The degree.
        /// </param>
        /// <returns>
        ///     The Sin.
        /// </returns>
        private static double SinDegree(double degree) {
            return Math.Sin(DegreeToRadian(degree));
        }
        #endregion double SinDegree(double degree)

        #region float TruncateDegree(float degree)
        /// <summary>
        ///     Truncates a degree.
        /// </summary>
        /// <param name="degree">
        ///     The degree to truncate.
        /// </param>
        /// <returns>
        ///     The truncated degree value.
        /// </returns>
        private static float TruncateDegree(float degree) {
            if(degree >= 360.0f) {
                return (degree - 360.0f);
            }
            else {
                return degree;
            }
        }
        #endregion float TruncateDegree(float degree)

        // --- Private GLFW Callback Methods ---
        #region Reshape(int width, int height)
        /// <summary>
        ///     Handles GLFW's window reshape callback.
        /// </summary>
        /// <param name="width">
        ///     New window width.
        /// </param>
        /// <param name="height">
        ///     New window height.
        /// </param>
        private static void Reshape(int width, int height) {
            Gl.glViewport(0, 0, width, height);

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();

            Glu.gluPerspective(PerspectiveAngle(RADIUS * 2, 200),
                (double) width / (double) height,
                1,
                VIEW_SCENE_DISTANCE);

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            Glu.gluLookAt(0, 0, VIEW_SCENE_DISTANCE,    // eye
                0, 0, 0,                                // center of vision
                0, -1, 0);                              // up vector
        }
        #endregion Reshape(int width, int height)
    }
}