commit 42529be0f8189b2cba23d28bbef80fd19344a3c7 Author: bereck-work Date: Wed Dec 17 21:31:12 2025 +0000 (Feat): Initial Commit. diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..90029e6 --- /dev/null +++ b/.clangd @@ -0,0 +1,5 @@ +CompileFlags: + Add: + - -xc + - -std=c11 + - -I/home/zanewalker/Development/Programming/C_Projects/ASCIIRenderer/include diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfa1419 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.qodo +.vscode/ +bin/ +obj/ +*.o +*.out +*.log \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..467169b --- /dev/null +++ b/Makefile @@ -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" diff --git a/README.md b/README.md new file mode 100644 index 0000000..1683699 --- /dev/null +++ b/README.md @@ -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 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 Render mode: + 0 = Solid (filled) + 1 = Wireframe (edges only) + 2 = Points (sparse) + 3 = Shaded (full Phong lighting) [default] + +COLOR OPTIONS: + -c Color mode: + 0 = Monochrome ASCII [default] + 1 = 16-color ANSI + 2 = 256-color ANSI + 3 = Truecolor (24-bit RGB) + +QUALITY OPTIONS: + -q Render quality (0.5 - 2.0, default: 1.0) + -A Enable anti-aliasing + -p 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 diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..1204072 --- /dev/null +++ b/include/config.h @@ -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 */ diff --git a/include/font.h b/include/font.h new file mode 100644 index 0000000..45fc11a --- /dev/null +++ b/include/font.h @@ -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 + +#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 */ diff --git a/include/lighting.h b/include/lighting.h new file mode 100644 index 0000000..8fa59d1 --- /dev/null +++ b/include/lighting.h @@ -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 + +#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 */ diff --git a/include/renderer.h b/include/renderer.h new file mode 100644 index 0000000..d57ffd9 --- /dev/null +++ b/include/renderer.h @@ -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 + +#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 */ diff --git a/include/timing.h b/include/timing.h new file mode 100644 index 0000000..ae0e4b7 --- /dev/null +++ b/include/timing.h @@ -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 */ diff --git a/include/vec3.h b/include/vec3.h new file mode 100644 index 0000000..664a08b --- /dev/null +++ b/include/vec3.h @@ -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 */ diff --git a/src/font.c b/src/font.c new file mode 100644 index 0000000..e84a525 --- /dev/null +++ b/src/font.c @@ -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 +#include + +/** + * @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; +} diff --git a/src/lighting.c b/src/lighting.c new file mode 100644 index 0000000..f73ce03 --- /dev/null +++ b/src/lighting.c @@ -0,0 +1,259 @@ +/** + * @file lighting.c + * @brief Advanced lighting system implementation + * @author ASCII3D Project + * @version 2.0.0 + */ + +#include "lighting.h" +#include +#include + +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; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e01c03f --- /dev/null +++ b/src/main.c @@ -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 +#include +#include +#include +#include + +/* 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 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 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 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 Render quality (0.5 - 2.0, default: 1.0)\n"); + printf(" -A Enable anti-aliasing\n"); + printf(" -p 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; +} diff --git a/src/renderer.c b/src/renderer.c new file mode 100644 index 0000000..33fd77d --- /dev/null +++ b/src/renderer.c @@ -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 +#include +#include +#include + +/*============================================================================ + * 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); +} diff --git a/src/timing.c b/src/timing.c new file mode 100644 index 0000000..b9eada8 --- /dev/null +++ b/src/timing.c @@ -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 + +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); + } +} diff --git a/src/vec3.c b/src/vec3.c new file mode 100644 index 0000000..552c993 --- /dev/null +++ b/src/vec3.c @@ -0,0 +1,91 @@ +/** + * @file vec3.c + * @brief 3D Vector mathematics implementation + * @author ASCII3D Project + * @version 1.0.0 + */ + +#include "vec3.h" +#include + +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 + ); +}