cpp
Graphics
04_Graphics⚙️cpp
// Module 10: Expert Topics
// Lesson: Graphics Fundamentals (Console Rasterization)
// Build: g++ -std=c++17 01_graphics.cpp -o graphics_demo
// Description: Demonstrates a lightweight pixel buffer, drawing primitives, and animation timing.
#include <chrono>
#include <cmath>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
using namespace std;
class Canvas
{
public:
Canvas(int w, int h, char fill = ' ') : width_(w), height_(h), pixels_(w * h, fill) {}
void clear(char fill = ' ')
{
fill_n(pixels_.begin(), pixels_.size(), fill);
}
void setPixel(int x, int y, char c)
{
if (x < 0 || y < 0 || x >= width_ || y >= height_)
{
return;
}
pixels_[y * width_ + x] = c;
}
void drawLine(int x0, int y0, int x1, int y1, char c)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
while (true)
{
setPixel(x0, y0, c);
if (x0 == x1 && y0 == y1)
{
break;
}
int e2 = 2 * err;
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
}
void drawCircle(int cx, int cy, int radius, char c)
{
for (int angle = 0; angle < 360; angle += 5)
{
double rad = angle * M_PI / 180.0;
int x = static_cast<int>(cx + radius * cos(rad));
int y = static_cast<int>(cy + radius * sin(rad));
setPixel(x, y, c);
}
}
void render() const
{
for (int y = 0; y < height_; ++y)
{
for (int x = 0; x < width_; ++x)
{
cout << pixels_[y * width_ + x];
}
cout << '\n';
}
}
int width() const { return width_; }
int height() const { return height_; }
private:
int width_;
int height_;
vector<char> pixels_;
};
void printPart(const string &title)
{
cout << "\n========== " << title << " ==========" << endl;
}
// PART 1: Drawing primitives.
void part1_primitives()
{
printPart("PART 1: Static Drawing");
Canvas canvas(40, 15);
canvas.drawLine(0, 0, 39, 14, '/');
canvas.drawLine(0, 14, 39, 0, '\\');
canvas.drawCircle(20, 7, 6, '*');
canvas.render();
}
// PART 2: Sprite blitting (simple bitmap copy).
void blitSprite(Canvas &canvas, int x, int y, const vector<string> &sprite)
{
for (size_t row = 0; row < sprite.size(); ++row)
{
for (size_t col = 0; col < sprite[row].size(); ++col)
{
if (sprite[row][col] != ' ')
{
canvas.setPixel(static_cast<int>(x + col), static_cast<int>(y + row), sprite[row][col]);
}
}
}
}
void part2_sprites()
{
printPart("PART 2: Sprite Blitting");
Canvas canvas(30, 10);
vector<string> rocket = {
" ^ ",
" /|\\ ",
"/_|_\\",
" | ",
" / \",
};
blitSprite(canvas, 12, 2, rocket);
canvas.render();
}
// PART 3: Basic animation loop.
void part3_animation()
{
printPart("PART 3: Animation Loop");
Canvas canvas(30, 10);
for (int frame = 0; frame < 20; ++frame)
{
canvas.clear();
int x = frame;
int y = static_cast<int>(4 + 2 * sin(frame * 0.3));
canvas.drawCircle(x, y, 2, '#');
cout << "Frame " << setw(2) << frame << "\n";
canvas.render();
this_thread::sleep_for(chrono::milliseconds(120));
cout << string(15, '\n');
}
}
// PART 4: Discussion of real graphics stacks.
void part4_ecosystem()
{
printPart("PART 4: Graphics Ecosystem Overview");
cout << "- 2D: SDL2, SFML, Raylib offer windowing, input, textures." << endl;
cout << "- 3D Low-level: OpenGL, Vulkan, Direct3D expose GPU pipelines." << endl;
cout << "- High-level: Unreal, Unity, Godot wrap rendering + tooling." << endl;
cout << "- Modern C++ engines use ECS (Entity Component System) and data-oriented design." << endl;
}
int main()
{
part1_primitives();
part2_sprites();
part3_animation();
part4_ecosystem();
cout << "\nExercises:\n"
<< "1. Add Bresenham circles to avoid floating-point trig calls.\n"
<< "2. Implement double buffering to reduce flicker in the animation.\n"
<< "3. Extend Canvas to export PPM images for inspection.\n"
<< "4. Port the sprite system to SDL2 textures for real rendering.\n";
return 0;
}