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
|
OBJDIR := obj
|
||||||
BINDIR := bin
|
BINDIR := bin
|
||||||
|
|
||||||
# Compiler and flags
|
# Core compiler config: we favor strict standards and high warnings for C11
|
||||||
CC := clang
|
CC := clang
|
||||||
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Werror
|
CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Werror \
|
||||||
CFLAGS += -Wshadow -Wconversion -Wdouble-promotion
|
-Wshadow -Wconversion -Wdouble-promotion \
|
||||||
CFLAGS += -Wformat=2 -Wundef -fno-common
|
-Wformat=2 -Wundef -fno-common -I$(INCDIR)
|
||||||
CFLAGS += -I$(INCDIR)
|
|
||||||
|
|
||||||
# Target
|
|
||||||
TARGET := $(BINDIR)/ascii3d
|
TARGET := $(BINDIR)/ascii3d
|
||||||
|
|
||||||
# Source files
|
|
||||||
SRCS := $(wildcard $(SRCDIR)/*.c)
|
SRCS := $(wildcard $(SRCDIR)/*.c)
|
||||||
OBJS := $(SRCS:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
|
OBJS := $(SRCS:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
|
||||||
DEPS := $(OBJS:.o=.d)
|
DEPS := $(OBJS:.o=.d)
|
||||||
|
|
||||||
# Libraries
|
# Linking libm for math logic
|
||||||
LDFLAGS := -lm
|
LDFLAGS := -lm
|
||||||
|
|
||||||
# Build modes
|
.PHONY: all debug release clean install uninstall help tui
|
||||||
.PHONY: all debug release clean install uninstall help
|
|
||||||
|
|
||||||
# Default target
|
|
||||||
all: release
|
all: release
|
||||||
|
|
||||||
# Release build (optimized)
|
# Standard LTO and extreme optimization flags for our main build
|
||||||
release: CFLAGS += -O3 -DNDEBUG -march=native -flto
|
release: CFLAGS += -O3 -DNDEBUG -march=native -flto
|
||||||
release: LDFLAGS += -flto
|
release: LDFLAGS += -flto
|
||||||
release: $(TARGET)
|
release: $(TARGET)
|
||||||
|
|
||||||
# Debug build (with symbols and sanitizers)
|
# Build configuration suitable for gdb integration and memory sanity checking
|
||||||
debug: CFLAGS += -O0 -g3 -DDEBUG
|
debug: CFLAGS += -O0 -g3 -DDEBUG -fsanitize=address,undefined
|
||||||
debug: CFLAGS += -fsanitize=address,undefined
|
|
||||||
debug: LDFLAGS += -fsanitize=address,undefined
|
debug: LDFLAGS += -fsanitize=address,undefined
|
||||||
debug: $(TARGET)
|
debug: $(TARGET)
|
||||||
|
|
||||||
# Profile build (optimized with debug symbols)
|
|
||||||
profile: CFLAGS += -O2 -g -pg
|
profile: CFLAGS += -O2 -g -pg
|
||||||
profile: LDFLAGS += -pg
|
profile: LDFLAGS += -pg
|
||||||
profile: $(TARGET)
|
profile: $(TARGET)
|
||||||
|
|
||||||
# Create directories
|
|
||||||
$(OBJDIR) $(BINDIR):
|
$(OBJDIR) $(BINDIR):
|
||||||
@mkdir -p $@
|
@mkdir -p $@
|
||||||
|
|
||||||
# Link
|
|
||||||
$(TARGET): $(OBJS) | $(BINDIR)
|
$(TARGET): $(OBJS) | $(BINDIR)
|
||||||
@echo "Linking $@..."
|
@echo "Linking $@..."
|
||||||
@$(CC) $(OBJS) -o $@ $(LDFLAGS)
|
@$(CC) $(OBJS) -o $@ $(LDFLAGS)
|
||||||
@echo "Build complete: $@"
|
@echo "Build complete: $@"
|
||||||
|
|
||||||
# Compile
|
|
||||||
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
|
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
|
||||||
@echo "Compiling $<..."
|
@echo "Compiling $<..."
|
||||||
@$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
|
@$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
|
||||||
|
|
||||||
# Include dependencies
|
|
||||||
-include $(DEPS)
|
-include $(DEPS)
|
||||||
|
|
||||||
# Clean build artifacts
|
|
||||||
clean:
|
clean:
|
||||||
@echo "Cleaning..."
|
@echo "Cleaning artifacts..."
|
||||||
@rm -rf $(OBJDIR) $(BINDIR)
|
@rm -rf $(OBJDIR) $(BINDIR)
|
||||||
@echo "Clean complete."
|
|
||||||
|
|
||||||
# Install to system
|
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
install: release
|
install: release
|
||||||
@echo "Installing to $(PREFIX)/bin..."
|
@echo "Deploying to $(PREFIX)/bin..."
|
||||||
@install -d $(PREFIX)/bin
|
@install -d $(PREFIX)/bin
|
||||||
@install -m 755 $(TARGET) $(PREFIX)/bin/
|
@install -m 755 $(TARGET) $(PREFIX)/bin/
|
||||||
@echo "Installation complete."
|
|
||||||
|
|
||||||
# Uninstall from system
|
|
||||||
uninstall:
|
uninstall:
|
||||||
@echo "Uninstalling from $(PREFIX)/bin..."
|
|
||||||
@rm -f $(PREFIX)/bin/ascii3d
|
@rm -f $(PREFIX)/bin/ascii3d
|
||||||
@echo "Uninstallation complete."
|
@echo "Removed $(PREFIX)/bin/ascii3d."
|
||||||
|
|
||||||
# Run the program
|
|
||||||
run: release
|
run: release
|
||||||
@./$(TARGET)
|
@./$(TARGET)
|
||||||
|
|
||||||
# Run with demo text
|
|
||||||
demo: release
|
demo: release
|
||||||
@./$(TARGET) -a HELLO
|
@./$(TARGET) -a HELLO
|
||||||
|
|
||||||
# Static analysis
|
|
||||||
analyze:
|
analyze:
|
||||||
@echo "Running static analysis..."
|
|
||||||
@cppcheck --enable=all --std=c11 -I$(INCDIR) $(SRCDIR)/*.c
|
@cppcheck --enable=all --std=c11 -I$(INCDIR) $(SRCDIR)/*.c
|
||||||
|
|
||||||
# Format code
|
|
||||||
format:
|
format:
|
||||||
@echo "Formatting code..."
|
|
||||||
@clang-format -i $(SRCDIR)/*.c $(INCDIR)/*.h
|
@clang-format -i $(SRCDIR)/*.c $(INCDIR)/*.h
|
||||||
|
|
||||||
# Help
|
|
||||||
help:
|
help:
|
||||||
@echo "ASCII 3D Renderer - Build System"
|
@echo "ASCII 3D Renderer - Build System"
|
||||||
@echo "================================"
|
@echo "================================"
|
||||||
@echo ""
|
@echo "Targets: all (default), release, debug, profile, clean, install, uninstall, run, demo, analyze, format"
|
||||||
@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"
|
|
||||||
|
|||||||
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
|
## Features
|
||||||
|
|
||||||
### Rendering
|
- 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
|
||||||
- **Phong Lighting Model** - Realistic lighting with ambient, diffuse, and specular components
|
- Multiple render modes: solid, wireframe, points, and shaded
|
||||||
- **Multiple Light Sources** - Key light, fill light, and rim light for professional 3-point lighting
|
- Anti-aliasing via multi-sample jittered rays
|
||||||
- **Smooth Normals** - Averaged surface normals for smoother shading on edges
|
- Color output: monochrome, 16-color ANSI, 256-color ANSI, or full RGB truecolor
|
||||||
- **Multiple Render Modes** - Solid, wireframe, points, and full shaded rendering
|
- Four different ASCII shade palettes (standard, extended 70-char, block characters, minimal)
|
||||||
- **Anti-Aliasing** - Optional sub-pixel sampling for smoother output
|
- 60 FPS target with frame timing via `CLOCK_MONOTONIC` and `nanosleep`
|
||||||
|
|
||||||
### Visual Quality
|
|
||||||
|
|
||||||
- **Extended ASCII Palettes** - 70-level gradient for detailed shading
|
|
||||||
- **Block Characters** - Unicode block shading option (░▒▓█)
|
|
||||||
- **ANSI Color Support** - 16-color, 256-color, and 24-bit truecolor modes
|
|
||||||
- **Configurable Quality** - Adjustable voxel density for performance/quality tradeoff
|
|
||||||
|
|
||||||
### Technical
|
|
||||||
|
|
||||||
- **Pure C11** - No external graphics dependencies
|
|
||||||
- **60 FPS** - Smooth real-time animation
|
|
||||||
- **Depth Buffering** - Proper 3D occlusion
|
|
||||||
- **Modular Architecture** - Clean separation of concerns
|
|
||||||
- **Production-Ready** - Strict compiler warnings, proper error handling
|
|
||||||
|
|
||||||
## Supported Characters
|
|
||||||
|
|
||||||
- Uppercase letters: `A-Z`
|
|
||||||
- Lowercase letters: `a-z` (rendered as uppercase)
|
|
||||||
- Digits: `0-9`
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
### Requirements
|
You need `clang` and `make`. The only library dependency is `libm` (math).
|
||||||
|
|
||||||
- Clang or GCC (C11 support)
|
|
||||||
- GNU Make
|
|
||||||
- POSIX-compliant system (Linux, macOS, WSL)
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build release version
|
# optimized build with -O3 and LTO
|
||||||
make
|
make release
|
||||||
|
|
||||||
# Run with default text
|
# debug build with AddressSanitizer and UBSan
|
||||||
|
make debug
|
||||||
|
|
||||||
|
# run it
|
||||||
./bin/ascii3d
|
./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
|
```bash
|
||||||
make release # Optimized build (default)
|
# Rotate on all axes, truecolor mode
|
||||||
make debug # Debug build with sanitizers
|
|
||||||
make profile # Build with profiling support
|
|
||||||
make clean # Remove build artifacts
|
|
||||||
make install # Install to /usr/local/bin
|
|
||||||
make help # Show all targets
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ascii3d [OPTIONS] [TEXT]
|
|
||||||
|
|
||||||
ROTATION OPTIONS:
|
|
||||||
-s <speed> Rotation speed multiplier (default: 1.0)
|
|
||||||
-x Enable X-axis rotation
|
|
||||||
-y Enable Y-axis rotation (default)
|
|
||||||
-z Enable Z-axis rotation
|
|
||||||
-a Enable all axis rotations
|
|
||||||
|
|
||||||
RENDER MODE OPTIONS:
|
|
||||||
-m <mode> Render mode:
|
|
||||||
0 = Solid (filled)
|
|
||||||
1 = Wireframe (edges only)
|
|
||||||
2 = Points (sparse)
|
|
||||||
3 = Shaded (full Phong lighting) [default]
|
|
||||||
|
|
||||||
COLOR OPTIONS:
|
|
||||||
-c <mode> Color mode:
|
|
||||||
0 = Monochrome ASCII [default]
|
|
||||||
1 = 16-color ANSI
|
|
||||||
2 = 256-color ANSI
|
|
||||||
3 = Truecolor (24-bit RGB)
|
|
||||||
|
|
||||||
QUALITY OPTIONS:
|
|
||||||
-q <quality> Render quality (0.5 - 2.0, default: 1.0)
|
|
||||||
-A Enable anti-aliasing
|
|
||||||
-p <palette> Shading palette:
|
|
||||||
0 = Standard (10 levels)
|
|
||||||
1 = Extended (70 levels) [default]
|
|
||||||
2 = Block characters
|
|
||||||
3 = Minimal (6 levels)
|
|
||||||
|
|
||||||
OTHER OPTIONS:
|
|
||||||
-f Show FPS counter
|
|
||||||
-h Show help message
|
|
||||||
```
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Simple Y-axis rotation (default)
|
|
||||||
./bin/ascii3d HELLO
|
|
||||||
|
|
||||||
# Tumbling rotation with truecolor
|
|
||||||
./bin/ascii3d -a -c3 WORLD
|
./bin/ascii3d -a -c3 WORLD
|
||||||
|
|
||||||
# Fast wireframe mode
|
# Anti-aliasing on, 1.5x oversampling, 256-color mode
|
||||||
./bin/ascii3d -m1 -s2 WIRE
|
|
||||||
|
|
||||||
# High quality with anti-aliasing
|
|
||||||
./bin/ascii3d -A -q1.5 -c2 HQ
|
./bin/ascii3d -A -q1.5 -c2 HQ
|
||||||
|
|
||||||
# Block character style
|
# Show all options
|
||||||
./bin/ascii3d -p2 BLOCKS
|
./bin/ascii3d -h
|
||||||
|
|
||||||
# Show FPS counter
|
|
||||||
./bin/ascii3d -f -a TEST
|
|
||||||
|
|
||||||
# Slow, high quality render
|
|
||||||
./bin/ascii3d -s0.3 -q2 -A SMOOTH
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Press `Ctrl+C` to exit.
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ASCIIRenderer/
|
|
||||||
├── include/
|
|
||||||
│ ├── config.h # Configuration and constants
|
|
||||||
│ ├── vec3.h # 3D vector mathematics
|
|
||||||
│ ├── font.h # Bitmap font interface
|
|
||||||
│ ├── lighting.h # Phong lighting system
|
|
||||||
│ ├── renderer.h # Core rendering engine
|
|
||||||
│ └── timing.h # High-precision timing
|
|
||||||
├── src/
|
|
||||||
│ ├── vec3.c # Vector operations
|
|
||||||
│ ├── font.c # 5x7 bitmap font data
|
|
||||||
│ ├── lighting.c # Lighting calculations
|
|
||||||
│ ├── renderer.c # Advanced rendering
|
|
||||||
│ ├── timing.c # Timing utilities
|
|
||||||
│ └── main.c # Entry point and CLI
|
|
||||||
├── Makefile # Build system (Clang)
|
|
||||||
└── README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### Rendering Pipeline
|
|
||||||
|
|
||||||
1. **Font Lookup** - Characters are defined as 5x7 bitmap glyphs
|
|
||||||
2. **3D Extrusion** - Each pixel is extruded along Z-axis to create depth
|
|
||||||
3. **Surface Detection** - Only surface voxels are rendered (optimization)
|
|
||||||
4. **Normal Calculation** - Smooth normals computed from adjacent faces
|
|
||||||
5. **Rotation** - 3D rotation matrices transform points and normals
|
|
||||||
6. **Projection** - Perspective projection maps 3D to 2D screen space
|
|
||||||
7. **Lighting** - Phong model calculates illumination per voxel
|
|
||||||
8. **Depth Test** - Z-buffer ensures correct occlusion
|
|
||||||
9. **Shading** - Intensity mapped to ASCII character from palette
|
|
||||||
10. **Color** - Optional ANSI escape codes for colored output
|
|
||||||
|
|
||||||
### Lighting Model
|
|
||||||
|
|
||||||
The renderer uses a 3-point lighting setup:
|
|
||||||
|
|
||||||
- **Key Light** - Main light from upper-right-front (warm white)
|
|
||||||
- **Fill Light** - Softer light from left (cool blue tint)
|
|
||||||
- **Rim Light** - Back light for edge definition
|
|
||||||
|
|
||||||
Phong components:
|
|
||||||
|
|
||||||
- **Ambient** - Base illumination (15%)
|
|
||||||
- **Diffuse** - Lambertian reflection (70%)
|
|
||||||
- **Specular** - Blinn-Phong highlights (40%, shininess 32)
|
|
||||||
|
|
||||||
### ASCII Shading Palettes
|
|
||||||
|
|
||||||
**Standard (10 levels):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.:-=+*#%@
|
|
||||||
```
|
|
||||||
|
|
||||||
**Extended (70 levels):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.'`^",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$
|
|
||||||
```
|
|
||||||
|
|
||||||
**Block (5 levels):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
░▒▓█
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Edit `include/config.h` to customize:
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* Screen dimensions */
|
|
||||||
#define SCREEN_WIDTH 120
|
|
||||||
#define SCREEN_HEIGHT 45
|
|
||||||
|
|
||||||
/* Rendering quality */
|
|
||||||
#define EXTRUSION_DEPTH 4.0f // 3D depth of characters
|
|
||||||
#define CHAR_SCALE 2.0f // Character size
|
|
||||||
#define VOXEL_STEP 0.15f // Voxel density (smaller = higher quality)
|
|
||||||
|
|
||||||
/* Lighting */
|
|
||||||
#define AMBIENT_INTENSITY 0.15f
|
|
||||||
#define DIFFUSE_INTENSITY 0.70f
|
|
||||||
#define SPECULAR_INTENSITY 0.40f
|
|
||||||
#define SPECULAR_POWER 32.0f
|
|
||||||
|
|
||||||
/* Animation */
|
|
||||||
#define TARGET_FPS 60
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
|
|
||||||
- **60 FPS** on modern hardware
|
|
||||||
- **Surface-only rendering** - Interior voxels skipped
|
|
||||||
- **Efficient depth buffer** - Single-pass rendering
|
|
||||||
- **Minimal memory** - Static buffers, no dynamic allocation
|
|
||||||
- **Quality scaling** - `-q` option for performance tuning
|
|
||||||
|
|||||||
125
include/config.h
125
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
|
#ifndef ASCII3D_CONFIG_H
|
||||||
#define ASCII3D_CONFIG_H
|
#define ASCII3D_CONFIG_H
|
||||||
|
|
||||||
@@ -12,159 +5,73 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*============================================================================
|
// Base viewport dimensions. Increase for monolithic monitors.
|
||||||
* SCREEN CONFIGURATION
|
|
||||||
*============================================================================*/
|
|
||||||
#define SCREEN_WIDTH 120
|
#define SCREEN_WIDTH 120
|
||||||
#define SCREEN_HEIGHT 45
|
#define SCREEN_HEIGHT 45
|
||||||
|
|
||||||
/*============================================================================
|
|
||||||
* RENDERING QUALITY SETTINGS
|
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
/* Depth buffer initialization value */
|
|
||||||
#define DEPTH_BUFFER_INIT 1e9f
|
#define DEPTH_BUFFER_INIT 1e9f
|
||||||
|
|
||||||
/* Character extrusion depth (3D thickness) */
|
|
||||||
#define EXTRUSION_DEPTH 4.0f
|
#define EXTRUSION_DEPTH 4.0f
|
||||||
|
|
||||||
/* Base character scale */
|
|
||||||
#define CHAR_SCALE 2.0f
|
#define CHAR_SCALE 2.0f
|
||||||
|
|
||||||
/* Camera settings */
|
|
||||||
#define CAMERA_DISTANCE 30.0f
|
#define CAMERA_DISTANCE 30.0f
|
||||||
#define FIELD_OF_VIEW 50.0f
|
#define FIELD_OF_VIEW 50.0f
|
||||||
#define NEAR_PLANE 0.1f
|
#define NEAR_PLANE 0.1f
|
||||||
#define FAR_PLANE 100.0f
|
#define FAR_PLANE 100.0f
|
||||||
|
|
||||||
/* Sub-pixel sampling for anti-aliasing (NxN samples per pixel) */
|
// NxN jitter patterns for anti-aliasing passes
|
||||||
#define AA_SAMPLES 2
|
#define AA_SAMPLES 2
|
||||||
|
|
||||||
/* Voxel rendering step (smaller = higher quality, slower) */
|
// Depth steps for voxel ray casting computation
|
||||||
#define VOXEL_STEP 0.15f
|
#define VOXEL_STEP 0.15f
|
||||||
|
|
||||||
/* Surface smoothing iterations */
|
|
||||||
#define SMOOTH_PASSES 1
|
#define SMOOTH_PASSES 1
|
||||||
|
|
||||||
/*============================================================================
|
|
||||||
* ANIMATION PARAMETERS
|
|
||||||
*============================================================================*/
|
|
||||||
#define TARGET_FPS 60
|
#define TARGET_FPS 60
|
||||||
#define FRAME_TIME_US (1000000 / TARGET_FPS)
|
#define FRAME_TIME_US (1000000 / TARGET_FPS)
|
||||||
|
|
||||||
/*============================================================================
|
|
||||||
* FONT CONFIGURATION
|
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
/* Standard 5x7 font */
|
|
||||||
#define FONT_WIDTH 5
|
#define FONT_WIDTH 5
|
||||||
#define FONT_HEIGHT 7
|
#define FONT_HEIGHT 7
|
||||||
#define FONT_CHAR_SPACING 2
|
#define FONT_CHAR_SPACING 2
|
||||||
|
|
||||||
/*============================================================================
|
// Physics-Based lighting configurations
|
||||||
* LIGHTING CONFIGURATION
|
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
/* Ambient light intensity (0.0 - 1.0) */
|
|
||||||
#define AMBIENT_INTENSITY 0.15f
|
#define AMBIENT_INTENSITY 0.15f
|
||||||
|
|
||||||
/* Diffuse light intensity */
|
|
||||||
#define DIFFUSE_INTENSITY 0.70f
|
#define DIFFUSE_INTENSITY 0.70f
|
||||||
|
|
||||||
/* Specular light intensity */
|
|
||||||
#define SPECULAR_INTENSITY 0.40f
|
#define SPECULAR_INTENSITY 0.40f
|
||||||
|
|
||||||
/* Specular shininess exponent */
|
|
||||||
#define SPECULAR_POWER 32.0f
|
#define SPECULAR_POWER 32.0f
|
||||||
|
|
||||||
/* Number of light sources */
|
|
||||||
#define MAX_LIGHTS 3
|
#define MAX_LIGHTS 3
|
||||||
|
|
||||||
/*============================================================================
|
// Shader maps determining intensity mappings
|
||||||
* ASCII SHADING PALETTES
|
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
/* Standard gradient (10 levels) */
|
|
||||||
#define SHADE_CHARS_STANDARD " .:-=+*#%@"
|
#define SHADE_CHARS_STANDARD " .:-=+*#%@"
|
||||||
#define SHADE_COUNT_STANDARD 10
|
#define SHADE_COUNT_STANDARD 10
|
||||||
|
#define SHADE_CHARS_EXTENDED \
|
||||||
/* Extended gradient (16 levels) - more detail */
|
" .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
|
||||||
#define SHADE_CHARS_EXTENDED " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
|
|
||||||
#define SHADE_COUNT_EXTENDED 70
|
#define SHADE_COUNT_EXTENDED 70
|
||||||
|
|
||||||
/* Block characters for solid look */
|
|
||||||
#define SHADE_CHARS_BLOCK " ░▒▓█"
|
#define SHADE_CHARS_BLOCK " ░▒▓█"
|
||||||
#define SHADE_COUNT_BLOCK 5
|
#define SHADE_COUNT_BLOCK 5
|
||||||
|
|
||||||
/* Minimal gradient */
|
|
||||||
#define SHADE_CHARS_MINIMAL " .:+#@"
|
#define SHADE_CHARS_MINIMAL " .:+#@"
|
||||||
#define SHADE_COUNT_MINIMAL 6
|
#define SHADE_COUNT_MINIMAL 6
|
||||||
|
|
||||||
/* Default palette */
|
|
||||||
#define SHADE_CHARS SHADE_CHARS_EXTENDED
|
#define SHADE_CHARS SHADE_CHARS_EXTENDED
|
||||||
#define SHADE_COUNT SHADE_COUNT_EXTENDED
|
#define SHADE_COUNT SHADE_COUNT_EXTENDED
|
||||||
|
|
||||||
/*============================================================================
|
|
||||||
* RENDER MODES
|
|
||||||
*============================================================================*/
|
|
||||||
typedef enum RenderMode {
|
typedef enum RenderMode {
|
||||||
RENDER_MODE_SOLID = 0, /* Filled solid rendering */
|
RENDER_MODE_SOLID = 0,
|
||||||
RENDER_MODE_WIREFRAME, /* Edge-only wireframe */
|
RENDER_MODE_WIREFRAME,
|
||||||
RENDER_MODE_POINTS, /* Point cloud */
|
RENDER_MODE_POINTS,
|
||||||
RENDER_MODE_SHADED, /* Full Phong shading */
|
RENDER_MODE_SHADED,
|
||||||
RENDER_MODE_COUNT
|
RENDER_MODE_COUNT
|
||||||
} RenderMode;
|
} RenderMode;
|
||||||
|
|
||||||
/*============================================================================
|
|
||||||
* COLOR MODES
|
|
||||||
*============================================================================*/
|
|
||||||
typedef enum ColorMode {
|
typedef enum ColorMode {
|
||||||
COLOR_MODE_MONO = 0, /* Monochrome ASCII */
|
COLOR_MODE_MONO = 0,
|
||||||
COLOR_MODE_ANSI_16, /* 16-color ANSI */
|
COLOR_MODE_ANSI_16,
|
||||||
COLOR_MODE_ANSI_256, /* 256-color ANSI */
|
COLOR_MODE_ANSI_256,
|
||||||
COLOR_MODE_TRUECOLOR, /* 24-bit RGB */
|
COLOR_MODE_TRUECOLOR,
|
||||||
COLOR_MODE_COUNT
|
COLOR_MODE_COUNT
|
||||||
} ColorMode;
|
} ColorMode;
|
||||||
|
|
||||||
/*============================================================================
|
|
||||||
* ANSI COLOR CODES
|
|
||||||
*============================================================================*/
|
|
||||||
#define ANSI_RESET "\033[0m"
|
#define ANSI_RESET "\033[0m"
|
||||||
#define ANSI_BOLD "\033[1m"
|
|
||||||
#define ANSI_DIM "\033[2m"
|
|
||||||
|
|
||||||
/* Foreground colors */
|
|
||||||
#define ANSI_FG_BLACK "\033[30m"
|
|
||||||
#define ANSI_FG_RED "\033[31m"
|
|
||||||
#define ANSI_FG_GREEN "\033[32m"
|
|
||||||
#define ANSI_FG_YELLOW "\033[33m"
|
|
||||||
#define ANSI_FG_BLUE "\033[34m"
|
|
||||||
#define ANSI_FG_MAGENTA "\033[35m"
|
|
||||||
#define ANSI_FG_CYAN "\033[36m"
|
|
||||||
#define ANSI_FG_WHITE "\033[37m"
|
|
||||||
|
|
||||||
/* Bright foreground colors */
|
|
||||||
#define ANSI_FG_BRIGHT_BLACK "\033[90m"
|
|
||||||
#define ANSI_FG_BRIGHT_RED "\033[91m"
|
|
||||||
#define ANSI_FG_BRIGHT_GREEN "\033[92m"
|
|
||||||
#define ANSI_FG_BRIGHT_YELLOW "\033[93m"
|
|
||||||
#define ANSI_FG_BRIGHT_BLUE "\033[94m"
|
|
||||||
#define ANSI_FG_BRIGHT_MAGENTA "\033[95m"
|
|
||||||
#define ANSI_FG_BRIGHT_CYAN "\033[96m"
|
|
||||||
#define ANSI_FG_BRIGHT_WHITE "\033[97m"
|
|
||||||
|
|
||||||
/* Background colors */
|
|
||||||
#define ANSI_BG_BLACK "\033[40m"
|
|
||||||
#define ANSI_BG_RED "\033[41m"
|
|
||||||
#define ANSI_BG_GREEN "\033[42m"
|
|
||||||
#define ANSI_BG_YELLOW "\033[43m"
|
|
||||||
#define ANSI_BG_BLUE "\033[44m"
|
|
||||||
#define ANSI_BG_MAGENTA "\033[45m"
|
|
||||||
#define ANSI_BG_CYAN "\033[46m"
|
|
||||||
#define ANSI_BG_WHITE "\033[47m"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifndef ASCII3D_FONT_H
|
||||||
#define ASCII3D_FONT_H
|
#define ASCII3D_FONT_H
|
||||||
|
|
||||||
@@ -14,31 +7,18 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
// Returns a direct pointer to the 7-byte glyph row data if the char is present
|
||||||
* @brief Get the font glyph data for a character
|
// in our sprite sheet
|
||||||
* @param c Character to look up (A-Z, a-z, 0-9)
|
|
||||||
* @return Pointer to 7-byte glyph data, or NULL if not found
|
|
||||||
*/
|
|
||||||
const unsigned char *font_get_glyph(char c);
|
const unsigned char *font_get_glyph(char c);
|
||||||
|
|
||||||
/**
|
// Validates the bitmap bounds and extracts a specific pixel bit
|
||||||
* @brief Check if a pixel is set in a glyph
|
|
||||||
* @param glyph Pointer to glyph data
|
|
||||||
* @param x X coordinate (0-4)
|
|
||||||
* @param y Y coordinate (0-6)
|
|
||||||
* @return true if pixel is set, false otherwise
|
|
||||||
*/
|
|
||||||
bool font_pixel_set(const unsigned char *glyph, int x, int y);
|
bool font_pixel_set(const unsigned char *glyph, int x, int y);
|
||||||
|
|
||||||
/**
|
// Tests an ASCII character against our mapped glyph dictionary
|
||||||
* @brief Check if character is renderable
|
|
||||||
* @param c Character to check
|
|
||||||
* @return true if character can be rendered
|
|
||||||
*/
|
|
||||||
bool font_is_renderable(char c);
|
bool font_is_renderable(char c);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* ASCII3D_FONT_H */
|
#endif
|
||||||
|
|||||||
@@ -1,65 +1,45 @@
|
|||||||
/**
|
|
||||||
* @file lighting.h
|
|
||||||
* @brief Advanced lighting system for ASCII 3D Renderer
|
|
||||||
* @author ASCII3D Project
|
|
||||||
* @version 2.0.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASCII3D_LIGHTING_H
|
#ifndef ASCII3D_LIGHTING_H
|
||||||
#define ASCII3D_LIGHTING_H
|
#define ASCII3D_LIGHTING_H
|
||||||
|
|
||||||
#include "vec3.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "vec3.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Light types
|
|
||||||
*/
|
|
||||||
typedef enum LightType {
|
typedef enum LightType {
|
||||||
LIGHT_DIRECTIONAL = 0, /* Parallel rays (sun-like) */
|
LIGHT_DIRECTIONAL = 0,
|
||||||
LIGHT_POINT, /* Point source with falloff */
|
LIGHT_POINT,
|
||||||
LIGHT_SPOT /* Cone-shaped spotlight */
|
LIGHT_SPOT
|
||||||
} LightType;
|
} LightType;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief RGB Color structure
|
|
||||||
*/
|
|
||||||
typedef struct Color {
|
typedef struct Color {
|
||||||
float r, g, b;
|
float r, g, b;
|
||||||
} Color;
|
} Color;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Light source structure
|
|
||||||
*/
|
|
||||||
typedef struct Light {
|
typedef struct Light {
|
||||||
LightType type;
|
LightType type;
|
||||||
Vec3 position; /* Position for point/spot lights */
|
Vec3 position;
|
||||||
Vec3 direction; /* Direction for directional/spot lights */
|
Vec3 direction;
|
||||||
Color color; /* Light color */
|
Color color;
|
||||||
float intensity; /* Light intensity multiplier */
|
float intensity;
|
||||||
float falloff; /* Attenuation for point lights */
|
float falloff;
|
||||||
float spot_angle; /* Cone angle for spotlights (radians) */
|
float spot_angle;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
} Light;
|
} Light;
|
||||||
|
|
||||||
/**
|
// PBR surface components
|
||||||
* @brief Material properties for surfaces
|
|
||||||
*/
|
|
||||||
typedef struct Material {
|
typedef struct Material {
|
||||||
Color ambient; /* Ambient color */
|
Color ambient;
|
||||||
Color diffuse; /* Diffuse color */
|
Color diffuse;
|
||||||
Color specular; /* Specular highlight color */
|
Color specular;
|
||||||
float shininess; /* Specular exponent */
|
float shininess;
|
||||||
float reflectivity; /* For future use */
|
float reflectivity;
|
||||||
} Material;
|
} Material;
|
||||||
|
|
||||||
/**
|
// Encapsulates the entire render context's virtual ecosystem
|
||||||
* @brief Lighting system state
|
|
||||||
*/
|
|
||||||
typedef struct LightingSystem {
|
typedef struct LightingSystem {
|
||||||
Light lights[MAX_LIGHTS];
|
Light lights[MAX_LIGHTS];
|
||||||
int light_count;
|
int light_count;
|
||||||
@@ -68,103 +48,34 @@ typedef struct LightingSystem {
|
|||||||
Vec3 camera_position;
|
Vec3 camera_position;
|
||||||
} LightingSystem;
|
} LightingSystem;
|
||||||
|
|
||||||
/**
|
// Instantiates default 3-point portrait lighting scheme
|
||||||
* @brief Initialize the lighting system with default lights
|
|
||||||
* @param system Lighting system to initialize
|
|
||||||
*/
|
|
||||||
void lighting_init(LightingSystem *system);
|
void lighting_init(LightingSystem *system);
|
||||||
|
|
||||||
/**
|
// Registers a new light emitter into the system context
|
||||||
* @brief Add a light to the system
|
|
||||||
* @param system Lighting system
|
|
||||||
* @param light Light to add
|
|
||||||
* @return Index of added light, or -1 if full
|
|
||||||
*/
|
|
||||||
int lighting_add_light(LightingSystem *system, const Light *light);
|
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_directional(Vec3 direction, Color color, float intensity);
|
||||||
|
Light lighting_create_point(Vec3 position, Color color, float intensity,
|
||||||
|
float falloff);
|
||||||
|
|
||||||
/**
|
// Computes monochrome or RGB shading based on surface interactions
|
||||||
* @brief Create a point light
|
float lighting_calculate(const LightingSystem *system, Vec3 point, Vec3 normal,
|
||||||
* @param position Light position
|
const Material *material);
|
||||||
* @param color Light color
|
|
||||||
* @param intensity Light intensity
|
|
||||||
* @param falloff Attenuation factor
|
|
||||||
* @return Configured light structure
|
|
||||||
*/
|
|
||||||
Light lighting_create_point(Vec3 position, Color color, float intensity, float falloff);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculate lighting at a surface point (Phong model)
|
|
||||||
* @param system Lighting system
|
|
||||||
* @param point Surface point position
|
|
||||||
* @param normal Surface normal (must be normalized)
|
|
||||||
* @param material Surface material
|
|
||||||
* @return Final illumination value (0.0 - 1.0+)
|
|
||||||
*/
|
|
||||||
float lighting_calculate(const LightingSystem *system, Vec3 point,
|
|
||||||
Vec3 normal, const Material *material);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculate full color lighting
|
|
||||||
* @param system Lighting system
|
|
||||||
* @param point Surface point position
|
|
||||||
* @param normal Surface normal
|
|
||||||
* @param material Surface material
|
|
||||||
* @return Final color
|
|
||||||
*/
|
|
||||||
Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
|
Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
|
||||||
Vec3 normal, const Material *material);
|
Vec3 normal, const Material *material);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create default material
|
|
||||||
* @return Default white material
|
|
||||||
*/
|
|
||||||
Material lighting_default_material(void);
|
Material lighting_default_material(void);
|
||||||
|
|
||||||
/**
|
// Floating point color combinators
|
||||||
* @brief Create a color
|
|
||||||
* @param r Red (0-1)
|
|
||||||
* @param g Green (0-1)
|
|
||||||
* @param b Blue (0-1)
|
|
||||||
* @return Color structure
|
|
||||||
*/
|
|
||||||
Color color_create(float r, float g, float b);
|
Color color_create(float r, float g, float b);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Multiply color by scalar
|
|
||||||
*/
|
|
||||||
Color color_scale(Color c, float s);
|
Color color_scale(Color c, float s);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Add two colors
|
|
||||||
*/
|
|
||||||
Color color_add(Color a, Color b);
|
Color color_add(Color a, Color b);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Multiply two colors component-wise
|
|
||||||
*/
|
|
||||||
Color color_multiply(Color a, Color b);
|
Color color_multiply(Color a, Color b);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Clamp color components to 0-1 range
|
|
||||||
*/
|
|
||||||
Color color_clamp(Color c);
|
Color color_clamp(Color c);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Convert color to grayscale intensity
|
|
||||||
*/
|
|
||||||
float color_to_intensity(Color c);
|
float color_to_intensity(Color c);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifndef ASCII3D_RENDERER_H
|
||||||
#define ASCII3D_RENDERER_H
|
#define ASCII3D_RENDERER_H
|
||||||
|
|
||||||
@@ -16,9 +9,6 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Rotation state for animation
|
|
||||||
*/
|
|
||||||
typedef struct RotationState {
|
typedef struct RotationState {
|
||||||
float angle_x;
|
float angle_x;
|
||||||
float angle_y;
|
float angle_y;
|
||||||
@@ -29,151 +19,63 @@ typedef struct RotationState {
|
|||||||
float speed;
|
float speed;
|
||||||
} RotationState;
|
} RotationState;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Render settings structure
|
|
||||||
*/
|
|
||||||
typedef struct RenderSettings {
|
typedef struct RenderSettings {
|
||||||
RenderMode mode; /* Rendering mode */
|
RenderMode mode;
|
||||||
ColorMode color_mode; /* Color output mode */
|
ColorMode color_mode;
|
||||||
bool anti_aliasing; /* Enable AA */
|
bool anti_aliasing;
|
||||||
bool show_fps; /* Display FPS counter */
|
bool show_fps;
|
||||||
bool auto_rotate; /* Auto rotation enabled */
|
bool auto_rotate;
|
||||||
float quality; /* Quality multiplier (0.5 - 2.0) */
|
float quality;
|
||||||
int palette_index; /* Shading palette selection */
|
int palette_index;
|
||||||
Color base_color; /* Base color for colored modes */
|
Color base_color;
|
||||||
Color highlight_color; /* Highlight/specular color */
|
Color highlight_color;
|
||||||
} RenderSettings;
|
} RenderSettings;
|
||||||
|
|
||||||
/**
|
// Diagnostics profile updated each tick
|
||||||
* @brief Frame statistics
|
|
||||||
*/
|
|
||||||
typedef struct FrameStats {
|
typedef struct FrameStats {
|
||||||
double frame_time; /* Last frame time in ms */
|
double frame_time;
|
||||||
double fps; /* Current FPS */
|
double fps;
|
||||||
double avg_fps; /* Average FPS */
|
double avg_fps;
|
||||||
int frame_count; /* Total frames rendered */
|
int frame_count;
|
||||||
int triangles; /* Triangles/voxels rendered */
|
int triangles;
|
||||||
} FrameStats;
|
} FrameStats;
|
||||||
|
|
||||||
/**
|
// Core Engine Lifecycles
|
||||||
* @brief Initialize the renderer
|
|
||||||
* @return 0 on success, -1 on failure
|
|
||||||
*/
|
|
||||||
int renderer_init(void);
|
int renderer_init(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Cleanup renderer resources
|
|
||||||
*/
|
|
||||||
void renderer_cleanup(void);
|
void renderer_cleanup(void);
|
||||||
|
|
||||||
/**
|
// Nulls out all raster buffers ready for drawing
|
||||||
* @brief Clear the screen and depth buffers
|
|
||||||
*/
|
|
||||||
void renderer_clear(void);
|
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);
|
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,
|
void renderer_draw_text_ex(const char *text, const RotationState *rotation,
|
||||||
const RenderSettings *settings);
|
const RenderSettings *settings);
|
||||||
|
|
||||||
/**
|
// Flushes the rendering pipeline out to standard output
|
||||||
* @brief Display the rendered frame to terminal
|
|
||||||
*/
|
|
||||||
void renderer_present(void);
|
void renderer_present(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Present with color support
|
|
||||||
* @param settings Render settings for color mode
|
|
||||||
*/
|
|
||||||
void renderer_present_color(const RenderSettings *settings);
|
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);
|
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);
|
RotationState renderer_default_rotation(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create default render settings
|
|
||||||
* @return Default settings
|
|
||||||
*/
|
|
||||||
RenderSettings renderer_default_settings(void);
|
RenderSettings renderer_default_settings(void);
|
||||||
|
|
||||||
/**
|
// Dynamic rendering reconfiguration toggles
|
||||||
* @brief Set the render mode
|
|
||||||
* @param mode New render mode
|
|
||||||
*/
|
|
||||||
void renderer_set_mode(RenderMode mode);
|
void renderer_set_mode(RenderMode mode);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the color mode
|
|
||||||
* @param mode New color mode
|
|
||||||
*/
|
|
||||||
void renderer_set_color_mode(ColorMode 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);
|
void renderer_set_palette(int palette_index);
|
||||||
|
|
||||||
/**
|
FrameStats renderer_get_stats(void);
|
||||||
* @brief Get the lighting system for modification
|
|
||||||
* @return Pointer to lighting system
|
|
||||||
*/
|
|
||||||
LightingSystem *renderer_get_lighting(void);
|
LightingSystem *renderer_get_lighting(void);
|
||||||
|
|
||||||
/**
|
// Console visual hacks
|
||||||
* @brief Hide terminal cursor
|
|
||||||
*/
|
|
||||||
void renderer_hide_cursor(void);
|
void renderer_hide_cursor(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Show terminal cursor
|
|
||||||
*/
|
|
||||||
void renderer_show_cursor(void);
|
void renderer_show_cursor(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Clear terminal screen
|
|
||||||
*/
|
|
||||||
void renderer_clear_terminal(void);
|
void renderer_clear_terminal(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set terminal to alternate screen buffer
|
|
||||||
*/
|
|
||||||
void renderer_enter_alternate_screen(void);
|
void renderer_enter_alternate_screen(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Restore terminal to main screen buffer
|
|
||||||
*/
|
|
||||||
void renderer_exit_alternate_screen(void);
|
void renderer_exit_alternate_screen(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifndef ASCII3D_TIMING_H
|
||||||
#define ASCII3D_TIMING_H
|
#define ASCII3D_TIMING_H
|
||||||
|
|
||||||
@@ -12,27 +5,18 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
// Returns the monotonic wall clock time in seconds, suitable for frame timing.
|
||||||
* @brief Get current time in seconds (monotonic clock)
|
|
||||||
* @return Time in seconds with nanosecond precision
|
|
||||||
*/
|
|
||||||
double timing_get_seconds(void);
|
double timing_get_seconds(void);
|
||||||
|
|
||||||
/**
|
// Microsecond-scale thread suspension layer.
|
||||||
* @brief Sleep for specified microseconds
|
|
||||||
* @param microseconds Duration to sleep
|
|
||||||
*/
|
|
||||||
void timing_sleep_us(unsigned int microseconds);
|
void timing_sleep_us(unsigned int microseconds);
|
||||||
|
|
||||||
/**
|
// Calculates dynamic sleep padding against a target FPS to prevent high CPU
|
||||||
* @brief Frame rate limiter - sleeps to maintain target FPS
|
// utilization
|
||||||
* @param frame_start_time Time when frame started (from timing_get_seconds)
|
|
||||||
* @param target_fps Target frames per second
|
|
||||||
*/
|
|
||||||
void timing_limit_fps(double frame_start_time, int target_fps);
|
void timing_limit_fps(double frame_start_time, int target_fps);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifndef ASCII3D_VEC3_H
|
||||||
#define ASCII3D_VEC3_H
|
#define ASCII3D_VEC3_H
|
||||||
|
|
||||||
@@ -12,104 +5,29 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
// A standard 3-component float vector
|
||||||
* @brief 3D Vector structure
|
|
||||||
*/
|
|
||||||
typedef struct Vec3 {
|
typedef struct Vec3 {
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
float z;
|
float z;
|
||||||
} Vec3;
|
} 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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
Vec3 vec3_cross(Vec3 a, Vec3 b);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculate length of a vector
|
|
||||||
* @param v Vector
|
|
||||||
* @return Length
|
|
||||||
*/
|
|
||||||
float vec3_length(Vec3 v);
|
float vec3_length(Vec3 v);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Normalize a vector
|
|
||||||
* @param v Vector to normalize
|
|
||||||
* @return Normalized vector
|
|
||||||
*/
|
|
||||||
Vec3 vec3_normalize(Vec3 v);
|
Vec3 vec3_normalize(Vec3 v);
|
||||||
|
|
||||||
/**
|
// Rotating points through standard Euler coordinate transformations
|
||||||
* @brief Rotate vector around X axis
|
|
||||||
* @param v Vector to rotate
|
|
||||||
* @param angle Angle in radians
|
|
||||||
* @return Rotated vector
|
|
||||||
*/
|
|
||||||
Vec3 vec3_rotate_x(Vec3 v, float angle);
|
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);
|
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);
|
Vec3 vec3_rotate_z(Vec3 v, float angle);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* ASCII3D_VEC3_H */
|
#endif
|
||||||
|
|||||||
130
src/font.c
130
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 "font.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
/**
|
// 5x7 ASCII grid font map.
|
||||||
* @brief 5x7 bitmap font data
|
// Each bitmaps uses 7 bytes representing rows. Bits 4 through 0 denote the set
|
||||||
* Each character is represented by 7 bytes (rows)
|
// pixels.
|
||||||
* Each byte contains 5 bits (columns), MSB = leftmost pixel
|
|
||||||
*/
|
|
||||||
static const unsigned char g_font_data[][FONT_HEIGHT] = {
|
static const unsigned char g_font_data[][FONT_HEIGHT] = {
|
||||||
/* A */ {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
|
// A -> Z
|
||||||
/* B */ {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E},
|
{0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // A
|
||||||
/* C */ {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E},
|
{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E},
|
||||||
/* D */ {0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C},
|
{0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E},
|
||||||
/* E */ {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F},
|
{0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C},
|
||||||
/* F */ {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10},
|
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}, // E
|
||||||
/* G */ {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F},
|
{0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10},
|
||||||
/* H */ {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
|
{0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F},
|
||||||
/* I */ {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E},
|
{0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
|
||||||
/* J */ {0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C},
|
{0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, // I
|
||||||
/* K */ {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11},
|
{0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C},
|
||||||
/* L */ {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F},
|
{0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11},
|
||||||
/* M */ {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11},
|
{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F},
|
||||||
/* N */ {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11},
|
{0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, // M
|
||||||
/* O */ {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
|
{0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11},
|
||||||
/* P */ {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10},
|
{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
|
||||||
/* Q */ {0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D},
|
{0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10},
|
||||||
/* R */ {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11},
|
{0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D}, // Q
|
||||||
/* S */ {0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E},
|
{0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11},
|
||||||
/* T */ {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04},
|
{0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E},
|
||||||
/* U */ {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
|
{0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04},
|
||||||
/* V */ {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04},
|
{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // U
|
||||||
/* W */ {0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A},
|
{0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04},
|
||||||
/* X */ {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11},
|
{0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A},
|
||||||
/* Y */ {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04},
|
{0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11},
|
||||||
/* Z */ {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F},
|
{0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04}, // Y
|
||||||
/* 0 */ {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E},
|
{0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F}, // Z
|
||||||
/* 1 */ {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E},
|
// 0 -> 9
|
||||||
/* 2 */ {0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F},
|
{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, // 0
|
||||||
/* 3 */ {0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E},
|
{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E},
|
||||||
/* 4 */ {0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02},
|
{0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F},
|
||||||
/* 5 */ {0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E},
|
{0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E},
|
||||||
/* 6 */ {0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E},
|
{0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, // 4
|
||||||
/* 7 */ {0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08},
|
{0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E},
|
||||||
/* 8 */ {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E},
|
{0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E},
|
||||||
/* 9 */ {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C},
|
{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_LETTER_COUNT 26
|
||||||
#define FONT_DIGIT_COUNT 10
|
#define FONT_DIGIT_COUNT 10
|
||||||
|
|
||||||
/**
|
static int font_get_index(char c) {
|
||||||
* @brief Get font index for a character
|
if (c >= 'A' && c <= 'Z')
|
||||||
* @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';
|
return c - 'A';
|
||||||
}
|
if (c >= 'a' && c <= 'z')
|
||||||
if (c >= 'a' && c <= 'z') {
|
|
||||||
return c - 'a';
|
return c - 'a';
|
||||||
}
|
if (c >= '0' && c <= '9')
|
||||||
if (c >= '0' && c <= '9') {
|
|
||||||
return FONT_LETTER_COUNT + (c - '0');
|
return FONT_LETTER_COUNT + (c - '0');
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned char *font_get_glyph(char c)
|
const unsigned char *font_get_glyph(char c) {
|
||||||
{
|
|
||||||
int index = font_get_index(c);
|
int index = font_get_index(c);
|
||||||
if (index < 0) {
|
if (index < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
return g_font_data[index];
|
return g_font_data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool font_pixel_set(const unsigned char *glyph, int x, int y)
|
bool font_pixel_set(const unsigned char *glyph, int x, int y) {
|
||||||
{
|
if (glyph == NULL)
|
||||||
if (glyph == NULL) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
if (x < 0 || x >= FONT_WIDTH || y < 0 || y >= FONT_HEIGHT)
|
||||||
if (x < 0 || x >= FONT_WIDTH || y < 0 || y >= FONT_HEIGHT) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
// Map bit flags directly onto screen pixel planes
|
||||||
/* Bit 4 is leftmost (x=0), bit 0 is rightmost (x=4) */
|
|
||||||
return (glyph[y] & (1 << (FONT_WIDTH - 1 - x))) != 0;
|
return (glyph[y] & (1 << (FONT_WIDTH - 1 - x))) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool font_is_renderable(char c)
|
bool font_is_renderable(char c) { return font_get_index(c) >= 0; }
|
||||||
{
|
|
||||||
return font_get_index(c) >= 0;
|
|
||||||
}
|
|
||||||
|
|||||||
173
src/lighting.c
173
src/lighting.c
@@ -1,17 +1,10 @@
|
|||||||
/**
|
|
||||||
* @file lighting.c
|
|
||||||
* @brief Advanced lighting system implementation
|
|
||||||
* @author ASCII3D Project
|
|
||||||
* @version 2.0.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "lighting.h"
|
#include "lighting.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
void lighting_init(LightingSystem *system)
|
void lighting_init(LightingSystem *system) {
|
||||||
{
|
if (system == NULL)
|
||||||
if (system == NULL) return;
|
return;
|
||||||
|
|
||||||
memset(system, 0, sizeof(LightingSystem));
|
memset(system, 0, sizeof(LightingSystem));
|
||||||
|
|
||||||
@@ -20,162 +13,133 @@ void lighting_init(LightingSystem *system)
|
|||||||
system->camera_position = vec3_create(0.0f, 0.0f, -CAMERA_DISTANCE);
|
system->camera_position = vec3_create(0.0f, 0.0f, -CAMERA_DISTANCE);
|
||||||
system->light_count = 0;
|
system->light_count = 0;
|
||||||
|
|
||||||
/* Add default key light (main light from upper-right-front) */
|
// Default 3-point light setup for standard rendering
|
||||||
|
// Key light
|
||||||
Light key_light = lighting_create_directional(
|
Light key_light = lighting_create_directional(
|
||||||
vec3_create(0.5f, 0.8f, 1.0f),
|
vec3_create(0.5f, 0.8f, 1.0f), color_create(1.0f, 0.98f, 0.95f),
|
||||||
color_create(1.0f, 0.98f, 0.95f),
|
DIFFUSE_INTENSITY);
|
||||||
DIFFUSE_INTENSITY
|
|
||||||
);
|
|
||||||
lighting_add_light(system, &key_light);
|
lighting_add_light(system, &key_light);
|
||||||
|
|
||||||
/* Add fill light (softer light from left) */
|
// Fill light
|
||||||
Light fill_light = lighting_create_directional(
|
Light fill_light = lighting_create_directional(
|
||||||
vec3_create(-0.7f, 0.3f, 0.5f),
|
vec3_create(-0.7f, 0.3f, 0.5f), color_create(0.6f, 0.7f, 1.0f), 0.3f);
|
||||||
color_create(0.6f, 0.7f, 1.0f),
|
|
||||||
0.3f
|
|
||||||
);
|
|
||||||
lighting_add_light(system, &fill_light);
|
lighting_add_light(system, &fill_light);
|
||||||
|
|
||||||
/* Add rim/back light for edge definition */
|
// Rim light to edge definition
|
||||||
Light rim_light = lighting_create_directional(
|
Light rim_light = lighting_create_directional(
|
||||||
vec3_create(0.0f, -0.5f, -1.0f),
|
vec3_create(0.0f, -0.5f, -1.0f), color_create(1.0f, 0.9f, 0.8f), 0.2f);
|
||||||
color_create(1.0f, 0.9f, 0.8f),
|
|
||||||
0.2f
|
|
||||||
);
|
|
||||||
lighting_add_light(system, &rim_light);
|
lighting_add_light(system, &rim_light);
|
||||||
}
|
}
|
||||||
|
|
||||||
int lighting_add_light(LightingSystem *system, const Light *light)
|
int lighting_add_light(LightingSystem *system, const Light *light) {
|
||||||
{
|
if (system == NULL || light == NULL)
|
||||||
if (system == NULL || light == NULL) return -1;
|
return -1;
|
||||||
if (system->light_count >= MAX_LIGHTS) return -1;
|
if (system->light_count >= MAX_LIGHTS)
|
||||||
|
return -1;
|
||||||
system->lights[system->light_count] = *light;
|
system->lights[system->light_count] = *light;
|
||||||
return system->light_count++;
|
return system->light_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Light lighting_create_directional(Vec3 direction, Color color, float intensity)
|
Light lighting_create_directional(Vec3 direction, Color color,
|
||||||
{
|
float intensity) {
|
||||||
Light light;
|
Light light;
|
||||||
memset(&light, 0, sizeof(Light));
|
memset(&light, 0, sizeof(Light));
|
||||||
|
|
||||||
light.type = LIGHT_DIRECTIONAL;
|
light.type = LIGHT_DIRECTIONAL;
|
||||||
light.direction = vec3_normalize(direction);
|
light.direction = vec3_normalize(direction);
|
||||||
light.color = color;
|
light.color = color;
|
||||||
light.intensity = intensity;
|
light.intensity = intensity;
|
||||||
light.enabled = true;
|
light.enabled = true;
|
||||||
|
|
||||||
return light;
|
return light;
|
||||||
}
|
}
|
||||||
|
|
||||||
Light lighting_create_point(Vec3 position, Color color, float intensity, float falloff)
|
Light lighting_create_point(Vec3 position, Color color, float intensity,
|
||||||
{
|
float falloff) {
|
||||||
Light light;
|
Light light;
|
||||||
memset(&light, 0, sizeof(Light));
|
memset(&light, 0, sizeof(Light));
|
||||||
|
|
||||||
light.type = LIGHT_POINT;
|
light.type = LIGHT_POINT;
|
||||||
light.position = position;
|
light.position = position;
|
||||||
light.color = color;
|
light.color = color;
|
||||||
light.intensity = intensity;
|
light.intensity = intensity;
|
||||||
light.falloff = falloff;
|
light.falloff = falloff;
|
||||||
light.enabled = true;
|
light.enabled = true;
|
||||||
|
|
||||||
return light;
|
return light;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static float calculate_diffuse(Vec3 light_dir, Vec3 normal) {
|
||||||
* @brief Calculate diffuse lighting contribution
|
|
||||||
*/
|
|
||||||
static float calculate_diffuse(Vec3 light_dir, Vec3 normal)
|
|
||||||
{
|
|
||||||
float ndotl = vec3_dot(normal, light_dir);
|
float ndotl = vec3_dot(normal, light_dir);
|
||||||
return fmaxf(0.0f, ndotl);
|
return fmaxf(0.0f, ndotl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static float calculate_specular(Vec3 light_dir, Vec3 normal, Vec3 view_dir,
|
||||||
* @brief Calculate specular lighting contribution (Blinn-Phong)
|
float shininess) {
|
||||||
*/
|
// Blinn-Phong half vector
|
||||||
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));
|
Vec3 half_vec = vec3_normalize(vec3_add(light_dir, view_dir));
|
||||||
float ndoth = vec3_dot(normal, half_vec);
|
float ndoth = vec3_dot(normal, half_vec);
|
||||||
|
if (ndoth <= 0.0f)
|
||||||
if (ndoth <= 0.0f) return 0.0f;
|
return 0.0f;
|
||||||
|
|
||||||
return powf(ndoth, shininess);
|
return powf(ndoth, shininess);
|
||||||
}
|
}
|
||||||
|
|
||||||
float lighting_calculate(const LightingSystem *system, Vec3 point,
|
float lighting_calculate(const LightingSystem *system, Vec3 point, Vec3 normal,
|
||||||
Vec3 normal, const Material *material)
|
const Material *material) {
|
||||||
{
|
if (system == NULL || material == NULL)
|
||||||
if (system == NULL || material == NULL) return 0.5f;
|
return 0.5f;
|
||||||
|
|
||||||
/* Start with ambient */
|
|
||||||
float total = system->ambient_intensity;
|
float total = system->ambient_intensity;
|
||||||
|
|
||||||
/* View direction (from point to camera) */
|
|
||||||
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
|
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
|
||||||
|
|
||||||
/* Accumulate contribution from each light */
|
// Accumulate each light source against surface topology
|
||||||
for (int i = 0; i < system->light_count; i++) {
|
for (int i = 0; i < system->light_count; i++) {
|
||||||
const Light *light = &system->lights[i];
|
const Light *light = &system->lights[i];
|
||||||
if (!light->enabled) continue;
|
if (!light->enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
Vec3 light_dir;
|
Vec3 light_dir;
|
||||||
float attenuation = 1.0f;
|
float attenuation = 1.0f;
|
||||||
|
|
||||||
if (light->type == LIGHT_DIRECTIONAL) {
|
if (light->type == LIGHT_DIRECTIONAL) {
|
||||||
/* Directional light - direction is constant */
|
|
||||||
light_dir = light->direction;
|
light_dir = light->direction;
|
||||||
} else if (light->type == LIGHT_POINT) {
|
} else if (light->type == LIGHT_POINT) {
|
||||||
/* Point light - calculate direction and attenuation */
|
|
||||||
Vec3 to_light = vec3_sub(light->position, point);
|
Vec3 to_light = vec3_sub(light->position, point);
|
||||||
float dist = vec3_length(to_light);
|
float dist = vec3_length(to_light);
|
||||||
light_dir = vec3_scale(to_light, 1.0f / dist);
|
light_dir = vec3_scale(to_light, 1.0f / dist);
|
||||||
|
// Inverse-square law derived falloff
|
||||||
attenuation = 1.0f / (1.0f + light->falloff * dist * dist);
|
attenuation = 1.0f / (1.0f + light->falloff * dist * dist);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Diffuse */
|
|
||||||
float diffuse = calculate_diffuse(light_dir, normal);
|
float diffuse = calculate_diffuse(light_dir, normal);
|
||||||
|
float specular =
|
||||||
|
calculate_specular(light_dir, normal, view_dir, material->shininess);
|
||||||
|
|
||||||
/* Specular */
|
float contribution =
|
||||||
float specular = calculate_specular(light_dir, normal, view_dir, material->shininess);
|
(diffuse * DIFFUSE_INTENSITY + specular * SPECULAR_INTENSITY) *
|
||||||
|
|
||||||
/* Combine */
|
|
||||||
float contribution = (diffuse * DIFFUSE_INTENSITY +
|
|
||||||
specular * SPECULAR_INTENSITY) *
|
|
||||||
light->intensity * attenuation;
|
light->intensity * attenuation;
|
||||||
|
|
||||||
total += contribution;
|
total += contribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clamp to reasonable range */
|
// We heavily clamp the signal because ASCII has hard boundaries
|
||||||
return fminf(1.0f, fmaxf(0.0f, total));
|
return fminf(1.0f, fmaxf(0.0f, total));
|
||||||
}
|
}
|
||||||
|
|
||||||
Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
|
Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
|
||||||
Vec3 normal, const Material *material)
|
Vec3 normal, const Material *material) {
|
||||||
{
|
|
||||||
if (system == NULL || material == NULL) {
|
if (system == NULL || material == NULL) {
|
||||||
return color_create(0.5f, 0.5f, 0.5f);
|
return color_create(0.5f, 0.5f, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start with ambient */
|
Color result =
|
||||||
Color result = color_scale(
|
color_scale(color_multiply(system->ambient_color, material->ambient),
|
||||||
color_multiply(system->ambient_color, material->ambient),
|
system->ambient_intensity);
|
||||||
system->ambient_intensity
|
|
||||||
);
|
|
||||||
|
|
||||||
/* View direction */
|
|
||||||
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
|
Vec3 view_dir = vec3_normalize(vec3_sub(system->camera_position, point));
|
||||||
|
|
||||||
/* Accumulate from each light */
|
|
||||||
for (int i = 0; i < system->light_count; i++) {
|
for (int i = 0; i < system->light_count; i++) {
|
||||||
const Light *light = &system->lights[i];
|
const Light *light = &system->lights[i];
|
||||||
if (!light->enabled) continue;
|
if (!light->enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
Vec3 light_dir;
|
Vec3 light_dir;
|
||||||
float attenuation = 1.0f;
|
float attenuation = 1.0f;
|
||||||
@@ -191,19 +155,15 @@ Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Diffuse */
|
|
||||||
float diff = calculate_diffuse(light_dir, normal);
|
float diff = calculate_diffuse(light_dir, normal);
|
||||||
Color diffuse = color_scale(
|
Color diffuse = color_scale(color_multiply(light->color, material->diffuse),
|
||||||
color_multiply(light->color, material->diffuse),
|
diff * light->intensity * attenuation);
|
||||||
diff * light->intensity * attenuation
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Specular */
|
float spec =
|
||||||
float spec = calculate_specular(light_dir, normal, view_dir, material->shininess);
|
calculate_specular(light_dir, normal, view_dir, material->shininess);
|
||||||
Color specular = color_scale(
|
Color specular =
|
||||||
color_multiply(light->color, material->specular),
|
color_scale(color_multiply(light->color, material->specular),
|
||||||
spec * SPECULAR_INTENSITY * light->intensity * attenuation
|
spec * SPECULAR_INTENSITY * light->intensity * attenuation);
|
||||||
);
|
|
||||||
|
|
||||||
result = color_add(result, color_add(diffuse, specular));
|
result = color_add(result, color_add(diffuse, specular));
|
||||||
}
|
}
|
||||||
@@ -211,8 +171,7 @@ Color lighting_calculate_color(const LightingSystem *system, Vec3 point,
|
|||||||
return color_clamp(result);
|
return color_clamp(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Material lighting_default_material(void)
|
Material lighting_default_material(void) {
|
||||||
{
|
|
||||||
Material mat;
|
Material mat;
|
||||||
mat.ambient = color_create(0.2f, 0.2f, 0.2f);
|
mat.ambient = color_create(0.2f, 0.2f, 0.2f);
|
||||||
mat.diffuse = color_create(0.8f, 0.8f, 0.8f);
|
mat.diffuse = color_create(0.8f, 0.8f, 0.8f);
|
||||||
@@ -222,38 +181,30 @@ Material lighting_default_material(void)
|
|||||||
return mat;
|
return mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color_create(float r, float g, float b)
|
Color color_create(float r, float g, float b) {
|
||||||
{
|
|
||||||
Color c = {r, g, b};
|
Color c = {r, g, b};
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color_scale(Color c, float s)
|
Color color_scale(Color c, float s) {
|
||||||
{
|
|
||||||
return color_create(c.r * s, c.g * s, c.b * s);
|
return color_create(c.r * s, c.g * s, c.b * s);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color_add(Color a, Color b)
|
Color color_add(Color a, Color b) {
|
||||||
{
|
|
||||||
return color_create(a.r + b.r, a.g + b.g, a.b + b.b);
|
return color_create(a.r + b.r, a.g + b.g, a.b + b.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color_multiply(Color a, Color b)
|
Color color_multiply(Color a, Color b) {
|
||||||
{
|
|
||||||
return color_create(a.r * b.r, a.g * b.g, a.b * b.b);
|
return color_create(a.r * b.r, a.g * b.g, a.b * b.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color color_clamp(Color c)
|
Color color_clamp(Color c) {
|
||||||
{
|
return color_create(fminf(1.0f, fmaxf(0.0f, c.r)),
|
||||||
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.g)),
|
||||||
fminf(1.0f, fmaxf(0.0f, c.b))
|
fminf(1.0f, fmaxf(0.0f, c.b)));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float color_to_intensity(Color c)
|
float color_to_intensity(Color c) {
|
||||||
{
|
// Rec. 601 Luma mapping coefficients
|
||||||
/* Luminance formula (perceptual weights) */
|
|
||||||
return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
|
return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
|
||||||
}
|
}
|
||||||
|
|||||||
234
src/main.c
234
src/main.c
@@ -1,170 +1,120 @@
|
|||||||
/**
|
|
||||||
* @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
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "timing.h"
|
#include "timing.h"
|
||||||
|
#include "tui.h"
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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 volatile sig_atomic_t g_running = 1;
|
||||||
static RenderSettings g_settings;
|
static RenderSettings g_settings;
|
||||||
|
|
||||||
/**
|
static void signal_handler(int sig) {
|
||||||
* @brief Signal handler for graceful shutdown
|
|
||||||
*/
|
|
||||||
static void signal_handler(int sig)
|
|
||||||
{
|
|
||||||
(void)sig;
|
(void)sig;
|
||||||
g_running = 0;
|
g_running = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void setup_signals(void) {
|
||||||
* @brief Setup signal handlers
|
|
||||||
*/
|
|
||||||
static void setup_signals(void)
|
|
||||||
{
|
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
sa.sa_handler = signal_handler;
|
sa.sa_handler = signal_handler;
|
||||||
sigemptyset(&sa.sa_mask);
|
sigemptyset(&sa.sa_mask);
|
||||||
sa.sa_flags = 0;
|
sa.sa_flags = 0;
|
||||||
|
|
||||||
|
// We catch these to orchestrate a clean renderer shutdown
|
||||||
|
// Without this, the terminal will stay trapped in alternate mode
|
||||||
sigaction(SIGINT, &sa, NULL);
|
sigaction(SIGINT, &sa, NULL);
|
||||||
sigaction(SIGTERM, &sa, NULL);
|
sigaction(SIGTERM, &sa, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void print_usage(const char *program_name) {
|
||||||
* @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("\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("Usage: %s [OPTIONS] [TEXT]\n\n", program_name);
|
||||||
|
|
||||||
printf("ROTATION OPTIONS:\n");
|
printf("ROTATION OPTIONS:\n");
|
||||||
printf(" -s <speed> Rotation speed multiplier (default: 1.0)\n");
|
printf(" -s <speed> Playback speed multiplier (default: 1.0)\n");
|
||||||
printf(" -x Enable X-axis rotation\n");
|
printf(" -x Enable X-axis rotation\n");
|
||||||
printf(" -y Enable Y-axis rotation (default)\n");
|
printf(" -y Enable Y-axis rotation (default)\n");
|
||||||
printf(" -z Enable Z-axis rotation\n");
|
printf(" -z Enable Z-axis rotation\n");
|
||||||
printf(" -a Enable all axis rotations\n");
|
printf(" -a Enable all rotational axes\n\n");
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
printf("RENDER MODE OPTIONS:\n");
|
printf("RENDER MODE OPTIONS:\n");
|
||||||
printf(" -m <mode> Render mode:\n");
|
printf(" -m <mode> Mode override:\n");
|
||||||
printf(" 0 = Solid (filled)\n");
|
printf(" 0 = Solid, 1 = Wireframe\n");
|
||||||
printf(" 1 = Wireframe (edges only)\n");
|
printf(" 2 = Points, 3 = Shaded [default]\n\n");
|
||||||
printf(" 2 = Points (sparse)\n");
|
|
||||||
printf(" 3 = Shaded (full Phong lighting) [default]\n");
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
printf("COLOR OPTIONS:\n");
|
printf("COLOR OPTIONS:\n");
|
||||||
printf(" -c <mode> Color mode:\n");
|
printf(" -c <mode> Color strategy:\n");
|
||||||
printf(" 0 = Monochrome ASCII [default]\n");
|
printf(" 0 = Mono [default], 1 = ANSI 16\n");
|
||||||
printf(" 1 = 16-color ANSI\n");
|
printf(" 2 = ANSI 256, 3 = Truecolor RGB\n\n");
|
||||||
printf(" 2 = 256-color ANSI\n");
|
|
||||||
printf(" 3 = Truecolor (24-bit RGB)\n");
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
printf("QUALITY OPTIONS:\n");
|
printf("QUALITY OPTIONS:\n");
|
||||||
printf(" -q <quality> Render quality (0.5 - 2.0, default: 1.0)\n");
|
printf(" -q <quality> Oversampling quality (0.5 - 2.0, default: 1.0)\n");
|
||||||
printf(" -A Enable anti-aliasing\n");
|
printf(" -A Force Anti-aliasing\n");
|
||||||
printf(" -p <palette> Shading palette:\n");
|
printf(" -p <palette> Ascii palette tier (0-3, default: 1)\n\n");
|
||||||
printf(" 0 = Standard (10 levels)\n");
|
|
||||||
printf(" 1 = Extended (70 levels) [default]\n");
|
|
||||||
printf(" 2 = Block characters\n");
|
|
||||||
printf(" 3 = Minimal (6 levels)\n");
|
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
printf("OTHER OPTIONS:\n");
|
printf("OTHER OPTIONS:\n");
|
||||||
printf(" -f Show FPS counter\n");
|
printf(" -f Display real-time FPS overlay\n");
|
||||||
printf(" -h Show this help message\n");
|
printf(" -h Show this help dialog\n\n");
|
||||||
printf("\n");
|
|
||||||
|
|
||||||
printf("EXAMPLES:\n");
|
printf("TUI CONTROLS (active during render):\n");
|
||||||
printf(" %s HELLO Simple Y-axis rotation\n", program_name);
|
printf(" [Typing] Changes dynamically rendered text\n");
|
||||||
printf(" %s -a -c3 WORLD Truecolor tumbling\n", program_name);
|
printf(" [Space] Pause/Resume rotation\n");
|
||||||
printf(" %s -m1 -s2 WIRE Fast wireframe\n", program_name);
|
printf(" [w/s] Modify rotation speed\n");
|
||||||
printf(" %s -A -q1.5 -c2 HQ High quality with AA\n", program_name);
|
printf(" [m] Cycle render modes\n");
|
||||||
printf(" %s -p2 BLOCKS Block character style\n", program_name);
|
printf(" [c] Cycle color pipelines\n");
|
||||||
printf("\n");
|
printf(" [p] Cycle ASCII density palettes\n");
|
||||||
|
printf(" [q / ESC] Terminate gracefully\n\n");
|
||||||
printf("INTERACTIVE KEYS (during rendering):\n");
|
|
||||||
printf(" Ctrl+C Exit\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("Supported characters: A-Z, a-z, 0-9\n");
|
|
||||||
printf("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static bool parse_cli_args(int argc, char *argv[], RotationState *rotation,
|
||||||
* @brief Parse command line arguments
|
char *text_buffer, size_t buf_len) {
|
||||||
*/
|
|
||||||
static bool parse_arguments(int argc, char *argv[],
|
|
||||||
RotationState *rotation, const char **text)
|
|
||||||
{
|
|
||||||
*rotation = renderer_default_rotation();
|
*rotation = renderer_default_rotation();
|
||||||
g_settings = renderer_default_settings();
|
g_settings = renderer_default_settings();
|
||||||
*text = "3D";
|
|
||||||
|
// Set a default demo text if none is supplied
|
||||||
|
strncpy(text_buffer, "3D", buf_len - 1);
|
||||||
|
text_buffer[buf_len - 1] = '\0';
|
||||||
|
|
||||||
bool explicit_rotation = false;
|
bool explicit_rotation = false;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (argv[i][0] == '-') {
|
if (argv[i][0] == '-') {
|
||||||
const char *opt = &argv[i][1];
|
const char *opt = &argv[i][1];
|
||||||
|
|
||||||
while (*opt) {
|
while (*opt) {
|
||||||
switch (*opt) {
|
switch (*opt) {
|
||||||
case 's':
|
case 's':
|
||||||
if (i + 1 < argc) {
|
if (i + 1 < argc) {
|
||||||
rotation->speed = (float)atof(argv[++i]);
|
rotation->speed = (float)atof(argv[++i]);
|
||||||
if (rotation->speed <= 0.0f) rotation->speed = 1.0f;
|
if (rotation->speed <= 0.0f)
|
||||||
|
rotation->speed = 1.0f;
|
||||||
}
|
}
|
||||||
goto next_arg;
|
goto next_arg;
|
||||||
|
|
||||||
case 'x':
|
case 'x':
|
||||||
rotation->enable_x = true;
|
rotation->enable_x = true;
|
||||||
explicit_rotation = true;
|
explicit_rotation = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'y':
|
case 'y':
|
||||||
rotation->enable_y = true;
|
rotation->enable_y = true;
|
||||||
explicit_rotation = true;
|
explicit_rotation = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'z':
|
case 'z':
|
||||||
rotation->enable_z = true;
|
rotation->enable_z = true;
|
||||||
explicit_rotation = true;
|
explicit_rotation = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
rotation->enable_x = true;
|
rotation->enable_x = true;
|
||||||
rotation->enable_y = true;
|
rotation->enable_y = true;
|
||||||
rotation->enable_z = true;
|
rotation->enable_z = true;
|
||||||
explicit_rotation = true;
|
explicit_rotation = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'm':
|
case 'm':
|
||||||
if (i + 1 < argc) {
|
if (i + 1 < argc) {
|
||||||
int mode = atoi(argv[++i]);
|
int mode = atoi(argv[++i]);
|
||||||
@@ -173,7 +123,6 @@ static bool parse_arguments(int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto next_arg;
|
goto next_arg;
|
||||||
|
|
||||||
case 'c':
|
case 'c':
|
||||||
if (i + 1 < argc) {
|
if (i + 1 < argc) {
|
||||||
int cmode = atoi(argv[++i]);
|
int cmode = atoi(argv[++i]);
|
||||||
@@ -182,19 +131,18 @@ static bool parse_arguments(int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto next_arg;
|
goto next_arg;
|
||||||
|
|
||||||
case 'q':
|
case 'q':
|
||||||
if (i + 1 < argc) {
|
if (i + 1 < argc) {
|
||||||
g_settings.quality = (float)atof(argv[++i]);
|
g_settings.quality = (float)atof(argv[++i]);
|
||||||
if (g_settings.quality < 0.5f) g_settings.quality = 0.5f;
|
if (g_settings.quality < 0.5f)
|
||||||
if (g_settings.quality > 2.0f) g_settings.quality = 2.0f;
|
g_settings.quality = 0.5f;
|
||||||
|
if (g_settings.quality > 2.0f)
|
||||||
|
g_settings.quality = 2.0f;
|
||||||
}
|
}
|
||||||
goto next_arg;
|
goto next_arg;
|
||||||
|
|
||||||
case 'A':
|
case 'A':
|
||||||
g_settings.anti_aliasing = true;
|
g_settings.anti_aliasing = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
if (i + 1 < argc) {
|
if (i + 1 < argc) {
|
||||||
int pal = atoi(argv[++i]);
|
int pal = atoi(argv[++i]);
|
||||||
@@ -203,49 +151,49 @@ static bool parse_arguments(int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto next_arg;
|
goto next_arg;
|
||||||
|
|
||||||
case 'f':
|
case 'f':
|
||||||
g_settings.show_fps = true;
|
g_settings.show_fps = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'h':
|
case 'h':
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "Unknown option: -%c\n", *opt);
|
fprintf(stderr, "Unrecognized param: -%c\n", *opt);
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
opt++;
|
opt++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*text = argv[i];
|
strncpy(text_buffer, argv[i], buf_len - 1);
|
||||||
|
text_buffer[buf_len - 1] = '\0';
|
||||||
}
|
}
|
||||||
next_arg:;
|
next_arg:;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle rotation defaults */
|
// Inject Y-axis animation as a baseline if the user provided no explicit axis
|
||||||
if (explicit_rotation && !rotation->enable_y) {
|
// constraints
|
||||||
/* User explicitly chose axes */
|
if (!explicit_rotation) {
|
||||||
} else if (!explicit_rotation) {
|
|
||||||
rotation->enable_y = true;
|
rotation->enable_y = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void tui_help_overlay(void) {
|
||||||
* @brief Main render loop with advanced features
|
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",
|
||||||
static void render_loop(const char *text, RotationState *rotation)
|
SCREEN_HEIGHT);
|
||||||
{
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void core_render_loop(char *text_input, RotationState *rotation) {
|
||||||
double last_time = timing_get_seconds();
|
double last_time = timing_get_seconds();
|
||||||
double fps_update_time = last_time;
|
double fps_update_time = last_time;
|
||||||
int frame_count = 0;
|
int frame_count = 0;
|
||||||
double current_fps = 0.0;
|
double current_fps = 0.0;
|
||||||
|
|
||||||
/* Apply settings */
|
// Mount the requested settings into our rendering context
|
||||||
renderer_set_mode(g_settings.mode);
|
renderer_set_mode(g_settings.mode);
|
||||||
renderer_set_color_mode(g_settings.color_mode);
|
renderer_set_color_mode(g_settings.color_mode);
|
||||||
renderer_set_palette(g_settings.palette_index);
|
renderer_set_palette(g_settings.palette_index);
|
||||||
@@ -255,7 +203,17 @@ static void render_loop(const char *text, RotationState *rotation)
|
|||||||
double delta_time = current_time - last_time;
|
double delta_time = current_time - last_time;
|
||||||
last_time = current_time;
|
last_time = current_time;
|
||||||
|
|
||||||
/* Update FPS counter */
|
// 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);
|
||||||
|
|
||||||
frame_count++;
|
frame_count++;
|
||||||
if (current_time - fps_update_time >= 1.0) {
|
if (current_time - fps_update_time >= 1.0) {
|
||||||
current_fps = (double)frame_count / (current_time - fps_update_time);
|
current_fps = (double)frame_count / (current_time - fps_update_time);
|
||||||
@@ -263,68 +221,70 @@ static void render_loop(const char *text, RotationState *rotation)
|
|||||||
fps_update_time = current_time;
|
fps_update_time = current_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update rotation */
|
if (g_settings.auto_rotate) {
|
||||||
renderer_update_rotation(rotation, delta_time);
|
renderer_update_rotation(rotation, delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
/* Render frame */
|
|
||||||
renderer_clear();
|
renderer_clear();
|
||||||
renderer_draw_text_ex(text, rotation, &g_settings);
|
renderer_draw_text_ex(text_input, rotation, &g_settings);
|
||||||
|
|
||||||
/* Present with appropriate color mode */
|
|
||||||
if (g_settings.color_mode != COLOR_MODE_MONO) {
|
if (g_settings.color_mode != COLOR_MODE_MONO) {
|
||||||
renderer_present_color(&g_settings);
|
renderer_present_color(&g_settings);
|
||||||
} else {
|
} else {
|
||||||
renderer_present();
|
renderer_present();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show FPS if enabled */
|
|
||||||
if (g_settings.show_fps) {
|
if (g_settings.show_fps) {
|
||||||
printf("\033[1;1H\033[7m FPS: %.1f | Voxels: %d \033[0m",
|
printf("\033[1;1H\033[7m FPS: %.1f | Voxels: %d \033[0m", current_fps,
|
||||||
current_fps, renderer_get_stats().triangles);
|
renderer_get_stats().triangles);
|
||||||
fflush(stdout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Limit frame rate */
|
tui_help_overlay();
|
||||||
|
|
||||||
timing_limit_fps(current_time, TARGET_FPS);
|
timing_limit_fps(current_time, TARGET_FPS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
int main(int argc, char *argv[]) {
|
||||||
* @brief Program entry point
|
|
||||||
*/
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
RotationState rotation;
|
RotationState rotation;
|
||||||
const char *text;
|
char text_buffer[64] = {0};
|
||||||
|
|
||||||
/* Parse command line */
|
if (!parse_cli_args(argc, argv, &rotation, text_buffer,
|
||||||
if (!parse_arguments(argc, argv, &rotation, &text)) {
|
sizeof(text_buffer))) {
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Setup signal handlers */
|
|
||||||
setup_signals();
|
setup_signals();
|
||||||
|
|
||||||
/* Initialize renderer */
|
|
||||||
if (renderer_init() != 0) {
|
if (renderer_init() != 0) {
|
||||||
fprintf(stderr, "Failed to initialize renderer\n");
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"Renderer initialization failed due to context limits or IO error.\n");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enter alternate screen buffer for clean exit */
|
// Elevating the console environment
|
||||||
renderer_enter_alternate_screen();
|
renderer_enter_alternate_screen();
|
||||||
renderer_clear_terminal();
|
renderer_clear_terminal();
|
||||||
renderer_hide_cursor();
|
renderer_hide_cursor();
|
||||||
|
|
||||||
/* Run main loop */
|
if (tui_init() < 0) {
|
||||||
render_loop(text, &rotation);
|
renderer_show_cursor();
|
||||||
|
renderer_exit_alternate_screen();
|
||||||
|
renderer_cleanup();
|
||||||
|
fprintf(stderr, "Failed to instantiate TTY interface. Falling back.\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
/* Cleanup */
|
core_render_loop(text_buffer, &rotation);
|
||||||
|
|
||||||
|
// Tearing down custom modes properly ensures the user doesn't end up with a
|
||||||
|
// broken shell prompt
|
||||||
|
tui_cleanup();
|
||||||
renderer_show_cursor();
|
renderer_show_cursor();
|
||||||
renderer_exit_alternate_screen();
|
renderer_exit_alternate_screen();
|
||||||
renderer_cleanup();
|
renderer_cleanup();
|
||||||
|
|
||||||
printf("Goodbye!\n");
|
printf("Goodbye!\n");
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
449
src/renderer.c
449
src/renderer.c
@@ -1,108 +1,54 @@
|
|||||||
/**
|
|
||||||
* @file renderer.c
|
|
||||||
* @brief Advanced rendering engine implementation
|
|
||||||
* @author ASCII3D Project
|
|
||||||
* @version 2.0.0
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Phong lighting model with multiple light sources
|
|
||||||
* - Sub-pixel anti-aliasing
|
|
||||||
* - Multiple render modes (solid, wireframe, points)
|
|
||||||
* - ANSI color support (16, 256, truecolor)
|
|
||||||
* - High-quality ASCII shading with extended palettes
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "vec3.h"
|
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
|
#include "vec3.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
/*============================================================================
|
// Internal Frame Buffer state
|
||||||
* GLOBAL STATE
|
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
/* Screen buffer - character display */
|
|
||||||
static char g_screen[SCREEN_HEIGHT][SCREEN_WIDTH + 1];
|
static char g_screen[SCREEN_HEIGHT][SCREEN_WIDTH + 1];
|
||||||
|
|
||||||
/* Depth buffer for z-testing */
|
|
||||||
static float g_zbuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
static float g_zbuffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||||
|
|
||||||
/* Normal buffer for post-processing effects */
|
|
||||||
static Vec3 g_normal_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
static Vec3 g_normal_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||||
|
|
||||||
/* Color buffer for colored output */
|
|
||||||
static Color g_color_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
static Color g_color_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||||
|
|
||||||
/* Intensity accumulator for anti-aliasing */
|
// Multisampling accumulator layers
|
||||||
static float g_intensity_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
static float g_intensity_buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||||
static int g_sample_count[SCREEN_HEIGHT][SCREEN_WIDTH];
|
static int g_sample_count[SCREEN_HEIGHT][SCREEN_WIDTH];
|
||||||
|
|
||||||
/* Lighting system */
|
|
||||||
static LightingSystem g_lighting;
|
static LightingSystem g_lighting;
|
||||||
|
|
||||||
/* Current render settings */
|
|
||||||
static RenderSettings g_settings;
|
static RenderSettings g_settings;
|
||||||
|
|
||||||
/* Current render mode */
|
|
||||||
static RenderMode g_render_mode = RENDER_MODE_SHADED;
|
static RenderMode g_render_mode = RENDER_MODE_SHADED;
|
||||||
|
|
||||||
/* Current color mode */
|
|
||||||
static ColorMode g_color_mode = COLOR_MODE_MONO;
|
static ColorMode g_color_mode = COLOR_MODE_MONO;
|
||||||
|
|
||||||
/* Frame statistics */
|
|
||||||
static FrameStats g_stats;
|
static FrameStats g_stats;
|
||||||
|
|
||||||
/* Shading palettes */
|
static const char *g_palettes[] = {SHADE_CHARS_STANDARD, SHADE_CHARS_EXTENDED,
|
||||||
static const char *g_palettes[] = {
|
SHADE_CHARS_BLOCK, SHADE_CHARS_MINIMAL};
|
||||||
SHADE_CHARS_STANDARD,
|
static const int g_palette_sizes[] = {SHADE_COUNT_STANDARD,
|
||||||
SHADE_CHARS_EXTENDED,
|
SHADE_COUNT_EXTENDED, SHADE_COUNT_BLOCK,
|
||||||
SHADE_CHARS_BLOCK,
|
SHADE_COUNT_MINIMAL};
|
||||||
SHADE_CHARS_MINIMAL
|
static int g_current_palette = 1;
|
||||||
};
|
|
||||||
static const int g_palette_sizes[] = {
|
|
||||||
SHADE_COUNT_STANDARD,
|
|
||||||
SHADE_COUNT_EXTENDED,
|
|
||||||
SHADE_COUNT_BLOCK,
|
|
||||||
SHADE_COUNT_MINIMAL
|
|
||||||
};
|
|
||||||
static int g_current_palette = 1; /* Extended by default */
|
|
||||||
|
|
||||||
/* Default material */
|
|
||||||
static Material g_material;
|
static Material g_material;
|
||||||
|
|
||||||
/* Voxel counter for stats */
|
|
||||||
static int g_voxel_count;
|
static int g_voxel_count;
|
||||||
|
|
||||||
/*============================================================================
|
// Translates 3D world space points down to 2D screen coordinates, applying FOV
|
||||||
* PROJECTION
|
// projection matrix scaling
|
||||||
*============================================================================*/
|
static bool project_point(Vec3 point, float *out_x, float *out_y,
|
||||||
|
float *out_z) {
|
||||||
/**
|
// Standard terminal font width/height aspect ratio is roughly 2.2 for
|
||||||
* @brief Project a 3D point to 2D screen coordinates with perspective
|
// consistent rendering
|
||||||
*/
|
|
||||||
static bool project_point(Vec3 point, float *out_x, float *out_y, float *out_z)
|
|
||||||
{
|
|
||||||
/* Character aspect ratio compensation */
|
|
||||||
float aspect = (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT / 2.2f;
|
float aspect = (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT / 2.2f;
|
||||||
|
|
||||||
/* Camera space transformation */
|
|
||||||
float z = point.z + CAMERA_DISTANCE;
|
float z = point.z + CAMERA_DISTANCE;
|
||||||
|
|
||||||
/* Near plane clipping */
|
// Discard geometry outside camera frustum planes
|
||||||
if (z < NEAR_PLANE) {
|
if (z < NEAR_PLANE || z > FAR_PLANE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Far plane clipping */
|
|
||||||
if (z > FAR_PLANE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Perspective division */
|
|
||||||
float scale = FIELD_OF_VIEW / z;
|
float scale = FIELD_OF_VIEW / z;
|
||||||
*out_x = point.x * scale * aspect + (float)SCREEN_WIDTH / 2.0f;
|
*out_x = point.x * scale * aspect + (float)SCREEN_WIDTH / 2.0f;
|
||||||
*out_y = -point.y * scale + (float)SCREEN_HEIGHT / 2.0f;
|
*out_y = -point.y * scale + (float)SCREEN_HEIGHT / 2.0f;
|
||||||
@@ -111,47 +57,34 @@ static bool project_point(Vec3 point, float *out_x, float *out_y, float *out_z)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*============================================================================
|
static void plot_point_lit(float sx, float sy, float sz, Vec3 point,
|
||||||
* PIXEL PLOTTING
|
Vec3 normal) {
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Plot a point with full lighting calculation
|
|
||||||
*/
|
|
||||||
static void plot_point_lit(float sx, float sy, float sz, Vec3 point, Vec3 normal)
|
|
||||||
{
|
|
||||||
int ix = (int)sx;
|
int ix = (int)sx;
|
||||||
int iy = (int)sy;
|
int iy = (int)sy;
|
||||||
|
|
||||||
/* Bounds check */
|
if (ix < 0 || ix >= SCREEN_WIDTH || iy < 0 || iy >= SCREEN_HEIGHT)
|
||||||
if (ix < 0 || ix >= SCREEN_WIDTH || iy < 0 || iy >= SCREEN_HEIGHT) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* Depth test */
|
// Obscurance clipping (standard Z-buffer check)
|
||||||
if (sz >= g_zbuffer[iy][ix]) {
|
if (sz >= g_zbuffer[iy][ix])
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
g_zbuffer[iy][ix] = sz;
|
g_zbuffer[iy][ix] = sz;
|
||||||
g_normal_buffer[iy][ix] = normal;
|
g_normal_buffer[iy][ix] = normal;
|
||||||
g_voxel_count++;
|
g_voxel_count++;
|
||||||
|
|
||||||
/* Calculate lighting */
|
|
||||||
float intensity;
|
float intensity;
|
||||||
Color color;
|
Color color;
|
||||||
|
|
||||||
if (g_render_mode == RENDER_MODE_SHADED) {
|
if (g_render_mode == RENDER_MODE_SHADED) {
|
||||||
/* Full Phong lighting */
|
// Evaluate the full lighting model recursively for shading
|
||||||
color = lighting_calculate_color(&g_lighting, point, normal, &g_material);
|
color = lighting_calculate_color(&g_lighting, point, normal, &g_material);
|
||||||
intensity = color_to_intensity(color);
|
intensity = color_to_intensity(color);
|
||||||
} else if (g_render_mode == RENDER_MODE_SOLID) {
|
} else if (g_render_mode == RENDER_MODE_SOLID) {
|
||||||
/* Simple normal-based shading */
|
|
||||||
intensity = (normal.z + 1.0f) * 0.5f;
|
intensity = (normal.z + 1.0f) * 0.5f;
|
||||||
intensity = fmaxf(0.0f, fminf(1.0f, intensity));
|
intensity = fmaxf(0.0f, fminf(1.0f, intensity));
|
||||||
color = color_scale(g_settings.base_color, intensity);
|
color = color_scale(g_settings.base_color, intensity);
|
||||||
} else {
|
} else {
|
||||||
/* Wireframe/points - full brightness */
|
|
||||||
intensity = 1.0f;
|
intensity = 1.0f;
|
||||||
color = g_settings.base_color;
|
color = g_settings.base_color;
|
||||||
}
|
}
|
||||||
@@ -160,35 +93,31 @@ static void plot_point_lit(float sx, float sy, float sz, Vec3 point, Vec3 normal
|
|||||||
g_intensity_buffer[iy][ix] = intensity;
|
g_intensity_buffer[iy][ix] = intensity;
|
||||||
g_sample_count[iy][ix] = 1;
|
g_sample_count[iy][ix] = 1;
|
||||||
|
|
||||||
/* Convert intensity to ASCII character */
|
|
||||||
const char *palette = g_palettes[g_current_palette];
|
const char *palette = g_palettes[g_current_palette];
|
||||||
int palette_size = g_palette_sizes[g_current_palette];
|
int palette_size = g_palette_sizes[g_current_palette];
|
||||||
|
|
||||||
int shade_idx = (int)(intensity * (float)(palette_size - 1) + 0.5f);
|
int shade_idx = (int)(intensity * (float)(palette_size - 1) + 0.5f);
|
||||||
shade_idx = shade_idx < 0 ? 0 : (shade_idx >= palette_size ? palette_size - 1 : shade_idx);
|
shade_idx = shade_idx < 0
|
||||||
|
? 0
|
||||||
|
: (shade_idx >= palette_size ? palette_size - 1 : shade_idx);
|
||||||
g_screen[iy][ix] = palette[shade_idx];
|
g_screen[iy][ix] = palette[shade_idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Sub-pixel accumulation pass for anti-aliasing edges
|
||||||
* @brief Plot with anti-aliasing (accumulate samples)
|
static void plot_point_aa(float sx, float sy, float sz, Vec3 point,
|
||||||
*/
|
Vec3 normal) {
|
||||||
static void plot_point_aa(float sx, float sy, float sz, Vec3 point, Vec3 normal)
|
|
||||||
{
|
|
||||||
/* Sub-pixel offset for AA */
|
|
||||||
int ix = (int)sx;
|
int ix = (int)sx;
|
||||||
int iy = (int)sy;
|
int iy = (int)sy;
|
||||||
|
|
||||||
if (ix < 0 || ix >= SCREEN_WIDTH || iy < 0 || iy >= SCREEN_HEIGHT) {
|
if (ix < 0 || ix >= SCREEN_WIDTH || iy < 0 || iy >= SCREEN_HEIGHT)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* For AA, we accumulate and average */
|
|
||||||
if (sz < g_zbuffer[iy][ix]) {
|
if (sz < g_zbuffer[iy][ix]) {
|
||||||
g_zbuffer[iy][ix] = sz;
|
g_zbuffer[iy][ix] = sz;
|
||||||
g_normal_buffer[iy][ix] = normal;
|
g_normal_buffer[iy][ix] = normal;
|
||||||
|
|
||||||
Color color = lighting_calculate_color(&g_lighting, point, normal, &g_material);
|
Color color =
|
||||||
|
lighting_calculate_color(&g_lighting, point, normal, &g_material);
|
||||||
float intensity = color_to_intensity(color);
|
float intensity = color_to_intensity(color);
|
||||||
|
|
||||||
g_intensity_buffer[iy][ix] += intensity;
|
g_intensity_buffer[iy][ix] += intensity;
|
||||||
@@ -198,104 +127,70 @@ static void plot_point_aa(float sx, float sy, float sz, Vec3 point, Vec3 normal)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*============================================================================
|
static bool neighbor_empty(const unsigned char *glyph, int x, int y, int dx,
|
||||||
* EDGE DETECTION FOR SURFACE RENDERING
|
int dy) {
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
static bool neighbor_empty(const unsigned char *glyph, int x, int y, int dx, int dy)
|
|
||||||
{
|
|
||||||
int nx = x + dx;
|
int nx = x + dx;
|
||||||
int ny = y + dy;
|
int ny = y + dy;
|
||||||
|
if (nx < 0 || nx >= FONT_WIDTH || ny < 0 || ny >= FONT_HEIGHT)
|
||||||
if (nx < 0 || nx >= FONT_WIDTH || ny < 0 || ny >= FONT_HEIGHT) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
return !font_pixel_set(glyph, nx, ny);
|
return !font_pixel_set(glyph, nx, ny);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Derives pseudo-SMOOTH normals for voxel shapes based on neighboring vacancies
|
||||||
* @brief Calculate smooth normal by averaging adjacent face normals
|
|
||||||
*/
|
|
||||||
static Vec3 calculate_smooth_normal(const unsigned char *glyph, int gx, int gy,
|
static Vec3 calculate_smooth_normal(const unsigned char *glyph, int gx, int gy,
|
||||||
float gz, bool is_front, bool is_back)
|
float gz, bool is_front, bool is_back) {
|
||||||
{
|
|
||||||
Vec3 normal = vec3_create(0.0f, 0.0f, 0.0f);
|
Vec3 normal = vec3_create(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
/* Front/back face contribution */
|
if (is_front)
|
||||||
if (is_front) {
|
|
||||||
normal = vec3_add(normal, vec3_create(0.0f, 0.0f, 1.0f));
|
normal = vec3_add(normal, vec3_create(0.0f, 0.0f, 1.0f));
|
||||||
}
|
if (is_back)
|
||||||
if (is_back) {
|
|
||||||
normal = vec3_add(normal, vec3_create(0.0f, 0.0f, -1.0f));
|
normal = vec3_add(normal, vec3_create(0.0f, 0.0f, -1.0f));
|
||||||
}
|
|
||||||
|
|
||||||
/* Side face contributions */
|
if (neighbor_empty(glyph, gx, gy, -1, 0))
|
||||||
if (neighbor_empty(glyph, gx, gy, -1, 0)) {
|
|
||||||
normal = vec3_add(normal, vec3_create(-1.0f, 0.0f, 0.0f));
|
normal = vec3_add(normal, vec3_create(-1.0f, 0.0f, 0.0f));
|
||||||
}
|
if (neighbor_empty(glyph, gx, gy, 1, 0))
|
||||||
if (neighbor_empty(glyph, gx, gy, 1, 0)) {
|
|
||||||
normal = vec3_add(normal, vec3_create(1.0f, 0.0f, 0.0f));
|
normal = vec3_add(normal, vec3_create(1.0f, 0.0f, 0.0f));
|
||||||
}
|
if (neighbor_empty(glyph, gx, gy, 0, -1))
|
||||||
if (neighbor_empty(glyph, gx, gy, 0, -1)) {
|
|
||||||
normal = vec3_add(normal, vec3_create(0.0f, 1.0f, 0.0f));
|
normal = vec3_add(normal, vec3_create(0.0f, 1.0f, 0.0f));
|
||||||
}
|
if (neighbor_empty(glyph, gx, gy, 0, 1))
|
||||||
if (neighbor_empty(glyph, gx, gy, 0, 1)) {
|
|
||||||
normal = vec3_add(normal, vec3_create(0.0f, -1.0f, 0.0f));
|
normal = vec3_add(normal, vec3_create(0.0f, -1.0f, 0.0f));
|
||||||
}
|
|
||||||
|
|
||||||
/* Diagonal contributions for smoother edges */
|
if (neighbor_empty(glyph, gx, gy, -1, -1))
|
||||||
if (neighbor_empty(glyph, gx, gy, -1, -1)) {
|
normal =
|
||||||
normal = vec3_add(normal, vec3_scale(vec3_create(-0.707f, 0.707f, 0.0f), 0.5f));
|
vec3_add(normal, vec3_scale(vec3_create(-0.707f, 0.707f, 0.0f), 0.5f));
|
||||||
}
|
if (neighbor_empty(glyph, gx, gy, 1, -1))
|
||||||
if (neighbor_empty(glyph, gx, gy, 1, -1)) {
|
normal =
|
||||||
normal = vec3_add(normal, vec3_scale(vec3_create(0.707f, 0.707f, 0.0f), 0.5f));
|
vec3_add(normal, vec3_scale(vec3_create(0.707f, 0.707f, 0.0f), 0.5f));
|
||||||
}
|
if (neighbor_empty(glyph, gx, gy, -1, 1))
|
||||||
if (neighbor_empty(glyph, gx, gy, -1, 1)) {
|
normal =
|
||||||
normal = vec3_add(normal, vec3_scale(vec3_create(-0.707f, -0.707f, 0.0f), 0.5f));
|
vec3_add(normal, vec3_scale(vec3_create(-0.707f, -0.707f, 0.0f), 0.5f));
|
||||||
}
|
if (neighbor_empty(glyph, gx, gy, 1, 1))
|
||||||
if (neighbor_empty(glyph, gx, gy, 1, 1)) {
|
normal =
|
||||||
normal = vec3_add(normal, vec3_scale(vec3_create(0.707f, -0.707f, 0.0f), 0.5f));
|
vec3_add(normal, vec3_scale(vec3_create(0.707f, -0.707f, 0.0f), 0.5f));
|
||||||
}
|
|
||||||
|
|
||||||
(void)gz; /* Suppress unused warning */
|
(void)gz;
|
||||||
|
|
||||||
return vec3_normalize(normal);
|
return vec3_normalize(normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*============================================================================
|
static void render_char_advanced(char c, float offset_x,
|
||||||
* CHARACTER RENDERING
|
const RotationState *rotation,
|
||||||
*============================================================================*/
|
const RenderSettings *settings) {
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Render a single 3D character with advanced lighting
|
|
||||||
*/
|
|
||||||
static void render_char_advanced(char c, float offset_x, const RotationState *rotation,
|
|
||||||
const RenderSettings *settings)
|
|
||||||
{
|
|
||||||
const unsigned char *glyph = font_get_glyph(c);
|
const unsigned char *glyph = font_get_glyph(c);
|
||||||
if (glyph == NULL) {
|
if (!glyph)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* Center offsets */
|
|
||||||
float cx = (float)FONT_WIDTH / 2.0f;
|
float cx = (float)FONT_WIDTH / 2.0f;
|
||||||
float cy = (float)FONT_HEIGHT / 2.0f;
|
float cy = (float)FONT_HEIGHT / 2.0f;
|
||||||
float cz = EXTRUSION_DEPTH / 2.0f;
|
float cz = EXTRUSION_DEPTH / 2.0f;
|
||||||
|
|
||||||
/* Quality-adjusted voxel step */
|
|
||||||
float step = VOXEL_STEP / settings->quality;
|
float step = VOXEL_STEP / settings->quality;
|
||||||
|
|
||||||
/* Iterate through each voxel */
|
|
||||||
for (int gy = 0; gy < FONT_HEIGHT; gy++) {
|
for (int gy = 0; gy < FONT_HEIGHT; gy++) {
|
||||||
for (int gx = 0; gx < FONT_WIDTH; gx++) {
|
for (int gx = 0; gx < FONT_WIDTH; gx++) {
|
||||||
if (!font_pixel_set(glyph, gx, gy)) {
|
if (!font_pixel_set(glyph, gx, gy))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
/* Render along Z depth (extrusion) */
|
|
||||||
for (float gz = 0.0f; gz <= EXTRUSION_DEPTH; gz += step) {
|
for (float gz = 0.0f; gz <= EXTRUSION_DEPTH; gz += step) {
|
||||||
/* Determine if this is a surface voxel */
|
|
||||||
bool is_front = (gz < step);
|
bool is_front = (gz < step);
|
||||||
bool is_back = (gz > EXTRUSION_DEPTH - step);
|
bool is_back = (gz > EXTRUSION_DEPTH - step);
|
||||||
bool left_empty = neighbor_empty(glyph, gx, gy, -1, 0);
|
bool left_empty = neighbor_empty(glyph, gx, gy, -1, 0);
|
||||||
@@ -303,38 +198,29 @@ static void render_char_advanced(char c, float offset_x, const RotationState *ro
|
|||||||
bool top_empty = neighbor_empty(glyph, gx, gy, 0, -1);
|
bool top_empty = neighbor_empty(glyph, gx, gy, 0, -1);
|
||||||
bool bottom_empty = neighbor_empty(glyph, gx, gy, 0, 1);
|
bool bottom_empty = neighbor_empty(glyph, gx, gy, 0, 1);
|
||||||
|
|
||||||
bool is_surface = is_front || is_back ||
|
bool is_surface = is_front || is_back || left_empty || right_empty ||
|
||||||
left_empty || right_empty ||
|
|
||||||
top_empty || bottom_empty;
|
top_empty || bottom_empty;
|
||||||
|
|
||||||
/* Wireframe mode: only render edges */
|
|
||||||
if (g_render_mode == RENDER_MODE_WIREFRAME) {
|
if (g_render_mode == RENDER_MODE_WIREFRAME) {
|
||||||
int edge_count = (is_front ? 1 : 0) + (is_back ? 1 : 0) +
|
int edge_count = (is_front ? 1 : 0) + (is_back ? 1 : 0) +
|
||||||
(left_empty ? 1 : 0) + (right_empty ? 1 : 0) +
|
(left_empty ? 1 : 0) + (right_empty ? 1 : 0) +
|
||||||
(top_empty ? 1 : 0) + (bottom_empty ? 1 : 0);
|
(top_empty ? 1 : 0) + (bottom_empty ? 1 : 0);
|
||||||
if (edge_count < 2) continue;
|
if (edge_count < 2)
|
||||||
}
|
|
||||||
|
|
||||||
/* Points mode: sparse rendering */
|
|
||||||
if (g_render_mode == RENDER_MODE_POINTS) {
|
|
||||||
if (!is_front && !is_back) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_surface && g_render_mode != RENDER_MODE_POINTS) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create 3D point centered at origin */
|
if (g_render_mode == RENDER_MODE_POINTS && (!is_front && !is_back))
|
||||||
Vec3 point = vec3_create(
|
continue;
|
||||||
((float)gx - cx) * CHAR_SCALE + offset_x,
|
if (!is_surface && g_render_mode != RENDER_MODE_POINTS)
|
||||||
(cy - (float)gy) * CHAR_SCALE,
|
continue;
|
||||||
(gz - cz) * CHAR_SCALE
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Calculate smooth normal for better shading */
|
Vec3 point =
|
||||||
Vec3 normal = calculate_smooth_normal(glyph, gx, gy, gz, is_front, is_back);
|
vec3_create(((float)gx - cx) * CHAR_SCALE + offset_x,
|
||||||
|
(cy - (float)gy) * CHAR_SCALE, (gz - cz) * CHAR_SCALE);
|
||||||
|
|
||||||
|
Vec3 normal =
|
||||||
|
calculate_smooth_normal(glyph, gx, gy, gz, is_front, is_back);
|
||||||
|
|
||||||
/* Apply rotations */
|
|
||||||
if (rotation->enable_x) {
|
if (rotation->enable_x) {
|
||||||
point = vec3_rotate_x(point, rotation->angle_x);
|
point = vec3_rotate_x(point, rotation->angle_x);
|
||||||
normal = vec3_rotate_x(normal, rotation->angle_x);
|
normal = vec3_rotate_x(normal, rotation->angle_x);
|
||||||
@@ -348,16 +234,15 @@ static void render_char_advanced(char c, float offset_x, const RotationState *ro
|
|||||||
normal = vec3_rotate_z(normal, rotation->angle_z);
|
normal = vec3_rotate_z(normal, rotation->angle_z);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Project and render */
|
|
||||||
float sx, sy, sz;
|
float sx, sy, sz;
|
||||||
if (project_point(point, &sx, &sy, &sz)) {
|
if (project_point(point, &sx, &sy, &sz)) {
|
||||||
if (settings->anti_aliasing) {
|
if (settings->anti_aliasing) {
|
||||||
/* Multi-sample AA */
|
|
||||||
for (int aay = 0; aay < AA_SAMPLES; aay++) {
|
for (int aay = 0; aay < AA_SAMPLES; aay++) {
|
||||||
for (int aax = 0; aax < AA_SAMPLES; aax++) {
|
for (int aax = 0; aax < AA_SAMPLES; aax++) {
|
||||||
float ox = (float)aax / (float)AA_SAMPLES - 0.5f;
|
float ox = (float)aax / (float)AA_SAMPLES - 0.5f;
|
||||||
float oy = (float)aay / (float)AA_SAMPLES - 0.5f;
|
float oy = (float)aay / (float)AA_SAMPLES - 0.5f;
|
||||||
plot_point_aa(sx + ox * 0.5f, sy + oy * 0.5f, sz, point, normal);
|
plot_point_aa(sx + ox * 0.5f, sy + oy * 0.5f, sz, point,
|
||||||
|
normal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -369,40 +254,24 @@ static void render_char_advanced(char c, float offset_x, const RotationState *ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*============================================================================
|
int renderer_init(void) {
|
||||||
* PUBLIC API
|
|
||||||
*============================================================================*/
|
|
||||||
|
|
||||||
int renderer_init(void)
|
|
||||||
{
|
|
||||||
/* Initialize lighting system */
|
|
||||||
lighting_init(&g_lighting);
|
lighting_init(&g_lighting);
|
||||||
|
|
||||||
/* Initialize default material */
|
|
||||||
g_material = lighting_default_material();
|
g_material = lighting_default_material();
|
||||||
g_material.diffuse = color_create(0.9f, 0.9f, 0.95f);
|
g_material.diffuse = color_create(0.9f, 0.9f, 0.95f);
|
||||||
g_material.specular = color_create(1.0f, 1.0f, 1.0f);
|
g_material.specular = color_create(1.0f, 1.0f, 1.0f);
|
||||||
g_material.shininess = 64.0f;
|
g_material.shininess = 64.0f;
|
||||||
|
|
||||||
/* Initialize settings */
|
|
||||||
g_settings = renderer_default_settings();
|
g_settings = renderer_default_settings();
|
||||||
|
|
||||||
/* Clear buffers */
|
|
||||||
renderer_clear();
|
renderer_clear();
|
||||||
|
|
||||||
/* Initialize stats */
|
|
||||||
memset(&g_stats, 0, sizeof(g_stats));
|
memset(&g_stats, 0, sizeof(g_stats));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_cleanup(void)
|
void renderer_cleanup(void) {}
|
||||||
{
|
|
||||||
/* Nothing to clean up */
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer_clear(void)
|
void renderer_clear(void) {
|
||||||
{
|
|
||||||
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
||||||
memset(g_screen[y], ' ', SCREEN_WIDTH);
|
memset(g_screen[y], ' ', SCREEN_WIDTH);
|
||||||
g_screen[y][SCREEN_WIDTH] = '\0';
|
g_screen[y][SCREEN_WIDTH] = '\0';
|
||||||
@@ -418,38 +287,31 @@ void renderer_clear(void)
|
|||||||
g_voxel_count = 0;
|
g_voxel_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_draw_text(const char *text, const RotationState *rotation)
|
void renderer_draw_text(const char *text, const RotationState *rotation) {
|
||||||
{
|
|
||||||
renderer_draw_text_ex(text, rotation, &g_settings);
|
renderer_draw_text_ex(text, rotation, &g_settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_draw_text_ex(const char *text, const RotationState *rotation,
|
void renderer_draw_text_ex(const char *text, const RotationState *rotation,
|
||||||
const RenderSettings *settings)
|
const RenderSettings *settings) {
|
||||||
{
|
if (!text || !rotation || !settings)
|
||||||
if (text == NULL || rotation == NULL || settings == NULL) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
size_t len = strlen(text);
|
size_t len = strlen(text);
|
||||||
if (len == 0) {
|
if (len == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate total width for centering */
|
|
||||||
float char_width = (float)(FONT_WIDTH + FONT_CHAR_SPACING) * CHAR_SCALE;
|
float char_width = (float)(FONT_WIDTH + FONT_CHAR_SPACING) * CHAR_SCALE;
|
||||||
float total_width = (float)len * char_width;
|
float total_width = (float)len * char_width;
|
||||||
float start_x = -total_width / 2.0f + char_width / 2.0f;
|
float start_x = -total_width / 2.0f + char_width / 2.0f;
|
||||||
|
|
||||||
/* Render each character */
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
for (size_t i = 0; i < len; i++) {
|
||||||
if (text[i] == ' ') {
|
if (text[i] == ' ')
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
float offset_x = start_x + (float)i * char_width;
|
float offset_x = start_x + (float)i * char_width;
|
||||||
render_char_advanced(text[i], offset_x, rotation, settings);
|
render_char_advanced(text[i], offset_x, rotation, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Finalize AA samples */
|
// Perform sub-pixel reduction averages across accumulated geometry
|
||||||
if (settings->anti_aliasing) {
|
if (settings->anti_aliasing) {
|
||||||
const char *palette = g_palettes[g_current_palette];
|
const char *palette = g_palettes[g_current_palette];
|
||||||
int palette_size = g_palette_sizes[g_current_palette];
|
int palette_size = g_palette_sizes[g_current_palette];
|
||||||
@@ -457,9 +319,14 @@ void renderer_draw_text_ex(const char *text, const RotationState *rotation,
|
|||||||
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
||||||
for (int x = 0; x < SCREEN_WIDTH; x++) {
|
for (int x = 0; x < SCREEN_WIDTH; x++) {
|
||||||
if (g_sample_count[y][x] > 0) {
|
if (g_sample_count[y][x] > 0) {
|
||||||
float avg_intensity = g_intensity_buffer[y][x] / (float)g_sample_count[y][x];
|
float avg_intensity =
|
||||||
int shade_idx = (int)(avg_intensity * (float)(palette_size - 1) + 0.5f);
|
g_intensity_buffer[y][x] / (float)g_sample_count[y][x];
|
||||||
shade_idx = shade_idx < 0 ? 0 : (shade_idx >= palette_size ? palette_size - 1 : shade_idx);
|
int shade_idx =
|
||||||
|
(int)(avg_intensity * (float)(palette_size - 1) + 0.5f);
|
||||||
|
shade_idx =
|
||||||
|
shade_idx < 0
|
||||||
|
? 0
|
||||||
|
: (shade_idx >= palette_size ? palette_size - 1 : shade_idx);
|
||||||
g_screen[y][x] = palette[shade_idx];
|
g_screen[y][x] = palette[shade_idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,28 +336,21 @@ void renderer_draw_text_ex(const char *text, const RotationState *rotation,
|
|||||||
g_stats.triangles = g_voxel_count;
|
g_stats.triangles = g_voxel_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_present(void)
|
void renderer_present(void) {
|
||||||
{
|
|
||||||
/* Move cursor to home position */
|
|
||||||
printf("\033[H");
|
printf("\033[H");
|
||||||
|
|
||||||
/* Output screen buffer */
|
|
||||||
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
||||||
printf("%s\n", g_screen[y]);
|
printf("%s\n", g_screen[y]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_present_color(const RenderSettings *settings)
|
void renderer_present_color(const RenderSettings *settings) {
|
||||||
{
|
if (!settings || settings->color_mode == COLOR_MODE_MONO) {
|
||||||
if (settings == NULL || settings->color_mode == COLOR_MODE_MONO) {
|
|
||||||
renderer_present();
|
renderer_present();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\033[H");
|
printf("\033[H");
|
||||||
|
|
||||||
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
for (int y = 0; y < SCREEN_HEIGHT; y++) {
|
||||||
for (int x = 0; x < SCREEN_WIDTH; x++) {
|
for (int x = 0; x < SCREEN_WIDTH; x++) {
|
||||||
char c = g_screen[y][x];
|
char c = g_screen[y][x];
|
||||||
@@ -503,7 +363,6 @@ void renderer_present_color(const RenderSettings *settings)
|
|||||||
Color col = g_color_buffer[y][x];
|
Color col = g_color_buffer[y][x];
|
||||||
|
|
||||||
if (settings->color_mode == COLOR_MODE_TRUECOLOR) {
|
if (settings->color_mode == COLOR_MODE_TRUECOLOR) {
|
||||||
/* 24-bit truecolor */
|
|
||||||
int r = (int)(col.r * 255.0f);
|
int r = (int)(col.r * 255.0f);
|
||||||
int g = (int)(col.g * 255.0f);
|
int g = (int)(col.g * 255.0f);
|
||||||
int b = (int)(col.b * 255.0f);
|
int b = (int)(col.b * 255.0f);
|
||||||
@@ -512,7 +371,6 @@ void renderer_present_color(const RenderSettings *settings)
|
|||||||
b = b < 0 ? 0 : (b > 255 ? 255 : b);
|
b = b < 0 ? 0 : (b > 255 ? 255 : b);
|
||||||
printf("\033[38;2;%d;%d;%dm%c", r, g, b, c);
|
printf("\033[38;2;%d;%d;%dm%c", r, g, b, c);
|
||||||
} else if (settings->color_mode == COLOR_MODE_ANSI_256) {
|
} else if (settings->color_mode == COLOR_MODE_ANSI_256) {
|
||||||
/* 256-color mode - approximate */
|
|
||||||
int r = (int)(col.r * 5.0f);
|
int r = (int)(col.r * 5.0f);
|
||||||
int g_val = (int)(col.g * 5.0f);
|
int g_val = (int)(col.g * 5.0f);
|
||||||
int b = (int)(col.b * 5.0f);
|
int b = (int)(col.b * 5.0f);
|
||||||
@@ -522,84 +380,72 @@ void renderer_present_color(const RenderSettings *settings)
|
|||||||
int color_code = 16 + 36 * r + 6 * g_val + b;
|
int color_code = 16 + 36 * r + 6 * g_val + b;
|
||||||
printf("\033[38;5;%dm%c", color_code, c);
|
printf("\033[38;5;%dm%c", color_code, c);
|
||||||
} else {
|
} else {
|
||||||
/* 16-color ANSI */
|
|
||||||
float intensity = color_to_intensity(col);
|
float intensity = color_to_intensity(col);
|
||||||
int bright = intensity > 0.5f ? 1 : 0;
|
int bright = intensity > 0.5f ? 1 : 0;
|
||||||
int base = 30;
|
int base = 30;
|
||||||
|
|
||||||
/* Simple color mapping */
|
if (col.r > col.g && col.r > col.b)
|
||||||
if (col.r > col.g && col.r > col.b) {
|
base = 31;
|
||||||
base = 31; /* Red */
|
else if (col.g > col.r && col.g > col.b)
|
||||||
} else if (col.g > col.r && col.g > col.b) {
|
base = 32;
|
||||||
base = 32; /* Green */
|
else if (col.b > col.r && col.b > col.g)
|
||||||
} else if (col.b > col.r && col.b > col.g) {
|
base = 34;
|
||||||
base = 34; /* Blue */
|
else if (col.r > 0.5f && col.g > 0.5f)
|
||||||
} else if (col.r > 0.5f && col.g > 0.5f) {
|
base = 33;
|
||||||
base = 33; /* Yellow */
|
else if (col.r > 0.5f && col.b > 0.5f)
|
||||||
} else if (col.r > 0.5f && col.b > 0.5f) {
|
base = 35;
|
||||||
base = 35; /* Magenta */
|
else if (col.g > 0.5f && col.b > 0.5f)
|
||||||
} else if (col.g > 0.5f && col.b > 0.5f) {
|
base = 36;
|
||||||
base = 36; /* Cyan */
|
else
|
||||||
} else {
|
base = 37;
|
||||||
base = 37; /* White */
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bright) {
|
if (bright)
|
||||||
printf("\033[1;%dm%c", base, c);
|
printf("\033[1;%dm%c", base, c);
|
||||||
} else {
|
else
|
||||||
printf("\033[%dm%c", base, c);
|
printf("\033[%dm%c", base, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
printf("\033[0m\n");
|
||||||
printf(ANSI_RESET "\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_update_rotation(RotationState *rotation, double delta_time)
|
void renderer_update_rotation(RotationState *rotation, double delta_time) {
|
||||||
{
|
if (!rotation)
|
||||||
if (rotation == NULL) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
float dt = (float)delta_time * rotation->speed;
|
float dt = (float)delta_time * rotation->speed;
|
||||||
|
|
||||||
if (rotation->enable_x) {
|
if (rotation->enable_x)
|
||||||
rotation->angle_x += dt * 0.7f;
|
rotation->angle_x += dt * 0.7f;
|
||||||
}
|
if (rotation->enable_y)
|
||||||
if (rotation->enable_y) {
|
|
||||||
rotation->angle_y += dt * 1.0f;
|
rotation->angle_y += dt * 1.0f;
|
||||||
}
|
if (rotation->enable_z)
|
||||||
if (rotation->enable_z) {
|
|
||||||
rotation->angle_z += dt * 0.5f;
|
rotation->angle_z += dt * 0.5f;
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep angles in reasonable range */
|
|
||||||
const float TWO_PI = 6.283185307f;
|
const float TWO_PI = 6.283185307f;
|
||||||
if (rotation->angle_x > TWO_PI) rotation->angle_x -= TWO_PI;
|
if (rotation->angle_x > TWO_PI)
|
||||||
if (rotation->angle_y > TWO_PI) rotation->angle_y -= TWO_PI;
|
rotation->angle_x -= TWO_PI;
|
||||||
if (rotation->angle_z > TWO_PI) rotation->angle_z -= TWO_PI;
|
if (rotation->angle_y > TWO_PI)
|
||||||
|
rotation->angle_y -= TWO_PI;
|
||||||
|
if (rotation->angle_z > TWO_PI)
|
||||||
|
rotation->angle_z -= TWO_PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
RotationState renderer_default_rotation(void)
|
RotationState renderer_default_rotation(void) {
|
||||||
{
|
RotationState state = {.angle_x = 0.0f,
|
||||||
RotationState state = {
|
|
||||||
.angle_x = 0.0f,
|
|
||||||
.angle_y = 0.0f,
|
.angle_y = 0.0f,
|
||||||
.angle_z = 0.0f,
|
.angle_z = 0.0f,
|
||||||
.enable_x = false,
|
.enable_x = false,
|
||||||
.enable_y = true,
|
.enable_y = true,
|
||||||
.enable_z = false,
|
.enable_z = false,
|
||||||
.speed = 1.0f
|
.speed = 1.0f};
|
||||||
};
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderSettings renderer_default_settings(void)
|
RenderSettings renderer_default_settings(void) {
|
||||||
{
|
RenderSettings settings = {.mode = RENDER_MODE_SHADED,
|
||||||
RenderSettings settings = {
|
|
||||||
.mode = RENDER_MODE_SHADED,
|
|
||||||
.color_mode = COLOR_MODE_MONO,
|
.color_mode = COLOR_MODE_MONO,
|
||||||
.anti_aliasing = false,
|
.anti_aliasing = false,
|
||||||
.show_fps = false,
|
.show_fps = false,
|
||||||
@@ -607,71 +453,56 @@ RenderSettings renderer_default_settings(void)
|
|||||||
.quality = 1.0f,
|
.quality = 1.0f,
|
||||||
.palette_index = 1,
|
.palette_index = 1,
|
||||||
.base_color = color_create(1.0f, 1.0f, 1.0f),
|
.base_color = color_create(1.0f, 1.0f, 1.0f),
|
||||||
.highlight_color = color_create(1.0f, 1.0f, 1.0f)
|
.highlight_color = color_create(1.0f, 1.0f, 1.0f)};
|
||||||
};
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_set_mode(RenderMode mode)
|
void renderer_set_mode(RenderMode mode) {
|
||||||
{
|
|
||||||
if (mode < RENDER_MODE_COUNT) {
|
if (mode < RENDER_MODE_COUNT) {
|
||||||
g_render_mode = mode;
|
g_render_mode = mode;
|
||||||
g_settings.mode = mode;
|
g_settings.mode = mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_set_color_mode(ColorMode mode)
|
void renderer_set_color_mode(ColorMode mode) {
|
||||||
{
|
|
||||||
if (mode < COLOR_MODE_COUNT) {
|
if (mode < COLOR_MODE_COUNT) {
|
||||||
g_color_mode = mode;
|
g_color_mode = mode;
|
||||||
g_settings.color_mode = mode;
|
g_settings.color_mode = mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameStats renderer_get_stats(void)
|
FrameStats renderer_get_stats(void) { return g_stats; }
|
||||||
{
|
|
||||||
return g_stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer_set_palette(int palette_index)
|
void renderer_set_palette(int palette_index) {
|
||||||
{
|
|
||||||
if (palette_index >= 0 && palette_index < 4) {
|
if (palette_index >= 0 && palette_index < 4) {
|
||||||
g_current_palette = palette_index;
|
g_current_palette = palette_index;
|
||||||
g_settings.palette_index = palette_index;
|
g_settings.palette_index = palette_index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LightingSystem *renderer_get_lighting(void)
|
LightingSystem *renderer_get_lighting(void) { return &g_lighting; }
|
||||||
{
|
|
||||||
return &g_lighting;
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer_hide_cursor(void)
|
void renderer_hide_cursor(void) {
|
||||||
{
|
|
||||||
printf("\033[?25l");
|
printf("\033[?25l");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_show_cursor(void)
|
void renderer_show_cursor(void) {
|
||||||
{
|
|
||||||
printf("\033[?25h");
|
printf("\033[?25h");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_clear_terminal(void)
|
void renderer_clear_terminal(void) {
|
||||||
{
|
|
||||||
printf("\033[2J");
|
printf("\033[2J");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_enter_alternate_screen(void)
|
void renderer_enter_alternate_screen(void) {
|
||||||
{
|
|
||||||
printf("\033[?1049h");
|
printf("\033[?1049h");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderer_exit_alternate_screen(void)
|
void renderer_exit_alternate_screen(void) {
|
||||||
{
|
|
||||||
printf("\033[?1049l");
|
printf("\033[?1049l");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/timing.c
20
src/timing.c
@@ -1,33 +1,24 @@
|
|||||||
/**
|
|
||||||
* @file timing.c
|
|
||||||
* @brief High-precision timing utilities implementation
|
|
||||||
* @author ASCII3D Project
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
#include "timing.h"
|
#include "timing.h"
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
double timing_get_seconds(void)
|
double timing_get_seconds(void) {
|
||||||
{
|
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
|
// We enforce monotonic clock so system daylight saving updates don't break
|
||||||
|
// delta logic
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9;
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
void timing_sleep_us(unsigned int microseconds)
|
void timing_sleep_us(unsigned int microseconds) {
|
||||||
{
|
|
||||||
struct timespec req;
|
struct timespec req;
|
||||||
req.tv_sec = microseconds / 1000000;
|
req.tv_sec = microseconds / 1000000;
|
||||||
req.tv_nsec = (microseconds % 1000000) * 1000L;
|
req.tv_nsec = (microseconds % 1000000) * 1000L;
|
||||||
nanosleep(&req, NULL);
|
nanosleep(&req, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void timing_limit_fps(double frame_start_time, int target_fps)
|
void timing_limit_fps(double frame_start_time, int target_fps) {
|
||||||
{
|
|
||||||
if (target_fps <= 0) {
|
if (target_fps <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -36,6 +27,7 @@ void timing_limit_fps(double frame_start_time, int target_fps)
|
|||||||
double elapsed = timing_get_seconds() - frame_start_time;
|
double elapsed = timing_get_seconds() - frame_start_time;
|
||||||
double sleep_time = target_frame_time - elapsed;
|
double sleep_time = target_frame_time - elapsed;
|
||||||
|
|
||||||
|
// Attempt dynamic throttle
|
||||||
if (sleep_time > 0.0) {
|
if (sleep_time > 0.0) {
|
||||||
unsigned int sleep_us = (unsigned int)(sleep_time * 1.0e6);
|
unsigned int sleep_us = (unsigned int)(sleep_time * 1.0e6);
|
||||||
timing_sleep_us(sleep_us);
|
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;
|
||||||
|
}
|
||||||
75
src/vec3.c
75
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 "vec3.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
Vec3 vec3_create(float x, float y, float z)
|
// Vector Math Library
|
||||||
{
|
// Optimized for simple local 3D transformations
|
||||||
|
|
||||||
|
Vec3 vec3_create(float x, float y, float z) {
|
||||||
Vec3 v = {x, y, z};
|
Vec3 v = {x, y, z};
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 vec3_add(Vec3 a, Vec3 b)
|
Vec3 vec3_add(Vec3 a, Vec3 b) {
|
||||||
{
|
|
||||||
return vec3_create(a.x + b.x, a.y + b.y, a.z + b.z);
|
return vec3_create(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 vec3_sub(Vec3 a, Vec3 b)
|
Vec3 vec3_sub(Vec3 a, Vec3 b) {
|
||||||
{
|
|
||||||
return vec3_create(a.x - b.x, a.y - b.y, a.z - b.z);
|
return vec3_create(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 vec3_scale(Vec3 v, float s)
|
Vec3 vec3_scale(Vec3 v, float s) {
|
||||||
{
|
|
||||||
return vec3_create(v.x * s, v.y * s, v.z * s);
|
return vec3_create(v.x * s, v.y * s, v.z * s);
|
||||||
}
|
}
|
||||||
|
|
||||||
float vec3_dot(Vec3 a, Vec3 b)
|
float vec3_dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
|
||||||
{
|
|
||||||
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)
|
float vec3_length(Vec3 v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); }
|
||||||
{
|
|
||||||
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)
|
Vec3 vec3_normalize(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);
|
float len = vec3_length(v);
|
||||||
|
// Arbitrarily high epsilon cut-off to avoid division by zero artifacts
|
||||||
if (len > 0.0001f) {
|
if (len > 0.0001f) {
|
||||||
return vec3_scale(v, 1.0f / len);
|
return vec3_scale(v, 1.0f / len);
|
||||||
}
|
}
|
||||||
return vec3_create(0.0f, 0.0f, 0.0f);
|
return vec3_create(0.0f, 0.0f, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 vec3_rotate_x(Vec3 v, float angle)
|
Vec3 vec3_rotate_x(Vec3 v, float angle) {
|
||||||
{
|
|
||||||
float c = cosf(angle);
|
float c = cosf(angle);
|
||||||
float s = sinf(angle);
|
float s = sinf(angle);
|
||||||
return vec3_create(
|
return vec3_create(v.x, v.y * c - v.z * s, v.y * s + v.z * c);
|
||||||
v.x,
|
|
||||||
v.y * c - v.z * s,
|
|
||||||
v.y * s + v.z * c
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 vec3_rotate_y(Vec3 v, float angle)
|
Vec3 vec3_rotate_y(Vec3 v, float angle) {
|
||||||
{
|
|
||||||
float c = cosf(angle);
|
float c = cosf(angle);
|
||||||
float s = sinf(angle);
|
float s = sinf(angle);
|
||||||
return vec3_create(
|
return vec3_create(v.x * c + v.z * s, v.y, -v.x * s + v.z * c);
|
||||||
v.x * c + v.z * s,
|
|
||||||
v.y,
|
|
||||||
-v.x * s + v.z * c
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 vec3_rotate_z(Vec3 v, float angle)
|
Vec3 vec3_rotate_z(Vec3 v, float angle) {
|
||||||
{
|
|
||||||
float c = cosf(angle);
|
float c = cosf(angle);
|
||||||
float s = sinf(angle);
|
float s = sinf(angle);
|
||||||
return vec3_create(
|
return vec3_create(v.x * c - v.y * s, v.x * s + v.y * c, v.z);
|
||||||
v.x * c - v.y * s,
|
|
||||||
v.x * s + v.y * c,
|
|
||||||
v.z
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user