Compare commits
2 Commits
42529be0f8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 71dd8b8354 | |||
| 72e0ef4022 |
29
CHANGELOG.md
Normal file
29
CHANGELOG.md
Normal 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
144
DOCUMENTATION.md
Normal 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 3–8 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 A–Z (case-insensitive) and 0–9 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 |
|
||||
69
Makefile
69
Makefile
@@ -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"
|
||||
|
||||
269
README.md
269
README.md
@@ -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
|
||||
# Show all options
|
||||
./bin/ascii3d -h
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
193
include/config.h
193
include/config.h
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
29
include/tui.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
154
src/font.c
154
src/font.c
@@ -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; }
|
||||
|
||||
369
src/lighting.c
369
src/lighting.c
@@ -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;
|
||||
void lighting_init(LightingSystem *system) {
|
||||
if (system == NULL)
|
||||
return;
|
||||
|
||||
memset(system, 0, sizeof(LightingSystem));
|
||||
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;
|
||||
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);
|
||||
// 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);
|
||||
|
||||
/* 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);
|
||||
// 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);
|
||||
|
||||
/* 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);
|
||||
// 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;
|
||||
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;
|
||||
float total = system->ambient_intensity;
|
||||
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
|
||||
|
||||
/* View direction (from point to camera) */
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
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
|
||||
);
|
||||
float diff = calculate_diffuse(light_dir, normal);
|
||||
Color diffuse = color_scale(color_multiply(light->color, material->diffuse),
|
||||
diff * light->intensity * attenuation);
|
||||
|
||||
/* View direction */
|
||||
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
|
||||
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);
|
||||
|
||||
/* Accumulate from each light */
|
||||
for (int i = 0; i < system->light_count; i++) {
|
||||
const Light *light = &system->lights[i];
|
||||
if (!light->enabled) continue;
|
||||
result = color_add(result, color_add(diffuse, specular));
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
510
src/main.c
510
src/main.c
@@ -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;
|
||||
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);
|
||||
// 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);
|
||||
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> 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("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> 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("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 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("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> 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("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 Show FPS counter\n");
|
||||
printf(" -h Show this help message\n");
|
||||
printf("\n");
|
||||
printf("OTHER OPTIONS:\n");
|
||||
printf(" -f Display real-time FPS overlay\n");
|
||||
printf(" -h Show this help dialog\n\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");
|
||||
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";
|
||||
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();
|
||||
|
||||
bool explicit_rotation = false;
|
||||
// Set a default demo text if none is supplied
|
||||
strncpy(text_buffer, "3D", buf_len - 1);
|
||||
text_buffer[buf_len - 1] = '\0';
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (argv[i][0] == '-') {
|
||||
const char *opt = &argv[i][1];
|
||||
bool explicit_rotation = false;
|
||||
|
||||
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++;
|
||||
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';
|
||||
}
|
||||
next_arg:;
|
||||
}
|
||||
|
||||
/* Handle rotation defaults */
|
||||
if (explicit_rotation && !rotation->enable_y) {
|
||||
/* User explicitly chose axes */
|
||||
} else if (!explicit_rotation) {
|
||||
rotation->enable_y = true;
|
||||
}
|
||||
// Inject Y-axis animation as a baseline if the user provided no explicit axis
|
||||
// constraints
|
||||
if (!explicit_rotation) {
|
||||
rotation->enable_y = true;
|
||||
}
|
||||
|
||||
return 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;
|
||||
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);
|
||||
}
|
||||
|
||||
/* Apply settings */
|
||||
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;
|
||||
int main(int argc, char *argv[]) {
|
||||
RotationState rotation;
|
||||
char text_buffer[64] = {0};
|
||||
|
||||
/* Parse command line */
|
||||
if (!parse_arguments(argc, argv, &rotation, &text)) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (!parse_cli_args(argc, argv, &rotation, text_buffer,
|
||||
sizeof(text_buffer))) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* Setup signal handlers */
|
||||
setup_signals();
|
||||
setup_signals();
|
||||
|
||||
/* Initialize renderer */
|
||||
if (renderer_init() != 0) {
|
||||
fprintf(stderr, "Failed to initialize renderer\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (renderer_init() != 0) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Renderer initialization failed due to context limits or IO error.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Enter alternate screen buffer for clean exit */
|
||||
renderer_enter_alternate_screen();
|
||||
renderer_clear_terminal();
|
||||
renderer_hide_cursor();
|
||||
// Elevating the console environment
|
||||
renderer_enter_alternate_screen();
|
||||
renderer_clear_terminal();
|
||||
renderer_hide_cursor();
|
||||
|
||||
/* Run main loop */
|
||||
render_loop(text, &rotation);
|
||||
|
||||
/* Cleanup */
|
||||
if (tui_init() < 0) {
|
||||
renderer_show_cursor();
|
||||
renderer_exit_alternate_screen();
|
||||
renderer_cleanup();
|
||||
fprintf(stderr, "Failed to instantiate TTY interface. Falling back.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
printf("Goodbye!\n");
|
||||
core_render_loop(text_buffer, &rotation);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
// 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;
|
||||
}
|
||||
|
||||
977
src/renderer.c
977
src/renderer.c
File diff suppressed because it is too large
Load Diff
54
src/timing.c
54
src/timing.c
@@ -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;
|
||||
}
|
||||
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;
|
||||
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);
|
||||
}
|
||||
// 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
98
src/tui.c
Normal 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;
|
||||
}
|
||||
109
src/vec3.c
109
src/vec3.c
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user