(Feat): Initial Commit.

This commit is contained in:
2025-12-17 21:31:12 +00:00
commit 42529be0f8
16 changed files with 2597 additions and 0 deletions

5
.clangd Normal file
View File

@@ -0,0 +1,5 @@
CompileFlags:
Add:
- -xc
- -std=c11
- -I/home/zanewalker/Development/Programming/C_Projects/ASCIIRenderer/include

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.qodo
.vscode/
bin/
obj/
*.o
*.out
*.log

125
Makefile Normal file
View File

@@ -0,0 +1,125 @@
SRCDIR := src
INCDIR := include
OBJDIR := obj
BINDIR := bin
# Compiler and flags
CC := clang
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Werror
CFLAGS += -Wshadow -Wconversion -Wdouble-promotion
CFLAGS += -Wformat=2 -Wundef -fno-common
CFLAGS += -I$(INCDIR)
# Target
TARGET := $(BINDIR)/ascii3d
# Source files
SRCS := $(wildcard $(SRCDIR)/*.c)
OBJS := $(SRCS:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
DEPS := $(OBJS:.o=.d)
# Libraries
LDFLAGS := -lm
# Build modes
.PHONY: all debug release clean install uninstall help
# Default target
all: release
# Release build (optimized)
release: CFLAGS += -O3 -DNDEBUG -march=native -flto
release: LDFLAGS += -flto
release: $(TARGET)
# Debug build (with symbols and sanitizers)
debug: CFLAGS += -O0 -g3 -DDEBUG
debug: CFLAGS += -fsanitize=address,undefined
debug: LDFLAGS += -fsanitize=address,undefined
debug: $(TARGET)
# Profile build (optimized with debug symbols)
profile: CFLAGS += -O2 -g -pg
profile: LDFLAGS += -pg
profile: $(TARGET)
# Create directories
$(OBJDIR) $(BINDIR):
@mkdir -p $@
# Link
$(TARGET): $(OBJS) | $(BINDIR)
@echo "Linking $@..."
@$(CC) $(OBJS) -o $@ $(LDFLAGS)
@echo "Build complete: $@"
# Compile
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
@echo "Compiling $<..."
@$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
# Include dependencies
-include $(DEPS)
# Clean build artifacts
clean:
@echo "Cleaning..."
@rm -rf $(OBJDIR) $(BINDIR)
@echo "Clean complete."
# Install to system
PREFIX ?= /usr/local
install: release
@echo "Installing to $(PREFIX)/bin..."
@install -d $(PREFIX)/bin
@install -m 755 $(TARGET) $(PREFIX)/bin/
@echo "Installation complete."
# Uninstall from system
uninstall:
@echo "Uninstalling from $(PREFIX)/bin..."
@rm -f $(PREFIX)/bin/ascii3d
@echo "Uninstallation complete."
# Run the program
run: release
@./$(TARGET)
# Run with demo text
demo: release
@./$(TARGET) -a HELLO
# Static analysis
analyze:
@echo "Running static analysis..."
@cppcheck --enable=all --std=c11 -I$(INCDIR) $(SRCDIR)/*.c
# Format code
format:
@echo "Formatting code..."
@clang-format -i $(SRCDIR)/*.c $(INCDIR)/*.h
# Help
help:
@echo "ASCII 3D Renderer - Build System"
@echo "================================"
@echo ""
@echo "Targets:"
@echo " all - Build release version (default)"
@echo " release - Build optimized release version"
@echo " debug - Build debug version with sanitizers"
@echo " profile - Build with profiling support"
@echo " clean - Remove build artifacts"
@echo " install - Install to system (PREFIX=/usr/local)"
@echo " uninstall - Remove from system"
@echo " run - Build and run"
@echo " demo - Build and run demo"
@echo " analyze - Run static analysis (requires cppcheck)"
@echo " format - Format source code (requires clang-format)"
@echo " help - Show this help"
@echo ""
@echo "Examples:"
@echo " make # Build release"
@echo " make debug # Build debug"
@echo " make run # Build and run"
@echo " make install PREFIX=~ # Install to home directory"

239
README.md Normal file
View File

@@ -0,0 +1,239 @@
# ASCII 3D Renderer v2.0
An advanced, high-quality ASCII 3D text renderer written in C for fun.
## Features
### Rendering
- **Phong Lighting Model** - Realistic lighting with ambient, diffuse, and specular components
- **Multiple Light Sources** - Key light, fill light, and rim light for professional 3-point lighting
- **Smooth Normals** - Averaged surface normals for smoother shading on edges
- **Multiple Render Modes** - Solid, wireframe, points, and full shaded rendering
- **Anti-Aliasing** - Optional sub-pixel sampling for smoother output
### Visual Quality
- **Extended ASCII Palettes** - 70-level gradient for detailed shading
- **Block Characters** - Unicode block shading option (░▒▓█)
- **ANSI Color Support** - 16-color, 256-color, and 24-bit truecolor modes
- **Configurable Quality** - Adjustable voxel density for performance/quality tradeoff
### Technical
- **Pure C11** - No external graphics dependencies
- **60 FPS** - Smooth real-time animation
- **Depth Buffering** - Proper 3D occlusion
- **Modular Architecture** - Clean separation of concerns
- **Production-Ready** - Strict compiler warnings, proper error handling
## Supported Characters
- Uppercase letters: `A-Z`
- Lowercase letters: `a-z` (rendered as uppercase)
- Digits: `0-9`
## Building
### Requirements
- Clang or GCC (C11 support)
- GNU Make
- POSIX-compliant system (Linux, macOS, WSL)
### Quick Start
```bash
# Build release version
make
# Run with default text
./bin/ascii3d
# Run with custom text
./bin/ascii3d HELLO
# Truecolor with all-axis rotation
./bin/ascii3d -a -c3 WORLD
```
### Build Targets
```bash
make release # Optimized build (default)
make debug # Debug build with sanitizers
make profile # Build with profiling support
make clean # Remove build artifacts
make install # Install to /usr/local/bin
make help # Show all targets
```
## Usage
```bash
ascii3d [OPTIONS] [TEXT]
ROTATION OPTIONS:
-s <speed> Rotation speed multiplier (default: 1.0)
-x Enable X-axis rotation
-y Enable Y-axis rotation (default)
-z Enable Z-axis rotation
-a Enable all axis rotations
RENDER MODE OPTIONS:
-m <mode> Render mode:
0 = Solid (filled)
1 = Wireframe (edges only)
2 = Points (sparse)
3 = Shaded (full Phong lighting) [default]
COLOR OPTIONS:
-c <mode> Color mode:
0 = Monochrome ASCII [default]
1 = 16-color ANSI
2 = 256-color ANSI
3 = Truecolor (24-bit RGB)
QUALITY OPTIONS:
-q <quality> Render quality (0.5 - 2.0, default: 1.0)
-A Enable anti-aliasing
-p <palette> Shading palette:
0 = Standard (10 levels)
1 = Extended (70 levels) [default]
2 = Block characters
3 = Minimal (6 levels)
OTHER OPTIONS:
-f Show FPS counter
-h Show help message
```
### Examples
```bash
# Simple Y-axis rotation (default)
./bin/ascii3d HELLO
# Tumbling rotation with truecolor
./bin/ascii3d -a -c3 WORLD
# Fast wireframe mode
./bin/ascii3d -m1 -s2 WIRE
# High quality with anti-aliasing
./bin/ascii3d -A -q1.5 -c2 HQ
# Block character style
./bin/ascii3d -p2 BLOCKS
# Show FPS counter
./bin/ascii3d -f -a TEST
# Slow, high quality render
./bin/ascii3d -s0.3 -q2 -A SMOOTH
```
Press `Ctrl+C` to exit.
## Project Structure
```bash
ASCIIRenderer/
├── include/
│ ├── config.h # Configuration and constants
│ ├── vec3.h # 3D vector mathematics
│ ├── font.h # Bitmap font interface
│ ├── lighting.h # Phong lighting system
│ ├── renderer.h # Core rendering engine
│ └── timing.h # High-precision timing
├── src/
│ ├── vec3.c # Vector operations
│ ├── font.c # 5x7 bitmap font data
│ ├── lighting.c # Lighting calculations
│ ├── renderer.c # Advanced rendering
│ ├── timing.c # Timing utilities
│ └── main.c # Entry point and CLI
├── Makefile # Build system (Clang)
└── README.md # This file
```
## How It Works
### Rendering Pipeline
1. **Font Lookup** - Characters are defined as 5x7 bitmap glyphs
2. **3D Extrusion** - Each pixel is extruded along Z-axis to create depth
3. **Surface Detection** - Only surface voxels are rendered (optimization)
4. **Normal Calculation** - Smooth normals computed from adjacent faces
5. **Rotation** - 3D rotation matrices transform points and normals
6. **Projection** - Perspective projection maps 3D to 2D screen space
7. **Lighting** - Phong model calculates illumination per voxel
8. **Depth Test** - Z-buffer ensures correct occlusion
9. **Shading** - Intensity mapped to ASCII character from palette
10. **Color** - Optional ANSI escape codes for colored output
### Lighting Model
The renderer uses a 3-point lighting setup:
- **Key Light** - Main light from upper-right-front (warm white)
- **Fill Light** - Softer light from left (cool blue tint)
- **Rim Light** - Back light for edge definition
Phong components:
- **Ambient** - Base illumination (15%)
- **Diffuse** - Lambertian reflection (70%)
- **Specular** - Blinn-Phong highlights (40%, shininess 32)
### ASCII Shading Palettes
**Standard (10 levels):**
```bash
.:-=+*#%@
```
**Extended (70 levels):**
```bash
.'`^",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$
```
**Block (5 levels):**
```bash
░▒▓█
```
## Configuration
Edit `include/config.h` to customize:
```c
/* Screen dimensions */
#define SCREEN_WIDTH 120
#define SCREEN_HEIGHT 45
/* Rendering quality */
#define EXTRUSION_DEPTH 4.0f // 3D depth of characters
#define CHAR_SCALE 2.0f // Character size
#define VOXEL_STEP 0.15f // Voxel density (smaller = higher quality)
/* Lighting */
#define AMBIENT_INTENSITY 0.15f
#define DIFFUSE_INTENSITY 0.70f
#define SPECULAR_INTENSITY 0.40f
#define SPECULAR_POWER 32.0f
/* Animation */
#define TARGET_FPS 60
```
## Performance
- **60 FPS** on modern hardware
- **Surface-only rendering** - Interior voxels skipped
- **Efficient depth buffer** - Single-pass rendering
- **Minimal memory** - Static buffers, no dynamic allocation
- **Quality scaling** - `-q` option for performance tuning

170
include/config.h Normal file
View File

@@ -0,0 +1,170 @@
/**
* @file config.h
* @brief Configuration constants for ASCII 3D Renderer
* @author ASCII3D Project
* @version 2.0.0
*/
#ifndef ASCII3D_CONFIG_H
#define ASCII3D_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
/*============================================================================
* SCREEN CONFIGURATION
*============================================================================*/
#define SCREEN_WIDTH 120
#define SCREEN_HEIGHT 45
/*============================================================================
* RENDERING QUALITY SETTINGS
*============================================================================*/
/* Depth buffer initialization value */
#define DEPTH_BUFFER_INIT 1e9f
/* Character extrusion depth (3D thickness) */
#define EXTRUSION_DEPTH 4.0f
/* Base character scale */
#define CHAR_SCALE 2.0f
/* Camera settings */
#define CAMERA_DISTANCE 30.0f
#define FIELD_OF_VIEW 50.0f
#define NEAR_PLANE 0.1f
#define FAR_PLANE 100.0f
/* Sub-pixel sampling for anti-aliasing (NxN samples per pixel) */
#define AA_SAMPLES 2
/* Voxel rendering step (smaller = higher quality, slower) */
#define VOXEL_STEP 0.15f
/* Surface smoothing iterations */
#define SMOOTH_PASSES 1
/*============================================================================
* ANIMATION PARAMETERS
*============================================================================*/
#define TARGET_FPS 60
#define FRAME_TIME_US (1000000 / TARGET_FPS)
/*============================================================================
* FONT CONFIGURATION
*============================================================================*/
/* Standard 5x7 font */
#define FONT_WIDTH 5
#define FONT_HEIGHT 7
#define FONT_CHAR_SPACING 2
/*============================================================================
* LIGHTING CONFIGURATION
*============================================================================*/
/* Ambient light intensity (0.0 - 1.0) */
#define AMBIENT_INTENSITY 0.15f
/* Diffuse light intensity */
#define DIFFUSE_INTENSITY 0.70f
/* Specular light intensity */
#define SPECULAR_INTENSITY 0.40f
/* Specular shininess exponent */
#define SPECULAR_POWER 32.0f
/* Number of light sources */
#define MAX_LIGHTS 3
/*============================================================================
* ASCII SHADING PALETTES
*============================================================================*/
/* Standard gradient (10 levels) */
#define SHADE_CHARS_STANDARD " .:-=+*#%@"
#define SHADE_COUNT_STANDARD 10
/* Extended gradient (16 levels) - more detail */
#define SHADE_CHARS_EXTENDED " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
#define SHADE_COUNT_EXTENDED 70
/* Block characters for solid look */
#define SHADE_CHARS_BLOCK " ░▒▓█"
#define SHADE_COUNT_BLOCK 5
/* Minimal gradient */
#define SHADE_CHARS_MINIMAL " .:+#@"
#define SHADE_COUNT_MINIMAL 6
/* Default palette */
#define SHADE_CHARS SHADE_CHARS_EXTENDED
#define SHADE_COUNT SHADE_COUNT_EXTENDED
/*============================================================================
* RENDER MODES
*============================================================================*/
typedef enum RenderMode {
RENDER_MODE_SOLID = 0, /* Filled solid rendering */
RENDER_MODE_WIREFRAME, /* Edge-only wireframe */
RENDER_MODE_POINTS, /* Point cloud */
RENDER_MODE_SHADED, /* Full Phong shading */
RENDER_MODE_COUNT
} RenderMode;
/*============================================================================
* COLOR MODES
*============================================================================*/
typedef enum ColorMode {
COLOR_MODE_MONO = 0, /* Monochrome ASCII */
COLOR_MODE_ANSI_16, /* 16-color ANSI */
COLOR_MODE_ANSI_256, /* 256-color ANSI */
COLOR_MODE_TRUECOLOR, /* 24-bit RGB */
COLOR_MODE_COUNT
} ColorMode;
/*============================================================================
* ANSI COLOR CODES
*============================================================================*/
#define ANSI_RESET "\033[0m"
#define ANSI_BOLD "\033[1m"
#define ANSI_DIM "\033[2m"
/* Foreground colors */
#define ANSI_FG_BLACK "\033[30m"
#define ANSI_FG_RED "\033[31m"
#define ANSI_FG_GREEN "\033[32m"
#define ANSI_FG_YELLOW "\033[33m"
#define ANSI_FG_BLUE "\033[34m"
#define ANSI_FG_MAGENTA "\033[35m"
#define ANSI_FG_CYAN "\033[36m"
#define ANSI_FG_WHITE "\033[37m"
/* Bright foreground colors */
#define ANSI_FG_BRIGHT_BLACK "\033[90m"
#define ANSI_FG_BRIGHT_RED "\033[91m"
#define ANSI_FG_BRIGHT_GREEN "\033[92m"
#define ANSI_FG_BRIGHT_YELLOW "\033[93m"
#define ANSI_FG_BRIGHT_BLUE "\033[94m"
#define ANSI_FG_BRIGHT_MAGENTA "\033[95m"
#define ANSI_FG_BRIGHT_CYAN "\033[96m"
#define ANSI_FG_BRIGHT_WHITE "\033[97m"
/* Background colors */
#define ANSI_BG_BLACK "\033[40m"
#define ANSI_BG_RED "\033[41m"
#define ANSI_BG_GREEN "\033[42m"
#define ANSI_BG_YELLOW "\033[43m"
#define ANSI_BG_BLUE "\033[44m"
#define ANSI_BG_MAGENTA "\033[45m"
#define ANSI_BG_CYAN "\033[46m"
#define ANSI_BG_WHITE "\033[47m"
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_CONFIG_H */

44
include/font.h Normal file
View File

@@ -0,0 +1,44 @@
/**
* @file font.h
* @brief Bitmap font data for ASCII 3D Renderer
* @author ASCII3D Project
* @version 1.0.0
*/
#ifndef ASCII3D_FONT_H
#define ASCII3D_FONT_H
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get the font glyph data for a character
* @param c Character to look up (A-Z, a-z, 0-9)
* @return Pointer to 7-byte glyph data, or NULL if not found
*/
const unsigned char *font_get_glyph(char c);
/**
* @brief Check if a pixel is set in a glyph
* @param glyph Pointer to glyph data
* @param x X coordinate (0-4)
* @param y Y coordinate (0-6)
* @return true if pixel is set, false otherwise
*/
bool font_pixel_set(const unsigned char *glyph, int x, int y);
/**
* @brief Check if character is renderable
* @param c Character to check
* @return true if character can be rendered
*/
bool font_is_renderable(char c);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_FONT_H */

170
include/lighting.h Normal file
View File

@@ -0,0 +1,170 @@
/**
* @file lighting.h
* @brief Advanced lighting system for ASCII 3D Renderer
* @author ASCII3D Project
* @version 2.0.0
*/
#ifndef ASCII3D_LIGHTING_H
#define ASCII3D_LIGHTING_H
#include "vec3.h"
#include "config.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Light types
*/
typedef enum LightType {
LIGHT_DIRECTIONAL = 0, /* Parallel rays (sun-like) */
LIGHT_POINT, /* Point source with falloff */
LIGHT_SPOT /* Cone-shaped spotlight */
} LightType;
/**
* @brief RGB Color structure
*/
typedef struct Color {
float r, g, b;
} Color;
/**
* @brief Light source structure
*/
typedef struct Light {
LightType type;
Vec3 position; /* Position for point/spot lights */
Vec3 direction; /* Direction for directional/spot lights */
Color color; /* Light color */
float intensity; /* Light intensity multiplier */
float falloff; /* Attenuation for point lights */
float spot_angle; /* Cone angle for spotlights (radians) */
bool enabled;
} Light;
/**
* @brief Material properties for surfaces
*/
typedef struct Material {
Color ambient; /* Ambient color */
Color diffuse; /* Diffuse color */
Color specular; /* Specular highlight color */
float shininess; /* Specular exponent */
float reflectivity; /* For future use */
} Material;
/**
* @brief Lighting system state
*/
typedef struct LightingSystem {
Light lights[MAX_LIGHTS];
int light_count;
Color ambient_color;
float ambient_intensity;
Vec3 camera_position;
} LightingSystem;
/**
* @brief Initialize the lighting system with default lights
* @param system Lighting system to initialize
*/
void lighting_init(LightingSystem *system);
/**
* @brief Add a light to the system
* @param system Lighting system
* @param light Light to add
* @return Index of added light, or -1 if full
*/
int lighting_add_light(LightingSystem *system, const Light *light);
/**
* @brief Create a directional light
* @param direction Light direction (will be normalized)
* @param color Light color
* @param intensity Light intensity
* @return Configured light structure
*/
Light lighting_create_directional(Vec3 direction, Color color, float intensity);
/**
* @brief Create a point light
* @param position Light position
* @param color Light color
* @param intensity Light intensity
* @param falloff Attenuation factor
* @return Configured light structure
*/
Light lighting_create_point(Vec3 position, Color color, float intensity, float falloff);
/**
* @brief Calculate lighting at a surface point (Phong model)
* @param system Lighting system
* @param point Surface point position
* @param normal Surface normal (must be normalized)
* @param material Surface material
* @return Final illumination value (0.0 - 1.0+)
*/
float lighting_calculate(const LightingSystem *system, Vec3 point,
Vec3 normal, const Material *material);
/**
* @brief Calculate full color lighting
* @param system Lighting system
* @param point Surface point position
* @param normal Surface normal
* @param material Surface material
* @return Final color
*/
Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
Vec3 normal, const Material *material);
/**
* @brief Create default material
* @return Default white material
*/
Material lighting_default_material(void);
/**
* @brief Create a color
* @param r Red (0-1)
* @param g Green (0-1)
* @param b Blue (0-1)
* @return Color structure
*/
Color color_create(float r, float g, float b);
/**
* @brief Multiply color by scalar
*/
Color color_scale(Color c, float s);
/**
* @brief Add two colors
*/
Color color_add(Color a, Color b);
/**
* @brief Multiply two colors component-wise
*/
Color color_multiply(Color a, Color b);
/**
* @brief Clamp color components to 0-1 range
*/
Color color_clamp(Color c);
/**
* @brief Convert color to grayscale intensity
*/
float color_to_intensity(Color c);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_LIGHTING_H */

179
include/renderer.h Normal file
View File

@@ -0,0 +1,179 @@
/**
* @file renderer.h
* @brief Advanced rendering engine for ASCII 3D Renderer
* @author ASCII3D Project
* @version 2.0.0
*/
#ifndef ASCII3D_RENDERER_H
#define ASCII3D_RENDERER_H
#include "config.h"
#include "lighting.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Rotation state for animation
*/
typedef struct RotationState {
float angle_x;
float angle_y;
float angle_z;
bool enable_x;
bool enable_y;
bool enable_z;
float speed;
} RotationState;
/**
* @brief Render settings structure
*/
typedef struct RenderSettings {
RenderMode mode; /* Rendering mode */
ColorMode color_mode; /* Color output mode */
bool anti_aliasing; /* Enable AA */
bool show_fps; /* Display FPS counter */
bool auto_rotate; /* Auto rotation enabled */
float quality; /* Quality multiplier (0.5 - 2.0) */
int palette_index; /* Shading palette selection */
Color base_color; /* Base color for colored modes */
Color highlight_color; /* Highlight/specular color */
} RenderSettings;
/**
* @brief Frame statistics
*/
typedef struct FrameStats {
double frame_time; /* Last frame time in ms */
double fps; /* Current FPS */
double avg_fps; /* Average FPS */
int frame_count; /* Total frames rendered */
int triangles; /* Triangles/voxels rendered */
} FrameStats;
/**
* @brief Initialize the renderer
* @return 0 on success, -1 on failure
*/
int renderer_init(void);
/**
* @brief Cleanup renderer resources
*/
void renderer_cleanup(void);
/**
* @brief Clear the screen and depth buffers
*/
void renderer_clear(void);
/**
* @brief Render a 3D text string
* @param text Text to render (A-Z, 0-9)
* @param rotation Current rotation state
*/
void renderer_draw_text(const char *text, const RotationState *rotation);
/**
* @brief Render with full settings control
* @param text Text to render
* @param rotation Rotation state
* @param settings Render settings
*/
void renderer_draw_text_ex(const char *text, const RotationState *rotation,
const RenderSettings *settings);
/**
* @brief Display the rendered frame to terminal
*/
void renderer_present(void);
/**
* @brief Present with color support
* @param settings Render settings for color mode
*/
void renderer_present_color(const RenderSettings *settings);
/**
* @brief Update rotation angles based on time delta
* @param rotation Rotation state to update
* @param delta_time Time since last update in seconds
*/
void renderer_update_rotation(RotationState *rotation, double delta_time);
/**
* @brief Create default rotation state
* @return Default rotation state with Y-axis rotation enabled
*/
RotationState renderer_default_rotation(void);
/**
* @brief Create default render settings
* @return Default settings
*/
RenderSettings renderer_default_settings(void);
/**
* @brief Set the render mode
* @param mode New render mode
*/
void renderer_set_mode(RenderMode mode);
/**
* @brief Set the color mode
* @param mode New color mode
*/
void renderer_set_color_mode(ColorMode mode);
/**
* @brief Get current frame statistics
* @return Frame stats structure
*/
FrameStats renderer_get_stats(void);
/**
* @brief Set the shading palette
* @param palette_index 0=standard, 1=extended, 2=block, 3=minimal
*/
void renderer_set_palette(int palette_index);
/**
* @brief Get the lighting system for modification
* @return Pointer to lighting system
*/
LightingSystem *renderer_get_lighting(void);
/**
* @brief Hide terminal cursor
*/
void renderer_hide_cursor(void);
/**
* @brief Show terminal cursor
*/
void renderer_show_cursor(void);
/**
* @brief Clear terminal screen
*/
void renderer_clear_terminal(void);
/**
* @brief Set terminal to alternate screen buffer
*/
void renderer_enter_alternate_screen(void);
/**
* @brief Restore terminal to main screen buffer
*/
void renderer_exit_alternate_screen(void);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_RENDERER_H */

38
include/timing.h Normal file
View File

@@ -0,0 +1,38 @@
/**
* @file timing.h
* @brief High-precision timing utilities
* @author ASCII3D Project
* @version 1.0.0
*/
#ifndef ASCII3D_TIMING_H
#define ASCII3D_TIMING_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get current time in seconds (monotonic clock)
* @return Time in seconds with nanosecond precision
*/
double timing_get_seconds(void);
/**
* @brief Sleep for specified microseconds
* @param microseconds Duration to sleep
*/
void timing_sleep_us(unsigned int microseconds);
/**
* @brief Frame rate limiter - sleeps to maintain target FPS
* @param frame_start_time Time when frame started (from timing_get_seconds)
* @param target_fps Target frames per second
*/
void timing_limit_fps(double frame_start_time, int target_fps);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_TIMING_H */

115
include/vec3.h Normal file
View File

@@ -0,0 +1,115 @@
/**
* @file vec3.h
* @brief 3D Vector mathematics for ASCII 3D Renderer
* @author ASCII3D Project
* @version 1.0.0
*/
#ifndef ASCII3D_VEC3_H
#define ASCII3D_VEC3_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 3D Vector structure
*/
typedef struct Vec3 {
float x;
float y;
float z;
} Vec3;
/**
* @brief Create a new Vec3
* @param x X component
* @param y Y component
* @param z Z component
* @return New Vec3 instance
*/
Vec3 vec3_create(float x, float y, float z);
/**
* @brief Add two vectors
* @param a First vector
* @param b Second vector
* @return Result of a + b
*/
Vec3 vec3_add(Vec3 a, Vec3 b);
/**
* @brief Subtract two vectors
* @param a First vector
* @param b Second vector
* @return Result of a - b
*/
Vec3 vec3_sub(Vec3 a, Vec3 b);
/**
* @brief Scale a vector by a scalar
* @param v Vector to scale
* @param s Scalar value
* @return Scaled vector
*/
Vec3 vec3_scale(Vec3 v, float s);
/**
* @brief Calculate dot product of two vectors
* @param a First vector
* @param b Second vector
* @return Dot product
*/
float vec3_dot(Vec3 a, Vec3 b);
/**
* @brief Calculate cross product of two vectors
* @param a First vector
* @param b Second vector
* @return Cross product
*/
Vec3 vec3_cross(Vec3 a, Vec3 b);
/**
* @brief Calculate length of a vector
* @param v Vector
* @return Length
*/
float vec3_length(Vec3 v);
/**
* @brief Normalize a vector
* @param v Vector to normalize
* @return Normalized vector
*/
Vec3 vec3_normalize(Vec3 v);
/**
* @brief Rotate vector around X axis
* @param v Vector to rotate
* @param angle Angle in radians
* @return Rotated vector
*/
Vec3 vec3_rotate_x(Vec3 v, float angle);
/**
* @brief Rotate vector around Y axis
* @param v Vector to rotate
* @param angle Angle in radians
* @return Rotated vector
*/
Vec3 vec3_rotate_y(Vec3 v, float angle);
/**
* @brief Rotate vector around Z axis
* @param v Vector to rotate
* @param angle Angle in radians
* @return Rotated vector
*/
Vec3 vec3_rotate_z(Vec3 v, float angle);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_VEC3_H */

105
src/font.c Normal file
View File

@@ -0,0 +1,105 @@
/**
* @file font.c
* @brief Bitmap font data implementation
* @author ASCII3D Project
* @version 1.0.0
*
* Contains 5x7 bitmap font data for A-Z and 0-9
*/
#include "font.h"
#include "config.h"
#include <stdbool.h>
#include <stddef.h>
/**
* @brief 5x7 bitmap font data
* Each character is represented by 7 bytes (rows)
* Each byte contains 5 bits (columns), MSB = leftmost pixel
*/
static const unsigned char g_font_data[][FONT_HEIGHT] = {
/* A */ {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
/* B */ {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E},
/* C */ {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E},
/* D */ {0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C},
/* E */ {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F},
/* F */ {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10},
/* G */ {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F},
/* H */ {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
/* I */ {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E},
/* J */ {0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C},
/* K */ {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11},
/* L */ {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F},
/* M */ {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11},
/* N */ {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11},
/* O */ {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
/* P */ {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10},
/* Q */ {0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D},
/* R */ {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11},
/* S */ {0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E},
/* T */ {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04},
/* U */ {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
/* V */ {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04},
/* W */ {0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A},
/* X */ {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11},
/* Y */ {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04},
/* Z */ {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F},
/* 0 */ {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E},
/* 1 */ {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E},
/* 2 */ {0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F},
/* 3 */ {0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E},
/* 4 */ {0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02},
/* 5 */ {0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E},
/* 6 */ {0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E},
/* 7 */ {0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08},
/* 8 */ {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E},
/* 9 */ {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C},
};
#define FONT_LETTER_COUNT 26
#define FONT_DIGIT_COUNT 10
/**
* @brief Get font index for a character
* @param c Character to look up
* @return Index into font data, or -1 if not found
*/
static int font_get_index(char c)
{
if (c >= 'A' && c <= 'Z') {
return c - 'A';
}
if (c >= 'a' && c <= 'z') {
return c - 'a';
}
if (c >= '0' && c <= '9') {
return FONT_LETTER_COUNT + (c - '0');
}
return -1;
}
const unsigned char *font_get_glyph(char c)
{
int index = font_get_index(c);
if (index < 0) {
return NULL;
}
return g_font_data[index];
}
bool font_pixel_set(const unsigned char *glyph, int x, int y)
{
if (glyph == NULL) {
return false;
}
if (x < 0 || x >= FONT_WIDTH || y < 0 || y >= FONT_HEIGHT) {
return false;
}
/* Bit 4 is leftmost (x=0), bit 0 is rightmost (x=4) */
return (glyph[y] & (1 << (FONT_WIDTH - 1 - x))) != 0;
}
bool font_is_renderable(char c)
{
return font_get_index(c) >= 0;
}

259
src/lighting.c Normal file
View File

@@ -0,0 +1,259 @@
/**
* @file lighting.c
* @brief Advanced lighting system implementation
* @author ASCII3D Project
* @version 2.0.0
*/
#include "lighting.h"
#include <math.h>
#include <string.h>
void lighting_init(LightingSystem *system)
{
if (system == NULL) return;
memset(system, 0, sizeof(LightingSystem));
system->ambient_color = color_create(1.0f, 1.0f, 1.0f);
system->ambient_intensity = AMBIENT_INTENSITY;
system->camera_position = vec3_create(0.0f, 0.0f, -CAMERA_DISTANCE);
system->light_count = 0;
/* Add default key light (main light from upper-right-front) */
Light key_light = lighting_create_directional(
vec3_create(0.5f, 0.8f, 1.0f),
color_create(1.0f, 0.98f, 0.95f),
DIFFUSE_INTENSITY
);
lighting_add_light(system, &key_light);
/* Add fill light (softer light from left) */
Light fill_light = lighting_create_directional(
vec3_create(-0.7f, 0.3f, 0.5f),
color_create(0.6f, 0.7f, 1.0f),
0.3f
);
lighting_add_light(system, &fill_light);
/* Add rim/back light for edge definition */
Light rim_light = lighting_create_directional(
vec3_create(0.0f, -0.5f, -1.0f),
color_create(1.0f, 0.9f, 0.8f),
0.2f
);
lighting_add_light(system, &rim_light);
}
int lighting_add_light(LightingSystem *system, const Light *light)
{
if (system == NULL || light == NULL) return -1;
if (system->light_count >= MAX_LIGHTS) return -1;
system->lights[system->light_count] = *light;
return system->light_count++;
}
Light lighting_create_directional(Vec3 direction, Color color, float intensity)
{
Light light;
memset(&light, 0, sizeof(Light));
light.type = LIGHT_DIRECTIONAL;
light.direction = vec3_normalize(direction);
light.color = color;
light.intensity = intensity;
light.enabled = true;
return light;
}
Light lighting_create_point(Vec3 position, Color color, float intensity, float falloff)
{
Light light;
memset(&light, 0, sizeof(Light));
light.type = LIGHT_POINT;
light.position = position;
light.color = color;
light.intensity = intensity;
light.falloff = falloff;
light.enabled = true;
return light;
}
/**
* @brief Calculate diffuse lighting contribution
*/
static float calculate_diffuse(Vec3 light_dir, Vec3 normal)
{
float ndotl = vec3_dot(normal, light_dir);
return fmaxf(0.0f, ndotl);
}
/**
* @brief Calculate specular lighting contribution (Blinn-Phong)
*/
static float calculate_specular(Vec3 light_dir, Vec3 normal, Vec3 view_dir, float shininess)
{
/* Blinn-Phong half-vector */
Vec3 half_vec = vec3_normalize(vec3_add(light_dir, view_dir));
float ndoth = vec3_dot(normal, half_vec);
if (ndoth <= 0.0f) return 0.0f;
return powf(ndoth, shininess);
}
float lighting_calculate(const LightingSystem *system, Vec3 point,
Vec3 normal, const Material *material)
{
if (system == NULL || material == NULL) return 0.5f;
/* Start with ambient */
float total = system->ambient_intensity;
/* View direction (from point to camera) */
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
/* Accumulate contribution from each light */
for (int i = 0; i < system->light_count; i++) {
const Light *light = &system->lights[i];
if (!light->enabled) continue;
Vec3 light_dir;
float attenuation = 1.0f;
if (light->type == LIGHT_DIRECTIONAL) {
/* Directional light - direction is constant */
light_dir = light->direction;
} else if (light->type == LIGHT_POINT) {
/* Point light - calculate direction and attenuation */
Vec3 to_light = vec3_sub(light->position, point);
float dist = vec3_length(to_light);
light_dir = vec3_scale(to_light, 1.0f / dist);
attenuation = 1.0f / (1.0f + light->falloff * dist * dist);
} else {
continue;
}
/* Diffuse */
float diffuse = calculate_diffuse(light_dir, normal);
/* Specular */
float specular = calculate_specular(light_dir, normal, view_dir, material->shininess);
/* Combine */
float contribution = (diffuse * DIFFUSE_INTENSITY +
specular * SPECULAR_INTENSITY) *
light->intensity * attenuation;
total += contribution;
}
/* Clamp to reasonable range */
return fminf(1.0f, fmaxf(0.0f, total));
}
Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
Vec3 normal, const Material *material)
{
if (system == NULL || material == NULL) {
return color_create(0.5f, 0.5f, 0.5f);
}
/* Start with ambient */
Color result = color_scale(
color_multiply(system->ambient_color, material->ambient),
system->ambient_intensity
);
/* View direction */
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
/* Accumulate from each light */
for (int i = 0; i < system->light_count; i++) {
const Light *light = &system->lights[i];
if (!light->enabled) continue;
Vec3 light_dir;
float attenuation = 1.0f;
if (light->type == LIGHT_DIRECTIONAL) {
light_dir = light->direction;
} else if (light->type == LIGHT_POINT) {
Vec3 to_light = vec3_sub(light->position, point);
float dist = vec3_length(to_light);
light_dir = vec3_scale(to_light, 1.0f / dist);
attenuation = 1.0f / (1.0f + light->falloff * dist * dist);
} else {
continue;
}
/* Diffuse */
float diff = calculate_diffuse(light_dir, normal);
Color diffuse = color_scale(
color_multiply(light->color, material->diffuse),
diff * light->intensity * attenuation
);
/* Specular */
float spec = calculate_specular(light_dir, normal, view_dir, material->shininess);
Color specular = color_scale(
color_multiply(light->color, material->specular),
spec * SPECULAR_INTENSITY * light->intensity * attenuation
);
result = color_add(result, color_add(diffuse, specular));
}
return color_clamp(result);
}
Material lighting_default_material(void)
{
Material mat;
mat.ambient = color_create(0.2f, 0.2f, 0.2f);
mat.diffuse = color_create(0.8f, 0.8f, 0.8f);
mat.specular = color_create(1.0f, 1.0f, 1.0f);
mat.shininess = SPECULAR_POWER;
mat.reflectivity = 0.0f;
return mat;
}
Color color_create(float r, float g, float b)
{
Color c = {r, g, b};
return c;
}
Color color_scale(Color c, float s)
{
return color_create(c.r * s, c.g * s, c.b * s);
}
Color color_add(Color a, Color b)
{
return color_create(a.r + b.r, a.g + b.g, a.b + b.b);
}
Color color_multiply(Color a, Color b)
{
return color_create(a.r * b.r, a.g * b.g, a.b * b.b);
}
Color color_clamp(Color c)
{
return color_create(
fminf(1.0f, fmaxf(0.0f, c.r)),
fminf(1.0f, fmaxf(0.0f, c.g)),
fminf(1.0f, fmaxf(0.0f, c.b))
);
}
float color_to_intensity(Color c)
{
/* Luminance formula (perceptual weights) */
return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
}

330
src/main.c Normal file
View File

@@ -0,0 +1,330 @@
/**
* @file main.c
* @brief ASCII 3D Renderer - Main entry point
* @author ASCII3D Project
* @version 2.0.0
*
* Advanced ASCII 3D text renderer with:
* - Phong lighting with multiple light sources
* - Multiple render modes (solid, wireframe, shaded)
* - ANSI color support (16, 256, truecolor)
* - Anti-aliasing
* - Multiple shading palettes
*/
#define _POSIX_C_SOURCE 200809L
#include "config.h"
#include "renderer.h"
#include "timing.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>
/* Global state */
static volatile sig_atomic_t g_running = 1;
static RenderSettings g_settings;
/**
* @brief Signal handler for graceful shutdown
*/
static void signal_handler(int sig)
{
(void)sig;
g_running = 0;
}
/**
* @brief Setup signal handlers
*/
static void setup_signals(void)
{
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
}
/**
* @brief Print usage information
*/
static void print_usage(const char *program_name)
{
printf("\n");
printf(" ╔═══════════════════════════════════════════════════════════╗\n");
printf(" ║ ASCII 3D RENDERER v2.0.0 ║\n");
printf(" ║ Advanced 3D Text Rendering with Phong Lighting ║\n");
printf(" ╚═══════════════════════════════════════════════════════════╝\n");
printf("\n");
printf("Usage: %s [OPTIONS] [TEXT]\n\n", program_name);
printf("ROTATION OPTIONS:\n");
printf(" -s <speed> Rotation speed multiplier (default: 1.0)\n");
printf(" -x Enable X-axis rotation\n");
printf(" -y Enable Y-axis rotation (default)\n");
printf(" -z Enable Z-axis rotation\n");
printf(" -a Enable all axis rotations\n");
printf("\n");
printf("RENDER MODE OPTIONS:\n");
printf(" -m <mode> Render mode:\n");
printf(" 0 = Solid (filled)\n");
printf(" 1 = Wireframe (edges only)\n");
printf(" 2 = Points (sparse)\n");
printf(" 3 = Shaded (full Phong lighting) [default]\n");
printf("\n");
printf("COLOR OPTIONS:\n");
printf(" -c <mode> Color mode:\n");
printf(" 0 = Monochrome ASCII [default]\n");
printf(" 1 = 16-color ANSI\n");
printf(" 2 = 256-color ANSI\n");
printf(" 3 = Truecolor (24-bit RGB)\n");
printf("\n");
printf("QUALITY OPTIONS:\n");
printf(" -q <quality> Render quality (0.5 - 2.0, default: 1.0)\n");
printf(" -A Enable anti-aliasing\n");
printf(" -p <palette> Shading palette:\n");
printf(" 0 = Standard (10 levels)\n");
printf(" 1 = Extended (70 levels) [default]\n");
printf(" 2 = Block characters\n");
printf(" 3 = Minimal (6 levels)\n");
printf("\n");
printf("OTHER OPTIONS:\n");
printf(" -f Show FPS counter\n");
printf(" -h Show this help message\n");
printf("\n");
printf("EXAMPLES:\n");
printf(" %s HELLO Simple Y-axis rotation\n", program_name);
printf(" %s -a -c3 WORLD Truecolor tumbling\n", program_name);
printf(" %s -m1 -s2 WIRE Fast wireframe\n", program_name);
printf(" %s -A -q1.5 -c2 HQ High quality with AA\n", program_name);
printf(" %s -p2 BLOCKS Block character style\n", program_name);
printf("\n");
printf("INTERACTIVE KEYS (during rendering):\n");
printf(" Ctrl+C Exit\n");
printf("\n");
printf("Supported characters: A-Z, a-z, 0-9\n");
printf("\n");
}
/**
* @brief Parse command line arguments
*/
static bool parse_arguments(int argc, char *argv[],
RotationState *rotation, const char **text)
{
*rotation = renderer_default_rotation();
g_settings = renderer_default_settings();
*text = "3D";
bool explicit_rotation = false;
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
const char *opt = &argv[i][1];
while (*opt) {
switch (*opt) {
case 's':
if (i + 1 < argc) {
rotation->speed = (float)atof(argv[++i]);
if (rotation->speed <= 0.0f) rotation->speed = 1.0f;
}
goto next_arg;
case 'x':
rotation->enable_x = true;
explicit_rotation = true;
break;
case 'y':
rotation->enable_y = true;
explicit_rotation = true;
break;
case 'z':
rotation->enable_z = true;
explicit_rotation = true;
break;
case 'a':
rotation->enable_x = true;
rotation->enable_y = true;
rotation->enable_z = true;
explicit_rotation = true;
break;
case 'm':
if (i + 1 < argc) {
int mode = atoi(argv[++i]);
if (mode >= 0 && mode < RENDER_MODE_COUNT) {
g_settings.mode = (RenderMode)mode;
}
}
goto next_arg;
case 'c':
if (i + 1 < argc) {
int cmode = atoi(argv[++i]);
if (cmode >= 0 && cmode < COLOR_MODE_COUNT) {
g_settings.color_mode = (ColorMode)cmode;
}
}
goto next_arg;
case 'q':
if (i + 1 < argc) {
g_settings.quality = (float)atof(argv[++i]);
if (g_settings.quality < 0.5f) g_settings.quality = 0.5f;
if (g_settings.quality > 2.0f) g_settings.quality = 2.0f;
}
goto next_arg;
case 'A':
g_settings.anti_aliasing = true;
break;
case 'p':
if (i + 1 < argc) {
int pal = atoi(argv[++i]);
if (pal >= 0 && pal < 4) {
g_settings.palette_index = pal;
}
}
goto next_arg;
case 'f':
g_settings.show_fps = true;
break;
case 'h':
print_usage(argv[0]);
return false;
default:
fprintf(stderr, "Unknown option: -%c\n", *opt);
print_usage(argv[0]);
return false;
}
opt++;
}
} else {
*text = argv[i];
}
next_arg:;
}
/* Handle rotation defaults */
if (explicit_rotation && !rotation->enable_y) {
/* User explicitly chose axes */
} else if (!explicit_rotation) {
rotation->enable_y = true;
}
return true;
}
/**
* @brief Main render loop with advanced features
*/
static void render_loop(const char *text, RotationState *rotation)
{
double last_time = timing_get_seconds();
double fps_update_time = last_time;
int frame_count = 0;
double current_fps = 0.0;
/* Apply settings */
renderer_set_mode(g_settings.mode);
renderer_set_color_mode(g_settings.color_mode);
renderer_set_palette(g_settings.palette_index);
while (g_running) {
double current_time = timing_get_seconds();
double delta_time = current_time - last_time;
last_time = current_time;
/* Update FPS counter */
frame_count++;
if (current_time - fps_update_time >= 1.0) {
current_fps = (double)frame_count / (current_time - fps_update_time);
frame_count = 0;
fps_update_time = current_time;
}
/* Update rotation */
renderer_update_rotation(rotation, delta_time);
/* Render frame */
renderer_clear();
renderer_draw_text_ex(text, rotation, &g_settings);
/* Present with appropriate color mode */
if (g_settings.color_mode != COLOR_MODE_MONO) {
renderer_present_color(&g_settings);
} else {
renderer_present();
}
/* Show FPS if enabled */
if (g_settings.show_fps) {
printf("\033[1;1H\033[7m FPS: %.1f | Voxels: %d \033[0m",
current_fps, renderer_get_stats().triangles);
fflush(stdout);
}
/* Limit frame rate */
timing_limit_fps(current_time, TARGET_FPS);
}
}
/**
* @brief Program entry point
*/
int main(int argc, char *argv[])
{
RotationState rotation;
const char *text;
/* Parse command line */
if (!parse_arguments(argc, argv, &rotation, &text)) {
return EXIT_SUCCESS;
}
/* Setup signal handlers */
setup_signals();
/* Initialize renderer */
if (renderer_init() != 0) {
fprintf(stderr, "Failed to initialize renderer\n");
return EXIT_FAILURE;
}
/* Enter alternate screen buffer for clean exit */
renderer_enter_alternate_screen();
renderer_clear_terminal();
renderer_hide_cursor();
/* Run main loop */
render_loop(text, &rotation);
/* Cleanup */
renderer_show_cursor();
renderer_exit_alternate_screen();
renderer_cleanup();
printf("Goodbye!\n");
return EXIT_SUCCESS;
}

677
src/renderer.c Normal file
View File

@@ -0,0 +1,677 @@
/**
* @file renderer.c
* @brief Advanced rendering engine implementation
* @author ASCII3D Project
* @version 2.0.0
*
* Features:
* - Phong lighting model with multiple light sources
* - Sub-pixel anti-aliasing
* - Multiple render modes (solid, wireframe, points)
* - ANSI color support (16, 256, truecolor)
* - High-quality ASCII shading with extended palettes
*/
#include "renderer.h"
#include "vec3.h"
#include "font.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/*============================================================================
* GLOBAL STATE
*============================================================================*/
/* Screen buffer - character display */
static char g_screen[SCREEN_HEIGHT][SCREEN_WIDTH + 1];
/* Depth buffer for z-testing */
static float g_zbuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
/* Normal buffer for post-processing effects */
static Vec3 g_normal_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
/* Color buffer for colored output */
static Color g_color_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
/* Intensity accumulator for anti-aliasing */
static float g_intensity_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
static int g_sample_count[SCREEN_HEIGHT][SCREEN_WIDTH];
/* Lighting system */
static LightingSystem g_lighting;
/* Current render settings */
static RenderSettings g_settings;
/* Current render mode */
static RenderMode g_render_mode = RENDER_MODE_SHADED;
/* Current color mode */
static ColorMode g_color_mode = COLOR_MODE_MONO;
/* Frame statistics */
static FrameStats g_stats;
/* Shading palettes */
static const char *g_palettes[] = {
SHADE_CHARS_STANDARD,
SHADE_CHARS_EXTENDED,
SHADE_CHARS_BLOCK,
SHADE_CHARS_MINIMAL
};
static const int g_palette_sizes[] = {
SHADE_COUNT_STANDARD,
SHADE_COUNT_EXTENDED,
SHADE_COUNT_BLOCK,
SHADE_COUNT_MINIMAL
};
static int g_current_palette = 1; /* Extended by default */
/* Default material */
static Material g_material;
/* Voxel counter for stats */
static int g_voxel_count;
/*============================================================================
* PROJECTION
*============================================================================*/
/**
* @brief Project a 3D point to 2D screen coordinates with perspective
*/
static bool project_point(Vec3 point, float *out_x, float *out_y, float *out_z)
{
/* Character aspect ratio compensation */
float aspect = (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT / 2.2f;
/* Camera space transformation */
float z = point.z + CAMERA_DISTANCE;
/* Near plane clipping */
if (z < NEAR_PLANE) {
return false;
}
/* Far plane clipping */
if (z > FAR_PLANE) {
return false;
}
/* Perspective division */
float scale = FIELD_OF_VIEW / z;
*out_x = point.x * scale * aspect + (float)SCREEN_WIDTH / 2.0f;
*out_y = -point.y * scale + (float)SCREEN_HEIGHT / 2.0f;
*out_z = z;
return true;
}
/*============================================================================
* PIXEL PLOTTING
*============================================================================*/
/**
* @brief Plot a point with full lighting calculation
*/
static void plot_point_lit(float sx, float sy, float sz, Vec3 point, Vec3 normal)
{
int ix = (int)sx;
int iy = (int)sy;
/* Bounds check */
if (ix < 0 || ix >= SCREEN_WIDTH || iy < 0 || iy >= SCREEN_HEIGHT) {
return;
}
/* Depth test */
if (sz >= g_zbuffer[iy][ix]) {
return;
}
g_zbuffer[iy][ix] = sz;
g_normal_buffer[iy][ix] = normal;
g_voxel_count++;
/* Calculate lighting */
float intensity;
Color color;
if (g_render_mode == RENDER_MODE_SHADED) {
/* Full Phong lighting */
color = lighting_calculate_color(&g_lighting, point, normal, &g_material);
intensity = color_to_intensity(color);
} else if (g_render_mode == RENDER_MODE_SOLID) {
/* Simple normal-based shading */
intensity = (normal.z + 1.0f) * 0.5f;
intensity = fmaxf(0.0f, fminf(1.0f, intensity));
color = color_scale(g_settings.base_color, intensity);
} else {
/* Wireframe/points - full brightness */
intensity = 1.0f;
color = g_settings.base_color;
}
g_color_buffer[iy][ix] = color;
g_intensity_buffer[iy][ix] = intensity;
g_sample_count[iy][ix] = 1;
/* Convert intensity to ASCII character */
const char *palette = g_palettes[g_current_palette];
int palette_size = g_palette_sizes[g_current_palette];
int shade_idx = (int)(intensity * (float)(palette_size - 1) + 0.5f);
shade_idx = shade_idx < 0 ? 0 : (shade_idx >= palette_size ? palette_size - 1 : shade_idx);
g_screen[iy][ix] = palette[shade_idx];
}
/**
* @brief Plot with anti-aliasing (accumulate samples)
*/
static void plot_point_aa(float sx, float sy, float sz, Vec3 point, Vec3 normal)
{
/* Sub-pixel offset for AA */
int ix = (int)sx;
int iy = (int)sy;
if (ix < 0 || ix >= SCREEN_WIDTH || iy < 0 || iy >= SCREEN_HEIGHT) {
return;
}
/* For AA, we accumulate and average */
if (sz < g_zbuffer[iy][ix]) {
g_zbuffer[iy][ix] = sz;
g_normal_buffer[iy][ix] = normal;
Color color = lighting_calculate_color(&g_lighting, point, normal, &g_material);
float intensity = color_to_intensity(color);
g_intensity_buffer[iy][ix] += intensity;
g_color_buffer[iy][ix] = color_add(g_color_buffer[iy][ix], color);
g_sample_count[iy][ix]++;
g_voxel_count++;
}
}
/*============================================================================
* EDGE DETECTION FOR SURFACE RENDERING
*============================================================================*/
static bool neighbor_empty(const unsigned char *glyph, int x, int y, int dx, int dy)
{
int nx = x + dx;
int ny = y + dy;
if (nx < 0 || nx >= FONT_WIDTH || ny < 0 || ny >= FONT_HEIGHT) {
return true;
}
return !font_pixel_set(glyph, nx, ny);
}
/**
* @brief Calculate smooth normal by averaging adjacent face normals
*/
static Vec3 calculate_smooth_normal(const unsigned char *glyph, int gx, int gy,
float gz, bool is_front, bool is_back)
{
Vec3 normal = vec3_create(0.0f, 0.0f, 0.0f);
/* Front/back face contribution */
if (is_front) {
normal = vec3_add(normal, vec3_create(0.0f, 0.0f, 1.0f));
}
if (is_back) {
normal = vec3_add(normal, vec3_create(0.0f, 0.0f, -1.0f));
}
/* Side face contributions */
if (neighbor_empty(glyph, gx, gy, -1, 0)) {
normal = vec3_add(normal, vec3_create(-1.0f, 0.0f, 0.0f));
}
if (neighbor_empty(glyph, gx, gy, 1, 0)) {
normal = vec3_add(normal, vec3_create(1.0f, 0.0f, 0.0f));
}
if (neighbor_empty(glyph, gx, gy, 0, -1)) {
normal = vec3_add(normal, vec3_create(0.0f, 1.0f, 0.0f));
}
if (neighbor_empty(glyph, gx, gy, 0, 1)) {
normal = vec3_add(normal, vec3_create(0.0f, -1.0f, 0.0f));
}
/* Diagonal contributions for smoother edges */
if (neighbor_empty(glyph, gx, gy, -1, -1)) {
normal = vec3_add(normal, vec3_scale(vec3_create(-0.707f, 0.707f, 0.0f), 0.5f));
}
if (neighbor_empty(glyph, gx, gy, 1, -1)) {
normal = vec3_add(normal, vec3_scale(vec3_create(0.707f, 0.707f, 0.0f), 0.5f));
}
if (neighbor_empty(glyph, gx, gy, -1, 1)) {
normal = vec3_add(normal, vec3_scale(vec3_create(-0.707f, -0.707f, 0.0f), 0.5f));
}
if (neighbor_empty(glyph, gx, gy, 1, 1)) {
normal = vec3_add(normal, vec3_scale(vec3_create(0.707f, -0.707f, 0.0f), 0.5f));
}
(void)gz; /* Suppress unused warning */
return vec3_normalize(normal);
}
/*============================================================================
* CHARACTER RENDERING
*============================================================================*/
/**
* @brief Render a single 3D character with advanced lighting
*/
static void render_char_advanced(char c, float offset_x, const RotationState *rotation,
const RenderSettings *settings)
{
const unsigned char *glyph = font_get_glyph(c);
if (glyph == NULL) {
return;
}
/* Center offsets */
float cx = (float)FONT_WIDTH / 2.0f;
float cy = (float)FONT_HEIGHT / 2.0f;
float cz = EXTRUSION_DEPTH / 2.0f;
/* Quality-adjusted voxel step */
float step = VOXEL_STEP / settings->quality;
/* Iterate through each voxel */
for (int gy = 0; gy < FONT_HEIGHT; gy++) {
for (int gx = 0; gx < FONT_WIDTH; gx++) {
if (!font_pixel_set(glyph, gx, gy)) {
continue;
}
/* Render along Z depth (extrusion) */
for (float gz = 0.0f; gz <= EXTRUSION_DEPTH; gz += step) {
/* Determine if this is a surface voxel */
bool is_front = (gz < step);
bool is_back = (gz > EXTRUSION_DEPTH - step);
bool left_empty = neighbor_empty(glyph, gx, gy, -1, 0);
bool right_empty = neighbor_empty(glyph, gx, gy, 1, 0);
bool top_empty = neighbor_empty(glyph, gx, gy, 0, -1);
bool bottom_empty = neighbor_empty(glyph, gx, gy, 0, 1);
bool is_surface = is_front || is_back ||
left_empty || right_empty ||
top_empty || bottom_empty;
/* Wireframe mode: only render edges */
if (g_render_mode == RENDER_MODE_WIREFRAME) {
int edge_count = (is_front ? 1 : 0) + (is_back ? 1 : 0) +
(left_empty ? 1 : 0) + (right_empty ? 1 : 0) +
(top_empty ? 1 : 0) + (bottom_empty ? 1 : 0);
if (edge_count < 2) continue;
}
/* Points mode: sparse rendering */
if (g_render_mode == RENDER_MODE_POINTS) {
if (!is_front && !is_back) continue;
}
if (!is_surface && g_render_mode != RENDER_MODE_POINTS) {
continue;
}
/* Create 3D point centered at origin */
Vec3 point = vec3_create(
((float)gx - cx) * CHAR_SCALE + offset_x,
(cy - (float)gy) * CHAR_SCALE,
(gz - cz) * CHAR_SCALE
);
/* Calculate smooth normal for better shading */
Vec3 normal = calculate_smooth_normal(glyph, gx, gy, gz, is_front, is_back);
/* Apply rotations */
if (rotation->enable_x) {
point = vec3_rotate_x(point, rotation->angle_x);
normal = vec3_rotate_x(normal, rotation->angle_x);
}
if (rotation->enable_y) {
point = vec3_rotate_y(point, rotation->angle_y);
normal = vec3_rotate_y(normal, rotation->angle_y);
}
if (rotation->enable_z) {
point = vec3_rotate_z(point, rotation->angle_z);
normal = vec3_rotate_z(normal, rotation->angle_z);
}
/* Project and render */
float sx, sy, sz;
if (project_point(point, &sx, &sy, &sz)) {
if (settings->anti_aliasing) {
/* Multi-sample AA */
for (int aay = 0; aay < AA_SAMPLES; aay++) {
for (int aax = 0; aax < AA_SAMPLES; aax++) {
float ox = (float)aax / (float)AA_SAMPLES - 0.5f;
float oy = (float)aay / (float)AA_SAMPLES - 0.5f;
plot_point_aa(sx + ox * 0.5f, sy + oy * 0.5f, sz, point, normal);
}
}
} else {
plot_point_lit(sx, sy, sz, point, normal);
}
}
}
}
}
}
/*============================================================================
* PUBLIC API
*============================================================================*/
int renderer_init(void)
{
/* Initialize lighting system */
lighting_init(&g_lighting);
/* Initialize default material */
g_material = lighting_default_material();
g_material.diffuse = color_create(0.9f, 0.9f, 0.95f);
g_material.specular = color_create(1.0f, 1.0f, 1.0f);
g_material.shininess = 64.0f;
/* Initialize settings */
g_settings = renderer_default_settings();
/* Clear buffers */
renderer_clear();
/* Initialize stats */
memset(&g_stats, 0, sizeof(g_stats));
return 0;
}
void renderer_cleanup(void)
{
/* Nothing to clean up */
}
void renderer_clear(void)
{
for (int y = 0; y < SCREEN_HEIGHT; y++) {
memset(g_screen[y], ' ', SCREEN_WIDTH);
g_screen[y][SCREEN_WIDTH] = '\0';
for (int x = 0; x < SCREEN_WIDTH; x++) {
g_zbuffer[y][x] = DEPTH_BUFFER_INIT;
g_normal_buffer[y][x] = vec3_create(0.0f, 0.0f, 0.0f);
g_color_buffer[y][x] = color_create(0.0f, 0.0f, 0.0f);
g_intensity_buffer[y][x] = 0.0f;
g_sample_count[y][x] = 0;
}
}
g_voxel_count = 0;
}
void renderer_draw_text(const char *text, const RotationState *rotation)
{
renderer_draw_text_ex(text, rotation, &g_settings);
}
void renderer_draw_text_ex(const char *text, const RotationState *rotation,
const RenderSettings *settings)
{
if (text == NULL || rotation == NULL || settings == NULL) {
return;
}
size_t len = strlen(text);
if (len == 0) {
return;
}
/* Calculate total width for centering */
float char_width = (float)(FONT_WIDTH + FONT_CHAR_SPACING) * CHAR_SCALE;
float total_width = (float)len * char_width;
float start_x = -total_width / 2.0f + char_width / 2.0f;
/* Render each character */
for (size_t i = 0; i < len; i++) {
if (text[i] == ' ') {
continue;
}
float offset_x = start_x + (float)i * char_width;
render_char_advanced(text[i], offset_x, rotation, settings);
}
/* Finalize AA samples */
if (settings->anti_aliasing) {
const char *palette = g_palettes[g_current_palette];
int palette_size = g_palette_sizes[g_current_palette];
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
if (g_sample_count[y][x] > 0) {
float avg_intensity = g_intensity_buffer[y][x] / (float)g_sample_count[y][x];
int shade_idx = (int)(avg_intensity * (float)(palette_size - 1) + 0.5f);
shade_idx = shade_idx < 0 ? 0 : (shade_idx >= palette_size ? palette_size - 1 : shade_idx);
g_screen[y][x] = palette[shade_idx];
}
}
}
}
g_stats.triangles = g_voxel_count;
}
void renderer_present(void)
{
/* Move cursor to home position */
printf("\033[H");
/* Output screen buffer */
for (int y = 0; y < SCREEN_HEIGHT; y++) {
printf("%s\n", g_screen[y]);
}
fflush(stdout);
}
void renderer_present_color(const RenderSettings *settings)
{
if (settings == NULL || settings->color_mode == COLOR_MODE_MONO) {
renderer_present();
return;
}
printf("\033[H");
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
char c = g_screen[y][x];
if (c == ' ') {
putchar(' ');
continue;
}
Color col = g_color_buffer[y][x];
if (settings->color_mode == COLOR_MODE_TRUECOLOR) {
/* 24-bit truecolor */
int r = (int)(col.r * 255.0f);
int g = (int)(col.g * 255.0f);
int b = (int)(col.b * 255.0f);
r = r < 0 ? 0 : (r > 255 ? 255 : r);
g = g < 0 ? 0 : (g > 255 ? 255 : g);
b = b < 0 ? 0 : (b > 255 ? 255 : b);
printf("\033[38;2;%d;%d;%dm%c", r, g, b, c);
} else if (settings->color_mode == COLOR_MODE_ANSI_256) {
/* 256-color mode - approximate */
int r = (int)(col.r * 5.0f);
int g_val = (int)(col.g * 5.0f);
int b = (int)(col.b * 5.0f);
r = r < 0 ? 0 : (r > 5 ? 5 : r);
g_val = g_val < 0 ? 0 : (g_val > 5 ? 5 : g_val);
b = b < 0 ? 0 : (b > 5 ? 5 : b);
int color_code = 16 + 36 * r + 6 * g_val + b;
printf("\033[38;5;%dm%c", color_code, c);
} else {
/* 16-color ANSI */
float intensity = color_to_intensity(col);
int bright = intensity > 0.5f ? 1 : 0;
int base = 30;
/* Simple color mapping */
if (col.r > col.g && col.r > col.b) {
base = 31; /* Red */
} else if (col.g > col.r && col.g > col.b) {
base = 32; /* Green */
} else if (col.b > col.r && col.b > col.g) {
base = 34; /* Blue */
} else if (col.r > 0.5f && col.g > 0.5f) {
base = 33; /* Yellow */
} else if (col.r > 0.5f && col.b > 0.5f) {
base = 35; /* Magenta */
} else if (col.g > 0.5f && col.b > 0.5f) {
base = 36; /* Cyan */
} else {
base = 37; /* White */
}
if (bright) {
printf("\033[1;%dm%c", base, c);
} else {
printf("\033[%dm%c", base, c);
}
}
}
printf(ANSI_RESET "\n");
}
fflush(stdout);
}
void renderer_update_rotation(RotationState *rotation, double delta_time)
{
if (rotation == NULL) {
return;
}
float dt = (float)delta_time * rotation->speed;
if (rotation->enable_x) {
rotation->angle_x += dt * 0.7f;
}
if (rotation->enable_y) {
rotation->angle_y += dt * 1.0f;
}
if (rotation->enable_z) {
rotation->angle_z += dt * 0.5f;
}
/* Keep angles in reasonable range */
const float TWO_PI = 6.283185307f;
if (rotation->angle_x > TWO_PI) rotation->angle_x -= TWO_PI;
if (rotation->angle_y > TWO_PI) rotation->angle_y -= TWO_PI;
if (rotation->angle_z > TWO_PI) rotation->angle_z -= TWO_PI;
}
RotationState renderer_default_rotation(void)
{
RotationState state = {
.angle_x = 0.0f,
.angle_y = 0.0f,
.angle_z = 0.0f,
.enable_x = false,
.enable_y = true,
.enable_z = false,
.speed = 1.0f
};
return state;
}
RenderSettings renderer_default_settings(void)
{
RenderSettings settings = {
.mode = RENDER_MODE_SHADED,
.color_mode = COLOR_MODE_MONO,
.anti_aliasing = false,
.show_fps = false,
.auto_rotate = true,
.quality = 1.0f,
.palette_index = 1,
.base_color = color_create(1.0f, 1.0f, 1.0f),
.highlight_color = color_create(1.0f, 1.0f, 1.0f)
};
return settings;
}
void renderer_set_mode(RenderMode mode)
{
if (mode < RENDER_MODE_COUNT) {
g_render_mode = mode;
g_settings.mode = mode;
}
}
void renderer_set_color_mode(ColorMode mode)
{
if (mode < COLOR_MODE_COUNT) {
g_color_mode = mode;
g_settings.color_mode = mode;
}
}
FrameStats renderer_get_stats(void)
{
return g_stats;
}
void renderer_set_palette(int palette_index)
{
if (palette_index >= 0 && palette_index < 4) {
g_current_palette = palette_index;
g_settings.palette_index = palette_index;
}
}
LightingSystem *renderer_get_lighting(void)
{
return &g_lighting;
}
void renderer_hide_cursor(void)
{
printf("\033[?25l");
fflush(stdout);
}
void renderer_show_cursor(void)
{
printf("\033[?25h");
fflush(stdout);
}
void renderer_clear_terminal(void)
{
printf("\033[2J");
fflush(stdout);
}
void renderer_enter_alternate_screen(void)
{
printf("\033[?1049h");
fflush(stdout);
}
void renderer_exit_alternate_screen(void)
{
printf("\033[?1049l");
fflush(stdout);
}

43
src/timing.c Normal file
View File

@@ -0,0 +1,43 @@
/**
* @file timing.c
* @brief High-precision timing utilities implementation
* @author ASCII3D Project
* @version 1.0.0
*/
#define _POSIX_C_SOURCE 200809L
#include "timing.h"
#include <time.h>
double timing_get_seconds(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9;
}
void timing_sleep_us(unsigned int microseconds)
{
struct timespec req;
req.tv_sec = microseconds / 1000000;
req.tv_nsec = (microseconds % 1000000) * 1000L;
nanosleep(&req, NULL);
}
void timing_limit_fps(double frame_start_time, int target_fps)
{
if (target_fps <= 0) {
return;
}
double target_frame_time = 1.0 / (double)target_fps;
double elapsed = timing_get_seconds() - frame_start_time;
double sleep_time = target_frame_time - elapsed;
if (sleep_time > 0.0) {
unsigned int sleep_us = (unsigned int)(sleep_time * 1.0e6);
timing_sleep_us(sleep_us);
}
}

91
src/vec3.c Normal file
View File

@@ -0,0 +1,91 @@
/**
* @file vec3.c
* @brief 3D Vector mathematics implementation
* @author ASCII3D Project
* @version 1.0.0
*/
#include "vec3.h"
#include <math.h>
Vec3 vec3_create(float x, float y, float z)
{
Vec3 v = {x, y, z};
return v;
}
Vec3 vec3_add(Vec3 a, Vec3 b)
{
return vec3_create(a.x + b.x, a.y + b.y, a.z + b.z);
}
Vec3 vec3_sub(Vec3 a, Vec3 b)
{
return vec3_create(a.x - b.x, a.y - b.y, a.z - b.z);
}
Vec3 vec3_scale(Vec3 v, float s)
{
return vec3_create(v.x * s, v.y * s, v.z * s);
}
float vec3_dot(Vec3 a, Vec3 b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
Vec3 vec3_cross(Vec3 a, Vec3 b)
{
return vec3_create(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
float vec3_length(Vec3 v)
{
return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
}
Vec3 vec3_normalize(Vec3 v)
{
float len = vec3_length(v);
if (len > 0.0001f) {
return vec3_scale(v, 1.0f / len);
}
return vec3_create(0.0f, 0.0f, 0.0f);
}
Vec3 vec3_rotate_x(Vec3 v, float angle)
{
float c = cosf(angle);
float s = sinf(angle);
return vec3_create(
v.x,
v.y * c - v.z * s,
v.y * s + v.z * c
);
}
Vec3 vec3_rotate_y(Vec3 v, float angle)
{
float c = cosf(angle);
float s = sinf(angle);
return vec3_create(
v.x * c + v.z * s,
v.y,
-v.x * s + v.z * c
);
}
Vec3 vec3_rotate_z(Vec3 v, float angle)
{
float c = cosf(angle);
float s = sinf(angle);
return vec3_create(
v.x * c - v.y * s,
v.x * s + v.y * c,
v.z
);
}