#include "CLI.h" #include #include #include #include namespace CLI { namespace { float parseFloat(const std::string& str, const std::string& argName) { try { return std::stof(str); } catch (const std::exception& e) { std::cerr << "Error: Invalid float value for " << argName << ": " << str << "\n"; return 0.0f; } } uint32_t parseUInt(const std::string& str, const std::string& argName) { try { return static_cast(std::stoul(str)); } catch (const std::exception& e) { std::cerr << "Error: Invalid unsigned integer value for " << argName << ": " << str << "\n"; return 0; } } glm::vec3 parseVec3(const std::vector& tokens, size_t offset, const std::string& argName) { if (offset + 3 > tokens.size()) { std::cerr << "Error: " << argName << " requires 3 float values (x y z)\n"; return glm::vec3(0, 0, 0); } return glm::vec3( parseFloat(tokens[offset], argName + " x"), parseFloat(tokens[offset + 1], argName + " y"), parseFloat(tokens[offset + 2], argName + " z") ); } glm::vec2 parseVec2(const std::vector& tokens, size_t offset, const std::string& argName) { if (offset + 2 > tokens.size()) { std::cerr << "Error: " << argName << " requires 2 float values\n"; return glm::vec2(0, 0); } return glm::vec2( parseFloat(tokens[offset], argName + " x"), parseFloat(tokens[offset + 1], argName + " y") ); } } void printHelp() { std::cout << R"( Usage: RayTracer [options] Render Parameters: -w, --width Image width (default: 1920) -h, --height Image height (default: 1080) -s, --samples Number of samples per pixel (default: 10000) -b, --bounces Maximum ray bounces (default: 4) --bg-color Background/sky color (default: 0.05 0.05 0.1) --throughput Minimum throughput threshold (default: 0.01) -o, --output Output image file path (PGM format) Camera Parameters: --cam-pos Camera position (default: 2 1 2) --cam-target Camera target (default: 0 0 0) --cam-focal Focal length (default: 0.7) --cam-aperture Aperture size (default: 0.35) --cam-distance Subject distance S_O (default: 20) --cam-sensor Sensor size (default: 0.036 0.024) Camera Shading Parameters: --fog-emission Fog emission color (default: 0 0 0) --specular-coeff Specular coefficient ks (default: 0) --ambient-coeff Ambient coefficient ka (default: 0) Light Parameters: --dir-light Add directional light (direction + color) --point-light Add point light (position + color + attenuation) Other: --help Show this help message Examples: RayTracer -w 1280 -h 720 -s 1000 RayTracer --cam-pos 3 2 3 --cam-focal 0.5 RayTracer --dir-light -0.4 -0.3 -0.2 1 1 1 RayTracer --point-light 0 5 0 1 0.5 0 0.5 RayTracer --fog-emission 0.1 0.2 0.3 --specular-coeff 0.5 --ambient-coeff 0.2 RayTracer -b 8 --bg-color 0.1 0.1 0.2 -o output.pgm )"; } CLIOptions parseArgs(int argc, const char* argv[]) { CLIOptions options; std::vector args; for (int i = 1; i < argc; ++i) { args.push_back(argv[i]); } size_t i = 0; while (i < args.size()) { const std::string& arg = args[i]; if (arg == "--help") { options.showHelp = true; printHelp(); return options; } else if (arg == "--width" || arg == "-w") { if (i + 1 >= args.size()) { std::cerr << "Error: --width requires a value\n"; return options; } options.width = parseUInt(args[++i], "width"); } else if (arg == "--height" || arg == "-h") { if (i + 1 >= args.size()) { std::cerr << "Error: --height requires a value\n"; return options; } options.height = parseUInt(args[++i], "height"); } else if (arg == "--samples" || arg == "-s") { if (i + 1 >= args.size()) { std::cerr << "Error: --samples requires a value\n"; return options; } options.numSamples = parseUInt(args[++i], "samples"); } else if (arg == "--bounces" || arg == "-b") { if (i + 1 >= args.size()) { std::cerr << "Error: --bounces requires a value\n"; return options; } options.maxBounces = parseUInt(args[++i], "bounces"); } else if (arg == "--bg-color") { if (i + 3 >= args.size()) { std::cerr << "Error: --bg-color requires 3 values (r g b)\n"; return options; } options.backgroundColor = parseVec3(args, i + 1, "bg-color"); i += 3; } else if (arg == "--throughput") { if (i + 1 >= args.size()) { std::cerr << "Error: --throughput requires a value\n"; return options; } options.throughputThreshold = parseFloat(args[++i], "throughput"); } else if (arg == "--output" || arg == "-o") { if (i + 1 >= args.size()) { std::cerr << "Error: --output requires a file path\n"; return options; } options.outputFile = args[++i]; } else if (arg == "--cam-pos") { if (i + 3 >= args.size()) { std::cerr << "Error: --cam-pos requires 3 values (x y z)\n"; return options; } options.camPosition = parseVec3(args, i + 1, "cam-pos"); i += 3; } else if (arg == "--cam-target") { if (i + 3 >= args.size()) { std::cerr << "Error: --cam-target requires 3 values (x y z)\n"; return options; } options.camTarget = parseVec3(args, i + 1, "cam-target"); i += 3; } else if (arg == "--cam-focal") { if (i + 1 >= args.size()) { std::cerr << "Error: --cam-focal requires a value\n"; return options; } options.camFocalLength = parseFloat(args[++i], "cam-focal"); } else if (arg == "--cam-aperture") { if (i + 1 >= args.size()) { std::cerr << "Error: --cam-aperture requires a value\n"; return options; } options.camAperture = parseFloat(args[++i], "cam-aperture"); } else if (arg == "--cam-distance") { if (i + 1 >= args.size()) { std::cerr << "Error: --cam-distance requires a value\n"; return options; } options.camDistance = parseFloat(args[++i], "cam-distance"); } else if (arg == "--cam-sensor") { if (i + 2 >= args.size()) { std::cerr << "Error: --cam-sensor requires 2 values (w h)\n"; return options; } options.camSensorSize = parseVec2(args, i + 1, "cam-sensor"); i += 2; } else if (arg == "--fog-emission") { if (i + 3 >= args.size()) { std::cerr << "Error: --fog-emission requires 3 values (r g b)\n"; return options; } options.fogEmission = parseVec3(args, i + 1, "fog-emission"); i += 3; } else if (arg == "--specular-coeff") { if (i + 1 >= args.size()) { std::cerr << "Error: --specular-coeff requires a value\n"; return options; } options.specularCoeff = parseFloat(args[++i], "specular-coeff"); } else if (arg == "--ambient-coeff") { if (i + 1 >= args.size()) { std::cerr << "Error: --ambient-coeff requires a value\n"; return options; } options.ambientCoeff = parseFloat(args[++i], "ambient-coeff"); } else if (arg == "--dir-light") { if (i + 6 > args.size()) { std::cerr << "Error: --dir-light requires 6 values (dx dy dz r g b)\n"; return options; } DirectionalLight light; light.direction = parseVec3(args, i + 1, "dir-light direction"); light.color = parseVec3(args, i + 4, "dir-light color"); options.directionalLights.push_back(light); i += 6; } else if (arg == "--point-light") { if (i + 7 > args.size()) { std::cerr << "Error: --point-light requires 7 values (x y z r g b attenuation)\n"; return options; } PointLight light; light.position = parseVec3(args, i + 1, "point-light position"); light.color = parseVec3(args, i + 4, "point-light color"); light.attenuation = parseFloat(args[i + 7], "point-light attenuation"); options.pointLights.push_back(light); i += 7; } else { std::cerr << "Warning: Unknown argument: " << arg << "\n"; } ++i; } return options; } Camera toCamera(const CLIOptions& opts) { return Camera{ .position = opts.camPosition, .target = opts.camTarget, .sensorSize = opts.camSensorSize, .S_O = opts.camDistance, .f = opts.camFocalLength, .A = opts.camAperture, .fogEmm = opts.fogEmission, .ks = opts.specularCoeff, .ka = opts.ambientCoeff }; } RenderParameter toRenderParameter(const CLIOptions& opts) { return RenderParameter{ .width = opts.width, .height = opts.height, .numSamples = opts.numSamples }; } void applyLights(Renderer* renderer, const CLIOptions& opts) { for (const auto& dirLight : opts.directionalLights) { renderer->addDirectionalLight(dirLight); } for (const auto& pointLight : opts.pointLights) { renderer->addPointLight(pointLight); } } }