diff --git a/Makefile b/Makefile index b91c4e1..cf2ce0d 100644 --- a/Makefile +++ b/Makefile @@ -272,10 +272,13 @@ gen_scene: @$(CC) -Wall -Wextra -Werror -o gen_scene tools/gen_scene.c -lm @echo "Built gen_scene tool" +render-cathedral: $(NAME) gen_scene + @scripts/render_cathedral.sh $(ARGS) + testclean: $(RM) run_test_math run_test_camera run_test_intersections run_test_lighting run_test_parsing run_test_pbr run_benchmark $(RM) cov_test_math cov_test_intersections cov_test_lighting cov_test_parsing cov_test_all $(RM) -r coverage $(RM) *.gcno *.gcda src/*.gcno src/*.gcda tests/*.gcno tests/*.gcda -.PHONY: re all clean fclean sanitize debug release test testclean coverage regression regression-generate lint benchmark gen_scene +.PHONY: re all clean fclean sanitize debug release test testclean coverage regression regression-generate lint benchmark gen_scene render-cathedral diff --git a/README.md b/README.md index d5554e3..9493e33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # miniRT -![Cathedral Ruins](./saves/cathedral_ruins.bmp?raw=true) +![Cathedral Ruins](./renders_preview/cathedral_ruins.png?raw=true) **A basic RayTracer using minilibX** project of 42 Paris's common-core diff --git a/renders_preview/cathedral_ruins.png b/renders_preview/cathedral_ruins.png index c24288c..844915c 100644 Binary files a/renders_preview/cathedral_ruins.png and b/renders_preview/cathedral_ruins.png differ diff --git a/saves/cathedral_ruins.bmp b/saves/cathedral_ruins.bmp index 1d46077..fe99969 100755 Binary files a/saves/cathedral_ruins.bmp and b/saves/cathedral_ruins.bmp differ diff --git a/saves/cathedral_ruins.png b/saves/cathedral_ruins.png new file mode 100644 index 0000000..844915c Binary files /dev/null and b/saves/cathedral_ruins.png differ diff --git a/scenes/cathedral_ruins.rt b/scenes/cathedral_ruins.rt index 1c86a47..771f7a3 100644 --- a/scenes/cathedral_ruins.rt +++ b/scenes/cathedral_ruins.rt @@ -2,7 +2,10 @@ R 2560 1440 A 0.08 200,190,170 E -c 0.0,6.0,12.0 0.03,0,0 70 +c 0.0,12.0,25.0 0.055,0,0 70 +c -4.0,7.0,18.0 0.04,-0.03,0 70 +c 8.0,6.0,-15.0 0.03,0.3,0 70 +c 0.0,2.0,16.0 0.0,0,0 70 l -3.0,25.0,-5.0 0.9 255,240,200 l 5.0,20.0,5.0 0.6 255,235,190 diff --git a/scripts/bmp2png.py b/scripts/bmp2png.py new file mode 100755 index 0000000..6e36f96 --- /dev/null +++ b/scripts/bmp2png.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +"""Convert BMP files to PNG format.""" +import sys +from PIL import Image + +def convert(src, dst=None): + if dst is None: + dst = src.rsplit('.', 1)[0] + '.png' + img = Image.open(src) + img.save(dst, 'PNG') + print(f"{src} -> {dst} ({img.width}x{img.height})") + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: bmp2png.py [output.png]") + sys.exit(1) + convert(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else None) diff --git a/scripts/render_cathedral.sh b/scripts/render_cathedral.sh new file mode 100755 index 0000000..d5f65e7 --- /dev/null +++ b/scripts/render_cathedral.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# Render the cathedral ruins scene +# Generates the .rt file via gen_scene and renders it with miniRT +# +# Usage: scripts/render_cathedral.sh [OPTIONS] +# --preview Quick render at 640x360 +# --full Full resolution render (2560x1440) +# --generate-only Create .rt file without rendering +# --png Convert output BMP to PNG (requires Pillow) +# --width N Custom width (default: 1280) +# --height N Custom height (default: 720) +# --threads N Thread count (default: auto) +# --seed N RNG seed for debris placement (default: 42) +# --output PATH Output BMP path (default: saves/cathedral_ruins.bmp) +# --scene PATH Use existing .rt file instead of generating + +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" + +WIDTH=1280 +HEIGHT=720 +THREADS="auto" +SEED=42 +OUTPUT="$PROJECT_DIR/saves/cathedral_ruins.bmp" +SCENE_FILE="" +GENERATE_ONLY=0 +USE_EXISTING=0 +CONVERT_PNG=0 + +while [ $# -gt 0 ]; do + case "$1" in + --preview) + WIDTH=640; HEIGHT=360 ;; + --full) + WIDTH=2560; HEIGHT=1440 ;; + --generate-only) + GENERATE_ONLY=1 ;; + --png) + CONVERT_PNG=1 ;; + --width) + WIDTH="$2"; shift ;; + --height) + HEIGHT="$2"; shift ;; + --threads) + THREADS="$2"; shift ;; + --seed) + SEED="$2"; shift ;; + --output) + OUTPUT="$2"; shift ;; + --scene) + SCENE_FILE="$2"; USE_EXISTING=1; shift ;; + --help|-h) + head -16 "$0" | tail -14 + exit 0 ;; + *) + echo "Unknown option: $1" >&2 + exit 1 ;; + esac + shift +done + +# Build miniRT if missing +if [ ! -f "$PROJECT_DIR/miniRT" ]; then + echo "Building miniRT..." + make -C "$PROJECT_DIR" -j"$(nproc)" 2>&1 | tail -1 +fi + +# Build gen_scene if missing +if [ ! -f "$PROJECT_DIR/gen_scene" ]; then + echo "Building gen_scene..." + make -C "$PROJECT_DIR" gen_scene 2>&1 +fi + +# Generate scene file +if [ "$USE_EXISTING" -eq 0 ]; then + SCENE_FILE="$PROJECT_DIR/scenes/cathedral_ruins_gen.rt" + echo "Generating cathedral scene (${WIDTH}x${HEIGHT}, seed=${SEED})..." + "$PROJECT_DIR/gen_scene" --preset cathedral \ + --width "$WIDTH" --height "$HEIGHT" \ + --seed "$SEED" \ + --output "$SCENE_FILE" + echo "Scene file: $SCENE_FILE" + SHAPE_COUNT=$(grep -c "^[a-z]" "$SCENE_FILE" || true) + echo "Shape/element count: $SHAPE_COUNT" +fi + +if [ "$GENERATE_ONLY" -eq 1 ]; then + echo "Done (generate-only mode)." + exit 0 +fi + +# Render +mkdir -p "$(dirname "$OUTPUT")" +echo "" +echo "Rendering ${WIDTH}x${HEIGHT} with --threads=${THREADS}..." +echo "Output: $OUTPUT" +echo "This may take a while for large resolutions." +echo "" + +START=$(date +%s) + +cd "$PROJECT_DIR" +xvfb-run -a "$PROJECT_DIR/miniRT" "$SCENE_FILE" -save \ + --output "$OUTPUT" --threads="$THREADS" || { + echo "Retrying with default output path..." + xvfb-run -a "$PROJECT_DIR/miniRT" "$SCENE_FILE" -save \ + --threads="$THREADS" + mv -f "$PROJECT_DIR/miniRT.bmp" "$OUTPUT" +} + +END=$(date +%s) +ELAPSED=$((END - START)) +MINUTES=$((ELAPSED / 60)) +SECONDS=$((ELAPSED % 60)) + +echo "" +echo "=== Render Complete ===" +echo "Time: ${MINUTES}m ${SECONDS}s" +ls -lh "$OUTPUT" | awk '{print "Size:", $5}' +file "$OUTPUT" | sed 's/.*: /Format: /' + +# Convert to PNG if requested +if [ "$CONVERT_PNG" -eq 1 ]; then + PNG_OUTPUT="${OUTPUT%.bmp}.png" + echo "" + echo "Converting to PNG..." + python3 "$PROJECT_DIR/scripts/bmp2png.py" "$OUTPUT" "$PNG_OUTPUT" +fi diff --git a/skills/scene-design.md b/skills/scene-design.md new file mode 100644 index 0000000..012d138 --- /dev/null +++ b/skills/scene-design.md @@ -0,0 +1,302 @@ +# Scene Design & Rendering Workflows + +## Architectural Scene Design Principles + +### Building from primitives + +Complex architectural scenes are built by composing simple shapes: + +| Architectural Element | Primary Shape | Supporting Shapes | +|----------------------|---------------|-------------------| +| Walls | `bx` (box) | Multiple boxes with gaps for ruin effect | +| Columns/pillars | `cy` (cylinder) | `dk` (disk) base + `sp` (sphere) capital | +| Gothic arches | `to` (torus) | Half-torus between column tops | +| Floors | `pl` (plane) | `sq` (square) overlays for tile variation | +| Roofs/vaults | `tr` (triangle) | Angled triangles + `sq` for panels | +| Windows | `sq` with `trans:` | `sp` for round window tops (oculi) | +| Towers | `cy` base | `co` (cone) roof | +| Debris | `bx`, `sp`, `tr` | Mixed small shapes at ground level | +| Vegetation | `el` (ellipsoid) | Organic shapes for ivy/moss | + +### Layered composition + +Build scenes in layers, from structural to atmospheric: + +1. **Structural shell** -- walls, floor plane, ceiling/roof +2. **Architectural features** -- columns, arches, windows, altar +3. **Decorative elements** -- crosses, candelabra, fonts, tomb slabs +4. **Decay/wear** -- broken columns, rubble, fallen roof pieces +5. **Atmospheric effects** -- dust motes, water puddles, stained glass light patches + +### Depth through repetition + +Colonnades (rows of repeated columns) and arcades (rows of arches) create visual depth and rhythm. Use loops in the scene generator: + +```c +// 8 columns spaced 5 units apart along Z axis +i = 0; +while (i < 8) { + col_z = 10.0 - (double)i * 5.0; + cathedral_column(f, -6.0, col_z, heights[i]); + i++; +} +``` + +Varying individual column heights within the row breaks monotony and suggests ruin/decay. + +## Cathedral Ruins Case Study + +### Scene overview + +- **File:** `scenes/cathedral_ruins.rt` (307 lines) +- **Shapes:** ~236 geometric objects +- **Resolution:** 2560x1440 +- **Render time:** ~20 minutes (multi-threaded, depends on hardware) + +### Shape census + +| Type | Count | Primary Use | +|------|-------|-------------| +| `sp` (sphere) | 68 | Column capitals, rubble, rose window glass, dust motes, candle flames | +| `bx` (box) | 49 | Walls, rubble blocks, altar, tower battlements, tomb slab | +| `cy` (cylinder) | 31 | Column shafts, candlesticks, cross, fallen columns, roof rafters | +| `dk` (disk) | 26 | Column bases, water puddles, candelabra top, font water | +| `to` (torus) | 20 | Gothic arches, rose window frame, fallen arch fragments | +| `tr` (triangle) | 16 | Vault fragments, rubble, fallen roof pieces | +| `sq` (square) | 13 | Floor tiles, stained glass windows, tilted roof panels | +| `el` (ellipsoid) | 10 | Ivy/vegetation, fallen glass shards | +| `pl` (plane) | 1 | Ground floor | +| `pa` (paraboloid) | 1 | Stone font/basin | +| `co` (cone) | 1 | Tower roof | + +### Structural breakdown + +| Section | Shapes | Z Range | Description | +|---------|--------|---------|-------------| +| Floor & tiles | 5 | all | Ground plane + 4 stone tile squares | +| Walls | 19 | -30 to 15 | Left/right walls, apse, entrance, transept stubs, buttresses | +| Left colonnade | 23 | -25 to 10 | 8 columns (cy+dk+sp), 2 broken | +| Right colonnade | 23 | -25 to 10 | 8 columns, 2 broken differently | +| Gothic arches | 18 | -28 to 7.5 | 12 side arches + 3 transverse + 1 apse + 2 fallen | +| Rose window | 22 | -29.3 | 2 torus frames + 12 outer + 6 inner + 1 center glass spheres | +| Altar area | 12 | -27 to -24 | Table, base, candles, cross, steps | +| Debris | ~45 | scattered | Fallen columns, rubble boxes/spheres, fragment triangles, capitals | +| Roof fragments | 18 | 0 to 17 | Vault triangles, tilted panels, floor debris, ridge beams, rafters | +| Bell tower | 15 | 11 to 23 | Base cylinder, upper section, cone roof, windows, battlements | +| Dust motes | 5 | 7 to 11 | Tiny transparent spheres in light beams | +| Water puddles | 8 | 0.02 | Reflective disks on floor | +| Stained glass | 15 | walls + floor | Transparent windows, floor glass shards, accent spheres | +| Decorations | 15 | various | Font, tomb, candelabra, ivy | + +### Design decisions + +**Sandstone palette (R: 170-200, G: 155-185, B: 130-160):** Warm earth tones with a consistent R > G > B bias. Each element varies by 5-10 per channel to prevent flat appearance while maintaining cohesion. + +**Column height variation:** Columns at z=-10 (left) and z=0 (right) are broken short. This is controlled by the `left_h[]` and `right_h[]` arrays in the generator. Broken columns omit the capital sphere and have fallen capital spheres placed on the ground nearby. + +**Torus for arches:** The torus is the closest available primitive to a semicircular arch. Placed at column-top height (y=14) with axis along Z, each torus spans the gap between adjacent columns. The 20 tori are the scene's biggest performance cost. + +**Transparent spheres for rose window:** 12 outer spheres in a circular pattern (computed with sin/cos) with `trans:0.7 ior:1.3` simulate stained glass. Saturated colors (reds, blues, greens, golds) create the jewel-like effect. + +## Material Strategy + +| Goal | Properties | Example | +|------|-----------|---------| +| Polished stone | `spec:0.2` | Column shafts -- subtle sheen on weathered stone | +| Wet stone / water | `refl:0.5-0.7 spec:0.7-0.9` | Floor puddles -- mirror-like reflections | +| Stained glass | `trans:0.7-0.85 ior:1.3-1.5` | Rose window, wall windows -- colored light | +| Metal / gold | `spec:0.6-0.9 refl:0.2-0.3` | Cross, candlesticks -- metallic shine | +| Pure diffuse | (none) | Rubble, ivy -- no special surface treatment | +| Atmospheric dust | `trans:0.9 ior:1.0` | Dust motes -- nearly invisible, catches light | + +**Guidelines:** +- Use `spec:` liberally (0.1-0.3) on stone for a wet/polished look +- Use `refl:` sparingly -- only on puddles and metal. Each reflective surface triggers recursive ray tracing (max depth 4) +- Combine `trans:` with `ior:` for glass effects. Higher `ior` = more distortion (1.0=air, 1.3=water, 1.5=glass, 1.7=crystal) +- Don't use materials on rubble/debris -- pure diffuse is faster and looks natural + +## Lighting Design + +### Warm/cool contrast + +The cathedral uses 6 lights with deliberate warm/cool separation: + +| Light | Position | Intensity | Color | Purpose | +|-------|----------|-----------|-------|---------| +| Main sun | (-3, 25, -5) | 0.9 | 255,240,200 | Primary warm illumination through roof gaps | +| Secondary sun | (5, 20, 5) | 0.6 | 255,235,190 | Fill warm light from side | +| Altar glow | (-8, 15, -15) | 0.5 | 255,220,180 | Warm backlight behind altar | +| Deep apse | (0, 12, -25) | 0.4 | 255,200,150 | Deep warm glow at back wall | +| Cool fill | (10, 18, -10) | 0.3 | 200,210,255 | Sky-blue through broken wall (contrast) | +| Ground bounce | (0, 3, 10) | 0.2 | 180,190,210 | Subtle cool ambient from floor | + +### Intensity cascade + +Lights decrease in intensity from 0.9 to 0.2. The key (brightest) light dominates shadow direction, while fill lights soften shadows without washing them out. + +### Ambient ratio + +Ambient is set very low (0.08) to preserve dramatic shadows. For gallery scenes, keep ambient at 0.05-0.15. Above 0.2 washes out the scene. + +## Color Palette Design + +### Base sandstone range + +``` +R: 170-200 (warm) +G: 155-185 (mid) +B: 130-160 (cool) +Offset: R > G by ~15, G > B by ~25 +``` + +### Variation strategy + +- **Per-element variation:** Each wall/column/rubble piece varies by 5-10 per channel from neighbors +- **Height gradient:** Upper elements slightly lighter than ground-level ones +- **Debris darkening:** Rubble is 10-20 units darker than intact structures +- **Accent colors:** Only on stained glass (saturated primaries), vegetation (40-80 green range), and metallic objects (gold: 150,120,40) + +## Performance Considerations + +### Torus cost + +The torus intersection requires solving a **quartic equation** (degree 4 polynomial) per ray-torus test. With 20 tori, 6 lights (each casting shadow rays), and 3.7M pixels at 2560x1440, torus shapes dominate render time. + +**Optimization options:** +- Replace decorative tori with cylinder approximations (save ~40% render time) +- Reduce torus count by removing some side arches +- Use `--width 1920 --height 1080` for faster iteration + +### Shape count impact + +| Shapes | Resolution | Approximate Time | +|--------|-----------|-----------------| +| ~236 (full) | 2560x1440 | 20-30 min | +| ~236 (full) | 1920x1080 | 10-15 min | +| ~236 (full) | 640x360 | 1-3 min | + +The BVH acceleration structure helps with large shape counts, but per-intersection cost (especially torus) dominates. + +### Shadow rays + +Each light source fires a shadow ray per intersection point. With 6 lights, shadow computation is 6x the cost of a single-light scene. Consider reducing to 3-4 lights for faster iteration. + +## Scene Improvement Tips + +### Adding detail +- More debris variation: smaller rubble at column bases +- Moss/lichen: tiny green ellipsoids on north-facing surfaces (wall x=-12) +- Cobwebs: very thin triangles spanning column gaps +- Broken furniture: small boxes with wood color (80,50,30) near altar + +### Camera angles +- **Low angle** (y=2): dramatic, emphasizes column height and vault +- **Bird's eye** (y=25, rot_x=0.2): shows floor plan layout +- **From altar** looking toward entrance: reverse perspective, entrance light as backlight +- **Close-up** on rose window (z=-28, y=12): detailed glass pattern + +### Material tuning +- Increase `spec:` on all stone to 0.3 for a rain-wet look +- Add `refl:0.05` to floor plane for subtle ground reflection +- Increase rose window `trans:` to 0.9 for more light-passing effect + +### Atmospheric effects +- Add more dust mote spheres (20-30) for visible light shafts +- Place a semi-transparent plane at y=2 with `trans:0.95` for ground fog + +## Using the Scene Generator + +### Available presets + +| Preset | Description | Key Features | +|--------|-------------|-------------| +| `cornell` | Cornell box | Reflective/refractive spheres in enclosed room | +| `checkerboard` | 5x5 sphere grid | Color palette showcase | +| `solar` | Solar system | 8 planets orbiting a sun | +| `spiral` | Spiral spheres | Parametric arrangement | +| `grid` | Mixed shape grid | Spheres, boxes, cylinders in grid | +| `scatter` | Random scatter | Mixed shapes, random placement | +| `reflections` | Reflection showcase | Mirror and colored spheres | +| `glass` | Glass spheres | Transparent/refractive materials | +| `cathedral` | Cathedral ruins | 236-shape architectural scene | + +### CLI reference + +```bash +# Build the generator +make gen_scene + +# Generate cathedral at default resolution (1920x1080) +./gen_scene --preset cathedral --output scenes/my_cathedral.rt + +# Generate at full resolution with custom seed +./gen_scene --preset cathedral --width 2560 --height 1440 --seed 42 \ + --output scenes/cathedral_full.rt + +# List all presets +./gen_scene --list +``` + +### Seed behavior + +The `--seed` parameter controls randomized debris placement. Structural elements (walls, columns, arches, altar) are deterministic regardless of seed. Only the 30 debris items (15 boxes + 15 spheres) vary with different seeds. + +### Adding new presets + +1. Add writer helpers for any new shape types needed (see `write_disk`, `write_torus`, etc.) +2. Write a `gen_yourscene(FILE *f, t_config *cfg)` function +3. Register in `generate_scene()` dispatcher and `list_presets()` +4. Rebuild: `make gen_scene` + +## Rendering Workflow + +### Preview -> Iterate -> Full Render + +```bash +# 1. Quick preview (~1-3 min) +scripts/render_cathedral.sh --preview + +# 2. Edit scene file or adjust generator parameters +vim scenes/cathedral_ruins_gen.rt + +# 3. Medium quality check (~5-10 min) +scripts/render_cathedral.sh + +# 4. Full resolution render (~20-30 min) +scripts/render_cathedral.sh --full + +# 5. Use existing .rt file (skip generation) +scripts/render_cathedral.sh --scene scenes/cathedral_ruins.rt --full +``` + +### Headless rendering + +miniRT requires X11 initialization even for `-save` mode. The render script uses `xvfb-run` automatically. For manual headless rendering: + +```bash +Xvfb :99 -screen 0 2560x1440x24 & +DISPLAY=:99 ./miniRT scene.rt -save --output out.bmp --threads=auto +``` + +### Makefile integration + +```bash +# Render with default settings (1280x720) +make render-cathedral + +# Pass custom arguments +make render-cathedral ARGS="--full" +make render-cathedral ARGS="--preview --seed 123" +``` + +### CLI flags reference + +| Flag | Purpose | Default | +|------|---------|---------| +| `-save` | Render and save BMP | Required for headless | +| `--output PATH` | BMP save location | `miniRT.bmp` in CWD | +| `--threads=auto` | Use all CPU cores | Single-threaded | +| `--threads N` | Use N threads (max 64) | Single-threaded | +| `--width N` | Override resolution width | From .rt file | +| `--height N` | Override resolution height | From .rt file | +| `--verbose` | Print scene info | Off | diff --git a/tools/gen_scene.c b/tools/gen_scene.c index 5e04596..269433b 100644 --- a/tools/gen_scene.c +++ b/tools/gen_scene.c @@ -136,6 +136,17 @@ static void write_cylinder(FILE *f, t_vec pos, t_vec ori, double diam, diam, height, col.r, col.g, col.b); } +static void write_cylinder_mat(FILE *f, t_vec pos, t_vec ori, double diam, + double height, t_color col, const char *mat) +{ + fprintf(f, "cy\t%.2f,%.2f,%.2f\t%.2f,%.2f,%.2f\t%.2f\t%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, + diam, height, col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + static void write_cone(FILE *f, t_vec pos, t_vec ori, double diam, double height, t_color col) { @@ -144,6 +155,17 @@ static void write_cone(FILE *f, t_vec pos, t_vec ori, double diam, diam, height, col.r, col.g, col.b); } +static void write_cone_mat(FILE *f, t_vec pos, t_vec ori, double diam, + double height, t_color col, const char *mat) +{ + fprintf(f, "co\t%.2f,%.2f,%.2f\t%.2f,%.2f,%.2f\t%.2f\t%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, + diam, height, col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + static void write_square(FILE *f, t_vec pos, t_vec ori, double side, t_color col) { @@ -152,12 +174,86 @@ static void write_square(FILE *f, t_vec pos, t_vec ori, double side, col.r, col.g, col.b); } +static void write_square_mat(FILE *f, t_vec pos, t_vec ori, double side, + t_color col, const char *mat) +{ + fprintf(f, "sq\t%.1f,%.1f,%.1f\t%.1f,%.1f,%.1f\t%.0f\t%d,%d,%d", + pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, side, + col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + static void write_box(FILE *f, t_vec pos, t_vec size, t_color col) { fprintf(f, "bx\t%.2f,%.2f,%.2f\t%.2f,%.2f,%.2f\t%d,%d,%d\n", pos.x, pos.y, pos.z, size.x, size.y, size.z, col.r, col.g, col.b); } +static void write_box_mat(FILE *f, t_vec pos, t_vec size, t_color col, + const char *mat) +{ + fprintf(f, "bx\t%.2f,%.2f,%.2f\t%.2f,%.2f,%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, size.x, size.y, size.z, col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + +static void write_disk(FILE *f, t_vec pos, t_vec ori, double diam, + t_color col, const char *mat) +{ + fprintf(f, "dk\t%.2f,%.2f,%.2f\t%.1f,%.1f,%.1f\t%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, diam, + col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + +static void write_torus(FILE *f, t_vec pos, t_vec ori, double major, + double minor, t_color col, const char *mat) +{ + fprintf(f, "to\t%.2f,%.2f,%.2f\t%.1f,%.1f,%.1f\t%.2f\t%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, major, minor, + col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + +static void write_ellipsoid(FILE *f, t_vec pos, t_vec radii, t_color col, + const char *mat) +{ + fprintf(f, "el\t%.2f,%.2f,%.2f\t%.2f,%.2f,%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, radii.x, radii.y, radii.z, + col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + +static void write_triangle(FILE *f, t_vec p1, t_vec p2, t_vec p3, + t_color col) +{ + fprintf(f, "tr\t%.1f,%.1f,%.1f\t%.1f,%.1f,%.1f\t%.1f,%.1f,%.1f" + "\t%d,%d,%d\n", + p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, + col.r, col.g, col.b); +} + +static void write_paraboloid(FILE *f, t_vec pos, t_vec ori, double scale, + double height, t_color col, const char *mat) +{ + fprintf(f, "pa\t%.2f,%.2f,%.2f\t%.1f,%.1f,%.1f\t%.2f\t%.2f\t%d,%d,%d", + pos.x, pos.y, pos.z, ori.x, ori.y, ori.z, scale, height, + col.r, col.g, col.b); + if (mat) + fprintf(f, "\t%s", mat); + fprintf(f, "\n"); +} + static void gen_cornell_box(FILE *f, t_config *cfg) { write_header(f, cfg); @@ -454,12 +550,459 @@ static void gen_glass_spheres(FILE *f, t_config *cfg) write_sphere(f, (t_vec){0, 0.6, 6}, 1.2, (t_color){50, 200, 50}, NULL); } +/* ---- Cathedral Ruins helpers ---- */ + +static void cathedral_column(FILE *f, double x, double z, double height) +{ + t_color shaft = {200, 185, 160}; + t_color base = {190, 175, 150}; + + write_cylinder_mat(f, (t_vec){x, 0, z}, (t_vec){0, 1, 0}, + 1.4, height, shaft, "spec:0.2"); + write_disk(f, (t_vec){x, 0.01, z}, (t_vec){0, 1, 0}, 2.4, base, NULL); + if (height >= 10.0) + write_sphere(f, (t_vec){x, height, z}, 1.6, shaft, "spec:0.2"); +} + +static void cathedral_walls(FILE *f) +{ + t_color s1 = {190, 175, 150}; + t_color s2 = {185, 170, 145}; + t_color s3 = {195, 180, 155}; + + write_box(f, (t_vec){-12, 6, -10}, (t_vec){1, 12, 40}, s1); + write_box(f, (t_vec){-12, 14, -25}, (t_vec){1, 4, 10}, s2); + write_box(f, (t_vec){-12, 3, 8}, (t_vec){1, 6, 8}, s3); + write_box(f, (t_vec){12, 7, -10}, (t_vec){1, 14, 40}, s1); + write_box(f, (t_vec){12, 16, -5}, (t_vec){1, 2, 15}, s2); + write_box(f, (t_vec){0, 7, -30}, (t_vec){10, 14, 1}, + (t_color){200, 185, 160}); + write_box(f, (t_vec){-6, 8, -29.5}, (t_vec){2, 16, 1}, s3); + write_box(f, (t_vec){6, 8, -29.5}, (t_vec){2, 16, 1}, s3); + write_box(f, (t_vec){-9, 5, 15}, (t_vec){3, 10, 1}, s1); + write_box(f, (t_vec){9, 5, 15}, (t_vec){3, 10, 1}, s1); + write_box(f, (t_vec){0, 15, 15}, (t_vec){12, 3, 1}, s2); + write_box(f, (t_vec){-12, 5, -15}, (t_vec){3, 10, 1}, s3); + write_box(f, (t_vec){12, 5, -15}, (t_vec){3, 10, 1}, s3); + write_box(f, (t_vec){-12, 5, -16}, (t_vec){1, 10, 3}, s1); + write_box(f, (t_vec){12, 5, -16}, (t_vec){1, 10, 3}, s1); + write_box(f, (t_vec){-12.5, 4, 0}, (t_vec){1.5, 8, 2}, s2); + write_box(f, (t_vec){12.5, 4, 0}, (t_vec){1.5, 8, 2}, s2); + write_box(f, (t_vec){-12.5, 4, -20}, (t_vec){1.5, 8, 2}, + (t_color){180, 165, 140}); + write_box(f, (t_vec){12.5, 4, -20}, (t_vec){1.5, 8, 2}, + (t_color){180, 165, 140}); +} + +static void cathedral_arches(FILE *f) +{ + t_color arch = {205, 190, 165}; + t_color big = {210, 195, 170}; + double left_z[] = {7.5, 2.5, -2.5, -7.5, -17.5, -22.5}; + double right_z[] = {7.5, 2.5, -7.5, -10.0, -17.5, -22.5}; + int i; + + i = 0; + while (i < 6) + { + write_torus(f, (t_vec){-6, 14, left_z[i]}, (t_vec){0, 0, 1}, + 2.5, 0.4, arch, "spec:0.1"); + write_torus(f, (t_vec){6, 14, right_z[i]}, (t_vec){0, 0, 1}, + 2.5, 0.4, arch, "spec:0.1"); + i++; + } + write_torus(f, (t_vec){0, 15, -15}, (t_vec){1, 0, 0}, 6, 0.5, big, + "spec:0.15"); + write_torus(f, (t_vec){0, 15, -5}, (t_vec){1, 0, 0}, 6, 0.5, big, + "spec:0.15"); + write_torus(f, (t_vec){0, 15, 5}, (t_vec){1, 0, 0}, 6, 0.5, big, + "spec:0.15"); + write_torus(f, (t_vec){0, 14, -28}, (t_vec){1, 0, 0}, 5, 0.5, big, + "spec:0.15"); + write_torus(f, (t_vec){-3, 1, -12}, (t_vec){0.3, 0.1, 0.7}, + 2.0, 0.3, (t_color){195, 180, 155}, NULL); + write_torus(f, (t_vec){5, 0.5, 3}, (t_vec){0.5, 0.2, 0.6}, + 1.8, 0.3, (t_color){190, 175, 150}, NULL); +} + +static void cathedral_rose_window(FILE *f) +{ + t_color outer[] = { + {200, 50, 50}, {220, 100, 30}, {230, 200, 50}, {50, 200, 50}, + {50, 150, 200}, {80, 50, 200}, {150, 50, 200}, {200, 50, 150}, + {50, 100, 220}, {100, 200, 100}, {220, 180, 50}, {200, 80, 80}}; + t_color inner[] = { + {255, 220, 100}, {100, 180, 255}, {255, 100, 100}, + {100, 255, 150}, {200, 100, 255}, {255, 200, 100}}; + double cx = 0.0; + double cy = 12.0; + double cz = -29.3; + double r = 3.2; + double ri = 1.3; + double angle; + int i; + + write_torus(f, (t_vec){0, 12, -29.2}, (t_vec){0, 0, 1}, 3.5, 0.3, + (t_color){180, 170, 155}, "spec:0.3"); + write_torus(f, (t_vec){0, 12, -29.2}, (t_vec){0, 0, 1}, 2.2, 0.2, + (t_color){175, 165, 150}, "spec:0.3"); + i = 0; + while (i < 12) + { + angle = (double)i * PI / 6.0; + write_sphere(f, (t_vec){cx + r * sin(angle), cy + r * cos(angle), cz}, + 0.8, outer[i], "trans:0.7 ior:1.3"); + i++; + } + i = 0; + while (i < 6) + { + angle = (double)i * PI / 3.0; + write_sphere(f, (t_vec){cx + ri * sin(angle), cy + ri * cos(angle), + cz}, 0.5, inner[i], "trans:0.8 ior:1.5"); + i++; + } + write_sphere(f, (t_vec){0, 12, -29.3}, 0.7, + (t_color){255, 255, 220}, "trans:0.85 ior:1.5\tspec:0.5"); +} + +static void cathedral_altar(FILE *f) +{ + t_color gold = {150, 120, 40}; + + write_box_mat(f, (t_vec){0, 1.5, -27}, (t_vec){3, 1.5, 1.5}, + (t_color){220, 210, 190}, "spec:0.3"); + write_box(f, (t_vec){0, 0.5, -27}, (t_vec){3.5, 0.5, 2}, + (t_color){210, 200, 180}); + write_cylinder_mat(f, (t_vec){-1, 2.25, -27}, (t_vec){0, 1, 0}, 0.15, + 1.5, (t_color){240, 230, 200}, "spec:0.4"); + write_sphere(f, (t_vec){-1, 3.75, -27}, 0.25, + (t_color){255, 220, 100}, "spec:0.8"); + write_cylinder_mat(f, (t_vec){1, 2.25, -27}, (t_vec){0, 1, 0}, 0.15, + 1.5, (t_color){240, 230, 200}, "spec:0.4"); + write_sphere(f, (t_vec){1, 3.75, -27}, 0.25, + (t_color){255, 220, 100}, "spec:0.8"); + write_cylinder_mat(f, (t_vec){0, 2.25, -27.5}, (t_vec){0, 1, 0}, 0.2, + 3.0, gold, "spec:0.6 refl:0.2"); + write_cylinder_mat(f, (t_vec){0, 4.0, -27.5}, (t_vec){1, 0, 0}, 0.2, + 1.5, gold, "spec:0.6 refl:0.2"); + write_box(f, (t_vec){0, 0.15, -25.5}, (t_vec){5, 0.15, 1}, + (t_color){200, 190, 170}); + write_box(f, (t_vec){0, 0.30, -24.5}, (t_vec){6, 0.15, 1}, + (t_color){200, 190, 170}); +} + +static void cathedral_debris(FILE *f, int seed) +{ + int i; + double x; + double z; + double sz; + int base_r; + + write_cylinder(f, (t_vec){-4, 0.5, -11}, (t_vec){0.2, 0.1, 0.9}, + 1.3, 3.0, (t_color){195, 180, 155}); + write_cylinder(f, (t_vec){-3, 0.6, -9}, (t_vec){0.8, 0.1, 0.3}, + 1.3, 2.5, (t_color){200, 185, 160}); + write_cylinder(f, (t_vec){5, 0.5, 1}, (t_vec){0.1, 0.05, 0.95}, + 1.3, 4.0, (t_color){190, 175, 150}); + write_cylinder(f, (t_vec){7, 0.6, -18}, (t_vec){0.7, 0.15, 0.5}, + 1.2, 2.0, (t_color){195, 180, 155}); + srand(seed); + i = 0; + while (i < 15) + { + x = randf_range(-9.0, 9.0); + z = randf_range(-25.0, 9.0); + sz = randf_range(0.4, 1.0); + base_r = 175 + (int)(randf() * 15); + write_box(f, (t_vec){x, sz / 2.0, z}, + (t_vec){sz, sz * randf_range(0.5, 1.0), sz * randf_range(0.5, 1.0)}, + (t_color){base_r, base_r - 15, base_r - 30}); + i++; + } + i = 0; + while (i < 15) + { + x = randf_range(-9.0, 9.0); + z = randf_range(-25.0, 9.0); + sz = randf_range(0.2, 0.4); + base_r = 170 + (int)(randf() * 15); + write_sphere(f, (t_vec){x, sz / 2.0, z}, sz, + (t_color){base_r, base_r - 15, base_r - 30}, NULL); + i++; + } + write_triangle(f, (t_vec){-3.5, 0.1, -10}, (t_vec){-2.5, 0.8, -10.5}, + (t_vec){-3, 0.1, -11}, (t_color){185, 170, 145}); + write_triangle(f, (t_vec){4, 0.1, -12}, (t_vec){5, 0.6, -11.5}, + (t_vec){4.5, 0.1, -12.5}, (t_color){180, 165, 140}); + write_triangle(f, (t_vec){-7, 0.1, -5}, (t_vec){-6.5, 0.5, -4.5}, + (t_vec){-7.5, 0.1, -5.5}, (t_color){175, 160, 135}); + write_triangle(f, (t_vec){7.5, 0.1, -8}, (t_vec){8, 0.7, -7.5}, + (t_vec){7, 0.1, -8.5}, (t_color){185, 170, 145}); + write_triangle(f, (t_vec){-1.5, 0.1, 7}, (t_vec){-0.5, 0.6, 6.5}, + (t_vec){-1, 0.1, 7.5}, (t_color){180, 165, 140}); + write_triangle(f, (t_vec){2.5, 0.1, 2.5}, (t_vec){3.5, 0.5, 2}, + (t_vec){3, 0.1, 3}, (t_color){175, 160, 135}); + write_sphere(f, (t_vec){-6, 0.6, -10.5}, 0.7, + (t_color){195, 180, 155}, NULL); + write_sphere(f, (t_vec){6, 0.5, 0.5}, 0.6, + (t_color){200, 185, 160}, NULL); + write_sphere(f, (t_vec){-5.5, 0.4, -20.5}, 0.5, + (t_color){190, 175, 150}, NULL); + write_sphere(f, (t_vec){6.5, 0.55, -15.5}, 0.65, + (t_color){195, 180, 155}, NULL); +} + +static void cathedral_roof(FILE *f) +{ + t_color r1 = {175, 165, 145}; + t_color r2 = {180, 170, 150}; + + write_triangle(f, (t_vec){-6, 14, -8}, (t_vec){-2, 10, -6}, + (t_vec){-6, 14, -4}, r1); + write_triangle(f, (t_vec){-2, 10, -6}, (t_vec){2, 10, -6}, + (t_vec){0, 16, -6}, r2); + write_triangle(f, (t_vec){6, 14, -8}, (t_vec){2, 10, -6}, + (t_vec){6, 14, -4}, r1); + write_triangle(f, (t_vec){-6, 15, 2}, (t_vec){-3, 12, 3}, + (t_vec){-6, 15, 4}, r2); + write_triangle(f, (t_vec){6, 15, 2}, (t_vec){3, 12, 3}, + (t_vec){6, 15, 4}, r1); + write_triangle(f, (t_vec){-4, 16, -20}, (t_vec){0, 18, -20}, + (t_vec){4, 16, -20}, r2); + write_square(f, (t_vec){-4, 10, -10}, (t_vec){0.3, 0.7, 0.1}, 4, + (t_color){170, 160, 140}); + write_square(f, (t_vec){5, 11, -3}, (t_vec){0.2, 0.8, 0.3}, 3.5, + (t_color){175, 165, 145}); + write_square(f, (t_vec){-3, 9, 5}, (t_vec){0.4, 0.6, 0.2}, 3, + (t_color){165, 155, 135}); + write_triangle(f, (t_vec){-8, 0.2, -8}, (t_vec){-5, 0.2, -7}, + (t_vec){-7, 0.2, -10}, (t_color){170, 160, 140}); + write_triangle(f, (t_vec){3, 0.2, 4}, (t_vec){5, 0.2, 3}, + (t_vec){4, 0.2, 6}, (t_color){165, 155, 135}); + write_triangle(f, (t_vec){-2, 0.2, -18}, (t_vec){1, 0.2, -17}, + (t_vec){-1, 0.2, -20}, (t_color){175, 165, 145}); + write_triangle(f, (t_vec){7, 0.2, -20}, (t_vec){9, 0.2, -19}, + (t_vec){8, 0.2, -22}, (t_color){170, 160, 140}); + write_box(f, (t_vec){0, 17, -5}, (t_vec){0.4, 0.4, 15}, + (t_color){190, 180, 160}); + write_box(f, (t_vec){0, 17, -22}, (t_vec){0.4, 0.4, 6}, + (t_color){185, 175, 155}); + write_cylinder(f, (t_vec){-3, 15.5, -5}, (t_vec){0.5, 0.3, 0}, 0.3, + 7.0, (t_color){195, 185, 165}); + write_cylinder(f, (t_vec){3, 15.5, -5}, (t_vec){0.5, 0.3, 0}, 0.3, + 7.0, (t_color){195, 185, 165}); + write_cylinder(f, (t_vec){-3, 15.5, -20}, (t_vec){0.5, 0.3, 0}, 0.3, + 7.0, (t_color){190, 180, 160}); +} + +static void cathedral_tower(FILE *f) +{ + write_cylinder_mat(f, (t_vec){-14, 0, 14}, (t_vec){0, 1, 0}, 5.0, 18.0, + (t_color){195, 180, 155}, "spec:0.15"); + write_cylinder_mat(f, (t_vec){-14, 18, 14}, (t_vec){0, 1, 0}, 4.5, 5.0, + (t_color){190, 175, 150}, "spec:0.15"); + write_cone_mat(f, (t_vec){-14, 23, 14}, (t_vec){0, 1, 0}, 5.0, 4.0, + (t_color){120, 100, 80}, "spec:0.2"); + write_box(f, (t_vec){-11.8, 12, 14}, (t_vec){0.5, 2, 1}, + (t_color){60, 55, 50}); + write_box(f, (t_vec){-14, 12, 11.8}, (t_vec){1, 2, 0.5}, + (t_color){60, 55, 50}); + write_box(f, (t_vec){-11.8, 8, 14}, (t_vec){0.5, 1.5, 0.8}, + (t_color){65, 60, 55}); + write_box(f, (t_vec){-14, 8, 11.8}, (t_vec){0.8, 1.5, 0.5}, + (t_color){65, 60, 55}); + write_box(f, (t_vec){-12, 23, 14}, (t_vec){0.8, 1, 0.8}, + (t_color){200, 185, 160}); + write_box(f, (t_vec){-14, 23, 12}, (t_vec){0.8, 1, 0.8}, + (t_color){200, 185, 160}); + write_box(f, (t_vec){-16, 23, 14}, (t_vec){0.8, 1, 0.8}, + (t_color){195, 180, 155}); + write_box(f, (t_vec){-14, 23, 16}, (t_vec){0.8, 1, 0.8}, + (t_color){195, 180, 155}); + write_cylinder(f, (t_vec){-10, 0.4, 11}, (t_vec){0.6, 0.1, 0.7}, 2.0, + 3.0, (t_color){190, 175, 150}); + write_sphere(f, (t_vec){-11, 0.5, 10}, 0.8, + (t_color){185, 170, 145}, NULL); +} + +static void cathedral_atmosphere(FILE *f) +{ + int i; + double dust_x[] = {-2, -1, 0.5, -0.5, 1}; + double dust_y[] = {8, 10, 9, 11, 7}; + double dust_z[] = {-4, -3, -5, -6, -2}; + double dust_sz[] = {0.15, 0.12, 0.10, 0.13, 0.11}; + + i = 0; + while (i < 5) + { + write_sphere(f, (t_vec){dust_x[i], dust_y[i], dust_z[i]}, dust_sz[i], + (t_color){255, 250, 230}, "trans:0.9 ior:1.0"); + i++; + } + write_disk(f, (t_vec){-2, 0.02, -8}, (t_vec){0, 1, 0}, 3.0, + (t_color){100, 110, 130}, "refl:0.6 spec:0.8"); + write_disk(f, (t_vec){3, 0.02, -3}, (t_vec){0, 1, 0}, 2.5, + (t_color){95, 105, 125}, "refl:0.6 spec:0.8"); + write_disk(f, (t_vec){-5, 0.02, 4}, (t_vec){0, 1, 0}, 2.0, + (t_color){100, 110, 130}, "refl:0.5 spec:0.7"); + write_disk(f, (t_vec){7, 0.02, -15}, (t_vec){0, 1, 0}, 1.8, + (t_color){95, 105, 125}, "refl:0.5 spec:0.7"); + write_disk(f, (t_vec){0, 0.02, -18}, (t_vec){0, 1, 0}, 3.5, + (t_color){100, 115, 135}, "refl:0.7 spec:0.9"); + write_disk(f, (t_vec){-8, 0.02, -12}, (t_vec){0, 1, 0}, 1.5, + (t_color){90, 100, 120}, "refl:0.5 spec:0.7"); + write_disk(f, (t_vec){4, 0.02, 8}, (t_vec){0, 1, 0}, 2.2, + (t_color){100, 110, 130}, "refl:0.6 spec:0.8"); + write_disk(f, (t_vec){-1, 0.02, -24}, (t_vec){0, 1, 0}, 2.8, + (t_color){95, 110, 130}, "refl:0.65 spec:0.85"); +} + +static void cathedral_stained_glass(FILE *f) +{ + write_square_mat(f, (t_vec){-11.5, 10, -5}, (t_vec){1, 0, 0}, 2, + (t_color){180, 40, 40}, "trans:0.75 ior:1.4"); + write_square_mat(f, (t_vec){-11.5, 10, -15}, (t_vec){1, 0, 0}, 2, + (t_color){40, 80, 180}, "trans:0.75 ior:1.4"); + write_square_mat(f, (t_vec){11.5, 10, -10}, (t_vec){1, 0, 0}, 2.5, + (t_color){40, 160, 80}, "trans:0.7 ior:1.4"); + write_square_mat(f, (t_vec){11.5, 10, 0}, (t_vec){1, 0, 0}, 2, + (t_color){180, 160, 40}, "trans:0.75 ior:1.4"); + write_square_mat(f, (t_vec){11.5, 10, -20}, (t_vec){1, 0, 0}, 2, + (t_color){160, 40, 160}, "trans:0.7 ior:1.4"); + write_ellipsoid(f, (t_vec){-3, 0.1, -6}, (t_vec){0.5, 0.05, 0.3}, + (t_color){200, 50, 50}, "trans:0.6 ior:1.3"); + write_ellipsoid(f, (t_vec){2, 0.1, -14}, (t_vec){0.4, 0.05, 0.4}, + (t_color){50, 50, 200}, "trans:0.6 ior:1.3"); + write_ellipsoid(f, (t_vec){-6, 0.1, -19}, (t_vec){0.3, 0.05, 0.5}, + (t_color){50, 180, 50}, "trans:0.6 ior:1.3"); + write_ellipsoid(f, (t_vec){4, 0.1, 5}, (t_vec){0.5, 0.05, 0.3}, + (t_color){200, 180, 50}, "trans:0.6 ior:1.3"); + write_ellipsoid(f, (t_vec){-1, 0.1, -2}, (t_vec){0.4, 0.05, 0.4}, + (t_color){180, 50, 180}, "trans:0.6 ior:1.3"); + write_square_mat(f, (t_vec){0, 11, -29.5}, (t_vec){0, 0, 1}, 3, + (t_color){240, 220, 180}, "trans:0.5 ior:1.4 spec:0.3"); + write_sphere(f, (t_vec){-11.5, 12.5, -5}, 0.5, + (t_color){220, 60, 60}, "trans:0.6 ior:1.3"); + write_sphere(f, (t_vec){-11.5, 12.5, -15}, 0.5, + (t_color){60, 100, 220}, "trans:0.6 ior:1.3"); + write_sphere(f, (t_vec){11.5, 12.5, -10}, 0.5, + (t_color){60, 200, 100}, "trans:0.6 ior:1.3"); + write_sphere(f, (t_vec){11.5, 12.5, 0}, 0.5, + (t_color){220, 200, 60}, "trans:0.6 ior:1.3"); +} + +static void cathedral_decorations(FILE *f) +{ + write_paraboloid(f, (t_vec){-3, 0, 10}, (t_vec){0, 1, 0}, 0.6, 1.5, + (t_color){200, 190, 170}, "spec:0.3"); + write_disk(f, (t_vec){-3, 1.5, 10}, (t_vec){0, 1, 0}, 1.8, + (t_color){180, 190, 210}, "refl:0.3 spec:0.5"); + write_box_mat(f, (t_vec){3, 0.2, -22}, (t_vec){2.5, 0.2, 1.2}, + (t_color){170, 165, 155}, "spec:0.15"); + write_sphere(f, (t_vec){3, 0.45, -22}, 0.3, + (t_color){160, 155, 145}, NULL); + write_cylinder_mat(f, (t_vec){-2, 0, -20}, (t_vec){0, 1, 0}, 0.2, 4.0, + (t_color){80, 60, 50}, "spec:0.7 refl:0.3"); + write_disk(f, (t_vec){-2, 4, -20}, (t_vec){0, 1, 0}, 1.2, + (t_color){80, 60, 50}, "spec:0.7 refl:0.3"); + write_sphere(f, (t_vec){-2, 4.2, -20}, 0.15, + (t_color){255, 200, 80}, "spec:0.9"); + write_sphere(f, (t_vec){-1.5, 4.2, -20}, 0.15, + (t_color){255, 200, 80}, "spec:0.9"); + write_sphere(f, (t_vec){-2.5, 4.2, -20}, 0.15, + (t_color){255, 200, 80}, "spec:0.9"); + write_ellipsoid(f, (t_vec){-11.3, 3, 5}, (t_vec){0.8, 1.5, 0.5}, + (t_color){40, 80, 30}, NULL); + write_ellipsoid(f, (t_vec){-11.3, 5, 4}, (t_vec){0.6, 1.2, 0.4}, + (t_color){35, 75, 25}, NULL); + write_ellipsoid(f, (t_vec){-11.3, 2, 6}, (t_vec){0.7, 1, 0.6}, + (t_color){45, 85, 35}, NULL); + write_ellipsoid(f, (t_vec){11.3, 4, -7}, (t_vec){0.5, 1.3, 0.5}, + (t_color){40, 80, 30}, NULL); + write_ellipsoid(f, (t_vec){11.3, 6, -8}, (t_vec){0.6, 1, 0.4}, + (t_color){35, 70, 25}, NULL); +} + +static void gen_cathedral_ruins(FILE *f, t_config *cfg) +{ + double left_h[] = {14, 14, 14, 14, 8, 14, 5, 14}; + double right_h[] = {14, 14, 6, 14, 14, 10, 14, 14}; + double col_z; + int i; + + fprintf(f, "R\t%d\t%d\n", cfg->width, cfg->height); + fprintf(f, "A\t0.08\t200,190,170\n"); + fprintf(f, "E\n\n"); + fprintf(f, "c\t0.0,12.0,25.0\t0.055,0,0\t70\n"); + fprintf(f, "c\t-4.0,7.0,18.0\t0.04,-0.03,0\t70\n"); + fprintf(f, "c\t8.0,6.0,-15.0\t0.03,0.3,0\t70\n"); + fprintf(f, "c\t0.0,2.0,16.0\t0.0,0,0\t70\n\n"); + write_light(f, (t_vec){-3, 25, -5}, 0.9, (t_color){255, 240, 200}); + write_light(f, (t_vec){5, 20, 5}, 0.6, (t_color){255, 235, 190}); + write_light(f, (t_vec){-8, 15, -15}, 0.5, (t_color){255, 220, 180}); + write_light(f, (t_vec){0, 12, -25}, 0.4, (t_color){255, 200, 150}); + write_light(f, (t_vec){10, 18, -10}, 0.3, (t_color){200, 210, 255}); + write_light(f, (t_vec){0, 3, 10}, 0.2, (t_color){180, 190, 210}); + fprintf(f, "\n"); + write_plane(f, (t_vec){0, 0, 0}, (t_vec){0, 1, 0}, + (t_color){170, 160, 140}); + fprintf(f, "\n"); + write_square(f, (t_vec){-2, 0.01, -20}, (t_vec){0, 1, 0}, 8, + (t_color){140, 130, 110}); + write_square(f, (t_vec){3, 0.01, -8}, (t_vec){0, 1, 0}, 6, + (t_color){155, 145, 125}); + write_square(f, (t_vec){-5, 0.01, 0}, (t_vec){0, 1, 0}, 5, + (t_color){145, 135, 120}); + write_square(f, (t_vec){6, 0.01, -15}, (t_vec){0, 1, 0}, 7, + (t_color){160, 150, 130}); + fprintf(f, "\n"); + cathedral_walls(f); + fprintf(f, "\n"); + i = 0; + while (i < 8) + { + col_z = 10.0 - (double)i * 5.0; + cathedral_column(f, -6.0, col_z, left_h[i]); + i++; + } + fprintf(f, "\n"); + i = 0; + while (i < 8) + { + col_z = 10.0 - (double)i * 5.0; + cathedral_column(f, 6.0, col_z, right_h[i]); + i++; + } + fprintf(f, "\n"); + cathedral_arches(f); + fprintf(f, "\n"); + cathedral_rose_window(f); + fprintf(f, "\n"); + cathedral_altar(f); + fprintf(f, "\n"); + cathedral_debris(f, cfg->seed); + fprintf(f, "\n"); + cathedral_roof(f); + fprintf(f, "\n"); + cathedral_tower(f); + fprintf(f, "\n"); + cathedral_atmosphere(f); + fprintf(f, "\n"); + cathedral_stained_glass(f); + fprintf(f, "\n"); + cathedral_decorations(f); +} + static void print_usage(void) { printf("Usage: gen_scene [OPTIONS]\n\n"); printf("Options:\n"); printf(" --preset NAME Preset scene (cornell, checkerboard, solar,\n"); - printf(" spiral, grid, scatter, reflections, glass)\n"); + printf(" spiral, grid, scatter, reflections, glass,\n"); + printf(" cathedral)\n"); printf(" --width N Image width (default: 1920)\n"); printf(" --height N Image height (default: 1080)\n"); printf(" --objects N Number of objects (default: 20)\n"); @@ -481,6 +1024,8 @@ static void list_presets(void) printf(" scatter - Random scatter of mixed shapes\n"); printf(" reflections - Showcase of reflective and refractive materials\n"); printf(" glass - Glass/transparent sphere showcase\n"); + printf(" cathedral - Medieval cathedral ruins with columns, arches," + " rose window\n"); } static void generate_scene(FILE *f, t_config *cfg) @@ -501,6 +1046,8 @@ static void generate_scene(FILE *f, t_config *cfg) gen_reflections(f, cfg); else if (strcmp(cfg->preset, "glass") == 0) gen_glass_spheres(f, cfg); + else if (strcmp(cfg->preset, "cathedral") == 0) + gen_cathedral_ruins(f, cfg); else { fprintf(stderr, "Error: unknown preset '%s'\n", cfg->preset);