diff --git a/src/cli/argument_parser.hpp b/src/cli/argument_parser.hpp new file mode 100644 index 0000000..ac96c5e --- /dev/null +++ b/src/cli/argument_parser.hpp @@ -0,0 +1,311 @@ +#pragma once + +#include +#include +#include +#include +#include "../core/image.hpp" +#include "../core/logger.hpp" + +// ArgumentParser encapsulates all command-line argument parsing logic +// Extracted from main.cpp to improve testability and maintainability +class ArgumentParser { +public: + // Parsed configuration + struct Config { + // Resolution settings + Resolution resolution = Resolution::parse_from_string("1024x768"); + + // Scene settings + std::string scene_filename = "../assets/showcase_scene.scene"; + bool use_scene_file = true; + bool scene_explicitly_set = false; + + // Material settings + std::string material_type = "lambert"; + float roughness = 0.5f; + float metallic = 0.0f; + float specular = 0.04f; + + // Mode settings + bool interactive_mode = false; + VerbosityLevel verbosity = VerbosityLevel::Verbose; + + // Parse result + bool show_help = false; + bool has_error = false; + std::string error_message; + }; + + // Parse command-line arguments + // Returns false if parsing failed or help was requested + static bool parse(int argc, char* argv[], Config& config) { + // Check for help request first + for (int i = 1; i < argc; i++) { + if (std::strcmp(argv[i], "--help") == 0 || std::strcmp(argv[i], "-h") == 0) { + config.show_help = true; + return false; + } + } + + // Parse all arguments + for (int i = 1; i < argc; i++) { + if (std::strcmp(argv[i], "--scene") == 0 && i + 1 < argc) { + config.scene_filename = argv[i + 1]; + config.use_scene_file = true; + config.scene_explicitly_set = true; + std::cout << "Scene file override: " << config.scene_filename << std::endl; + i++; + } + else if (std::strcmp(argv[i], "--no-scene") == 0) { + config.use_scene_file = false; + config.scene_explicitly_set = true; + std::cout << "Scene loading disabled - using hardcoded sphere" << std::endl; + } + else if (std::strcmp(argv[i], "--resolution") == 0 && i + 1 < argc) { + if (!parse_resolution(argv[i + 1], config)) { + return false; + } + i++; + } + else if (std::strcmp(argv[i], "--preset") == 0 && i + 1 < argc) { + if (!apply_preset(argv[i + 1], config)) { + return false; + } + i++; + } + else if (std::strcmp(argv[i], "--showcase") == 0) { + apply_preset("showcase", config); + } + else if (std::strcmp(argv[i], "--cook-torrance") == 0) { + apply_preset("cook-torrance", config); + } + else if (std::strcmp(argv[i], "--performance") == 0) { + apply_preset("performance", config); + } + else if (std::strcmp(argv[i], "--quality") == 0) { + apply_preset("quality", config); + } + else if (std::strcmp(argv[i], "--material") == 0 && i + 1 < argc) { + if (!parse_material(argv[i + 1], config)) { + return false; + } + i++; + } + else if (std::strcmp(argv[i], "--roughness") == 0 && i + 1 < argc) { + config.roughness = parse_float_clamped(argv[i + 1], 0.01f, 1.0f); + std::cout << "Roughness override: " << config.roughness << std::endl; + i++; + } + else if (std::strcmp(argv[i], "--metallic") == 0 && i + 1 < argc) { + config.metallic = parse_float_clamped(argv[i + 1], 0.0f, 1.0f); + std::cout << "Metallic override: " << config.metallic << std::endl; + i++; + } + else if (std::strcmp(argv[i], "--specular") == 0 && i + 1 < argc) { + config.specular = parse_float_clamped(argv[i + 1], 0.0f, 1.0f); + std::cout << "Specular override: " << config.specular << std::endl; + i++; + } + else if (std::strcmp(argv[i], "--quiet") == 0) { + config.verbosity = VerbosityLevel::Normal; + std::cout << "Quiet mode enabled - minimal output" << std::endl; + } + else if (std::strcmp(argv[i], "--verbose") == 0) { + config.verbosity = VerbosityLevel::Verbose; + std::cout << "Verbose mode enabled - full educational output" << std::endl; + } + else if (std::strcmp(argv[i], "--interactive") == 0) { + config.interactive_mode = true; + config.material_type = "cook-torrance"; + config.verbosity = VerbosityLevel::Normal; + if (!config.scene_explicitly_set) { + config.use_scene_file = false; + } + std::cout << "Interactive mode enabled - Cook-Torrance material editor (quiet mode default)" << std::endl; + } + else if (strncmp(argv[i], "--", 2) == 0) { + // Check if it's a known camera argument (handled by Camera class) + if (is_camera_argument(argv[i])) { + if (i + 1 < argc) i++; // Skip parameter if it exists + } else { + // Unknown argument + config.has_error = true; + config.error_message = std::string("Unknown argument '") + argv[i] + "'"; + print_suggestion(argv[i]); + return false; + } + } + } + + return true; + } + + // Print help text + static void print_help() { + std::cout << "\n=== Educational Ray Tracer - Command Line Help ===" << std::endl; + std::cout << "\nResolution parameters:" << std::endl; + std::cout << "--resolution Set image resolution (e.g., --resolution 1024x768)" << std::endl; + std::cout << " Common presets: 256x256, 512x512, 1024x1024, 2048x2048" << std::endl; + std::cout << " Default: 1024x768 (Epic 2 Showcase)" << std::endl; + + std::cout << "\nScene parameters:" << std::endl; + std::cout << "--scene Load scene from file (default: assets/showcase_scene.scene)" << std::endl; + std::cout << "--no-scene Use hardcoded single sphere for compatibility" << std::endl; + + std::cout << "\nMaterial parameters (Cook-Torrance BRDF):" << std::endl; + std::cout << "--material Material type: lambert, cook-torrance (default: lambert)" << std::endl; + std::cout << "--roughness Surface roughness for Cook-Torrance (0.0-1.0, default: 0.5)" << std::endl; + std::cout << "--metallic Metallic parameter for Cook-Torrance (0.0-1.0, default: 0.0)" << std::endl; + std::cout << "--specular Specular reflectance for dielectrics (0.0-1.0, default: 0.04)" << std::endl; + + std::cout << "\nDebug and verbosity parameters:" << std::endl; + std::cout << "--quiet Minimal output (no educational breakdowns, errors only)" << std::endl; + std::cout << "--verbose Full educational output (default behavior)" << std::endl; + + std::cout << "\nInteractive mode:" << std::endl; + std::cout << "--interactive Enable interactive Cook-Torrance material editor" << std::endl; + std::cout << " Supports real-time parameter adjustment, scene loading" << std::endl; + std::cout << " Material presets, and educational feedback" << std::endl; + + std::cout << "\nQuick presets:" << std::endl; + std::cout << "--preset showcase Epic 2 showcase (1024x768, complex scene, optimal camera)" << std::endl; + std::cout << "--showcase Shorthand for --preset showcase" << std::endl; + std::cout << "--preset cook-torrance Cook-Torrance demo (1024x1024, single sphere)" << std::endl; + std::cout << "--cook-torrance Shorthand for --preset cook-torrance" << std::endl; + std::cout << "--preset performance Fast render (512x512, simple scene, basic camera)" << std::endl; + std::cout << "--performance Shorthand for --preset performance" << std::endl; + std::cout << "--preset quality High quality (1024x1024, showcase scene, wide FOV)" << std::endl; + std::cout << "--quality Shorthand for --preset quality" << std::endl; + + std::cout << "\nCamera parameters:" << std::endl; + std::cout << "--camera-pos x,y,z Set camera position (default: 0,0,1)" << std::endl; + std::cout << "--camera-target x,y,z Set camera target (default: 0,0,-6)" << std::endl; + std::cout << "--fov degrees Set field of view (default: 60)" << std::endl; + + std::cout << "\nScene file format (Lambert materials only):" << std::endl; + std::cout << "material - Define material with RGB albedo" << std::endl; + std::cout << "sphere - Add sphere to scene" << std::endl; + + std::cout << "\nNOTE: Scene files only support Lambert materials. For Cook-Torrance materials," << std::endl; + std::cout << " use --cook-torrance preset (single sphere) or --no-scene with --material cook-torrance" << std::endl; + } + +private: + // Parse resolution string + static bool parse_resolution(const char* str, Config& config) { + try { + config.resolution = Resolution::parse_from_string(str); + std::cout << "Resolution override: " << config.resolution.width << "x" << config.resolution.height << std::endl; + config.resolution.print_memory_analysis(); + return true; + } catch (const std::exception& e) { + config.has_error = true; + config.error_message = std::string("Invalid resolution format '") + str + "'"; + std::cout << "ERROR: " << config.error_message << std::endl; + std::cout << "Error details: " << e.what() << std::endl; + std::cout << "Supported formats: WIDTHxHEIGHT (e.g., 512x512)" << std::endl; + std::cout << "Common presets: 256x256, 512x512, 1024x1024, 2048x2048" << std::endl; + return false; + } + } + + // Apply a preset configuration + static bool apply_preset(const std::string& preset, Config& config) { + std::cout << "Using preset: " << preset << std::endl; + + if (preset == "showcase") { + config.scene_filename = "../assets/showcase_scene.scene"; + config.resolution = Resolution::parse_from_string("1024x768"); + std::cout << "Epic 2 Showcase preset: 1024x768, complex scene, optimal camera" << std::endl; + } + else if (preset == "cook-torrance") { + config.use_scene_file = false; + config.resolution = Resolution::parse_from_string("1024x1024"); + config.material_type = "cook-torrance"; + config.verbosity = VerbosityLevel::Verbose; + std::cout << "Cook-Torrance Demo preset: 1024x1024, single sphere with Cook-Torrance material" << std::endl; + } + else if (preset == "performance") { + config.resolution = Resolution::MEDIUM; + config.scene_filename = "../assets/simple_scene.scene"; + std::cout << "Performance preset: 512x512, simple scene, fast render" << std::endl; + } + else if (preset == "quality") { + config.resolution = Resolution::LARGE; + config.scene_filename = "../assets/showcase_scene.scene"; + std::cout << "Quality preset: 1024x1024, showcase scene, maximum quality" << std::endl; + } + else { + config.has_error = true; + config.error_message = std::string("Unknown preset '") + preset + "'"; + std::cout << "ERROR: " << config.error_message << std::endl; + std::cout << "Available presets: showcase, cook-torrance, performance, quality" << std::endl; + return false; + } + return true; + } + + // Parse and validate material type + static bool parse_material(const char* str, Config& config) { + config.material_type = str; + std::cout << "Material type override: " << config.material_type << std::endl; + + if (config.material_type != "lambert" && config.material_type != "cook-torrance") { + config.has_error = true; + config.error_message = std::string("Unknown material type '") + str + "'"; + std::cout << "ERROR: " << config.error_message << std::endl; + std::cout << "Supported materials: lambert, cook-torrance" << std::endl; + return false; + } + return true; + } + + // Parse float and clamp to range + static float parse_float_clamped(const char* str, float min_val, float max_val) { + float value = std::stof(str); + return std::max(min_val, std::min(max_val, value)); + } + + // Check if argument is a camera-related argument + static bool is_camera_argument(const char* arg) { + return std::strcmp(arg, "--camera-pos") == 0 || + std::strcmp(arg, "--camera-target") == 0 || + std::strcmp(arg, "--fov") == 0; + } + + // Print suggestion for unknown argument + static void print_suggestion(const char* arg) { + std::cout << "ERROR: Unknown argument '" << arg << "'" << std::endl; + std::cout << "Did you mean:" << std::endl; + + std::string arg_str = arg; + + if (arg_str.find("quality") != std::string::npos) { + std::cout << " --preset quality (High quality preset)" << std::endl; + std::cout << " --quality (Shorthand for --preset quality)" << std::endl; + } else if (arg_str.find("performance") != std::string::npos) { + std::cout << " --preset performance (Fast render preset)" << std::endl; + std::cout << " --performance (Shorthand for --preset performance)" << std::endl; + } else if (arg_str.find("showcase") != std::string::npos) { + std::cout << " --preset showcase (Epic 2 showcase preset)" << std::endl; + std::cout << " --showcase (Shorthand for --preset showcase)" << std::endl; + } else if (arg_str.find("resolution") != std::string::npos || arg_str.find("res") != std::string::npos) { + std::cout << " --resolution WxH (e.g., --resolution 1024x768)" << std::endl; + } else if (arg_str.find("scene") != std::string::npos) { + std::cout << " --scene (Load custom scene file)" << std::endl; + std::cout << " --no-scene (Use hardcoded sphere)" << std::endl; + } else if (arg_str.find("camera") != std::string::npos || arg_str.find("pos") != std::string::npos) { + std::cout << " --camera-pos x,y,z (Set camera position)" << std::endl; + std::cout << " --camera-target x,y,z(Set camera target)" << std::endl; + } else if (arg_str.find("fov") != std::string::npos) { + std::cout << " --fov degrees (Set field of view)" << std::endl; + } else { + std::cout << " --help (Show all available options)" << std::endl; + std::cout << " --preset showcase (Epic 2 showcase)" << std::endl; + std::cout << " --resolution WxH (Set custom resolution)" << std::endl; + } + + std::cout << "\nUse --help to see all available options." << std::endl; + } +}; diff --git a/src/core/constants.hpp b/src/core/constants.hpp new file mode 100644 index 0000000..a8eef35 --- /dev/null +++ b/src/core/constants.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "vector3.hpp" +#include "point3.hpp" + +// Educational Ray Tracer - Common Constants +// Centralized location for magic numbers and default values + +namespace Constants { + // Default colors + inline const Vector3 BACKGROUND_COLOR(0.1f, 0.1f, 0.15f); // Dark blue background + inline const Vector3 DEFAULT_MATERIAL_COLOR(0.7f, 0.3f, 0.3f); // Reddish default for demos + + // Default geometry positions + inline const Point3 DEFAULT_SPHERE_POSITION(0.0f, 0.0f, -3.0f); // Default sphere position + inline const Point3 TEST_SPHERE_POSITION(0.0f, 0.0f, -5.0f); // Test sphere position + inline const float DEFAULT_SPHERE_RADIUS = 1.0f; + + // Default light parameters + inline const float DEFAULT_LIGHT_INTENSITY = 100.0f; +} diff --git a/src/core/logger.hpp b/src/core/logger.hpp new file mode 100644 index 0000000..192dcf7 --- /dev/null +++ b/src/core/logger.hpp @@ -0,0 +1,118 @@ +#pragma once +#include +#include +#include + +// Verbosity levels for controlling educational output +// Silent: Only critical errors +// Normal: Standard output without educational details +// Verbose: Full educational breakdowns (original behavior) +// Debug: Everything including debug info +enum class VerbosityLevel { + Silent = 0, // Only errors + Normal = 1, // Normal output, no educational details + Verbose = 2, // Full educational output (original default) + Debug = 3 // Debug info + all educational output +}; + +// Log message types with different display requirements +enum class LogType { + Error, // Always shown (unless silent) + Warning, // Shown in Normal and above + Info, // Shown in Normal and above + Educational, // Shown in Verbose and above (mathematical breakdowns, explanations) + Debug // Shown in Debug only +}; + +// Global logger for controlling verbosity throughout the raytracer +// Educational focus: allows toggling between learning mode (verbose) and production mode (quiet) +// Static design: single global verbosity setting accessible from all components +class Logger { +private: + static VerbosityLevel current_level; + +public: + // Set global verbosity level + static void set_level(VerbosityLevel level) { + current_level = level; + } + + static VerbosityLevel get_level() { + return current_level; + } + + // Check if a log type should be shown at current verbosity level + static bool should_log(LogType type) { + switch (type) { + case LogType::Error: + return current_level >= VerbosityLevel::Silent; + case LogType::Warning: + case LogType::Info: + return current_level >= VerbosityLevel::Normal; + case LogType::Educational: + return current_level >= VerbosityLevel::Verbose; + case LogType::Debug: + return current_level >= VerbosityLevel::Debug; + default: + return false; + } + } + + // Core logging function + static void log(LogType type, const std::string& message) { + if (should_log(type)) { + std::cout << message << std::endl; + } + } + + // Convenience functions for different log types + static void error(const std::string& msg) { + if (should_log(LogType::Error)) { + std::cout << "ERROR: " << msg << std::endl; + } + } + + static void warning(const std::string& msg) { + if (should_log(LogType::Warning)) { + std::cout << "WARNING: " << msg << std::endl; + } + } + + static void info(const std::string& msg) { + if (should_log(LogType::Info)) { + std::cout << msg << std::endl; + } + } + + static void educational(const std::string& msg) { + if (should_log(LogType::Educational)) { + std::cout << msg << std::endl; + } + } + + static void debug(const std::string& msg) { + if (should_log(LogType::Debug)) { + std::cout << "DEBUG: " << msg << std::endl; + } + } + + // Helper functions to check current verbosity state + static bool is_verbose() { + return current_level >= VerbosityLevel::Verbose; + } + + static bool is_debug() { + return current_level >= VerbosityLevel::Debug; + } + + static bool is_quiet() { + return current_level <= VerbosityLevel::Normal; + } + + static bool is_silent() { + return current_level == VerbosityLevel::Silent; + } +}; + +// Initialize static member - default to Verbose for educational mode +inline VerbosityLevel Logger::current_level = VerbosityLevel::Verbose; diff --git a/src/core/renderer.hpp b/src/core/renderer.hpp new file mode 100644 index 0000000..da6644f --- /dev/null +++ b/src/core/renderer.hpp @@ -0,0 +1,171 @@ +#pragma once + +#include "vector3.hpp" +#include "image.hpp" +#include "camera.hpp" +#include "scene.hpp" +#include "logger.hpp" +#include "performance_timer.hpp" +#include "progress_reporter.hpp" +#include "constants.hpp" +#include + +// Renderer class encapsulates the core ray tracing rendering logic +// Extracted from main.cpp lambda to improve code organization and testability +class Renderer { +public: + // Rendering statistics struct - consolidated performance counters + struct Stats { + int rays_generated = 0; + int intersection_tests = 0; + int shading_calculations = 0; + int background_pixels = 0; + + void reset() { + rays_generated = 0; + intersection_tests = 0; + shading_calculations = 0; + background_pixels = 0; + } + }; + + // Public stats for access after rendering + Stats stats; + + // Constructor with all required dependencies + Renderer(Camera& camera, + Scene& scene, + const Point3& camera_pos, + PerformanceTimer& perf_timer) + : render_camera(camera), + render_scene(scene), + camera_position(camera_pos), + performance_timer(perf_timer) {} + + // Main rendering method + // width, height: output image dimensions + // Always uses Scene system for unified rendering + Image render(int width, int height) { + Image rendered_image(width, height); + + std::cout << "\n--- Multi-Ray Rendering Process ---" << std::endl; + std::cout << "Beginning pixel-by-pixel ray generation with performance monitoring..." << std::endl; + + // Initialize comprehensive progress reporting (Story 2.4 AC: 4) + int local_total_pixels = width * height; + ProgressReporter local_progress_reporter(local_total_pixels, &performance_timer, Logger::is_quiet()); + + // Performance counters for this render + int local_rays_generated = 0; + int local_intersection_tests = 0; + int local_shading_calculations = 0; + int local_background_pixels = 0; + + // Multi-ray pixel sampling: one ray per pixel with comprehensive progress tracking + for (int y = 0; y < height; y++) { + + for (int x = 0; x < width; x++) { + // Phase 1: Ray Generation with precise timing + performance_timer.start_phase(PerformanceTimer::RAY_GENERATION); + Ray pixel_ray = render_camera.generate_ray( + static_cast(x), + static_cast(y), + width, + height + ); + performance_timer.end_phase(PerformanceTimer::RAY_GENERATION); + performance_timer.increment_counter(PerformanceTimer::RAY_GENERATION); + local_rays_generated++; + + // Unified Scene-based rendering path + Vector3 pixel_color = render_with_scene_materials(pixel_ray, x, y, width, + local_intersection_tests, + local_shading_calculations, + local_background_pixels); + + // Store pixel in image buffer (no additional timing - included in IMAGE_OUTPUT) + rendered_image.set_pixel(x, y, pixel_color); + } + + // Update progress reporting after each row for better granularity + int completed_pixels = (y + 1) * width; + size_t current_memory = rendered_image.memory_usage_bytes() + render_scene.calculate_scene_memory_usage(); + local_progress_reporter.update_progress(completed_pixels, current_memory); + + // Check for interrupt capability (placeholder for user cancellation) + if (local_progress_reporter.should_interrupt()) { + std::cout << "\nRendering interrupted by user request." << std::endl; + break; + } + } + + // Update global counters + stats.rays_generated += local_rays_generated; + stats.intersection_tests += local_intersection_tests; + stats.shading_calculations += local_shading_calculations; + stats.background_pixels += local_background_pixels; + + return rendered_image; + } + +private: + Camera& render_camera; + Scene& render_scene; + Point3 camera_position; + PerformanceTimer& performance_timer; + + // Scene-based rendering path + Vector3 render_with_scene_materials(const Ray& pixel_ray, int x, int y, int width, + int& local_intersection_tests, + int& local_shading_calculations, + int& local_background_pixels) { + performance_timer.start_phase(PerformanceTimer::INTERSECTION_TESTING); + Scene::Intersection intersection = render_scene.intersect(pixel_ray); + performance_timer.end_phase(PerformanceTimer::INTERSECTION_TESTING); + performance_timer.increment_counter(PerformanceTimer::INTERSECTION_TESTING); + local_intersection_tests++; + + if (intersection.hit) { + // Phase 3: Shading Calculation using scene materials + performance_timer.start_phase(PerformanceTimer::SHADING_CALCULATION); + local_shading_calculations++; + + // Multi-light accumulation (AC2 - Story 3.2) + Vector3 pixel_color(0, 0, 0); // Initialize accumulator + Vector3 surface_point = Vector3(intersection.point.x, intersection.point.y, intersection.point.z); + Vector3 view_direction = (camera_position - intersection.point).normalize(); + + // Multi-light accumulation from scene + for (const auto& light : render_scene.lights) { + Vector3 light_direction; + float light_distance; + Vector3 light_contribution = light->illuminate(surface_point, light_direction, light_distance); + + // Shadow ray testing (AC3) + if (!light->is_occluded(surface_point, light_direction, light_distance, render_scene)) { + // BRDF evaluation for this light + Vector3 brdf_contribution = intersection.material->scatter_light( + light_direction, view_direction, intersection.normal, + light_contribution + ); + pixel_color += brdf_contribution; + } + } + + // Educational output for multi-light (if enabled and first few pixels) + if (Logger::is_verbose() && (x + y * width) < 5) { + std::cout << "\n=== Multi-Light Accumulation (Pixel " << (x + y * width) << ") ===" << std::endl; + std::cout << "Scene lights: " << render_scene.lights.size() << std::endl; + std::cout << "Final accumulated color: (" << pixel_color.x << ", " << pixel_color.y << ", " << pixel_color.z << ")" << std::endl; + } + performance_timer.end_phase(PerformanceTimer::SHADING_CALCULATION); + performance_timer.increment_counter(PerformanceTimer::SHADING_CALCULATION); + + return pixel_color; + } else { + // No intersection - background color + local_background_pixels++; + return Constants::BACKGROUND_COLOR; + } + } +}; diff --git a/src/core/scene.hpp b/src/core/scene.hpp index 6e1fbdd..b904284 100644 --- a/src/core/scene.hpp +++ b/src/core/scene.hpp @@ -3,6 +3,7 @@ #include "point3.hpp" #include "ray.hpp" #include "sphere.hpp" +#include "logger.hpp" #include "../materials/lambert.hpp" #include "../materials/cook_torrance.hpp" #include "../materials/material_base.hpp" @@ -63,8 +64,8 @@ class Scene { // Algorithm: iterate through all primitives, track closest intersection with t-value comparison // Educational features: performance statistics, detailed console output for learning // Returns: complete intersection information including material and primitive references - Intersection intersect(const Ray& ray, bool verbose = true) const { - if (verbose) { + Intersection intersect(const Ray& ray) const { + if (Logger::is_verbose()) { std::cout << "\n=== Ray-Scene Intersection Testing ===" << std::endl; std::cout << "Ray origin: (" << ray.origin.x << ", " << ray.origin.y << ", " << ray.origin.z << ")" << std::endl; std::cout << "Ray direction: (" << ray.direction.x << ", " << ray.direction.y << ", " << ray.direction.z << ")" << std::endl; @@ -88,7 +89,7 @@ class Scene { current_test_count++; total_intersection_tests++; - if (verbose) { + if (Logger::is_verbose()) { std::cout << "\nTesting sphere " << i << ":" << std::endl; std::cout << " Center: (" << sphere.center.x << ", " << sphere.center.y << ", " << sphere.center.z << ")" << std::endl; std::cout << " Radius: " << sphere.radius << std::endl; @@ -96,18 +97,18 @@ class Scene { } // Perform ray-sphere intersection test - Sphere::Intersection sphere_hit = sphere.intersect(ray, verbose); + Sphere::Intersection sphere_hit = sphere.intersect(ray); if (sphere_hit.hit) { current_hit_count++; - if (verbose) { + if (Logger::is_verbose()) { std::cout << " HIT at t = " << sphere_hit.t << std::endl; } - + // Check if this is the closest intersection so far if (sphere_hit.t > 0.001f && sphere_hit.t < closest_hit.t) { successful_intersections++; - if (verbose) { + if (Logger::is_verbose()) { std::cout << " NEW CLOSEST HIT (previous closest t = " << closest_hit.t << ")" << std::endl; } @@ -119,27 +120,27 @@ class Scene { closest_hit.normal = sphere_hit.normal; closest_hit.material = materials[sphere.material_index].get(); closest_hit.primitive = &sphere; - - if (verbose) { + + if (Logger::is_verbose()) { std::cout << " Material assigned: " << sphere.material_index << std::endl; } } else { - if (verbose) { - std::cout << " ERROR: Invalid material index " << sphere.material_index + if (Logger::is_verbose()) { + std::cout << " ERROR: Invalid material index " << sphere.material_index << " (valid range: 0-" << (materials.size()-1) << ")" << std::endl; } } } else if (sphere_hit.t <= 0.001f) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << " REJECTED: t too small (self-intersection avoidance)" << std::endl; } } else { - if (verbose) { + if (Logger::is_verbose()) { std::cout << " REJECTED: farther than current closest hit" << std::endl; } } } else { - if (verbose) { + if (Logger::is_verbose()) { std::cout << " MISS" << std::endl; } } @@ -152,7 +153,7 @@ class Scene { total_intersection_time_ms += intersection_time; // Educational performance statistics output - if (verbose) { + if (Logger::is_verbose()) { std::cout << "\n=== Intersection Performance Statistics ===" << std::endl; std::cout << "Current ray tests: " << current_test_count << std::endl; std::cout << "Current ray hits: " << current_hit_count << std::endl; @@ -169,7 +170,7 @@ class Scene { total_intersection_time_ms / total_intersection_tests : 0.0f) << "ms" << std::endl; } - if (verbose) { + if (Logger::is_verbose()) { if (closest_hit.hit) { std::cout << "\n=== Final Closest Hit Result ===" << std::endl; std::cout << "Hit point: (" << closest_hit.point.x << ", " << closest_hit.point.y << ", " << closest_hit.point.z << ")" << std::endl; @@ -232,12 +233,7 @@ class Scene { return material_index; } - // Legacy add_material method for backward compatibility with existing Lambert code - // Wraps Lambert material in unique_ptr and delegates to polymorphic method - int add_material(const LambertMaterial& material) { - auto lambert_material = std::make_unique(material); - return add_material(std::move(lambert_material)); - } + // Note: Legacy add_material(const LambertMaterial&) removed - use add_material(std::make_unique(...)) instead // Add polymorphic light to scene and return its index // Educational transparency: reports light assignment and validates parameters diff --git a/src/core/scene_loader.hpp b/src/core/scene_loader.hpp index 7dbe999..65bc1f7 100644 --- a/src/core/scene_loader.hpp +++ b/src/core/scene_loader.hpp @@ -230,7 +230,7 @@ class SceneLoader { std::cout << " Roughness: " << roughness << ", Metallic: " << metallic << ", Specular: " << specular << std::endl; // Create Cook-Torrance material with validation - auto material = std::make_unique(Vector3(r, g, b), roughness, metallic, specular, true); + auto material = std::make_unique(Vector3(r, g, b), roughness, metallic, specular); // Educational parameter validation with detailed output if (!material->validate_parameters()) { diff --git a/src/core/sphere.hpp b/src/core/sphere.hpp index dc287bf..a525745 100644 --- a/src/core/sphere.hpp +++ b/src/core/sphere.hpp @@ -2,6 +2,7 @@ #include "point3.hpp" #include "vector3.hpp" #include "ray.hpp" +#include "logger.hpp" #include #include @@ -18,9 +19,9 @@ class Sphere { // Constructor with explicit center point, radius, and material index // Geometric interpretation: defines sphere as locus of points at radius distance from center // Material reference: enables material property lookup in Scene's materials container - Sphere(const Point3& center, float radius, int material_idx, bool verbose = false) + Sphere(const Point3& center, float radius, int material_idx) : center(center), radius(radius), material_index(material_idx) { - validate_and_clamp_parameters(verbose); + validate_and_clamp_parameters(); } // Intersection result structure containing all intersection information @@ -102,8 +103,8 @@ class Sphere { // // Reference: "Real-Time Rendering" by Akenine-Möller et al. (4th ed.) // "Ray Tracing Gems" edited by Haines & Shirley (2019) - Intersection intersect(const Ray& ray, bool verbose = true) const { - if (verbose) { + Intersection intersect(const Ray& ray) const { + if (Logger::is_verbose()) { std::cout << "\n=== Ray-Sphere Intersection Calculation ===" << std::endl; std::cout << "Ray origin: (" << ray.origin.x << ", " << ray.origin.y << ", " << ray.origin.z << ")" << std::endl; std::cout << "Ray direction: (" << ray.direction.x << ", " << ray.direction.y << ", " << ray.direction.z << ")" << std::endl; @@ -115,7 +116,7 @@ class Sphere { // Vector from ray origin to sphere center // Geometric interpretation: displacement needed to go from ray start to sphere center Vector3 oc = ray.origin - center; - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Origin-to-center vector (oc): (" << oc.x << ", " << oc.y << ", " << oc.z << ")" << std::endl; } @@ -127,21 +128,21 @@ class Sphere { // Coefficient 'a': D·D (direction vector dot product with itself) // Geometric interpretation: squared length of direction vector float a = ray.direction.dot(ray.direction); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Quadratic coefficient a = D·D = " << a << std::endl; } // Coefficient 'b': 2(OC·D) (twice the projection of oc onto direction) // Geometric interpretation: how much origin-center vector aligns with ray direction float b = 2.0f * oc.dot(ray.direction); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Quadratic coefficient b = 2(OC·D) = " << b << std::endl; } // Coefficient 'c': OC·OC - r² (squared distance from origin to center minus squared radius) // Geometric interpretation: how far ray origin is from sphere surface float c = oc.dot(oc) - radius * radius; - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Quadratic coefficient c = OC·OC - r² = " << c << std::endl; } @@ -150,13 +151,13 @@ class Sphere { // Δ = 0: one intersection (ray tangent to sphere) // Δ < 0: no intersection (ray misses sphere) float discriminant = b * b - 4 * a * c; - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Discriminant Δ = b² - 4ac = " << discriminant << std::endl; } // No intersection if discriminant is negative if (discriminant < 0) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "No intersection: discriminant < 0 (ray misses sphere)" << std::endl; } return Intersection(); // Default constructor creates hit=false @@ -165,13 +166,13 @@ class Sphere { // Calculate both intersection points using quadratic formula // t = (-b ± √Δ) / 2a float sqrt_discriminant = std::sqrt(discriminant); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Square root of discriminant: √Δ = " << sqrt_discriminant << std::endl; } float t1 = (-b - sqrt_discriminant) / (2 * a); // Near intersection float t2 = (-b + sqrt_discriminant) / (2 * a); // Far intersection - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Intersection parameters: t1 = " << t1 << ", t2 = " << t2 << std::endl; } @@ -180,16 +181,16 @@ class Sphere { float t_hit; if (t1 > 1e-6f) { // Use small epsilon to avoid self-intersection t_hit = t1; // Closer intersection is valid - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Using closer intersection t1 = " << t_hit << std::endl; } } else if (t2 > 1e-6f) { t_hit = t2; // Only far intersection is valid (ray starts inside sphere) - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Using farther intersection t2 = " << t_hit << " (ray starts inside sphere)" << std::endl; } } else { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "No valid intersection: both t values ≤ 0 (intersections behind ray origin)" << std::endl; } return Intersection(); // Both intersections behind ray origin @@ -197,7 +198,7 @@ class Sphere { // Calculate intersection point using ray equation P(t) = O + t*D Point3 hit_point = ray.at(t_hit); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Intersection point: (" << hit_point.x << ", " << hit_point.y << ", " << hit_point.z << ")" << std::endl; } @@ -205,7 +206,7 @@ class Sphere { // Normal points outward from sphere center to surface point // Formula: N = (P - C) / |P - C| where P=intersection point, C=center Vector3 normal = (hit_point - center).normalize(); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Surface normal: (" << normal.x << ", " << normal.y << ", " << normal.z << ")" << std::endl; // Verify normal is unit length @@ -275,8 +276,8 @@ class Sphere { // Parameter validation and clamping for robust sphere construction // Ensures sphere parameters are within valid mathematical ranges - void validate_and_clamp_parameters(bool verbose = false) { - if (verbose) { + void validate_and_clamp_parameters() { + if (Logger::is_verbose()) { std::cout << "\n=== Sphere Parameter Validation ===" << std::endl; std::cout << "Original parameters:" << std::endl; std::cout << " Center: (" << center.x << ", " << center.y << ", " << center.z << ")" << std::endl; @@ -286,7 +287,7 @@ class Sphere { // Validate and fix center coordinates if (!std::isfinite(center.x) || !std::isfinite(center.y) || !std::isfinite(center.z)) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "WARNING: Invalid sphere center coordinates, setting to origin" << std::endl; } center = Point3(0, 0, 0); @@ -294,17 +295,17 @@ class Sphere { // Validate and clamp radius if (radius <= 0.0f) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "WARNING: Invalid sphere radius " << radius << ", clamping to 0.1" << std::endl; } radius = 0.1f; } else if (radius > 1000.0f) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "WARNING: Very large sphere radius " << radius << ", clamping to 1000.0" << std::endl; } radius = 1000.0f; } else if (!std::isfinite(radius)) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "WARNING: Non-finite sphere radius, setting to 1.0" << std::endl; } radius = 1.0f; @@ -312,13 +313,13 @@ class Sphere { // Validate material index (cannot fix here as we don't know scene materials) if (material_index < 0) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "WARNING: Negative material index " << material_index << ", setting to 0" << std::endl; } material_index = 0; } - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Validated parameters:" << std::endl; std::cout << " Center: (" << center.x << ", " << center.y << ", " << center.z << ")" << std::endl; std::cout << " Radius: " << radius << std::endl; diff --git a/src/lights/area_light.hpp b/src/lights/area_light.hpp index da7ab49..6ab3194 100644 --- a/src/lights/area_light.hpp +++ b/src/lights/area_light.hpp @@ -81,7 +81,7 @@ class AreaLight : public Light { Ray shadow_ray(Point3(offset_point.x, offset_point.y, offset_point.z), light_direction); // Test intersection with scene - Scene::Intersection hit = scene.intersect(shadow_ray, false); // Disable verbose output + Scene::Intersection hit = scene.intersect(shadow_ray); // Check if intersection occurs before reaching the light return hit.hit && hit.t < (distance - epsilon); diff --git a/src/lights/directional_light.hpp b/src/lights/directional_light.hpp index c5bf6d9..641bbb0 100644 --- a/src/lights/directional_light.hpp +++ b/src/lights/directional_light.hpp @@ -44,7 +44,7 @@ class DirectionalLight : public Light { Ray shadow_ray(Point3(offset_point.x, offset_point.y, offset_point.z), light_direction); // Test intersection with scene - for directional lights, any intersection blocks the light - Scene::Intersection hit = scene.intersect(shadow_ray, false); // Disable verbose output + Scene::Intersection hit = scene.intersect(shadow_ray); // Any intersection along the ray blocks the directional light return hit.hit && hit.t > epsilon; diff --git a/src/lights/point_light.hpp b/src/lights/point_light.hpp index 08b3ec8..4e7a173 100644 --- a/src/lights/point_light.hpp +++ b/src/lights/point_light.hpp @@ -46,7 +46,7 @@ class PointLight : public Light { Ray shadow_ray(Point3(offset_point.x, offset_point.y, offset_point.z), light_direction); // Test intersection with scene - Scene::Intersection hit = scene.intersect(shadow_ray, false); // Disable verbose output for shadow rays + Scene::Intersection hit = scene.intersect(shadow_ray); // Check if intersection occurs before reaching the light return hit.hit && hit.t < (distance - epsilon); diff --git a/src/main.cpp b/src/main.cpp index dc76f4c..0c7f488 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #include "core/vector3.hpp" #include "core/point3.hpp" #include "core/ray.hpp" @@ -13,6 +12,10 @@ #include "core/image.hpp" #include "core/performance_timer.hpp" #include "core/progress_reporter.hpp" +#include "core/logger.hpp" +#include "core/renderer.hpp" +#include "core/constants.hpp" +#include "cli/argument_parser.hpp" #include "ui/material_editor.hpp" #include "ui/scene_manager.hpp" #include "ui/parameter_validator.hpp" @@ -34,238 +37,30 @@ #endif int main(int argc, char* argv[]) { - // Check for help request - for (int i = 1; i < argc; i++) { - if (std::strcmp(argv[i], "--help") == 0 || std::strcmp(argv[i], "-h") == 0) { + // Parse command-line arguments using ArgumentParser + ArgumentParser::Config config; + if (!ArgumentParser::parse(argc, argv, config)) { + if (config.show_help) { + ArgumentParser::print_help(); + std::cout << "\nCamera parameters (handled by Camera class):" << std::endl; Camera::print_command_line_help(); - std::cout << "\n=== Multi-Resolution and Performance Help ===" << std::endl; - std::cout << "Resolution parameters (Story 2.4):" << std::endl; - std::cout << "--resolution Set image resolution (e.g., --resolution 1024x768)" << std::endl; - std::cout << " Common presets: 256x256, 512x512, 1024x1024, 2048x2048" << std::endl; - std::cout << " Default: 1024x768 (Epic 2 Showcase)" << std::endl; - std::cout << "\nScene parameters:" << std::endl; - std::cout << "--scene Load scene from file (default: assets/showcase_scene.scene)" << std::endl; - std::cout << "--no-scene Use hardcoded single sphere for compatibility" << std::endl; - std::cout << "\nMaterial parameters (Cook-Torrance BRDF - Story 3.1):" << std::endl; - std::cout << "--material Material type: lambert, cook-torrance (default: lambert)" << std::endl; - std::cout << "--roughness Surface roughness for Cook-Torrance (0.0-1.0, default: 0.5)" << std::endl; - std::cout << "--metallic Metallic parameter for Cook-Torrance (0.0-1.0, default: 0.0)" << std::endl; - std::cout << "--specular Specular reflectance for dielectrics (0.0-1.0, default: 0.04)" << std::endl; - std::cout << "\nDebug and verbosity parameters:" << std::endl; - std::cout << "--quiet Minimal output (no educational breakdowns, errors only)" << std::endl; - std::cout << "--verbose Full educational output (default behavior)" << std::endl; - std::cout << "\nInteractive mode (Story 3.3):" << std::endl; - std::cout << "--interactive Enable interactive Cook-Torrance material editor" << std::endl; - std::cout << " Supports real-time parameter adjustment, scene loading" << std::endl; - std::cout << " Material presets, and educational feedback" << std::endl; - std::cout << "\nQuick presets:" << std::endl; - std::cout << "--preset showcase Epic 2 showcase (1024x768, complex scene, optimal camera)" << std::endl; - std::cout << "--showcase Shorthand for --preset showcase" << std::endl; - std::cout << "--preset cook-torrance Cook-Torrance demo (1024x1024, single sphere only, scene files not supported)" << std::endl; - std::cout << "--cook-torrance Shorthand for --preset cook-torrance" << std::endl; - std::cout << "--preset performance Fast render (512x512, simple scene, basic camera)" << std::endl; - std::cout << "--performance Shorthand for --preset performance" << std::endl; - std::cout << "--preset quality High quality (1024x1024, showcase scene, wide FOV)" << std::endl; - std::cout << "--quality Shorthand for --preset quality" << std::endl; - std::cout << "\nScene file format (Lambert materials only):" << std::endl; - std::cout << "material - Define material with RGB albedo" << std::endl; - std::cout << "sphere - Add sphere to scene" << std::endl; - std::cout << "\nNOTE: Scene files only support Lambert materials. For Cook-Torrance materials," << std::endl; - std::cout << " use --cook-torrance preset (single sphere) or --no-scene with --material cook-torrance" << std::endl; - return 0; - } - } - - // Check for scene file and resolution parameters - // Epic 2 Showcase Defaults - optimized to demonstrate all capabilities - std::string scene_filename = "../assets/showcase_scene.scene"; // Enhanced showcase scene - bool use_scene_file = true; - bool scene_explicitly_set = false; // Track if user explicitly set scene preferences - Resolution image_resolution = Resolution::parse_from_string("1024x768"); // High-quality 4:3 aspect ratio - - // Cook-Torrance material parameters (Story 3.1) - std::string material_type = "lambert"; // Default to Lambert for compatibility - float roughness_param = 0.5f; // Medium roughness - float metallic_param = 0.0f; // Dielectric (non-metal) by default - float specular_param = 0.04f; // Typical dielectric F0 - - // Debug and verbosity control parameters - bool quiet_mode = false; // Minimal output mode - bool interactive_mode = false; // Interactive Cook-Torrance editor mode (Story 3.3) - - for (int i = 1; i < argc; i++) { - if (std::strcmp(argv[i], "--scene") == 0 && i + 1 < argc) { - scene_filename = argv[i + 1]; - use_scene_file = true; - scene_explicitly_set = true; - std::cout << "Scene file override: " << scene_filename << std::endl; - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--no-scene") == 0) { - use_scene_file = false; - scene_explicitly_set = true; - std::cout << "Scene loading disabled - using hardcoded sphere" << std::endl; - } else if (std::strcmp(argv[i], "--resolution") == 0 && i + 1 < argc) { - try { - image_resolution = Resolution::parse_from_string(argv[i + 1]); - std::cout << "Resolution override: " << image_resolution.width << "x" << image_resolution.height << std::endl; - image_resolution.print_memory_analysis(); - } catch (const std::exception& e) { - std::cout << "ERROR: Invalid resolution format '" << argv[i + 1] << "'" << std::endl; - std::cout << "Error details: " << e.what() << std::endl; - std::cout << "Supported formats: WIDTHxHEIGHT (e.g., 512x512)" << std::endl; - std::cout << "Common presets: 256x256, 512x512, 1024x1024, 2048x2048" << std::endl; - return 1; - } - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--preset") == 0 && i + 1 < argc) { - std::string preset = argv[i + 1]; - std::cout << "Using preset: " << preset << std::endl; - - if (preset == "showcase") { - // Epic 2 Showcase - ensure scene file path works from build/ directory - scene_filename = "../assets/showcase_scene.scene"; - image_resolution = Resolution::parse_from_string("1024x768"); - std::cout << "Epic 2 Showcase preset: 1024x768, complex scene, optimal camera" << std::endl; - } else if (preset == "cook-torrance") { - // Cook-Torrance Demo - single sphere with Cook-Torrance material (scene files not supported) - use_scene_file = false; // Force single sphere mode - scene files don't support Cook-Torrance - image_resolution = Resolution::parse_from_string("1024x1024"); - material_type = "cook-torrance"; - quiet_mode = false; // Keep educational output - std::cout << "Cook-Torrance Demo preset: 1024x1024, single sphere with Cook-Torrance material" << std::endl; - std::cout << "NOTE: Scene files do not support Cook-Torrance materials - using single sphere mode" << std::endl; - } else if (preset == "performance") { - image_resolution = Resolution::MEDIUM; // 512x512 - scene_filename = "../assets/simple_scene.scene"; - std::cout << "Performance preset: 512x512, simple scene, fast render" << std::endl; - } else if (preset == "quality") { - image_resolution = Resolution::LARGE; // 1024x1024 - scene_filename = "../assets/showcase_scene.scene"; - std::cout << "Quality preset: 1024x1024, showcase scene, maximum quality" << std::endl; - } else { - std::cout << "ERROR: Unknown preset '" << preset << "'" << std::endl; - std::cout << "Available presets: showcase, cook-torrance, performance, quality" << std::endl; - return 1; - } - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--showcase") == 0) { - scene_filename = "../assets/showcase_scene.scene"; - image_resolution = Resolution::parse_from_string("1024x768"); - std::cout << "Using preset: showcase" << std::endl; - std::cout << "Epic 2 Showcase preset: 1024x768, complex scene, optimal camera" << std::endl; - } else if (std::strcmp(argv[i], "--cook-torrance") == 0) { - use_scene_file = false; // Force single sphere mode - scene files don't support Cook-Torrance - image_resolution = Resolution::parse_from_string("1024x1024"); - material_type = "cook-torrance"; - quiet_mode = false; - std::cout << "Using preset: cook-torrance" << std::endl; - std::cout << "Cook-Torrance Demo preset: 1024x1024, single sphere with Cook-Torrance material" << std::endl; - std::cout << "NOTE: Scene files do not support Cook-Torrance materials - using single sphere mode" << std::endl; - } else if (std::strcmp(argv[i], "--performance") == 0) { - image_resolution = Resolution::MEDIUM; // 512x512 - scene_filename = "../assets/simple_scene.scene"; - std::cout << "Using preset: performance" << std::endl; - std::cout << "Performance preset: 512x512, simple scene, fast render" << std::endl; - } else if (std::strcmp(argv[i], "--quality") == 0) { - image_resolution = Resolution::LARGE; // 1024x1024 - scene_filename = "../assets/showcase_scene.scene"; - std::cout << "Using preset: quality" << std::endl; - std::cout << "Quality preset: 1024x1024, showcase scene, maximum quality" << std::endl; - } else if (std::strcmp(argv[i], "--material") == 0 && i + 1 < argc) { - material_type = argv[i + 1]; - std::cout << "Material type override: " << material_type << std::endl; - if (material_type != "lambert" && material_type != "cook-torrance") { - std::cout << "ERROR: Unknown material type '" << material_type << "'" << std::endl; - std::cout << "Supported materials: lambert, cook-torrance" << std::endl; - return 1; - } - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--roughness") == 0 && i + 1 < argc) { - roughness_param = std::stof(argv[i + 1]); - roughness_param = std::max(0.01f, std::min(1.0f, roughness_param)); // Clamp to valid range - std::cout << "Roughness override: " << roughness_param << std::endl; - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--metallic") == 0 && i + 1 < argc) { - metallic_param = std::stof(argv[i + 1]); - metallic_param = std::max(0.0f, std::min(1.0f, metallic_param)); // Clamp to valid range - std::cout << "Metallic override: " << metallic_param << std::endl; - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--specular") == 0 && i + 1 < argc) { - specular_param = std::stof(argv[i + 1]); - specular_param = std::max(0.0f, std::min(1.0f, specular_param)); // Clamp to valid range - std::cout << "Specular override: " << specular_param << std::endl; - i++; // Skip next argument since we consumed it - } else if (std::strcmp(argv[i], "--quiet") == 0) { - quiet_mode = true; - std::cout << "Quiet mode enabled - minimal output" << std::endl; - } else if (std::strcmp(argv[i], "--verbose") == 0) { - quiet_mode = false; - std::cout << "Verbose mode enabled - full educational output" << std::endl; - } else if (std::strcmp(argv[i], "--interactive") == 0) { - interactive_mode = true; - material_type = "cook-torrance"; // Force Cook-Torrance for interactive mode - quiet_mode = true; // Default to quiet mode for better interactivity - if (!scene_explicitly_set) { - use_scene_file = false; // Default to single-sphere mode for interactive - } - std::cout << "Interactive mode enabled - Cook-Torrance material editor (quiet mode default)" << std::endl; - } else if (strncmp(argv[i], "--", 2) == 0) { - // Check if it's a known camera argument (handled later by camera.set_from_command_line_args) - if (std::strcmp(argv[i], "--camera-pos") == 0 || - std::strcmp(argv[i], "--camera-target") == 0 || - std::strcmp(argv[i], "--fov") == 0 || - std::strcmp(argv[i], "--material") == 0 || - std::strcmp(argv[i], "--roughness") == 0 || - std::strcmp(argv[i], "--metallic") == 0 || - std::strcmp(argv[i], "--specular") == 0 || - std::strcmp(argv[i], "--quiet") == 0 || - std::strcmp(argv[i], "--verbose") == 0 || - std::strcmp(argv[i], "--interactive") == 0) { - // Valid camera or material argument, skip it and its parameter - if (i + 1 < argc) i++; // Skip parameter if it exists - } else { - // Unknown argument starting with -- - std::cout << "ERROR: Unknown argument '" << argv[i] << "'" << std::endl; - std::cout << "Did you mean:" << std::endl; - - // Provide helpful suggestions based on common mistakes - if (std::strcmp(argv[i], "--help") == 0 || std::strcmp(argv[i], "-h") == 0) { - // This shouldn't happen since help is handled earlier, but just in case - } else if (std::strcmp(argv[i], "--showcase") == 0 || std::strcmp(argv[i], "--performance") == 0 || std::strcmp(argv[i], "--quality") == 0) { - // This shouldn't happen since these are handled above, but just in case - } else { - // Common typos and suggestions - std::string arg = argv[i]; - if (arg.find("quality") != std::string::npos) { - std::cout << " --preset quality (High quality preset)" << std::endl; - std::cout << " --quality (Shorthand for --preset quality)" << std::endl; - } else if (arg.find("performance") != std::string::npos) { - std::cout << " --preset performance (Fast render preset)" << std::endl; - std::cout << " --performance (Shorthand for --preset performance)" << std::endl; - } else if (arg.find("showcase") != std::string::npos) { - std::cout << " --preset showcase (Epic 2 showcase preset)" << std::endl; - std::cout << " --showcase (Shorthand for --preset showcase)" << std::endl; - } else if (arg.find("resolution") != std::string::npos || arg.find("res") != std::string::npos) { - std::cout << " --resolution WxH (e.g., --resolution 1024x768)" << std::endl; - } else if (arg.find("scene") != std::string::npos) { - std::cout << " --scene (Load custom scene file)" << std::endl; - std::cout << " --no-scene (Use hardcoded sphere)" << std::endl; - } else if (arg.find("camera") != std::string::npos || arg.find("pos") != std::string::npos) { - std::cout << " --camera-pos x,y,z (Set camera position)" << std::endl; - std::cout << " --camera-target x,y,z(Set camera target)" << std::endl; - } else if (arg.find("fov") != std::string::npos) { - std::cout << " --fov degrees (Set field of view)" << std::endl; - } else { - std::cout << " --help (Show all available options)" << std::endl; - std::cout << " --preset showcase (Epic 2 showcase)" << std::endl; - std::cout << " --resolution WxH (Set custom resolution)" << std::endl; - } - } - std::cout << "\nUse --help to see all available options." << std::endl; - return 1; - } } + return config.has_error ? 1 : 0; } + + // Apply verbosity setting + Logger::set_level(config.verbosity); + + // Extract config values for convenience + int image_width = config.resolution.width; + int image_height = config.resolution.height; + std::string scene_filename = config.scene_filename; + bool use_scene_file = config.use_scene_file; + std::string material_type = config.material_type; + float roughness_param = config.roughness; + float metallic_param = config.metallic; + float specular_param = config.specular; + bool interactive_mode = config.interactive_mode; std::cout << "=== Educational Ray Tracer - Epic 1 Foundation ===" << std::endl; std::cout << "Platform: " << PLATFORM_NAME << std::endl; @@ -381,13 +176,9 @@ int main(int argc, char* argv[]) { Scene test_scene; // Add materials to scene - LambertMaterial red_material(Vector3(0.7f, 0.3f, 0.3f)); - LambertMaterial blue_material(Vector3(0.3f, 0.3f, 0.7f)); - LambertMaterial green_material(Vector3(0.3f, 0.7f, 0.3f)); - - int red_mat_idx = test_scene.add_material(red_material); - int blue_mat_idx = test_scene.add_material(blue_material); - int green_mat_idx = test_scene.add_material(green_material); + int red_mat_idx = test_scene.add_material(std::make_unique(Vector3(0.7f, 0.3f, 0.3f))); + int blue_mat_idx = test_scene.add_material(std::make_unique(Vector3(0.3f, 0.3f, 0.7f))); + int green_mat_idx = test_scene.add_material(std::make_unique(Vector3(0.3f, 0.7f, 0.3f))); // Add spheres to scene (using new constructor with material index) Sphere sphere1(Point3(0.0f, 0.0f, -5.0f), 1.0f, red_mat_idx); // Central red sphere @@ -652,20 +443,12 @@ int main(int argc, char* argv[]) { PerformanceTimer performance_timer; auto total_start_time = std::chrono::high_resolution_clock::now(); performance_timer.start_phase(PerformanceTimer::TOTAL_RENDER); - - // Image resolution configuration (from command line or default) - int image_width = image_resolution.width; - int image_height = image_resolution.height; - + std::cout << "\n--- Multi-Ray Rendering Configuration ---" << std::endl; - std::cout << "Image resolution: " << image_width << " × " << image_height << " pixels" << std::endl; - std::cout << "Resolution preset: " << image_resolution.name << std::endl; + config.resolution.print_memory_analysis(); std::cout << "Total rays to generate: " << (image_width * image_height) << std::endl; std::cout << "Rendering approach: One ray per pixel (uniform sampling)" << std::endl; - // Display memory and performance predictions - image_resolution.print_memory_analysis(); - // Camera setup for image generation with aspect ratio management (Story 2.4 AC: 5) // Epic 2 Showcase Camera - positioned for optimal scene composition Point3 camera_position(0.0, 0.0, 1.0); // INSIDE the scene for guaranteed hits @@ -675,7 +458,6 @@ int main(int argc, char* argv[]) { float aspect_ratio = static_cast(image_width) / image_height; std::cout << "\n--- Camera Aspect Ratio Configuration (AC 5) ---" << std::endl; - std::cout << "Image resolution: " << image_width << " × " << image_height << " pixels" << std::endl; std::cout << "Calculated aspect ratio: " << aspect_ratio << ":1" << std::endl; Camera render_camera(camera_position, camera_target, camera_up, camera_fov, aspect_ratio); @@ -714,8 +496,7 @@ int main(int argc, char* argv[]) { // Scene setup: load from file or create default scene Scene render_scene; - PointLight image_light(Vector3(2, 2, 0), Vector3(1.0f, 1.0f, 1.0f), 100.0f); - + std::cout << "\n--- Scene Configuration ---" << std::endl; if (use_scene_file) { @@ -736,7 +517,7 @@ int main(int argc, char* argv[]) { std::cout << "Selected material type: " << material_type << std::endl; if (material_type == "cook-torrance") { - if (!quiet_mode) { + if (Logger::is_verbose()) { std::cout << "\n=== Cook-Torrance Material Configuration ===" << std::endl; std::cout << "Base Color: (0.7, 0.3, 0.3) - Reddish surface" << std::endl; std::cout << "Roughness: " << roughness_param << std::endl; @@ -747,9 +528,9 @@ int main(int argc, char* argv[]) { if (interactive_mode) { // For interactive mode, create Cook-Torrance sphere in Scene system std::cout << "Creating Cook-Torrance sphere in Scene system for interactive mode" << std::endl; - auto default_cook_material = std::make_unique(Vector3(0.7f, 0.3f, 0.3f), roughness_param, metallic_param, specular_param, !quiet_mode); + auto default_cook_material = std::make_unique(Constants::DEFAULT_MATERIAL_COLOR, roughness_param, metallic_param, specular_param); int material_idx = render_scene.add_material(std::move(default_cook_material)); - Sphere default_sphere(Point3(0, 0, -3), 1.0f, material_idx, !quiet_mode); + Sphere default_sphere(Constants::DEFAULT_SPHERE_POSITION, Constants::DEFAULT_SPHERE_RADIUS, material_idx); render_scene.add_sphere(default_sphere); // Add multiple point lights for better metallic illumination @@ -763,16 +544,24 @@ int main(int argc, char* argv[]) { render_scene.print_scene_statistics(); } else { - // Non-interactive mode: use original Cook-Torrance rendering path (bypassing Scene system) - if (!quiet_mode) { - std::cout << "\nUsing Cook-Torrance rendering path (bypassing Scene system)" << std::endl; - } + // Non-interactive mode: add Cook-Torrance material to Scene system + std::cout << "Creating Cook-Torrance sphere in Scene system" << std::endl; + auto default_cook_material = std::make_unique(Constants::DEFAULT_MATERIAL_COLOR, roughness_param, metallic_param, specular_param); + int material_idx = render_scene.add_material(std::move(default_cook_material)); + Sphere default_sphere(Constants::DEFAULT_SPHERE_POSITION, Constants::DEFAULT_SPHERE_RADIUS, material_idx); + render_scene.add_sphere(default_sphere); + + // Add point light to scene + std::cout << "Adding point light to scene for Cook-Torrance material" << std::endl; + auto scene_light = std::make_unique(Vector3(2, 2, 0), Vector3(1.0f, 1.0f, 1.0f), 100.0f); + render_scene.add_light(std::move(scene_light)); + + render_scene.print_scene_statistics(); } } else { // Use Scene system for Lambert materials - LambertMaterial default_material(Vector3(0.7f, 0.3f, 0.3f)); // Reddish diffuse - int material_idx = render_scene.add_material(default_material); - Sphere default_sphere(Point3(0, 0, -3), 1.0f, material_idx, !quiet_mode); + int material_idx = render_scene.add_material(std::make_unique(Constants::DEFAULT_MATERIAL_COLOR)); + Sphere default_sphere(Constants::DEFAULT_SPHERE_POSITION, Constants::DEFAULT_SPHERE_RADIUS, material_idx); render_scene.add_sphere(default_sphere); // Add the point light to the scene for proper lighting @@ -788,226 +577,23 @@ int main(int argc, char* argv[]) { std::cout << "Creating " << image_width << "×" << image_height << " image buffer" << std::endl; std::cout << "Pixel storage: Vector3 (linear RGB)" << std::endl; std::cout << "Color management: Clamping + gamma correction pipeline" << std::endl; - - // Performance counters for legacy compatibility - int rays_generated = 0; - int intersection_tests = 0; - int shading_calculations = 0; - int background_pixels = 0; - + // Start comprehensive timing for ray generation phase auto ray_generation_start = std::chrono::high_resolution_clock::now(); - + // Initialize main progress reporter for legacy compatibility int total_pixels = image_width * image_height; - ProgressReporter progress_reporter(total_pixels, &performance_timer, quiet_mode); - - // Abstract the main rendering logic into a reusable function - auto render_image = [&](int width, int height, bool use_scene_materials = true) -> Image { - Image rendered_image(width, height); - - std::cout << "\n--- Multi-Ray Rendering Process ---" << std::endl; - std::cout << "Beginning pixel-by-pixel ray generation with performance monitoring..." << std::endl; - - // Initialize comprehensive progress reporting (Story 2.4 AC: 4) - int local_total_pixels = width * height; - ProgressReporter local_progress_reporter(local_total_pixels, &performance_timer, quiet_mode); - - // Performance counters for this render - int local_rays_generated = 0; - int local_intersection_tests = 0; - int local_shading_calculations = 0; - int local_background_pixels = 0; - - // Multi-ray pixel sampling: one ray per pixel with comprehensive progress tracking - for (int y = 0; y < height; y++) { - - for (int x = 0; x < width; x++) { - // Phase 1: Ray Generation with precise timing - performance_timer.start_phase(PerformanceTimer::RAY_GENERATION); - Ray pixel_ray = render_camera.generate_ray( - static_cast(x), - static_cast(y), - width, - height - ); - performance_timer.end_phase(PerformanceTimer::RAY_GENERATION); - performance_timer.increment_counter(PerformanceTimer::RAY_GENERATION); - local_rays_generated++; - - Vector3 pixel_color(0, 0, 0); // Default background color (black) - - if (use_scene_materials) { - // Scene-based rendering path (use loaded scene) - performance_timer.start_phase(PerformanceTimer::INTERSECTION_TESTING); - Scene::Intersection intersection = render_scene.intersect(pixel_ray, !quiet_mode); - performance_timer.end_phase(PerformanceTimer::INTERSECTION_TESTING); - performance_timer.increment_counter(PerformanceTimer::INTERSECTION_TESTING); - local_intersection_tests++; - - if (intersection.hit) { - // Phase 3: Shading Calculation using scene materials - performance_timer.start_phase(PerformanceTimer::SHADING_CALCULATION); - local_shading_calculations++; - - // Multi-light accumulation (AC2 - Story 3.2) - pixel_color = Vector3(0, 0, 0); // Initialize accumulator - Vector3 surface_point = Vector3(intersection.point.x, intersection.point.y, intersection.point.z); - Vector3 view_direction = (camera_position - intersection.point).normalize(); - - if (render_scene.lights.empty()) { - // Fallback: Use hardcoded light for backward compatibility - float pdf_fallback; - Vector3 light_direction = image_light.sample_direction(surface_point, pdf_fallback); - Vector3 temp_light_dir; - float temp_distance; - Vector3 incident_irradiance = image_light.illuminate(surface_point, temp_light_dir, temp_distance); - - pixel_color = intersection.material->scatter_light( - light_direction, view_direction, intersection.normal, - incident_irradiance, !quiet_mode - ); - } else { - // Multi-light accumulation from scene - for (const auto& light : render_scene.lights) { - Vector3 light_direction; - float light_distance; - Vector3 light_contribution = light->illuminate(surface_point, light_direction, light_distance); - - // Shadow ray testing (AC3) - if (!light->is_occluded(surface_point, light_direction, light_distance, render_scene)) { - // BRDF evaluation for this light - Vector3 brdf_contribution = intersection.material->scatter_light( - light_direction, view_direction, intersection.normal, - light_contribution, false // Disable verbose per-light to avoid spam - ); - pixel_color += brdf_contribution; - } - } - - // Educational output for multi-light (if enabled and first few pixels) - if (!quiet_mode && (x + y * width) < 5) { - std::cout << "\n=== Multi-Light Accumulation (Pixel " << (x + y * width) << ") ===" << std::endl; - std::cout << "Scene lights: " << render_scene.lights.size() << std::endl; - std::cout << "Final accumulated color: (" << pixel_color.x << ", " << pixel_color.y << ", " << pixel_color.z << ")" << std::endl; - } - } - performance_timer.end_phase(PerformanceTimer::SHADING_CALCULATION); - performance_timer.increment_counter(PerformanceTimer::SHADING_CALCULATION); - } else { - // No intersection - background color - local_background_pixels++; - pixel_color = Vector3(0.1f, 0.1f, 0.15f); // Dark blue background - } - } else if (material_type == "cook-torrance") { - // Cook-Torrance rendering path (bypass Scene system) - performance_timer.start_phase(PerformanceTimer::INTERSECTION_TESTING); - - // Direct sphere intersection (single sphere at (0,0,-3) with radius 1.0) - Point3 sphere_center(0, 0, -3); - float sphere_radius = 1.0f; - Sphere cook_torrance_sphere(sphere_center, sphere_radius, 0, !quiet_mode); - Sphere::Intersection sphere_hit = cook_torrance_sphere.intersect(pixel_ray, !quiet_mode); - - performance_timer.end_phase(PerformanceTimer::INTERSECTION_TESTING); - performance_timer.increment_counter(PerformanceTimer::INTERSECTION_TESTING); - local_intersection_tests++; - - if (sphere_hit.hit) { - // Phase 3: Cook-Torrance Shading Calculation - performance_timer.start_phase(PerformanceTimer::SHADING_CALCULATION); - local_shading_calculations++; - - // Create Cook-Torrance material using command-line base_color - Vector3 base_color(0.7f, 0.3f, 0.3f); // Default base color, should be configurable in future - CookTorranceMaterial cook_torrance_material(base_color, roughness_param, metallic_param, specular_param, !quiet_mode); - - // Multi-light accumulation for Cook-Torrance (AC2 - Story 3.2) - pixel_color = Vector3(0, 0, 0); // Initialize accumulator - Vector3 surface_point = Vector3(sphere_hit.point.x, sphere_hit.point.y, sphere_hit.point.z); - Vector3 view_direction = (camera_position - sphere_hit.point).normalize(); - - if (render_scene.lights.empty()) { - // Fallback: Use hardcoded light for backward compatibility - float pdf_fallback; - Vector3 light_direction = image_light.sample_direction(surface_point, pdf_fallback); - Vector3 temp_light_dir; - float temp_distance; - Vector3 incident_irradiance = image_light.illuminate(surface_point, temp_light_dir, temp_distance); - - pixel_color = cook_torrance_material.scatter_light( - light_direction, view_direction, sphere_hit.normal, - incident_irradiance, !quiet_mode - ); - } else { - // Multi-light accumulation from scene for Cook-Torrance - for (const auto& light : render_scene.lights) { - Vector3 light_direction; - float light_distance; - Vector3 light_contribution = light->illuminate(surface_point, light_direction, light_distance); - - // Shadow ray testing (AC3) - if (!light->is_occluded(surface_point, light_direction, light_distance, render_scene)) { - // Cook-Torrance BRDF evaluation for this light - Vector3 brdf_contribution = cook_torrance_material.scatter_light( - light_direction, view_direction, sphere_hit.normal, - light_contribution, false // Disable verbose per-light to avoid spam - ); - pixel_color += brdf_contribution; - } - } - - // Educational output for multi-light Cook-Torrance (if enabled and first few pixels) - if (!quiet_mode && (x + y * width) < 3) { - std::cout << "\n=== Cook-Torrance Multi-Light Accumulation (Pixel " << (x + y * width) << ") ===" << std::endl; - std::cout << "Scene lights: " << render_scene.lights.size() << std::endl; - std::cout << "Final accumulated color: (" << pixel_color.x << ", " << pixel_color.y << ", " << pixel_color.z << ")" << std::endl; - } - } - performance_timer.end_phase(PerformanceTimer::SHADING_CALCULATION); - performance_timer.increment_counter(PerformanceTimer::SHADING_CALCULATION); - } else { - // No intersection - background color - local_background_pixels++; - pixel_color = Vector3(0.1f, 0.1f, 0.15f); // Dark blue background - } - } - - // Store pixel in image buffer (no additional timing - included in IMAGE_OUTPUT) - rendered_image.set_pixel(x, y, pixel_color); - } - - // Update progress reporting after each row for better granularity - int completed_pixels = (y + 1) * width; - size_t current_memory = rendered_image.memory_usage_bytes() + render_scene.calculate_scene_memory_usage(); - local_progress_reporter.update_progress(completed_pixels, current_memory); - - // Update the global progress reporter for main render compatibility - if (width == image_width && height == image_height) { - progress_reporter.update_progress(completed_pixels, current_memory); - } - - // Check for interrupt capability (placeholder for user cancellation) - if (local_progress_reporter.should_interrupt()) { - std::cout << "\nRendering interrupted by user request." << std::endl; - break; - } - } - - // Update global counters for compatibility - rays_generated += local_rays_generated; - intersection_tests += local_intersection_tests; - shading_calculations += local_shading_calculations; - background_pixels += local_background_pixels; - - return rendered_image; - }; - - // Render the main image using the abstracted function - // Use scene materials for interactive mode, original logic for non-interactive - bool use_scene_materials_for_main_render = interactive_mode || use_scene_file; - Image output_image = render_image(image_width, image_height, use_scene_materials_for_main_render); - + ProgressReporter progress_reporter(total_pixels, &performance_timer, Logger::is_quiet()); + + // Create Renderer instance with all dependencies + Renderer renderer(render_camera, render_scene, camera_position, performance_timer); + + // Render the main image using the Renderer (unified Scene-based path) + Image output_image = renderer.render(image_width, image_height); + + // Reference to rendering statistics for convenient access + auto& stats = renderer.stats; + // End comprehensive timing performance_timer.end_phase(PerformanceTimer::TOTAL_RENDER); @@ -1025,24 +611,24 @@ int main(int argc, char* argv[]) { std::cout << "\n=== Educational Performance Analysis ===" << std::endl; std::cout << "Ray Generation Statistics:" << std::endl; - std::cout << " Total rays generated: " << rays_generated << std::endl; + std::cout << " Total rays generated: " << stats.rays_generated << std::endl; std::cout << " Expected rays (width × height): " << (image_width * image_height) << std::endl; - std::cout << " Ray generation accuracy: " << (rays_generated == (image_width * image_height) ? "PERFECT" : "ERROR") << std::endl; - + std::cout << " Ray generation accuracy: " << (stats.rays_generated == (image_width * image_height) ? "PERFECT" : "ERROR") << std::endl; + std::cout << "Intersection Testing Statistics:" << std::endl; - std::cout << " Total intersection tests: " << intersection_tests << std::endl; - std::cout << " Tests per ray: " << (static_cast(intersection_tests) / rays_generated) << std::endl; + std::cout << " Total intersection tests: " << stats.intersection_tests << std::endl; + std::cout << " Tests per ray: " << (static_cast(stats.intersection_tests) / stats.rays_generated) << std::endl; std::cout << " Scene primitives tested: " << render_scene.primitives.size() << " per ray" << std::endl; - + std::cout << "Shading Calculation Statistics:" << std::endl; - std::cout << " Shading calculations performed: " << shading_calculations << std::endl; - std::cout << " Background pixels (no shading): " << background_pixels << std::endl; - std::cout << " Scene coverage: " << (100.0f * shading_calculations / rays_generated) << "%" << std::endl; - + std::cout << " Shading calculations performed: " << stats.shading_calculations << std::endl; + std::cout << " Background pixels (no shading): " << stats.background_pixels << std::endl; + std::cout << " Scene coverage: " << (100.0f * stats.shading_calculations / stats.rays_generated) << "%" << std::endl; + std::cout << "Performance Timing:" << std::endl; std::cout << " Ray generation time: " << ray_generation_duration.count() << " ms" << std::endl; std::cout << " Total rendering time: " << total_duration.count() << " ms" << std::endl; - std::cout << " Rays per second: " << (rays_generated * 1000.0f / total_duration.count()) << std::endl; + std::cout << " Rays per second: " << (stats.rays_generated * 1000.0f / total_duration.count()) << std::endl; // Comprehensive Educational Performance Analysis (Story 2.4) std::cout << "\n=== Story 2.4: Comprehensive Performance Analysis ===" << std::endl; @@ -1086,31 +672,16 @@ int main(int argc, char* argv[]) { } std::cout << "\n--- Multi-Ray Pipeline Summary ---" << std::endl; - std::cout << "1. Camera-to-pixel coordinate transformation: " << rays_generated << " rays generated" << std::endl; - std::cout << "2. Ray-scene intersection testing: " << intersection_tests << " tests performed across " + std::cout << "1. Camera-to-pixel coordinate transformation: " << stats.rays_generated << " rays generated" << std::endl; + std::cout << "2. Ray-scene intersection testing: " << stats.intersection_tests << " tests performed across " << render_scene.primitives.size() << " primitives" << std::endl; - std::cout << "3. Lambert BRDF shading calculations: " << shading_calculations << " evaluations" << std::endl; + std::cout << "3. Lambert BRDF shading calculations: " << stats.shading_calculations << " evaluations" << std::endl; std::cout << "4. Image buffer management: " << (image_width * image_height) << " pixels stored" << std::endl; std::cout << "5. Color management pipeline: clamping and gamma correction ready" << std::endl; - // Display final scene performance statistics + // Display final scene performance statistics (unified for all materials) std::cout << "\n=== Final Scene Performance Statistics ===" << std::endl; - if (material_type == "cook-torrance") { - // Special statistics for Cook-Torrance direct rendering path - std::cout << "=== Scene Statistics ===" << std::endl; - std::cout << "Geometry:" << std::endl; - std::cout << " Spheres: 1 (single Cook-Torrance sphere, direct rendering)" << std::endl; - std::cout << " Materials: 1 (Cook-Torrance material)" << std::endl; - std::cout << "\nPerformance Statistics:" << std::endl; - std::cout << " Total intersection tests: " << intersection_tests << std::endl; - std::cout << " Successful intersections: " << shading_calculations << std::endl; - std::cout << " Hit rate: " << (intersection_tests > 0 ? - (float)shading_calculations / intersection_tests * 100.0f : 0.0f) << "%" << std::endl; - std::cout << " Note: Direct rendering path bypasses Scene system for Cook-Torrance materials" << std::endl; - std::cout << "=== Scene statistics complete ===" << std::endl; - } else { - render_scene.print_scene_statistics(); - } + render_scene.print_scene_statistics(); std::cout << "\n=== Image Generation and Pixel Sampling Complete ===" << std::endl; std::cout << "Successfully generated " << image_width << "×" << image_height << " image" << std::endl; @@ -1155,7 +726,7 @@ int main(int argc, char* argv[]) { // Create initial Cook-Torrance material for interactive editing Vector3 initial_color(0.7f, 0.3f, 0.3f); - CookTorranceMaterial* interactive_material = new CookTorranceMaterial(initial_color, roughness_param, metallic_param, specular_param, false); + auto interactive_material = std::make_unique(initial_color, roughness_param, metallic_param, specular_param); // Initialize current scene name for tracking std::string current_scene_name = use_scene_file ? std::filesystem::path(scene_filename).stem().string() : "single-sphere"; @@ -1203,7 +774,7 @@ int main(int argc, char* argv[]) { std::cout << "\n=== Material Parameter Adjustment ===" << std::endl; auto param_start = std::chrono::steady_clock::now(); - bool params_changed = MaterialEditor::get_user_parameter_input(interactive_material); + bool params_changed = MaterialEditor::get_user_parameter_input(interactive_material.get()); auto param_end = std::chrono::steady_clock::now(); auto param_duration = std::chrono::duration_cast(param_end - param_start); std::cout << "Parameter update time: " << param_duration.count() << " ms" << std::endl; @@ -1233,7 +804,7 @@ int main(int argc, char* argv[]) { } std::cout << "\n=== Material Preset Loading ===" << std::endl; - if (PresetManager::select_and_apply_preset(interactive_material)) { + if (PresetManager::select_and_apply_preset(interactive_material.get())) { std::cout << "Preset applied successfully!" << std::endl; std::cout << "Use option 5 to render with the preset material." << std::endl; } @@ -1286,10 +857,10 @@ int main(int argc, char* argv[]) { } auto render_start = std::chrono::steady_clock::now(); - - // Use the exact same rendering pipeline as normal mode through abstracted function - Image interactive_image = render_image(image_width, image_height, true); - + + // Use the exact same rendering pipeline as normal mode through Renderer + Image interactive_image = renderer.render(image_width, image_height); + auto render_end = std::chrono::steady_clock::now(); auto render_duration = std::chrono::duration_cast(render_end - render_start); std::cout << "Interactive render time: " << render_duration.count() << " ms" << std::endl; @@ -1322,9 +893,8 @@ int main(int argc, char* argv[]) { } } - // Cleanup interactive material - delete interactive_material; - + // Cleanup interactive material (automatic via unique_ptr destructor) + std::cout << "\n=== Interactive Session Complete ===" << std::endl; std::cout << "Thank you for using the Cook-Torrance Interactive Material Editor!" << std::endl; std::cout << "All parameter changes and renders have been preserved." << std::endl; diff --git a/src/materials/cook_torrance.hpp b/src/materials/cook_torrance.hpp index d6bdbe5..d4daa9a 100644 --- a/src/materials/cook_torrance.hpp +++ b/src/materials/cook_torrance.hpp @@ -1,5 +1,6 @@ #pragma once #include "../core/vector3.hpp" +#include "../core/logger.hpp" #include "material_base.hpp" #include #include @@ -292,14 +293,13 @@ class CookTorranceMaterial : public Material { CookTorranceMaterial(const Vector3& color = Vector3(0.7f, 0.7f, 0.7f), float surface_roughness = 0.5f, float metallic_param = 0.0f, - float specular_param = 0.04f, - bool verbose = false) + float specular_param = 0.04f) : Material(color, MaterialType::CookTorrance), roughness(surface_roughness), metallic(metallic_param), specular(specular_param) { - + // Automatically clamp parameters to physically valid ranges clamp_cook_torrance_to_valid_ranges(); - - if (verbose) { + + if (Logger::is_verbose()) { std::cout << "=== Cook-Torrance Material Initialized ===" << std::endl; std::cout << "Base Color: (" << base_color.x << ", " << base_color.y << ", " << base_color.z << ")" << std::endl; std::cout << "Roughness: " << roughness << " (0.0=mirror, 1.0=diffuse)" << std::endl; @@ -315,8 +315,8 @@ class CookTorranceMaterial : public Material { // wi: incident light direction (pointing toward surface, normalized) // wo: outgoing view direction (pointing toward camera, normalized) // normal: surface normal at intersection point (outward-pointing, normalized) - Vector3 evaluate_brdf(const Vector3& wi, const Vector3& wo, const Vector3& normal, bool verbose = true) const override { - if (verbose) { + Vector3 evaluate_brdf(const Vector3& wi, const Vector3& wo, const Vector3& normal) const override { + if (Logger::is_verbose()) { std::cout << "\n=== Cook-Torrance BRDF Evaluation ===" << std::endl; std::cout << "Incident direction (wi): (" << wi.x << ", " << wi.y << ", " << wi.z << ")" << std::endl; std::cout << "Outgoing direction (wo): (" << wo.x << ", " << wo.y << ", " << wo.z << ")" << std::endl; @@ -326,7 +326,7 @@ class CookTorranceMaterial : public Material { // Calculate halfway vector between incident and outgoing directions // Halfway vector represents the microfacet orientation that would reflect wi toward wo Vector3 halfway = (wi + wo).normalize(); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Halfway vector (h): (" << halfway.x << ", " << halfway.y << ", " << halfway.z << ")" << std::endl; } @@ -336,14 +336,14 @@ class CookTorranceMaterial : public Material { float ndoth = std::max(0.0f, normal.dot(halfway)); // cos(θh) - halfway angle float vdoth = std::max(0.0f, wo.dot(halfway)); // cos(θ) for Fresnel - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Dot products: n·l=" << ndotl << ", n·v=" << ndotv << ", n·h=" << ndoth << ", v·h=" << vdoth << std::endl; } // Early exit for grazing angles or backfacing surfaces if (ndotl <= 0.0f || ndotv <= 0.0f) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Grazing angle or backface - BRDF = 0" << std::endl; } return Vector3(0.0f, 0.0f, 0.0f); @@ -352,19 +352,19 @@ class CookTorranceMaterial : public Material { // Convert roughness to alpha parameter (α = roughness²) // Alpha parameterization provides more intuitive roughness scaling float alpha = alpha_from_roughness(roughness); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Alpha parameter: α = roughness² = " << alpha << std::endl; } // Evaluate Normal Distribution Function D(h) float D = evaluate_normal_distribution(halfway, normal, alpha); - if (verbose) { + if (Logger::is_verbose()) { CookTorrance::NormalDistribution::explain_ggx_mathematics(ndoth, alpha, D); } // Evaluate Geometry Function G(wi, wo) float G = evaluate_geometry_function(wi, wo, normal, alpha); - if (verbose) { + if (Logger::is_verbose()) { CookTorrance::GeometryFunction::explain_geometry_mathematics(ndotl, ndotv, alpha, G); } @@ -373,7 +373,7 @@ class CookTorranceMaterial : public Material { // Evaluate Fresnel Function F(h, wo) Vector3 F = evaluate_fresnel(halfway, wo, f0); - if (verbose) { + if (Logger::is_verbose()) { bool is_conductor = (metallic > 0.5f); CookTorrance::FresnelFunction::explain_fresnel_mathematics(vdoth, f0, F, is_conductor); } @@ -384,7 +384,7 @@ class CookTorranceMaterial : public Material { // Prevent division by zero at grazing angles if (denominator <= 0.0f) { - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Invalid denominator - returning zero BRDF" << std::endl; } return Vector3(0.0f, 0.0f, 0.0f); @@ -413,7 +413,7 @@ class CookTorranceMaterial : public Material { // Final BRDF calculation: specular + diffuse Vector3 brdf_value = specular_component + diffuse_component; - if (verbose) { + if (Logger::is_verbose()) { std::cout << "\n=== Complete Cook-Torrance BRDF Result ===" << std::endl; std::cout << "D (Normal Distribution): " << D << std::endl; std::cout << "G (Geometry Function): " << G << std::endl; @@ -470,9 +470,9 @@ class CookTorranceMaterial : public Material { // view_direction: direction from surface to camera (normalized) // normal: surface normal at intersection point (outward-pointing, normalized) // incident_radiance: incoming light energy (L_i in rendering equation) - Vector3 scatter_light(const Vector3& light_direction, const Vector3& view_direction, - const Vector3& normal, const Vector3& incident_radiance, bool verbose = true) const override { - if (verbose) { + Vector3 scatter_light(const Vector3& light_direction, const Vector3& view_direction, + const Vector3& normal, const Vector3& incident_radiance) const override { + if (Logger::is_verbose()) { std::cout << "\n=== Cook-Torrance Light Scattering Calculation ===" << std::endl; std::cout << "Light direction: (" << light_direction.x << ", " << light_direction.y << ", " << light_direction.z << ")" << std::endl; std::cout << "View direction: (" << view_direction.x << ", " << view_direction.y << ", " << view_direction.z << ")" << std::endl; @@ -482,18 +482,18 @@ class CookTorranceMaterial : public Material { // Calculate cosine term: n·l (normal dot light_direction) float cos_theta = normal.dot(light_direction); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Raw cosine term n·l = " << cos_theta << std::endl; } // Clamp to positive values: surfaces don't emit light when facing away cos_theta = std::max(0.0f, cos_theta); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Clamped cosine term max(0, n·l) = " << cos_theta << std::endl; } // Evaluate Cook-Torrance BRDF for this direction pair - Vector3 brdf = evaluate_brdf(light_direction, view_direction, normal, verbose); + Vector3 brdf = evaluate_brdf(light_direction, view_direction, normal); // Full rendering equation evaluation: L_o = f_r * L_i * cos(θ) Vector3 outgoing_radiance = Vector3( @@ -502,7 +502,7 @@ class CookTorranceMaterial : public Material { brdf.z * incident_radiance.z * cos_theta // Blue channel ); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Final outgoing radiance: L_o = f_r * L_i * cos(θ) = (" << outgoing_radiance.x << ", " << outgoing_radiance.y << ", " << outgoing_radiance.z << ")" << std::endl; std::cout << "=== Cook-Torrance light scattering calculation complete ===" << std::endl; diff --git a/src/materials/lambert.hpp b/src/materials/lambert.hpp index 8408c5a..1a9295a 100644 --- a/src/materials/lambert.hpp +++ b/src/materials/lambert.hpp @@ -1,5 +1,6 @@ #pragma once #include "../core/vector3.hpp" +#include "../core/logger.hpp" #include "material_base.hpp" #include #include @@ -80,10 +81,10 @@ class LambertMaterial : public Material { // // Parameters: // wi: incident light direction (pointing toward surface, normalized) - // wo: outgoing view direction (pointing toward camera, normalized) + // wo: outgoing view direction (pointing toward camera, normalized) // normal: surface normal at intersection point (outward-pointing, normalized) - Vector3 evaluate_brdf(const Vector3& wi, const Vector3& wo, const Vector3& normal, bool verbose = true) const override { - if (verbose) { + Vector3 evaluate_brdf(const Vector3& wi, const Vector3& wo, const Vector3& normal) const override { + if (Logger::is_verbose()) { std::cout << "\n=== Lambert BRDF Evaluation ===" << std::endl; std::cout << "Incident direction (wi): (" << wi.x << ", " << wi.y << ", " << wi.z << ")" << std::endl; std::cout << "Outgoing direction (wo): (" << wo.x << ", " << wo.y << ", " << wo.z << ")" << std::endl; @@ -95,7 +96,7 @@ class LambertMaterial : public Material { // Formula: f_r = ρ/π where ρ = albedo, π ensures energy conservation // Mathematical derivation: ∫hemisphere f_r * cos(θ) * dω = ρ when f_r = ρ/π Vector3 brdf_value = base_color * (1.0f / M_PI); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Lambert BRDF value: f_r = ρ/π = (" << brdf_value.x << ", " << brdf_value.y << ", " << brdf_value.z << ")" << std::endl; // Verify energy conservation constraint: each component ≤ 1/π @@ -136,8 +137,8 @@ class LambertMaterial : public Material { // normal: surface normal at intersection point (outward-pointing, normalized) // incident_radiance: incoming light energy (L_i in rendering equation) Vector3 scatter_light(const Vector3& light_direction, const Vector3& view_direction, - const Vector3& normal, const Vector3& incident_radiance, bool verbose = true) const override { - if (verbose) { + const Vector3& normal, const Vector3& incident_radiance) const override { + if (Logger::is_verbose()) { std::cout << "\n=== Lambert Light Scattering Calculation ===" << std::endl; std::cout << "Light direction: (" << light_direction.x << ", " << light_direction.y << ", " << light_direction.z << ")" << std::endl; std::cout << "View direction: (" << view_direction.x << ", " << view_direction.y << ", " << view_direction.z << ")" << std::endl; @@ -149,19 +150,19 @@ class LambertMaterial : public Material { // Geometric interpretation: how much surface faces toward light source // Physics: Lambert's cosine law - apparent brightness ∝ cos(angle) float cos_theta = normal.dot(light_direction); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Raw cosine term n·l = " << cos_theta << std::endl; } // Clamp to positive values: surfaces don't emit light when facing away // Physical constraint: no negative light contribution from backlit surfaces cos_theta = std::max(0.0f, cos_theta); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Clamped cosine term max(0, n·l) = " << cos_theta << std::endl; } // Evaluate BRDF for this direction pair - Vector3 brdf = evaluate_brdf(light_direction, view_direction, normal, verbose); + Vector3 brdf = evaluate_brdf(light_direction, view_direction, normal); // Full rendering equation evaluation: L_o = f_r * L_i * cos(θ) // Each component computed separately for RGB channels @@ -171,7 +172,7 @@ class LambertMaterial : public Material { brdf.z * incident_radiance.z * cos_theta // Blue channel ); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Final outgoing radiance: L_o = f_r * L_i * cos(θ) = (" << outgoing_radiance.x << ", " << outgoing_radiance.y << ", " << outgoing_radiance.z << ")" << std::endl; std::cout << "=== Light scattering calculation complete ===" << std::endl; @@ -233,7 +234,7 @@ class LambertMaterial : public Material { std::cout << "• Viewing angle independent brightness (unlike metals/glossy surfaces)" << std::endl; // Demonstrate actual calculation - Vector3 result = evaluate_brdf(wi, wo, normal, false); + Vector3 result = evaluate_brdf(wi, wo, normal); std::cout << "\n=== LIVE CALCULATION DEMONSTRATION ===" << std::endl; std::cout << "Current albedo ρ: (" << base_color.x << ", " << base_color.y << ", " << base_color.z << ")" << std::endl; std::cout << "Lambert BRDF value: ρ/π = (" << result.x << ", " << result.y << ", " << result.z << ")" << std::endl; diff --git a/src/materials/material_base.hpp b/src/materials/material_base.hpp index 551f68b..2070ea9 100644 --- a/src/materials/material_base.hpp +++ b/src/materials/material_base.hpp @@ -1,5 +1,6 @@ #pragma once #include "../core/vector3.hpp" +#include "../core/logger.hpp" #include #include @@ -42,8 +43,7 @@ class Material { // wi: incident light direction (pointing toward surface, normalized) // wo: outgoing view direction (pointing toward camera, normalized) // normal: surface normal at intersection point (outward-pointing, normalized) - // verbose: enable educational mathematical breakdown output - virtual Vector3 evaluate_brdf(const Vector3& wi, const Vector3& wo, const Vector3& normal, bool verbose = true) const = 0; + virtual Vector3 evaluate_brdf(const Vector3& wi, const Vector3& wo, const Vector3& normal) const = 0; // Pure virtual parameter validation - implemented by concrete material classes // Each material type defines its own physically valid parameter ranges: @@ -81,28 +81,27 @@ class Material { // view_direction: direction from surface to camera (normalized) // normal: surface normal at intersection point (outward-pointing, normalized) // incident_radiance: incoming light energy (L_i in rendering equation) - // verbose: enable educational console output - virtual Vector3 scatter_light(const Vector3& light_direction, const Vector3& view_direction, - const Vector3& normal, const Vector3& incident_radiance, bool verbose = true) const { - if (verbose) { + virtual Vector3 scatter_light(const Vector3& light_direction, const Vector3& view_direction, + const Vector3& normal, const Vector3& incident_radiance) const { + if (Logger::is_verbose()) { std::cout << "\n=== Generic Material Light Scattering ===" << std::endl; std::cout << "Material Type: " << material_type_name() << std::endl; } // Calculate cosine term: n·l (normal dot light_direction) float cos_theta = normal.dot(light_direction); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Raw cosine term n·l = " << cos_theta << std::endl; } // Clamp to positive values: surfaces don't emit light when facing away cos_theta = std::max(0.0f, cos_theta); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Clamped cosine term max(0, n·l) = " << cos_theta << std::endl; } // Evaluate material-specific BRDF - Vector3 brdf = evaluate_brdf(light_direction, view_direction, normal, verbose); + Vector3 brdf = evaluate_brdf(light_direction, view_direction, normal); // Full rendering equation evaluation: L_o = f_r * L_i * cos(θ) Vector3 outgoing_radiance = Vector3( @@ -111,7 +110,7 @@ class Material { brdf.z * incident_radiance.z * cos_theta // Blue channel ); - if (verbose) { + if (Logger::is_verbose()) { std::cout << "Final outgoing radiance: L_o = f_r * L_i * cos(θ) = (" << outgoing_radiance.x << ", " << outgoing_radiance.y << ", " << outgoing_radiance.z << ")" << std::endl; std::cout << "=== Generic light scattering calculation complete ===" << std::endl; diff --git a/tests/test_math_correctness.cpp b/tests/test_math_correctness.cpp index bcf0324..dd4c992 100644 --- a/tests/test_math_correctness.cpp +++ b/tests/test_math_correctness.cpp @@ -1350,13 +1350,11 @@ namespace MathematicalTests { // Test Case 2: Material addition std::cout << "Test 2: Material addition..." << std::endl; - LambertMaterial red_material(Vector3(0.7f, 0.3f, 0.3f)); - int red_idx = empty_scene.add_material(red_material); + int red_idx = empty_scene.add_material(std::make_unique(Vector3(0.7f, 0.3f, 0.3f))); assert(red_idx == 0); // First material should have index 0 assert(empty_scene.materials.size() == 1); - - LambertMaterial blue_material(Vector3(0.3f, 0.3f, 0.7f)); - int blue_idx = empty_scene.add_material(blue_material); + + int blue_idx = empty_scene.add_material(std::make_unique(Vector3(0.3f, 0.3f, 0.7f))); assert(blue_idx == 1); // Second material should have index 1 assert(empty_scene.materials.size() == 2); @@ -1390,13 +1388,9 @@ namespace MathematicalTests { Scene test_scene; // Add materials - LambertMaterial red_mat(Vector3(0.7f, 0.3f, 0.3f)); - LambertMaterial green_mat(Vector3(0.3f, 0.7f, 0.3f)); - LambertMaterial blue_mat(Vector3(0.3f, 0.3f, 0.7f)); - - int red_idx = test_scene.add_material(red_mat); - int green_idx = test_scene.add_material(green_mat); - int blue_idx = test_scene.add_material(blue_mat); + int red_idx = test_scene.add_material(std::make_unique(Vector3(0.7f, 0.3f, 0.3f))); + int green_idx = test_scene.add_material(std::make_unique(Vector3(0.3f, 0.7f, 0.3f))); + int blue_idx = test_scene.add_material(std::make_unique(Vector3(0.3f, 0.3f, 0.7f))); // Add spheres at different depths Sphere near_sphere(Point3(0, 0, -4), 0.5f, green_idx); // Closest (z=-3.5 front surface) @@ -1537,8 +1531,7 @@ sphere 0 0 0 -1.0 nonexistent_material # Negative radius, invalid material Scene perf_scene; // Add materials and spheres - LambertMaterial test_material(Vector3(0.5f, 0.5f, 0.5f)); - int mat_idx = perf_scene.add_material(test_material); + int mat_idx = perf_scene.add_material(std::make_unique(Vector3(0.5f, 0.5f, 0.5f))); // Add multiple spheres for performance testing for (int i = 0; i < 5; i++) { @@ -1590,9 +1583,8 @@ sphere 0 0 0 -1.0 nonexistent_material # Negative radius, invalid material // Test Case 1: Scene with invalid spheres std::cout << "Test 1: Invalid sphere rejection..." << std::endl; Scene validation_scene; - - LambertMaterial valid_material(Vector3(0.5f, 0.5f, 0.5f)); - int mat_idx = validation_scene.add_material(valid_material); + + int mat_idx = validation_scene.add_material(std::make_unique(Vector3(0.5f, 0.5f, 0.5f))); // Try to add sphere with negative radius Sphere invalid_sphere(Point3(0, 0, -5), 1.0f, mat_idx); // Valid construction @@ -1620,8 +1612,8 @@ sphere 0 0 0 -1.0 nonexistent_material # Negative radius, invalid material // Scene should accept materials regardless of energy conservation for educational purposes // but warn about violations - int valid_energy_idx = energy_scene.add_material(valid_energy_material); - int invalid_energy_idx = energy_scene.add_material(invalid_energy_material); + int valid_energy_idx = energy_scene.add_material(std::make_unique(valid_energy_material)); + int invalid_energy_idx = energy_scene.add_material(std::make_unique(invalid_energy_material)); assert(valid_energy_idx >= 0); assert(invalid_energy_idx >= 0); assert(energy_scene.materials.size() == 2); @@ -1638,8 +1630,7 @@ sphere 0 0 0 -1.0 nonexistent_material # Negative radius, invalid material // Test Case 4: Self-intersection avoidance std::cout << "Test 4: Self-intersection avoidance..." << std::endl; Scene self_test_scene; - LambertMaterial self_material(Vector3(0.5f, 0.5f, 0.5f)); - int self_mat_idx = self_test_scene.add_material(self_material); + int self_mat_idx = self_test_scene.add_material(std::make_unique(Vector3(0.5f, 0.5f, 0.5f))); Sphere self_sphere(Point3(0, 0, -5), 1.0f, self_mat_idx); self_test_scene.add_sphere(self_sphere); @@ -2109,7 +2100,7 @@ sphere 0 0 0 -1.0 nonexistent_material # Negative radius, invalid material // Test 1: Lambert BRDF evaluation through polymorphic interface std::cout << "Test 1: Lambert BRDF polymorphic evaluation..." << std::endl; Material* lambert_ptr = new LambertMaterial(Vector3(0.7f, 0.7f, 0.7f)); - Vector3 lambert_brdf = lambert_ptr->evaluate_brdf(incident_dir, outgoing_dir, surface_normal, false); + Vector3 lambert_brdf = lambert_ptr->evaluate_brdf(incident_dir, outgoing_dir, surface_normal); // Lambert BRDF should be constant: base_color / π float expected_lambert = 0.7f / M_PI; @@ -2123,7 +2114,7 @@ sphere 0 0 0 -1.0 nonexistent_material # Negative radius, invalid material // Test 2: Cook-Torrance BRDF evaluation through polymorphic interface std::cout << "Test 2: Cook-Torrance BRDF polymorphic evaluation..." << std::endl; Material* ct_ptr = new CookTorranceMaterial(Vector3(0.8f, 0.8f, 0.8f), 0.3f, 0.0f, 0.04f); - Vector3 ct_brdf = ct_ptr->evaluate_brdf(incident_dir, outgoing_dir, surface_normal, false); + Vector3 ct_brdf = ct_ptr->evaluate_brdf(incident_dir, outgoing_dir, surface_normal); // Cook-Torrance should give different result than Lambert assert(std::abs(ct_brdf.x - lambert_brdf.x) > 1e-6); // Should be noticeably different @@ -2205,7 +2196,7 @@ sphere 2.0 0.0 -5.0 1.0 legacy_green // Test intersection with multi-material scene Ray test_ray(Point3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, -1.0f)); - Scene::Intersection hit = scene.intersect(test_ray, false); + Scene::Intersection hit = scene.intersect(test_ray); assert(hit.hit); assert(hit.material != nullptr); @@ -2243,13 +2234,13 @@ sphere 2.0 0.0 -5.0 1.0 legacy_green // Test intersection and BRDF evaluation integration Ray lambert_ray(Point3(-1.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, -1.0f)); - Scene::Intersection lambert_hit = scene.intersect(lambert_ray, false); + Scene::Intersection lambert_hit = scene.intersect(lambert_ray); assert(lambert_hit.hit); assert(lambert_hit.material->type == MaterialType::Lambert); Ray ct_ray(Point3(1.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, -1.0f)); - Scene::Intersection ct_hit = scene.intersect(ct_ray, false); + Scene::Intersection ct_hit = scene.intersect(ct_ray); assert(ct_hit.hit); assert(ct_hit.material->type == MaterialType::CookTorrance); @@ -2258,8 +2249,8 @@ sphere 2.0 0.0 -5.0 1.0 legacy_green Vector3 incident = Vector3(0.0f, 0.707f, -0.707f).normalize(); Vector3 outgoing = Vector3(0.0f, 0.0f, 1.0f); - Vector3 lambert_brdf = lambert_hit.material->evaluate_brdf(incident, outgoing, lambert_hit.normal, false); - Vector3 ct_brdf = ct_hit.material->evaluate_brdf(incident, outgoing, ct_hit.normal, false); + Vector3 lambert_brdf = lambert_hit.material->evaluate_brdf(incident, outgoing, lambert_hit.normal); + Vector3 ct_brdf = ct_hit.material->evaluate_brdf(incident, outgoing, ct_hit.normal); // Validate BRDF results are reasonable assert(lambert_brdf.x >= 0 && lambert_brdf.y >= 0 && lambert_brdf.z >= 0); @@ -2275,8 +2266,8 @@ sphere 2.0 0.0 -5.0 1.0 legacy_green Vector3 view_dir = outgoing; Vector3 incident_radiance(1.0f, 1.0f, 1.0f); - Vector3 lambert_scattered = lambert_hit.material->scatter_light(incident_light, view_dir, lambert_hit.normal, incident_radiance, false); - Vector3 ct_scattered = ct_hit.material->scatter_light(incident_light, view_dir, ct_hit.normal, incident_radiance, false); + Vector3 lambert_scattered = lambert_hit.material->scatter_light(incident_light, view_dir, lambert_hit.normal, incident_radiance); + Vector3 ct_scattered = ct_hit.material->scatter_light(incident_light, view_dir, ct_hit.normal, incident_radiance); assert(lambert_scattered.x >= 0 && ct_scattered.x >= 0); assert(std::isfinite(lambert_scattered.x) && std::isfinite(ct_scattered.x));