Compare commits

...

2 Commits

18 changed files with 1474 additions and 2109 deletions

29
CHANGELOG.md Normal file
View File

@@ -0,0 +1,29 @@
# Changelog
All notable changes to this project are documented here.
## [3.0.1] - 2026-04-16
### Changed
- **Rewrote all documentation**: The README, DOCUMENTATION, and CHANGELOG all read way too robotic and stiff — rewrote everything from scratch to sound like a human actually wrote it.
- **Expanded technical docs**: `DOCUMENTATION.md` now covers the full rendering pipeline step-by-step (normal estimation, Z-buffering, font bitmap layout, 3-point light rig, projection), with a configuration reference table and an actual example of how the bitmap font data maps to pixels.
- **README overhaul**: Cleaner structure, controls in a table, straightforward descriptions of what the project does instead of overengineered marketing-speak.
## [3.0.0] - 2026-04-16
### Added
- **Interactive TUI**: You can now change text, toggle settings, and control rotation without restarting the program. The terminal is put into raw, non-blocking mode via `termios.h` so keypresses register instantly.
- **Live text input**: Type alphanumeric characters while it's running and the 3D model updates immediately to show your new text.
- **Keybinding bar**: A help overlay at the bottom of the screen shows all available controls (space to pause, c for color mode, w/s for speed, etc.).
- **TUI module**: Added `tui.h` and `tui.c` as a separate module to keep the input handling code out of the renderer.
### Changed
- **Cleaned up comments**: Removed the old Doxygen-style block comments that were mostly restating what the code already said. Replaced them with shorter inline comments focused on explaining *why*, not *what*.
- **Makefile cleanup**: Simplified build targets, improved the comments so they're actually useful.
### Fixed
- **Terminal restore on exit**: The program now properly restores terminal settings (canonical mode, echo) when you quit or hit Ctrl+C. Before this, interrupting the process could leave your terminal in a broken state where it wouldn't echo input or handle line editing correctly.

144
DOCUMENTATION.md Normal file
View File

@@ -0,0 +1,144 @@
# ASCII 3D Renderer — Technical Documentation
## Overview
This is a software 3D renderer that outputs to a terminal. It takes text input, turns each character into a 3D shape, lights it with a multi-light Blinn-Phong model, and draws the result using ASCII characters (or colored output via ANSI escape sequences). Everything runs in a single-threaded loop at 60 FPS.
There are no GPU calls, no windowing system, and no third-party rendering libraries. The only external dependency beyond `libc` is `libm` for math functions (`powf`, `fmaxf`, `sqrtf`, etc.).
## How the rendering works
The pipeline, roughly, goes like this:
1. Look up the glyph bitmap for each character (from `font.c`)
2. Extrude the 2D bitmap into a 3D volume by adding depth along the Z-axis
3. Step through the volume at regular intervals (`VOXEL_STEP = 0.15`) and, at each point, check whether we're "inside" geometry
4. Calculate surface normals by looking at which neighboring cells are empty — a filled voxel next to an empty one means there's a surface edge there, and the normal points toward the gap
5. Apply rotation matrices (one per axis) based on the current angle
6. Project the 3D points into 2D screen coordinates using perspective projection (field of view, near/far planes)
7. For each projected point, run it through the lighting model to get a brightness value
8. Map that brightness to a character from the current shade palette
9. Write it to the framebuffer (a 2D array of chars), respecting the Z-buffer so closer surfaces win
10. Dump the entire framebuffer to stdout in one pass
When anti-aliasing is enabled, steps 38 are repeated with jittered sample offsets (2×2 pattern by default), and the results are averaged.
## Project structure
```bash
src/
main.c — CLI parsing, signal handling, main loop
renderer.c — the core rendering engine: projection, voxel traversal,
framebuffer management, screen output
lighting.c — Blinn-Phong lighting: diffuse, specular, multi-light
accumulation, color math
font.c — 5×7 bitmap font data for A-Z and 0-9, stored as
bitflag arrays
tui.c — terminal input handling: puts the TTY into raw mode
for non-blocking reads, processes keypresses
timing.c — frame rate limiter using CLOCK_MONOTONIC + nanosleep
vec3.c — basic 3D vector math (dot, cross, normalize, rotate)
include/
config.h — all the tuneable constants in one place: viewport size,
camera FOV, lighting params, shade palettes, etc.
(+ headers for each .c file)
```
## Module details
### `font.c` — Bitmap font
Each character is a 5-wide, 7-tall bitmap stored as 7 bytes. Each byte represents one row — bits 4 down to 0 tell you which pixels are filled. For example, the letter "A" is:
```bash
0x0E → .###.
0x11 → #...#
0x11 → #...#
0x1F → #####
0x11 → #...#
0x11 → #...#
0x11 → #...#
```
Only AZ (case-insensitive) and 09 are supported. Anything else gets skipped.
### `renderer.c` — 3D engine
This is the biggest file and does most of the heavy lifting.
**Glyph extrusion:** Each 5×7 bitmap gets depth. The renderer walks through the glyph volume at small Z increments. At each (x, y, z) point, if the corresponding 2D pixel is "on" in the bitmap, that point is considered solid geometry.
**Normal estimation:** For shading to work, you need surface normals. Since we're working with blocky voxel-like shapes, the normals are estimated by checking adjacent cells. If a filled cell has an empty neighbor to the left, the normal has a leftward component. Same for all six directions. The result is normalized to get a unit vector. This gives you smooth-ish shading on what are otherwise cube-shaped surfaces.
**Projection:** Standard perspective projection. A 3D point gets divided by its Z distance (scaled by field-of-view), then offset to the center of the screen. Points behind the near plane or past the far plane are clipped.
**Z-buffering:** The renderer keeps a depth buffer (initialized to a large value). When drawing a point, it only updates the framebuffer if the new point is closer than what was already there. This keeps front surfaces in front.
**Render modes:**
- *Shaded* — full lighting + ASCII shade mapping (the default)
- *Solid* — everything gets drawn with `@`, no lighting
- *Wireframe* — only draw points that sit on the boundary between filled and empty cells
- *Points* — draw all filled voxels with `.`
### `lighting.c` — Blinn-Phong model
The lighting system supports up to 3 lights (configurable via `MAX_LIGHTS`). By default it sets up a classic 3-point rig:
- **Key light** — the main light, slightly above and to the right, warm white
- **Fill light** — dimmer, coming from the left, slightly blue-tinted, softens shadows
- **Rim light** — behind and below the subject, adds edge definition
Each light can be directional (parallel rays, like the sun) or point (with inverse-square falloff).
For each surface point, the shader calculates:
- **Diffuse** — Lambert's cosine law: brightness depends on the angle between the surface normal and the light direction
- **Specular** — Blinn-Phong half-vector method: creates shiny highlights where the surface reflects light toward the camera
The final brightness is the sum of ambient + all light contributions, clamped to [0, 1]. In monochrome mode this maps to an index into the shade character palette. In color mode, the full RGB result is computed per-channel and output using ANSI escape sequences.
Material properties (ambient/diffuse/specular colors, shininess) are set to sensible defaults — there's no way to change them at runtime yet.
### `tui.c` — Terminal input
On startup, the terminal gets switched to non-canonical mode with echo disabled (via `termios`). This means keypresses are available immediately without waiting for Enter, and typed characters don't show up on screen.
The original terminal settings are saved and restored on exit — this is important because if the program crashes without restoring them, you'd end up with a terminal that doesn't echo your typing or respond to Ctrl+C properly. Signal handlers for SIGINT and SIGTERM are set up specifically to make sure cleanup happens even on interrupt.
### `timing.c` — Frame limiter
Pretty straightforward. After each frame, it checks how much time has passed since the frame started using `clock_gettime(CLOCK_MONOTONIC)`. If there's time remaining in the frame budget (1/60th of a second), it sleeps for the difference with `nanosleep`. This keeps the animation speed consistent regardless of how fast the machine is.
## Build system
The Makefile has three build profiles:
| Target | Flags | Purpose |
|--------|-------|---------|
| `release` | `-O3 -march=native -flto -DNDEBUG` | Production — fast as possible |
| `debug` | `-O0 -g3 -fsanitize=address,undefined` | Development — catches memory bugs and UB |
| `profile` | `-O2 -g -pg` | Profiling with gprof |
Dependency tracking is handled with `-MMD -MP`, so incremental builds work correctly.
The `install` target copies the binary to `/usr/local/bin` (or wherever `PREFIX` points).
## Configuration
Everything tuneable lives in `include/config.h`. Some notable values:
| Constant | Default | What it controls |
|----------|---------|-----------------|
| `SCREEN_WIDTH` / `SCREEN_HEIGHT` | 120 × 45 | Framebuffer size in characters |
| `CAMERA_DISTANCE` | 30.0 | How far back the virtual camera sits |
| `FIELD_OF_VIEW` | 50.0 | Perspective FOV in degrees |
| `EXTRUSION_DEPTH` | 4.0 | How thick the 3D letters are |
| `VOXEL_STEP` | 0.15 | Sampling resolution along Z when building geometry |
| `AA_SAMPLES` | 2 | Anti-aliasing grid (2 = 2×2 = 4 samples per pixel) |
| `AMBIENT_INTENSITY` | 0.15 | Base light level in shadows |
| `DIFFUSE_INTENSITY` | 0.70 | Strength of the diffuse lighting term |
| `SPECULAR_INTENSITY` | 0.40 | Strength of specular highlights |
| `SPECULAR_POWER` | 32.0 | Shininess exponent (higher = tighter highlights) |
| `TARGET_FPS` | 60 | Frame rate cap |

View File

@@ -3,123 +3,80 @@ INCDIR := include
OBJDIR := obj
BINDIR := bin
# Compiler and flags
# Core compiler config: we favor strict standards and high warnings for C11
CC := clang
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Werror
CFLAGS += -Wshadow -Wconversion -Wdouble-promotion
CFLAGS += -Wformat=2 -Wundef -fno-common
CFLAGS += -I$(INCDIR)
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Werror \
-Wshadow -Wconversion -Wdouble-promotion \
-Wformat=2 -Wundef -fno-common -I$(INCDIR)
# Target
TARGET := $(BINDIR)/ascii3d
# Source files
SRCS := $(wildcard $(SRCDIR)/*.c)
OBJS := $(SRCS:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
DEPS := $(OBJS:.o=.d)
# Libraries
# Linking libm for math logic
LDFLAGS := -lm
# Build modes
.PHONY: all debug release clean install uninstall help
.PHONY: all debug release clean install uninstall help tui
# Default target
all: release
# Release build (optimized)
# Standard LTO and extreme optimization flags for our main build
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
# Build configuration suitable for gdb integration and memory sanity checking
debug: CFLAGS += -O0 -g3 -DDEBUG -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..."
@echo "Cleaning artifacts..."
@rm -rf $(OBJDIR) $(BINDIR)
@echo "Clean complete."
# Install to system
PREFIX ?= /usr/local
install: release
@echo "Installing to $(PREFIX)/bin..."
@echo "Deploying 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."
@echo "Removed $(PREFIX)/bin/ascii3d."
# 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"
@echo "Targets: all (default), release, debug, profile, clean, install, uninstall, run, demo, analyze, format"

271
README.md
View File

@@ -1,239 +1,70 @@
# ASCII 3D Renderer v2.0
# ASCII 3D Renderer
An advanced, high-quality ASCII 3D text renderer written in C for fun.
A 3D text rendering engine that runs entirely in your terminal, written in C. Type any word and watch it spin in 3D with proper lighting, shading, and depth — all drawn with ASCII characters.
No OpenGL, no GPU, no graphics libraries. Just math, a framebuffer made of characters, and ANSI escape codes.
## What it does
You give it a string like `HELLO`, and it renders each letter as a 3D object by extruding a built-in 5×7 bitmap font along the Z-axis. A Blinn-Phong lighting model (with a 3-point light setup — key, fill, and rim) calculates the brightness at each surface point, which then gets mapped to an ASCII character from a density palette like `.:-=+*#%@`.
The whole thing runs as an interactive TUI. You can type new text on the fly, change render modes, toggle color output, adjust rotation speed — all without restarting.
## Docs
- [DOCUMENTATION.md](DOCUMENTATION.md) — how the rendering pipeline works, module breakdown, and how things fit together.
- [CHANGELOG.md](CHANGELOG.md) — version history.
## 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`
- Interactive TUI with non-blocking input via `termios` — no need to restart to change text or settings
- 3D normal estimation from 2D glyph bitmaps using neighbor-vacancy checks
- Multiple render modes: solid, wireframe, points, and shaded
- Anti-aliasing via multi-sample jittered rays
- Color output: monochrome, 16-color ANSI, 256-color ANSI, or full RGB truecolor
- Four different ASCII shade palettes (standard, extended 70-char, block characters, minimal)
- 60 FPS target with frame timing via `CLOCK_MONOTONIC` and `nanosleep`
## Building
### Requirements
- Clang or GCC (C11 support)
- GNU Make
- POSIX-compliant system (Linux, macOS, WSL)
### Quick Start
You need `clang` and `make`. The only library dependency is `libm` (math).
```bash
# Build release version
make
# optimized build with -O3 and LTO
make release
# Run with default text
# debug build with AddressSanitizer and UBSan
make debug
# run it
./bin/ascii3d
# Run with custom text
./bin/ascii3d HELLO
# Truecolor with all-axis rotation
./bin/ascii3d -a -c3 WORLD
```
### Build Targets
There's also `make profile` for gprof, `make analyze` for cppcheck, and `make format` for clang-format.
## Controls
Once it's running, you're in an interactive session:
| Key | What it does |
|-----|-------------|
| Any letter/number | Changes the displayed 3D text in real-time |
| `w` / `s` | Speed up / slow down rotation |
| `Space` | Pause or resume rotation |
| `m` | Cycle render mode (Solid → Wireframe → Points → Shaded) |
| `c` | Cycle color mode (Mono → ANSI 16 → ANSI 256 → Truecolor) |
| `p` | Cycle ASCII shade palette |
| `q` or `ESC` | Quit cleanly |
## Command-line examples
```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
# Rotate on all axes, truecolor mode
./bin/ascii3d -a -c3 WORLD
# Fast wireframe mode
./bin/ascii3d -m1 -s2 WIRE
# High quality with anti-aliasing
# Anti-aliasing on, 1.5x oversampling, 256-color mode
./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
# Show all options
./bin/ascii3d -h
```

View File

@@ -1,10 +1,3 @@
/**
* @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
@@ -12,159 +5,73 @@
extern "C" {
#endif
/*============================================================================
* SCREEN CONFIGURATION
*============================================================================*/
#define SCREEN_WIDTH 120
#define SCREEN_HEIGHT 45
// Base viewport dimensions. Increase for monolithic monitors.
#define SCREEN_WIDTH 120
#define SCREEN_HEIGHT 45
/*============================================================================
* RENDERING QUALITY SETTINGS
*============================================================================*/
#define DEPTH_BUFFER_INIT 1e9f
#define EXTRUSION_DEPTH 4.0f
#define CHAR_SCALE 2.0f
/* Depth buffer initialization value */
#define DEPTH_BUFFER_INIT 1e9f
#define CAMERA_DISTANCE 30.0f
#define FIELD_OF_VIEW 50.0f
#define NEAR_PLANE 0.1f
#define FAR_PLANE 100.0f
/* Character extrusion depth (3D thickness) */
#define EXTRUSION_DEPTH 4.0f
// NxN jitter patterns for anti-aliasing passes
#define AA_SAMPLES 2
/* Base character scale */
#define CHAR_SCALE 2.0f
// Depth steps for voxel ray casting computation
#define VOXEL_STEP 0.15f
#define SMOOTH_PASSES 1
/* Camera settings */
#define CAMERA_DISTANCE 30.0f
#define FIELD_OF_VIEW 50.0f
#define NEAR_PLANE 0.1f
#define FAR_PLANE 100.0f
#define TARGET_FPS 60
#define FRAME_TIME_US (1000000 / TARGET_FPS)
/* Sub-pixel sampling for anti-aliasing (NxN samples per pixel) */
#define AA_SAMPLES 2
#define FONT_WIDTH 5
#define FONT_HEIGHT 7
#define FONT_CHAR_SPACING 2
/* Voxel rendering step (smaller = higher quality, slower) */
#define VOXEL_STEP 0.15f
// Physics-Based lighting configurations
#define AMBIENT_INTENSITY 0.15f
#define DIFFUSE_INTENSITY 0.70f
#define SPECULAR_INTENSITY 0.40f
#define SPECULAR_POWER 32.0f
#define MAX_LIGHTS 3
/* Surface smoothing iterations */
#define SMOOTH_PASSES 1
// Shader maps determining intensity mappings
#define SHADE_CHARS_STANDARD " .:-=+*#%@"
#define SHADE_COUNT_STANDARD 10
#define SHADE_CHARS_EXTENDED \
" .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
#define SHADE_COUNT_EXTENDED 70
#define SHADE_CHARS_BLOCK " ░▒▓█"
#define SHADE_COUNT_BLOCK 5
#define SHADE_CHARS_MINIMAL " .:+#@"
#define SHADE_COUNT_MINIMAL 6
#define SHADE_CHARS SHADE_CHARS_EXTENDED
#define SHADE_COUNT SHADE_COUNT_EXTENDED
/*============================================================================
* 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
RENDER_MODE_SOLID = 0,
RENDER_MODE_WIREFRAME,
RENDER_MODE_POINTS,
RENDER_MODE_SHADED,
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
COLOR_MODE_MONO = 0,
COLOR_MODE_ANSI_16,
COLOR_MODE_ANSI_256,
COLOR_MODE_TRUECOLOR,
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"
#define ANSI_RESET "\033[0m"
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_CONFIG_H */
#endif

View File

@@ -1,10 +1,3 @@
/**
* @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
@@ -14,31 +7,18 @@
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
*/
// Returns a direct pointer to the 7-byte glyph row data if the char is present
// in our sprite sheet
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
*/
// Validates the bitmap bounds and extracts a specific pixel bit
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
*/
// Tests an ASCII character against our mapped glyph dictionary
bool font_is_renderable(char c);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_FONT_H */
#endif

View File

@@ -1,170 +1,81 @@
/**
* @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 "vec3.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 */
LIGHT_DIRECTIONAL = 0,
LIGHT_POINT,
LIGHT_SPOT
} LightType;
/**
* @brief RGB Color structure
*/
typedef struct Color {
float r, g, b;
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;
LightType type;
Vec3 position;
Vec3 direction;
Color color;
float intensity;
float falloff;
float spot_angle;
bool enabled;
} Light;
/**
* @brief Material properties for surfaces
*/
// PBR surface components
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 */
Color ambient;
Color diffuse;
Color specular;
float shininess;
float reflectivity;
} Material;
/**
* @brief Lighting system state
*/
// Encapsulates the entire render context's virtual ecosystem
typedef struct LightingSystem {
Light lights[MAX_LIGHTS];
int light_count;
Color ambient_color;
float ambient_intensity;
Vec3 camera_position;
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
*/
// Instantiates default 3-point portrait lighting scheme
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
*/
// Registers a new light emitter into the system context
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);
Light lighting_create_point(Vec3 position, Color color, float intensity,
float falloff);
/**
* @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
*/
// Computes monochrome or RGB shading based on surface interactions
float lighting_calculate(const LightingSystem *system, Vec3 point, Vec3 normal,
const Material *material);
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
*/
// Floating point color combinators
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 */
#endif

View File

@@ -1,10 +1,3 @@
/**
* @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
@@ -16,164 +9,73 @@
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;
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 */
RenderMode mode;
ColorMode color_mode;
bool anti_aliasing;
bool show_fps;
bool auto_rotate;
float quality;
int palette_index;
Color base_color;
Color highlight_color;
} RenderSettings;
/**
* @brief Frame statistics
*/
// Diagnostics profile updated each tick
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 */
double frame_time;
double fps;
double avg_fps;
int frame_count;
int triangles;
} FrameStats;
/**
* @brief Initialize the renderer
* @return 0 on success, -1 on failure
*/
// Core Engine Lifecycles
int renderer_init(void);
/**
* @brief Cleanup renderer resources
*/
void renderer_cleanup(void);
/**
* @brief Clear the screen and depth buffers
*/
// Nulls out all raster buffers ready for drawing
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
*/
// Flushes the rendering pipeline out to standard output
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
*/
// Dynamic rendering reconfiguration toggles
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
*/
FrameStats renderer_get_stats(void);
LightingSystem *renderer_get_lighting(void);
/**
* @brief Hide terminal cursor
*/
// Console visual hacks
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 */
#endif

View File

@@ -1,10 +1,3 @@
/**
* @file timing.h
* @brief High-precision timing utilities
* @author ASCII3D Project
* @version 1.0.0
*/
#ifndef ASCII3D_TIMING_H
#define ASCII3D_TIMING_H
@@ -12,27 +5,18 @@
extern "C" {
#endif
/**
* @brief Get current time in seconds (monotonic clock)
* @return Time in seconds with nanosecond precision
*/
// Returns the monotonic wall clock time in seconds, suitable for frame timing.
double timing_get_seconds(void);
/**
* @brief Sleep for specified microseconds
* @param microseconds Duration to sleep
*/
// Microsecond-scale thread suspension layer.
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
*/
// Calculates dynamic sleep padding against a target FPS to prevent high CPU
// utilization
void timing_limit_fps(double frame_start_time, int target_fps);
#ifdef __cplusplus
}
#endif
#endif /* ASCII3D_TIMING_H */
#endif

29
include/tui.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef ASCII3D_TUI_H
#define ASCII3D_TUI_H
#include "renderer.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// Reconfigures standard input to be non-blocking and disables canonical mode.
// This allows us to poll the keyboard gracefully in the render loop.
int tui_init(void);
// Restores terminal to its initial state to avoid leaving users with a broken
// prompt.
void tui_cleanup(void);
// Processes pending keys and updates application state.
// Standard typing appends to the text_buffer, while specific commands alter
// settings. Returns non-zero when an exit is requested.
int tui_process_input(RotationState *rotation, RenderSettings *settings,
char *text_buffer, size_t max_text_len);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,10 +1,3 @@
/**
* @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
@@ -12,104 +5,29 @@
extern "C" {
#endif
/**
* @brief 3D Vector structure
*/
// A standard 3-component float vector
typedef struct Vec3 {
float x;
float y;
float z;
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
*/
// Rotating points through standard Euler coordinate transformations
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 */
#endif

View File

@@ -1,105 +1,79 @@
/**
* @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
*/
// 5x7 ASCII grid font map.
// Each bitmaps uses 7 bytes representing rows. Bits 4 through 0 denote the set
// pixels.
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},
// A -> Z
{0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // A
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E},
{0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E},
{0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C},
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}, // E
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10},
{0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F},
{0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
{0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, // I
{0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C},
{0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11},
{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F},
{0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, // M
{0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11},
{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
{0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10},
{0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D}, // Q
{0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11},
{0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E},
{0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04},
{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // U
{0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04},
{0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A},
{0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11},
{0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04}, // Y
{0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F}, // Z
// 0 -> 9
{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, // 0
{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E},
{0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F},
{0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E},
{0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, // 4
{0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E},
{0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E},
{0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08},
{0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E},
{0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C}, // 9
};
#define FONT_LETTER_COUNT 26
#define FONT_DIGIT_COUNT 10
#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;
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];
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_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;
// Map bit flags directly onto screen pixel planes
return (glyph[y] & (1 << (FONT_WIDTH - 1 - x))) != 0;
}
bool font_is_renderable(char c)
{
return font_get_index(c) >= 0;
}
bool font_is_renderable(char c) { return font_get_index(c) >= 0; }

View File

@@ -1,259 +1,210 @@
/**
* @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);
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;
// Default 3-point light setup for standard rendering
// Key light
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);
// Fill light
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);
// Rim light to 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++;
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_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;
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);
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);
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;
float lighting_calculate(const LightingSystem *system, Vec3 point, Vec3 normal,
const Material *material) {
if (system == NULL || material == NULL)
return 0.5f;
float total = system->ambient_intensity;
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
// Accumulate each light source against surface topology
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);
// Inverse-square law derived falloff
attenuation = 1.0f / (1.0f + light->falloff * dist * dist);
} else {
continue;
}
/* Clamp to reasonable range */
return fminf(1.0f, fmaxf(0.0f, total));
float diffuse = calculate_diffuse(light_dir, normal);
float specular =
calculate_specular(light_dir, normal, view_dir, material->shininess);
float contribution =
(diffuse * DIFFUSE_INTENSITY + specular * SPECULAR_INTENSITY) *
light->intensity * attenuation;
total += contribution;
}
// We heavily clamp the signal because ASCII has hard boundaries
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);
Vec3 normal, const Material *material) {
if (system == NULL || material == NULL) {
return color_create(0.5f, 0.5f, 0.5f);
}
Color result =
color_scale(color_multiply(system->ambient_color, material->ambient),
system->ambient_intensity);
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
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;
}
/* 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);
float diff = calculate_diffuse(light_dir, normal);
Color diffuse = color_scale(color_multiply(light->color, material->diffuse),
diff * light->intensity * attenuation);
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;
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_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_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_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_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))
);
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;
float color_to_intensity(Color c) {
// Rec. 601 Luma mapping coefficients
return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
}

View File

@@ -1,330 +1,290 @@
/**
* @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 "tui.h"
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>
/* Global state */
// Global shutdown flag for sig handlers
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;
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);
static void setup_signals(void) {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
// We catch these to orchestrate a clean renderer shutdown
// Without this, the terminal will stay trapped in alternate mode
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");
static void print_usage(const char *program_name) {
printf("\n");
printf(" ASCII 3D RENDERER\n");
printf(" Advanced 3D Text Rendering with Phong Lighting & TUI\n\n");
printf("Usage: %s [OPTIONS] [TEXT]\n\n", program_name);
printf("ROTATION OPTIONS:\n");
printf(" -s <speed> Playback 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 rotational axes\n\n");
printf("RENDER MODE OPTIONS:\n");
printf(" -m <mode> Mode override:\n");
printf(" 0 = Solid, 1 = Wireframe\n");
printf(" 2 = Points, 3 = Shaded [default]\n\n");
printf("COLOR OPTIONS:\n");
printf(" -c <mode> Color strategy:\n");
printf(" 0 = Mono [default], 1 = ANSI 16\n");
printf(" 2 = ANSI 256, 3 = Truecolor RGB\n\n");
printf("QUALITY OPTIONS:\n");
printf(" -q <quality> Oversampling quality (0.5 - 2.0, default: 1.0)\n");
printf(" -A Force Anti-aliasing\n");
printf(" -p <palette> Ascii palette tier (0-3, default: 1)\n\n");
printf("OTHER OPTIONS:\n");
printf(" -f Display real-time FPS overlay\n");
printf(" -h Show this help dialog\n\n");
printf("TUI CONTROLS (active during render):\n");
printf(" [Typing] Changes dynamically rendered text\n");
printf(" [Space] Pause/Resume rotation\n");
printf(" [w/s] Modify rotation speed\n");
printf(" [m] Cycle render modes\n");
printf(" [c] Cycle color pipelines\n");
printf(" [p] Cycle ASCII density palettes\n");
printf(" [q / ESC] Terminate gracefully\n\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++;
static bool parse_cli_args(int argc, char *argv[], RotationState *rotation,
char *text_buffer, size_t buf_len) {
*rotation = renderer_default_rotation();
g_settings = renderer_default_settings();
// Set a default demo text if none is supplied
strncpy(text_buffer, "3D", buf_len - 1);
text_buffer[buf_len - 1] = '\0';
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;
}
} else {
*text = argv[i];
}
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, "Unrecognized param: -%c\n", *opt);
print_usage(argv[0]);
return false;
}
next_arg:;
opt++;
}
} else {
strncpy(text_buffer, argv[i], buf_len - 1);
text_buffer[buf_len - 1] = '\0';
}
/* Handle rotation defaults */
if (explicit_rotation && !rotation->enable_y) {
/* User explicitly chose axes */
} else if (!explicit_rotation) {
rotation->enable_y = true;
}
return true;
next_arg:;
}
// Inject Y-axis animation as a baseline if the user provided no explicit axis
// constraints
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 */
static void tui_help_overlay(void) {
printf("\033[%d;1H\033[90m [SPACE] Pause | [w/s] Speed | [c] Color | [m] "
"Mode | [p] Palette | [q/ESC] Quit | Type to edit \033[0m",
SCREEN_HEIGHT);
fflush(stdout);
}
static void core_render_loop(char *text_input, RotationState *rotation) {
double last_time = timing_get_seconds();
double fps_update_time = last_time;
int frame_count = 0;
double current_fps = 0.0;
// Mount the requested settings into our rendering context
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;
// Let's poll non-blocking CLI input to keep interactivity responsive
if (tui_process_input(rotation, &g_settings, text_input, 64)) {
g_running = 0;
break;
}
// Must sync local settings back to the engine incase TUI altered them
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);
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;
}
if (g_settings.auto_rotate) {
renderer_update_rotation(rotation, delta_time);
}
renderer_clear();
renderer_draw_text_ex(text_input, rotation, &g_settings);
if (g_settings.color_mode != COLOR_MODE_MONO) {
renderer_present_color(&g_settings);
} else {
renderer_present();
}
if (g_settings.show_fps) {
printf("\033[1;1H\033[7m FPS: %.1f | Voxels: %d \033[0m", current_fps,
renderer_get_stats().triangles);
}
tui_help_overlay();
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 */
int main(int argc, char *argv[]) {
RotationState rotation;
char text_buffer[64] = {0};
if (!parse_cli_args(argc, argv, &rotation, text_buffer,
sizeof(text_buffer))) {
return EXIT_SUCCESS;
}
setup_signals();
if (renderer_init() != 0) {
fprintf(
stderr,
"Renderer initialization failed due to context limits or IO error.\n");
return EXIT_FAILURE;
}
// Elevating the console environment
renderer_enter_alternate_screen();
renderer_clear_terminal();
renderer_hide_cursor();
if (tui_init() < 0) {
renderer_show_cursor();
renderer_exit_alternate_screen();
renderer_cleanup();
printf("Goodbye!\n");
return EXIT_SUCCESS;
fprintf(stderr, "Failed to instantiate TTY interface. Falling back.\n");
return EXIT_FAILURE;
}
core_render_loop(text_buffer, &rotation);
// Tearing down custom modes properly ensures the user doesn't end up with a
// broken shell prompt
tui_cleanup();
renderer_show_cursor();
renderer_exit_alternate_screen();
renderer_cleanup();
printf("Goodbye!\n");
return EXIT_SUCCESS;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,35 @@
/**
* @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;
double timing_get_seconds(void) {
struct timespec ts;
// We enforce monotonic clock so system daylight saving updates don't break
// delta logic
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_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);
}
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;
// Attempt dynamic throttle
if (sleep_time > 0.0) {
unsigned int sleep_us = (unsigned int)(sleep_time * 1.0e6);
timing_sleep_us(sleep_us);
}
}

98
src/tui.c Normal file
View File

@@ -0,0 +1,98 @@
#include "tui.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
static struct termios original_termios;
static int is_tui_initialized = 0;
int tui_init(void) {
if (tcgetattr(STDIN_FILENO, &original_termios) < 0) {
return -1;
}
struct termios raw = original_termios;
// We disable canonical mode and local echo so input is available instantly
// and invisible to the user until we handle it
raw.c_lflag &= ~(unsigned int)(ICANON | ECHO);
if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) < 0) {
return -1;
}
// Switch STDIN to non-blocking mode.
// If no keys are pressed, read() returns immediately without halting
// rendering.
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
is_tui_initialized = 1;
return 0;
}
void tui_cleanup(void) {
if (is_tui_initialized) {
// Re-enable canonical mode/echo and block reads
tcsetattr(STDIN_FILENO, TCSANOW, &original_termios);
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK);
}
}
int tui_process_input(RotationState *rotation, RenderSettings *settings,
char *text_buffer, size_t max_text_len) {
char c;
while (read(STDIN_FILENO, &c, 1) == 1) {
switch (c) {
case 27: // Physical ESC key
case 'q':
return 1;
case ' ': // Space bars act as play/pause for auto rotation
settings->auto_rotate = !settings->auto_rotate;
break;
case 'm':
settings->mode = (RenderMode)((settings->mode + 1) % RENDER_MODE_COUNT);
break;
case 'c':
settings->color_mode =
(ColorMode)((settings->color_mode + 1) % COLOR_MODE_COUNT);
break;
case 'p':
// Currently 4 palettes defined in our system
settings->palette_index = (settings->palette_index + 1) % 4;
break;
case 'w':
rotation->speed += 0.2f;
break;
case 's':
rotation->speed -= 0.2f;
if (rotation->speed < 0.0f) {
rotation->speed = 0.0f;
}
break;
case 127: // Backspace (DEL character)
case 8: // Backspace (BS character)
{
size_t len = strlen(text_buffer);
if (len > 0) {
text_buffer[len - 1] = '\0';
}
} break;
default:
// Only allow standard displayable ASCII.
// It makes the TUI feel more like a dynamic terminal app
if (c >= ' ' && c <= '~') {
size_t len = strlen(text_buffer);
if (len < max_text_len - 1) {
text_buffer[len] = c;
text_buffer[len + 1] = '\0';
}
}
break;
}
}
return 0;
}

View File

@@ -1,91 +1,58 @@
/**
* @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;
// Vector Math Library
// Optimized for simple local 3D transformations
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_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_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);
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;
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);
}
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);
// Arbitrarily high epsilon cut-off to avoid division by zero artifacts
if (len > 0.0001f) {
return vec3_scale(v, 1.0f / len);
}
return vec3_create(0.0f, 0.0f, 0.0f);
}
float vec3_length(Vec3 v)
{
return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
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_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_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_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
);
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);
}