Friday, February 28, 2020

Why you should do more sprint-level regressions and shift-left



Shift-Left Defect Detection and Remediation_3



Of software development in an (at least somewhat) agile environment, one challenge I've always had was to allocate enough time to spend in regression.  The word beckons the idea from statistics - to regress the amount of error to a minimum.  While developing features in a sprint level fashion, it's important to ensure that before the sprint is finished, the feature has been regressed, having very little defect.  As we develop more and more features that have not been regressed, well tested with a "test-left" strategy and hardened, then we punt on a plan makes the cost of fixing defects cheapest.  Instead, we end up testing features later rather than sooner, when they are more expensive to fix.  This is why regression periods need to occur during sprint, rather than at the end of a release cycle.

It is also much more enjoyable and preferable as a developer to fix bugs earlier, when they are discovered by the developer during sprint-level regression periods.  When bugs are found in testing at the end of a release-cycle, a bug report is drafted and a work item is handed to the developer.  This can have its own bevy of issues, spanning from the inability to properly communicate the problem and failing to convincingly sell the value of a fix.  Some level of pride is sure to be impacted as well - no developer wants to create bugs, although all developers should know the value of a bug.  It is a lesson we can all improve from and get better with age.  It is certainly a thing of pride however, to develop a feature and deliver it to the end of a release-cycle without bug.  To know that testers have thoroughly shifted through your code and have not found any problem is something to live for.  And it should motivate us all to improve on our challenge - to get better at ensuring our features are completely "done-done".

It can be hard to justify additional time spent re-running acceptance criteria and re-evaluating manual test cases to product owners.  They see merely the result of a sprint - a feature that has been delivered and demonstrated.  So when we've finished developing our feature in 8-9 days, it can be difficult to explain the value of the last 5-6 days.  Except by showing a decreased volume in the number of bugs near the end of a release-cycle, we have very little evidence mid-sprint.  So I think all we can do is rely on the time-proven results from case studies performed by others in the industry.  This stems from the topic of "shift-left", which means to shift testing more to the left to save on cost of fixing a bug.

Image result for happy developersAnother benefit of fixing bugs early that I want to point out is with the notion of developer happiness.  Developers are typically happy enough to find bugs before they "finish" the feature, while they are still working on it.  The reason is two-fold: first, to find a bug before the feature was finished means that no one else will ever know.  Pride: kept intact.  Secondly, while still working on a feature, the code and intricate details are still very fresh in your mind.  So finding bugs early mean a very easy fix, which developers very much enjoy.  

Finding bugs late would mean having to dig up old details and re-learning the code area to figure out what to do for a fix.  Even worse, when someone else finds a bug, it can make any developer feel stupid.  Although honestly, no developer should ever feel truly offended - bugs are indeed a part of life and we do learn from them.  And I don't think developers are do take offense.  Writing software can be very euphoric, almost like a drug.  When we go home in the evening, we usually feel pretty great about what we've done.  But when bugs arise, it can take us down a peg or two.  That's something we'd like to generally avoid, so suffice to say, bugs discovered late can lead to unhappy developers.  And unhappy developers can lead to decreases in productivity.  All the more reason to employ a shift to the left: make the time to find those bugs early.

To summarize: we should all make more time to complete our features before saying that they're "done".  Stop taking on new work and start finishing the existing work.  You'll thank yourself later.  And so will product owners.  In business environments where late-stage regression finds many bugs, it should stand as an important alarm that many developers still need to shift their testing much further to the left.  Once we do that, we should start noticing a decrease in the number of bugs that arise late-stage. 

More reading:

On the value of automated testing.

(2020) Details on what shift lest is.


(2018) Testing left reduces bugs found and reduces overall cost.

(2008) Devs realize bugs are life, but are happy to find their own.


(2008) On the dev & tester relationship and finding too many bugs

(2018) Happy devs are more motivated, do better process related work





Wednesday, August 22, 2018

Categorizing Faults to Playability

Video games are much more enjoyable if they are replayable.  But before they are even replayable, they need to first be playable in the first place.  This pertains to limiting and mitigating distractions to playability.  Distractions can include a wide array of faults and poor design decisions that hinder the player's ability to enjoy the game.  Hence, an enjoyable video game must provide strong playability.  Here, we try to categorize a few of these hindrances.

The Rules Change

Imagine playing a game where some rules are clearly define early.  For example, falling into a pit means that the player loses the game and must start from the beginning.  This is a form of punishment and through negative reinforcement, the player learns not to do that again.  Now, imagine later in the game, it is necessary to jump into a pit to advance the game.  The player will probably reach this point and not know what to do, unless by accident, they fell in the pit where the rules have changed.  This happens in Super Pitfall (NES), where the player must jump into a flying bird enemy to active a warp zone to continue advancing the game.  All other instances of the bird will kill the player and cause the player to lose progress, so the player has already learned to avoid birds.  With the rules changed, most players are clueless on how to advance the game and will give up.  Upon learning that the way forward was to jump into the bird, the player will feel even more frustrated, because now they realize they have to jump into every bird just to see if it kills them.  This is brutal when punishment is also brutal.

The rules change in other games as well, for example, in Bill and Ted's Excellent Video Game Adventure (NES), where jumping into grass typically causes the player to fall.  But in some cases, jumping into grass has no effect.  In Rocky and Bullwinkle (NES), the rules also change when stairs typically touch the top edge of the screen if they lead you to the next room above, but in one instance, the stairs don't touch the top edge of the screen and yet they still function like any other steps.

Imbalanced Punishment

Punishment is one way of teaching the player what not to do.  When the punishment is too severe, it can cause the player to quit.  Imagine playing through an arduous level, and just near the end of the stage, you have to try again from the beginning.  This can make the game near impossible to win, although the excitation effect of finally winning can translate into immense euphoria.  This fault to playability also fits in with the model of the Flow Zone, in which most players expect the game's difficulty to fall somewhere in between "too difficult" and "too easy".  Harsh punishment can make the game too difficult, and a lack of punishment can make the game too easy, and in some cases, a very confusing and non-intuitive game.

In Fester's Quest (NES), anytime the player dies, they must start from the very beginning - there are no "check points".  This also happens in a lot of games, including Ninja Gaiden (NES) - if the player is defeated while facing the final boss in Act 6-4, they must, contrary to the norm up until that part, begin again from Act 6-1.  Usually upon defeat, the player just begins at the previous checkpoint of the same stage, in which each stage typically has many checkpoints.  Because Act 6 is one of the hardest in the game, this kind of punishment led to many players quitting before they ever beat the game.

Non-Intuitive

Intuitive games can be played with little to no direct guidance.  These are games that "make sense" and lead the player where they need to be going.  A game that isn't intuitive will cause the player to wonder what they need to do.

In Super Pitfall (NES), there are items which are invisible.  The player must visit everywhere, jump everywhere, touch every spot to determine where they are.  Without any clear direction or sense of where to go, the player will start to lose their sense of intuition, which is a serious detriment to video games.  If a player feels like they don't know how to play it, then they're going to stop playing.  If the items were visible, this would give the player a sense of direction and a clear guidance system in what the player needs to do.  This way, the player can feel a sense of accomplishment in knowing what to do, and experience in the mystery of discovering new items and finding out what they do.

Something similar happens in Castlevania 2 (NES) where the player must kneel down in a corner to summon or wait for a tornado to carry them off-screen.  Since the wait time is more than just a second, this can become really confusing.  The same thing happens in Earthbound (SNES) where the player has to wait behind a waterfall for 3 whole minutes to advance the game.  At this point, some of these can feel more like interruptions to the game rather than actually playing and enjoying the game.

In many games, another common pitfall is when you can't see where gaps and death traps are.  If the floor appears whole, but there are actually holes there, this can be real frustrating to the player.  This might be fine for secret areas as long as they aren't necessary to advance the game, but when this becomes the norm for playing a game, it can seriously break the playability.

Interruptions

Interruptions are moments when the game stops the player from playing or slows their reactivity because the game needs a few moments to do something or display something.  This can be related to network latency or perhaps poor system performance.  But it can also be caused by poor game design or inefficient implementations.  When the player is stopped, they can no longer respond and are forced to watch.  In some games, this may be appropriate, such as with cut-scenes that show the reader how the story advances.  However, such interruptions need to be kept to a minimum, and as always, different players have different tolerances to these kinds of things.

In Kid Kool (NES), the game appears to be a common side-scroller that slides left and right as the player advances forward and backward.  However, there is also another screen to the top, and if the player visits that screen, the game has to scroll the window all-at-once resulting in a full second delay.  This could be fine if the stages were well-designed.  However, many of the stages result in the player visiting and leaving the top screen quickly, such as when they're on a high platform on the lower screen and have to jump across a pit - the jump transfers the player to the upper screen and as they player character falls, they are immediately transferred back to the lower screen.  A jump should be a fluid action, and certainly and distractions along the jump are serious detriments to playability that make the game that much harder to complete.

In Castlevania 2 (NES), the game cycles from day to night.  When this happens, the game freezes to display a message with very slow text that tells you just that.  This provides about a 5-second delay to the player, and it can happen at anytime - including right in the middle of a jump or while combating an enemy.  At least in some games, like Dragon Warrior (NES), the player is given the option to change the speed at which text is displayed.

In Eartbound (SNES), the same thing happens as the player's dad can sometimes call to "check-in".  This results in a dialog pop-up and the text is long and arduous.  As it happens repeatedly, it can be very frustrating.

In many games, the player moves too slow which can result in a disconnect of fluidity between the player and the game, essentially creating an interruption of sorts because the human mind can think faster than the game takes to actually execute the requested action.  This ties into our next category.

Lack of Fluidity

Fluidity is important in games, because as the player immerses into the game, there is an expected fluid interface between the player and the game.  If this interface is lacking, the fluidity isn't optimal and it can be difficult to remain immersed.

Take driving a car for example - you never have to look down to see the pedals and as a result, you can keep your eyes focused on the road.  The pedals provide a fluid interface between you and the car, meaning that you aren't too distracted while behind the wheel.

Sometimes, a game can be lacking in providing a fluid interface.  These are distractions and they can seriously decrease the game's playability.  In many games, there is a common complaint that the player can't kneel while they shoot, or that they can only shoot in so many directions.  This lack of "full control" is a detriment to fluidity, as it can remind you that you are playing a game with limitations and must adhere to the decreased control.

Another comment is that which was mentioned in the previous category - that the controls are too slipper or that the character is too slow.  Both of these again remind the player that they are inside the game and can break immersion.

In Fester's Quest (NES), sometimes the shots are curved, as in many other games.  The shots become difficult to "aim" so that the curved shots impact enemy targets.  These are also limitations that become distractions.

Why do games fail playability?

After categorizing some of the common pitfalls to playability, one has to ask themselves, how did the game developers ever produce a game with these kinds of detriments?  Some research suggests that the project leads or product owners don't actually play any games, so they don't really know what makes a game great or not.  At the same time, they insist on owning the direction of the game and don't listen to any suggestions.

Sometimes, quality assurance is to blame.  While developers and testers might be reporting these issues and even glitches, the developers don't fix them either because the bugs get listed as "As Designed" or a lack of funding or responsibility.

Even more frightening, sometimes developers come together without any game idea or story.  Totally unprepared, its hard to imagine what they were thinking of coming up with.

Without a playable demo, a game is essentially untested before it goes down a path it cannot return from.  Unfortunately, many games lack proper demos.

Sometimes companies can feel like they have experience and mastery over fun, such as with toy developers.  But when it comes to games, that experience has no carry-over.  Unfortunately, sometimes the the managers don't feel the same way.

Other times, games fail because of infrastructure and internal employee issues.  If there is any kind of block on communicating suggestions or what some may feel are bad game decisions, perhaps because there is a risk in being reprimanded by the frightening bosses, then these suggestions are never effectively communicated and a bad game is produced.

In any case, it seems clear that the way forward to better games is to learn from the mistakes the past. If these summarize very briefly those mistakes at all, then perhaps newer developers can learn without these kinds of failure.

Tolerances to Playability

Every player is different.  Some players have a high tolerance for faults to playability, and others have low tolerance.  It suffices to say however, that minimizing these faults is a good start for any game developer.  A successful game relies on players being able to play the game over and over.  The more hours played, the better.  At some point, the high hours of play turns into positive reviews which increase the virality of the game and generates more sales.  Hence, playability is an important aspect of game development, even more important than replayability, because if you can't play a game in the first place, how can you expect to play it over and over?

Saturday, May 5, 2018

Rating games according to the aspects of replayability - Deadly Towers

For more info on the game - https://en.wikipedia.org/wiki/Deadly_Towers
For more info on aspects of replability, refer to my technical journal paper, located at https://file.scirp.org/pdf/JSEA20120700001_38193851.pdf.

Social: 1
Challenge: 10
Experience: 8
Mastery: 8
Impact: 4
Completion: 6
Playability: 2

Deadly Towers is a notoriously horrific game of the NES era.  It features many reasons why it may be hard to continue playing, such as starting from the beginning when you die.  Most of these affect the game's playability and overall enjoyability.  Placing the game on the Schemico spectrum actually gives the game some positive merits, and with some improvements to the playability of the game, its sounds and enemy interactions, the game might've been one of the greatest NES hits.

Social


For social reasons, there's typically very little reason in playing classic NES games, except for the off-chance of playing with a group of friends.  While we don't play directly against one another, we might play by taking turns or by watching each other play.  For that reason, I give the game a single point in the social category.

Challenge


The level of challenge in Deadly Towers is quite high, simply because the game is actually pretty tough to beat.  The maze-like dungeons can be quite large and difficult to navigate (although a mini-map would help playability).  Shops for the purchase of potions, better equipment and other required items are sporadically located in those dungeons with very little direction on how to achieve.  Enemies can do quite a lot of damage, in addition to the knock-back effects that they incur.  Bosses can be quite punishing and difficult.  And overall, to reach a boss and destroy each bell tower, while deaths can take you back to the beginning, is quite a hurdle to overcome, although this could be a hit on the game's playability.  Overall, if one were able to beat Deadly Towers and see the ending credits, that would be quite the accomplishment - although it may be more of an achievement in patience.  For these reasons, I placed the game's challenge level all the way at the top, at a 10.

Experience


The experience in playing Deadly Towers is quite unique.  The gameplay offers a unique style of play, and the music is somewhat catchy (https://www.youtube.com/watch?v=JYIJzJb_r4w), although grossly repetitive (and it also restarts as you visit each different "room" in the game).  The story is somewhat interesting, although there is no interaction with the story throughout the game until defeating the final boss: you play the character of Prince Myer on the eve of his coronation, who is given the ominous warning by a strange shadowy figure that Rubas, "the devil of darkness" is soon coming and plans to use the seven magic bells to summon an army and overtake the kingdom.  Hence, Prince Myer must journey to the northern mountains, venture into each tower, collect the bells and burn them to prevent this from happening, and then finally defeat the devil Rubas himself.  For all this, I give Deadly Towers an experience placement of an 8.

Mastery


For mastery reasons, players may have a few good reasons to play Deadly Towers.  Speedruns are usually a fun resource to study for this category.  While TAS (tool-assisted speedrun) tools may exploit some of the game's bugs, RTS (real-time speedruns) generally offer a better glimpse of how people master the game.  See the link below for a decent speedrun by Youtuber WebNations on how Jeff Feasel plays the game.  I place Deadly Towers an 8 on mastery because the game is very challenging, not only on the enemy scale, but also in terms of finding difficult to locate items, navigate maze-like dungeons and find the best gear in the so-called "Parallel Zones" and "Secret Rooms".

https://www.youtube.com/watch?v=SfSngofeaus

Impact


Impact is a reason players may stay in the game because they want to experience the game in different variations.  Deadly Towers is essentially a game about journeying to several castles and towers, but fortunately you can choose the order in which you do the towers.  You can also choose which gear you want to quest for, and you certainly don't need any - although most probably make the game easier, defensively and/or offensively.  For these few points, I place Deadly Towers low on the scale at a 4.

Completion


Lastly, the completion aspect is a reason players may play because they want to obtain everything possible.  In Deadly Towers, there are hearts that you can find that increase your maximum life.  There are also "Parallel Zones" and "Secret Rooms" which are hidden points on the map that you find just by wandering into them.  Usually those places house some of the game's best gear.  Additionally, there are maze-like dungeons which are also hidden, and can be difficult at best to exit.  The game's variety of items also offer reason to explore and discover what they all do - as some teleport you different areas in the game.  For all these reasons, I rate Deadly Towers as having a moderate level of completion, at a 6.

Playability


The playability aspect of Deadly Towers is the game's low point.  Any time you die, you return to the beginning in a world where journeying to each tower is pretty arduous to begin with.  There are at times, too many enemies on the screen which make it difficult to advance for the normal player who has not spent much time mastering the game.  These enemies can knock you back, sometimes into death pits.  There are also hidden zones you may haplessly wander into without much hope of exit.  The music restarts its loop on every map change - and in the maze-like dungeons, you will be restarting the music every few seconds.  Items are hard to locate, and better gear is hidden in "Parallel Zones" and "Secret Rooms".  The sword mechanics of a slow flying sword are questionable when you can only have one sword at a time on the screen (without extra booster items).  Some enemies take an unusually large number of hits to kill.  The in game menu can be confusing to use.  These all hurt the game's playability, an even with great touches on the aspects of replability listed above, a poor level of playability can override all of that.  For all these reasons, I rank the game's playability fairly low, at a 2.

Wednesday, October 25, 2017

OpenGL Tutorial - Part 3 of 3

Welcome back to my OpenGL tutorial, this time we're on the third and final part where we will get to experience some hands-on coding with some of the more basic features of OpenGL to implement something a little more cooler than a simple stationery white square.

I'm going to dump some code that draws a rotating 3D-cube on the screen in various colors.  The first thing I'd like to do after that is explain what the code does to get the cube to display on the screen.  After that, we can better guide the reader through the process of implementing some extra bits.

At first, this code will draw a rotating 3D-cube, stationery on a black background.  And with some extra effort, we'll be animating the cube so that it bounces around inside a larger defined boundary cube.  This is like the old screen saver with a bouncing circle along the edges of your monitor.  Except this will be a cube.

Starting Point

Copy-and-paste the following code.  This is some code that I wrote.  Run it to make sure that it compiles, builds and executes properly.  It should be a rotating cube.

#include <GL\glew.h>
#include <GL\freeglut.h>
 
//disables console window so only our application window displays
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
 
//defining variables here
float rot = 0.01;
 
//draws six faces of a cube with varying colors
void drawCube(void)
{
    glBegin(GL_QUADS);
    {
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
 
        glColor3f(1.0f, 0.5f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
 
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
 
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glEnd();
    }
}
 
//draw our cube at its location and rotation angle
void render(void)
{
    //clear, reset, recenter, update rotation
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
 
    //snap our cube to its position and rotate it
    glTranslatef(-1.5f, 0.0f, -15.0f);
    glRotatef(rot, 1, 1, 0);
 
    //draw our cube
    drawCube();
 
    //standard call to dump our drawing into the next frame
    glutSwapBuffers();
}
 
//animation for rotating the cube
void animate()
{
 
    //update rotation angle
    rot += 1;
    rot = rot >= 360 ? 0 : rot;
 
    //refresh screen
    glutPostRedisplay();
}
 
//reshapes the drawing when window is resized
void reshape(int wint h)
{
    glViewport(0, 0, wh);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (float)w / (float)h, 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
//main entry point
int main(int argcchar** argv)
{
    //standard initialization routine
    glutInit(&argcargv);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(320, 230);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutCreateWindow("Simple Shape Drawing");
 
    //color to draw background
    glClearColor(0.0, 0.0, 0.0, 0.0);
 
    //enable depth viewing
    glEnable(GL_DEPTH_TEST);
 
    //call our display function which actually does some drawing
    glutDisplayFunc(render);
    glutIdleFunc(animate);
    glutReshapeFunc(reshape);
 
    //enters our display functions into a repeating loop
    glutMainLoop();
 
    return 0;
}

Explaining Code

Let's dig deeper and see what our code is really doing.

Creating the Window

Let's have a look at the main entry point: the method named main.  This serves as a basic naming function for C++ to know where to begin.  Inside that method is a pretty standard OpenGL initialization routine.  The first glutInit call initializes the OpenGL engine with the given command line arguments to main.  Then, the window position and size is initialized just after.  You can modify the values in these if you like.

The glutInitDisplayMode defines the display mode for the created window.  You can play around with these settings if you want.  The use of the "|" (bitwise or) operator allows settings to be chained together so that you can specify multiple different settings in the same line.  If you remove the GLUT_DOUBLE, which stands for double buffering, you may notice the cube rotates very hyper, i.e. very less smoothly.

Finally, glutCreateWindow will form the window with the given title.  You can name this however you want.  Shortly after, glEnable(GL_DEPTH_TEST) allows us to view our renderings in 3D by performing a depth test so that only the frontward faces are visible.  If you remove this line, you'll notice that you can see all six faces of the cube at once as they're blended together in weird ways.

Calling our Drawers

Next, we reference our method where the actual cube drawing takes place with a trio of methods.  The glutDisplayFunc is what gets called when the window first displays or displays after being gone.  The glutIdleFunc gets called constantly.  Lastly, the glutReshapeFunc is what will be called when changing the size of the window when it is running.  Here, we use the display func to store our actual drawing of the cube, and the idle func to animate our cube in its rotational manner.

Lastly, the reshape function is generally used to maintain any aspect ratio of the drawn shapes.  In this demo, we use it to also grow or shrink the cube as the window changes size.  You can see how this is done in the reshape method, which utilizes a bit of a standard routine.  The important call is in gluPerspective, which sets the field of view to a 45 degree cone, and the aspect ratio to whatever the ratio of the window is.  The final two parameters to that method define the clipping points in the depth.  Here, anything outside of Z=1 and Z=1000 will be clipped and not drawn.

Drawing the Cube

As stated just a bit ago, our cube will get drawn in the display func, which we've established is the function named render.  Going there, we notice the first thing done is to clear the screen and load the identity via glLoadIdentity.  This refers to the identity matrix, in which OpenGL uses a matrix to store the contents of drawn shapes.  This must be reset at the beginning of every display func so that it can successfully redraw the intended drawing properly from scratch.

The next thing done is to translate our "position" to the location where we want to draw the cube.  Again, this has to do with the matrix OpenGL uses.  We will first move to the location, and then we will perform the rotation at that point.  When rotation and translation are done in opposite order, the cube will rotate around the point instead of in place at the point.  This may be counter-intuitive, but to me it seems backwards.  So it may be best to think of these operations as working in reverse.

Then, the cube is drawn in our custom method named drawCube.  This is done by telling OpenGL that we're about to begin drawing some "gl_quads" primitives with glBegin(GL_QUADS).  These are four-sized rectangles specified by four vertices.  To draw a cube with rectangles, we basically stitch them together on common vertices to define the six faces.  This part may seem a bit too "numbery", so you may want to suffice it enough to say, that this sequence of vertex assignments is indeed a cube.  However, note at the beginning of each face, we've defined a new color so that our faces are more distinguishable when drawn.  Lastly, a call to glEnd indicates that we're done drawing these primitives.  I like to use curly brackets around these sections, but note that they're more for visual code style and are not necessary.

Finally, the end of our render function calls glutSwapBuffers as part of its double buffering routine to increase the smoothness of the animation.

Animating the Cube

Our last code bit basically is the animate method which is called by our constantly repeating idle func.  This part is simple: all we do is increment the rotation angle and keep it bounded cyclically between 0 and 360.  The important bit is when glutPostRedisplay is called to send a signal so that our display function is called to draw our cube.

That should be it!  Now that we've reviewed pretty much all the basics of our demo program, we can begin to add a feature to it.  Let's try to get this rotating cube to now move and then get it to bounce off walls.  Finally, we'll draw the wall we're bouncing the cube off and have ourselves a bit of a nice screensaver type of program.

Adding Features

Now for the fun part.  Basically, I'd like to get the cube to bounce off the walls like a screensaver, except in 3D.

More Member Variables

I've already provided the variable for our rotation angle, but let's add several more that we'll need in adding our feature.  Place these variables below just below the rotation angle variable, rot, so that these are inside our class file but outside of any methods.

Location, Size, Velocity

First, let's add the location.  This will be a 3D-coordinate system location defined as an array of 3 floating point values.  The first two represent the position left/right (X) and top/down (Y) in the view.  The last value represents the depth (Z).  Since we more or less want the view to be some distance away from us, we specify an initial deep depth of -15.

float location[3] = { -1.5f, 0.0f, -15.0f }; //coordinate vector

To control the size of our cube, let's define a scale variable that shrinks or inflates our cube based on its setting.  A value of 0.5 halves the size, and a value of 2 will double it.  We will see its use later and how it can be used to scale our drawn shapes.

float scale = 0.5;

To animate our cube, we define a 3D-velocity that represents how much our cube moves in every direction.  As far as animation goes, this is the amount the cube will move across every second.

float velocity[3] = { 1.0f, 1.0f, 1.0f }; //velocity vector

Boundary Walls

To allow our cube to bounce properly, we'll need to define where the walls are.  This is a bounding box with six faces, and so hence we'll have six values to define the location of those planar faces.

float velocity[3] = { 1.0f, 1.0f, 1.0f }; //velocity vector
float lbounds[3] = {-5.0f, -5.0f, -50.0f}; //boundary box lower limits

Animation Timing

Since the animation routine will be called as indeterminate intervals, we'll have to handle animation by checking against the amount of time between calls and scaling our velocity with those elapsed time periods.  These below variables will allow us to collect the time since epoch and use the elapsed milliseconds to more smoothly draw our animated cube.

First, add an import with the include tag at the top of the file:

#include <chrono>

Then, add these variables with our others:

__int64 elapsedTimeInMS = 0; //time since last draw
__int64 lastTimeEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::system_clock::now().time_since_epoch()).count(); //current time

Updates to Drawing

Since we have added some information to describe the position of our cube, let's update our display function so that the cube is always drawn at its given location.  To do this, simply edit our line where the glTranslatef function is located and pass in our shape information:

glTranslatef(location[0], location[1], location[2]);

As for another update, since we have a variable to store the size or scale of our shape, let's also add that below the call to glRotatef:

glScalef(scale, scale, scale);

The scale call basically scales what is drawn by a factor along each of the three dimensions.  If edited, we could scale unevenly across our three dimensions.  For example, a scale of 0.5 will halve the size, and a scale of 2.0 would double it.

Updating Animation

Now that we have some standard location animation variables, we can get to making our cube move.  Inside our idle function, that is, over in our animate function, we can make a few adjustments.  The first thing we'll want to do is find out how much time has elapsed since our last call to animate.  This allows us to smoothly move the cube at its given velocity instead of moving in large hops.  Add this code to the top of our animate function:

    //get elapsed milliseconds
    __int64 timeNow = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    elapsedTimeInMS = timeNow - lastTimeEpoch;
    lastTimeEpoch = timeNow;

Next, we can make the position updates using a loop across our three dimensions:

    //update position and detect wall bouncing
    for (int i = 0; i < 3; i++)
    {
        location[i] = location[i] + velocity[i] * (1.0/ elapsedTimeInMS);
    }  

Finally, we can then implement velocity changes when a "bounce" off the boundary wall is detected.  To do this, whenever a bounce is detected in that dimension, we simply reverse the velocity along that direction.  Add this code after the position updates, but inside our loop:

        if (location[i] > ubounds[i] || location[i] < lbounds[i])
        {
            velocity[i] *= -1; //when wall bounce detected, just simply go in opposite direction
        }

The last part of the animation should remain the same.  When put all together, it should look like this:

//routine to animate our cube with its velocity and elapsed time since last call
void animate(void)
{   
    //get elapsed milliseconds
    __int64 timeNow = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    elapsedTimeInMS = timeNow - lastTimeEpoch;
    lastTimeEpoch = timeNow;
 
    //update position and detect wall bouncing
    for (int i = 0; i < 3; i++)
    {
        location[i] = location[i] + velocity[i] * (1.0/ elapsedTimeInMS);
        if (location[i] > ubounds[i] || location[i] < lbounds[i])
        {
            velocity[i] *= -1; //when wall bounce detected, just simply go in opposite direction
        }
    }   
 
    //update rotation angle
    rot += 1;
    rot = rot >= 360 ? 0 : rot;
 
    //refresh screen
    glutPostRedisplay();
}

Drawing Our Boundary Walls

If you run the code thus far, you should see our cube bounce about its boundary box.  The trouble is, we can't see where our boundary walls are and that would be a nice feature.  So let's work on that.

What we want to do is draw a wire-frame cube.  To do the drawing, we'll do something similar to what was done for drawing our solid cube.  Let's add a method for that first:

//draw the boundary box where cube bounces as a wireframe
void drawBounds(void)
{
    glColor3f(1.0f, 1.0f, 1.0f);
    glPolygonMode(GL_FRONT_AND_BACKGL_LINE);
    glBegin(GL_QUADS);
    {      
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
 
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glEnd();
    }
    glPolygonMode(GL_FRONT_AND_BACKGL_FILL);
}

The first thing to note is that we define a polygon mode in glPolygonMode(GL_FRONT_AND_BACKGL_LINEto be that of a wireframe, using constants that let us know only draw the lines and not fill to a solid color.  The first constant (GL_FRONT_AND_BACK) tells OpenGL to go ahead and draw all faces, even the ones we can't see.  We unset this at the end so that our cube drawing doesn't draw in the same way.

Finally, we can hook up to our new drawBounds method from our display function.  This part is simple, but the complexity behind it is a little more sophisticated.  After having drawn our little cube, we'll want to undo what we've done to the matrix in our translate, rotate and scale operations.  In this way, we can draw the wireframe boundary box in its actual place, devoid of any rotation, scale or translation.

The trick to undoing those operations is in the math.  To undo a scale, we'd need an inverse.  To undo our translation and rotation, we just need a negative.  Additionally, the order here reflects that in the way they were first performed.  Since our scale was last to perform, it is first to be undone, and so on.

    //undo our snap, rotate and scale
    glScalef(1 / scale, 1 / scale, 1 / scale);
    glRotatef(-rot, 1, 1, 0);
    glTranslatef(-location[0], -location[1], -location[2]);
    

Afterwards, we can now draw our boundary box and complete the method.

    //draw the boundary wall
    drawBounds();

Wrapping Up

That should do it.  We've finished doing what we set out to do.  For reference, here is the completed new code.

#include <GL\glew.h>
#include <GL\freeglut.h>
#include <chrono>
 
//disables console window so only our application window displays
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) 
 
//defining variables here
float rot = 0.01;
float scale = 0.5;
float location[3] = { -1.5f, 0.0f, -15.0f }; //coordinate vector
float velocity[3] = { 1.0f, 1.0f, 1.0f }; //velocity vector
float lbounds[3] = {-5.0f, -5.0f, -50.0f}; //boundary box lower limits
float ubounds[3] = { 5.0f, 5.0f, -10.0f }; //boundary box upper limits
__int64 elapsedTimeInMS = 0; //time since last draw
__int64 lastTimeEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::system_clock::now().time_since_epoch()).count(); //current time
 
//routine to animate our cube with its velocity and elapsed time since last call
void animate(void)
{   
    //get elapsed milliseconds
    __int64 timeNow = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    elapsedTimeInMS = timeNow - lastTimeEpoch;
    lastTimeEpoch = timeNow;
 
    //update position and detect wall bouncing
    for (int i = 0; i < 3; i++)
    {
        location[i] = location[i] + velocity[i] * (1.0/ elapsedTimeInMS);
        if (location[i] > ubounds[i] || location[i] < lbounds[i])
        {
            velocity[i] *= -1; //when wall bounce detected, just simply go in opposite direction
        }
    }   
 
    //update rotation angle
    rot += 1;
    rot = rot >= 360 ? 0 : rot;
 
    //refresh screen
    glutPostRedisplay();
}
 
//draw the boundary box where cube bounces as a wireframe
void drawBounds(void)
{
    glColor3f(1.0f, 1.0f, 1.0f);
    glPolygonMode(GL_FRONT_AND_BACKGL_LINE);
    glBegin(GL_QUADS);
    {      
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
 
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glEnd();
    }
    glPolygonMode(GL_FRONT_AND_BACKGL_FILL);
}
 
//draws six faces of a cube with varying colors
void drawCube(void)
{
    glBegin(GL_QUADS);
    {
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
 
        glColor3f(1.0f, 0.5f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
 
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
 
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glEnd();
    }
}
 
//draw our cube at its location and rotation angle
void render(void)
{
    //clear, reset, recenter, update rotation
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
 
    //snap our cube to its position, rotate it and scale it
    glTranslatef(location[0], location[1], location[2]);
    glRotatef(rot, 1, 1, 0);
    glScalef(scale, scale, scale);
 
    //draw our cube
    drawCube();
    
    //undo our snap, rotate and scale
    glScalef(1 / scale, 1 / scale, 1 / scale);
    glRotatef(-rot, 1, 1, 0);
    glTranslatef(-location[0], -location[1], -location[2]);
    
    //draw the boundary wall
    drawBounds();
 
    //standard call to dump our drawing into the next frame
    glutSwapBuffers();
}
 
//reshapes the drawing when window is resized
void reshape(int wint h)
{
    glViewport(0, 0, wh);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (float)w / (float)h, 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
//main entry point
int main(int argcchar** argv) {
    //standard initialization routine
    glutInit(&argcargv);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(320, 230);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutCreateWindow("Simple Shape Drawing");
 
    //color to draw background
    glClearColor(0.0, 0.0, 0.0, 0.0);
 
    //more standard things
    glEnable(GL_DEPTH_TEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINTGL_NICEST);
 
    //call our display function which actually does some drawing
    glutDisplayFunc(render);
    glutIdleFunc(animate);
    glutReshapeFunc(reshape);
 
    //enters our display functions into a repeating loop
    glutMainLoop();
 
    return 0;
}

Road Map:

Part 1 - Introduction
Part 2 - Setting up OpenGL in Visual Studio
Part 3 - Implementing The Boundary Bouncing Cube

OpenGL Tutorial - Part 2 of 3

Welcome back to my three part OpenGL tutorial.  In the last part, we briefly introduced the idea of OpenGL, but in this part of the tutorial, we will guide the reader through the process of setting up a development environment so that they can write code and run programs in OpenGL.

Without any further ado, here are the steps.

1. Download Glut

The core OpenGL headers and libraries come from glut/freeglut.  Use the link below to download freeglut from the MSVC section (this was listed in a link with text "Download freeglut 3.0.0 for MSVC" as of this writing.)  Go ahead and keep the zip around for later reference.

2. Download Glew

Short for OpenGL Extension Wrangler Library.  This contains a lot of extended features that you may not need until you become more advanced with working in OpenGL.  You can obtain this from the link here:


3. Setup directories and copy files

Set up a directory so that you can copy/move the downloaded files from steps 1 and 2.  This can be mostly anywhere, but you may want to keep it simple.  For example, lets use:
  • Central root location for everything: E:\opengl
  • Place for code and projects to go: E:\opengl\projects
  • Put your library files (i.e. freeglut; other dependencies) in here: E:\opengl\lib
After creating the file structure above, unzip the downloads from steps 1 and 2 and paste their folders into E:\opengl\lib.  So your file tree should look something like:

E:\OPENGL
├───lib
│   ├───freeglut-3.0.0
│   └───glew-2.1.0
└───projects

Note that you can output cool text trees like this using the tree command in a terminal window on both Windows and Unix systems.

4. Create C++ Project

In this tutorial, we're going to create a simple OpenGL project to demonstrate some of its 3D capabilities.  This demo will just be a cube that bounces around inside of a 3D box, utilizing some simple math of calculating angles of the bounce off each wall, as well as the simple physics of constant animating velocity and rotation.

Fire up Visual Studio (in this guide, version 2015 is used) and create a new project through the dialog in File > New > Project.  In here, search for Visual C++ in the templates section on the left, and then select Empty Project from the middle.  Give the project a name, like DemoOpenGL, and also click on Browse to place the project in your projects directory from the previous step.

Now your file structure should appear similar to this:

E:\OPENGL
├───lib
│   ├───freeglut-3.0.0
│   └───glew-2.1.0
└───projects
    └───DemoOpenGL

4.1. Add First Source

First, we'll need to add at least one source file.  Right click on the DemoOpenGL project node in the Solution Explorer and navigate to Add > New Item.  Find Visual++ from the left, and select C++ file in the middle.  Name the file main.cpp and click the Add button.

Doing this enables some project settings for the next step so that we can configure properly.


5. Configuring your project

This step is an important one for anyone developing in OpenGL.  It is probably the most complicated one, and so its important to understand what's going on and become comfortable creating new projects with ease.

There's three things we basically need:

1. To see the OpenGL "include" header files.
2. To resolve external references via linked libraries
3. Runtime dlls

While you're doing this, be aware that there are two configurations: 32-bit and a 64-bit.  While headers are platform-independent, the dll's and libs are not, and hence, we will provide forked instructions for both platforms.

To make sure your build configurations have both a 32-bit and 64-bit setting, check out the box just to the right of where it says "Debug" on Visual Studio (up at the top, left of the middle).  Currently, it should by default say x86.  If you already have a setting in there for x64, then you should be fine.  The x86 is a 32-bit version while the x64 is the 64-bit version.  If not, then you can go inside to create a new build configuration (this should be simple).

5.1. Including headers

Inside Visual Studio, get to the project properties.  To do this, right click on your DemoOpenGL project node in the Solution Explorer and navigate to Properties.  Make sure the Configuration drop-down at the top-left is selected to "All Configurations".

Headers are platform independent, so be sure to set these settings under "All Platforms" in the drop down at the top-middle.

Navigate to C/C++ > General.  On the right, look for Additional Include Directories.  Click in the box beside it, and there should be a little down-arrow at the far right.  Click on that and then click on <Edit...>.  This brings up a dialog window where you can specify the location of directories to "include" so that C++ knows where to look in its attempts to resolve external references.

In that new little window that popped up, click on the New Line icon (looks like a folder).  This inserts a new line in the text area and you can click on the "..." button to browse to a location to include.  We want to do this twice and point to the include folders of each of our libraries:

 - e:\opengl\lib\freeglut-3.0.0\include
 - e:\opengl\lib\glew-2.1.0\include

Make sure you did this for "Additional Include Directories" and not "Additional #using Directories".

Click OK.


5.2. Including libraries

Next, we'll be doing something similar around libs instead of headers.  Inside the project properties dialog, browse to Configuration Properties > Linker > General and look for Additional Library Directories on the right.  Before you click, check that the Platform drop-down box is selected on the correct platform (Win32 is 32-bit and x64 is 64-bit).

With the correct platform, click the text box and then on the little arrow and then on <Edit...>.  Another similar window from the last step will launch.  Click the New Line icon and on the "..." button to browse to add the following folders:

[64 bit Version]
 - e:\opengl\lib\freeglut-3.0.0\lib\x64
 - e:\opengl\lib\glew-2.1.0\lib\Release\x64

[32 bit Version]
 - e:\opengl\lib\freeglut-3.0.0\lib
 - e:\opengl\lib\glew-2.1.0\lib\Release\Win32

Click Apply.

One more thing needs to be done in this section.  The above specified where libs could be found, but now we need to specify what libs to grab.  This part is platform independent, so change the Platform selector to All Platforms.

Go to Configuration Properties > Linker > Input and click in Additional Dependencies.  Bring up the dialog box with the <Edit...> link and type into the top empty text box the following libs:

freeglut.lib  (and press enter to get a new line)
glew32.lib

Click OK, and then OK again from the Property Pages.

5.3. Copy dll files

Last but not least, lets move our dll files over into our bin output folder.  This is also platform-dependent, so be sure to copy the correct files.

[64 bit]
Copy:
 - e:\opengl\lib\freeglut-3.0.0\bin\x64\freeglut.dll
 - e:\opengl\lib\glew-2.1.0\bin\Release\x64\glew32.dll (not a typo)

Paste into:
 - e:\opengl\projects\DemoOpenGL\x64\Debug

[32 bit]
Copy:
 - e:\opengl\lib\freeglut-3.0.0\bin\freeglut.dll
 - e:\opengl\lib\glew-2.1.0\bin\Release\Win32\glew32.dll

Paste into:
 - e:\opengl\projects\DemoOpenGL\Debug

6. Write code

Go ahead and paste this code into main.cpp, which I got from https://www.badprog.com/c-opengl-hello-world.  It should display a simple white square when run in the next step.

#include <GL/glut.h>
 
void displayMe(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_POLYGON);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(0.5, 0.0, 0.0);
    glVertex3f(0.5, 0.5, 0.0);
    glVertex3f(0.0, 0.5, 0.0);
    glEnd();
    glFlush();
}
 
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE);
    glutInitWindowSize(300, 300);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Hello world :D");
    glutDisplayFunc(displayMe);
    glutMainLoop();
    return 0;
}


7. Compile/build/run

There shouldn't be any red-lined errors.  If there are red-underlined errors indicating that Visual Studio is unable to find a header file, then you may want to revisit step 5.1 to ensure the directories were set properly.

If there are linker errors, you may want to revisit step 5.2, which indicates that the libraries can't be found to resolve those references.

Lastly, if there is a problem running the application but not in building, you may get an error specifying that a required dll was not found.  For this, revisit step 5.3

When the above code is run, it should display a stationary white square on a black background.  Pretty simplistic "hello world" program of sorts.  In the next step, we will work around something a little less basic to give the reader a little more hands-on experience.

Road Map:

Part 1 - Introduction
Part 2 - Setting up OpenGL in Visual Studio
Part 3 - Implementing The Boundary Bouncing Cube

OpenGL Tutorial - Part 1 of 3




Overview


Welcome to my tutorial on OpenGL, a graphics package for C++ that supports rendering of views in 3D and also in 2D.  It's a nice library for developing games as well as simulations that require visual representation.  Check out its website for a lot more information: https://www.opengl.org/about/.

This tutorial focuses on getting a developer up and started with using OpenGL using a Visual Studio environment.  This writing assumes a Windows platform of either 32-bit or 64-bit, but OpenGL is supported on many others, including Linux and Mac.

In the first several steps, we'll introduce the developer to setting up OpenGL for the first time, and then we will write a short program to demonstrate some of its 3D capabilities.

I learned about OpenGL while taking a trio of graduate level computer science courses at West Virginia University, the last of which used OpenGL around the topics of medical image analysis.  Some of what I thought the coolest features were focus on surface details such as textures, reflections and shaders, in which pixels and fragments can be dynamically colored based on specific lighting and material conditions.

For this blog, this will be a three-part post.  This first post just serves as an introduction, but the second one will help a user setup a Visual Studio environment where they can begin to work with and write code in OpenGL.  In the third part, I'll introduce some code around a simple little demo so that the user can get their hands on actually changing some given code and implementing the desired behavior.

Road Map:

Part 1 - Introduction
Part 2 - Setting up OpenGL in Visual Studio
Part 3 - Implementing The Boundary Bouncing Cube

Monday, August 21, 2017

What is playability?

What is playability?

When a gamer first takes to the controllers or keyboard to interface themselves with the game, they are creating a seamless connection with themselves and the game.  This interface should be fluid and easy to learn, the same way a person has a intimate connection when sitting in the driver seat of the car.  The player shouldn't have to look down to look for buttons, and there should be no distractions, from the game or elsewhere.

When a seamless interface is created, it is said that immersion has occurred.  At this point, the player is in the game and does not realize that they are connected via a controller.  This creates an optimal play experience and it is expected of any gaming experience.  Hence, the act of getting into this "magic circle" called immersion is what playability is all about.

There are a number of things that can distract.  Some are external and the game developer would have no natural control over there.  This could include the door bell ringing, a dog barking, or something as simple as an alarm going off indicating its time to quit playing.

External Distractions

  •  Unintentional
    • Door bell ringing, dog barking, health reasons, etc.
  •  Intentional
    • Scheduled alarms, conscious thoughts, etc.

On the other hand, there are internal sources of distraction that should and must be minimized by the game developer.  These include glitches in the game, discontinuities in stories, poor system performance, or poor network lag (which could be both a problem at the network side or with the poor usage of data streams being sent across the network which could've been made more efficient.)

Internal Distractions

  • Performance
    • Network Latency
    • Inefficient operations, menu takes too long to load, etc
  • Discontinuities
    • Gaps in story, missing but expected considerations or implementations, etc.
  • Defects
    • Glitches that make you go "what the... "
    • Designs that are disconcerting, tutorials that take too long/too obvious, slow load times that aren't due to inefficiencies, etc
Software Performance Engineering
https://www.slideshare.net/TanzaIratier/joe-krall-presentation
Study on latency in games leads to lower overall enjoyability index (http://dl.acm.org/citation.cfm?id=566500.566511&preflayout=tabs)

https://link.springer.com/content/pdf/10.1007/978-3-540-74873-1_53.pdf