diff --git a/CMakeLists.txt b/CMakeLists.txt index 68eb682..25b39d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ find_package(glm CONFIG REQUIRED) find_package(Ktx CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED) find_package(slang CONFIG REQUIRED) +find_package(CLI11 CONFIG REQUIRED) add_executable(RayTracer "") target_include_directories(RayTracer PUBLIC src/) @@ -28,6 +29,7 @@ target_link_libraries(RayTracer PUBLIC GLEW::GLEW) target_link_libraries(RayTracer PUBLIC glm::glm) target_link_libraries(RayTracer PUBLIC KTX::ktx) target_link_libraries(RayTracer PUBLIC slang::slang) +target_link_libraries(RayTracer PUBLIC CLI11::CLI11) if(APPLE) target_include_directories(RayTracer PUBLIC ${VCPKG_INSTALLED_DIR}/arm64-osx/include) diff --git a/src/main.cpp b/src/main.cpp index 1267fdc..a4baffd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,7 +8,7 @@ int main(int argc, const char* argv[]) { // Parse command-line arguments - CLIOptions cliOpts = CLI::parseArgs(argc, argv); + CLIOptions cliOpts = AppCLI::parseArgs(argc, argv); // If help was requested, just exit after printing if (cliOpts.showHelp) @@ -30,7 +30,7 @@ int main(int argc, const char* argv[]) } else { - CLI::applyLights(renderer.get(), cliOpts); + AppCLI::applyLights(renderer.get(), cliOpts); } renderer->addModels(ModelLoader::loadModel("../res/models/stanford-bunny.obj"), @@ -39,8 +39,8 @@ int main(int argc, const char* argv[]) renderer->generate(); // Create camera and render parameters from CLI options - Camera camera = CLI::toCamera(cliOpts); - RenderParameter render = CLI::toRenderParameter(cliOpts); + Camera camera = AppCLI::toCamera(cliOpts); + RenderParameter render = AppCLI::toRenderParameter(cliOpts); renderer->startRender(camera, render); diff --git a/src/util/CLI.cpp b/src/util/CLI.cpp index 7227ed2..6703bad 100644 --- a/src/util/CLI.cpp +++ b/src/util/CLI.cpp @@ -1,67 +1,9 @@ #include "CLI.h" -#include +#include #include -#include -#include -namespace CLI +namespace AppCLI { - 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"( @@ -69,7 +11,7 @@ Usage: RayTracer [options] Render Parameters: -w, --width Image width (default: 1920) - -h, --height Image height (default: 1080) + --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) @@ -96,10 +38,10 @@ Light Parameters: Add point light (position + color + attenuation) Other: - --help Show this help message + -h, --help Show this help message Examples: - RayTracer -w 1280 -h 720 -s 1000 + RayTracer -w 1280 --height 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 @@ -111,205 +53,135 @@ Examples: CLIOptions parseArgs(int argc, const char* argv[]) { CLIOptions options; - std::vector args; - for (int i = 1; i < argc; ++i) + ::CLI::App app{"RayTracer - A Path Tracing Renderer"}; + app.set_help_flag("-h,--help", "Show this help message"); + + // Render parameters + app.add_option_group("Render Parameters")->add_option( + "-w,--width", options.width, "Image width"); + app.add_option("--height", options.height, "Image height"); + app.add_option("-s,--samples", options.numSamples, "Number of samples per pixel"); + app.add_option("-b,--bounces", options.maxBounces, "Maximum ray bounces"); + app.add_option("--throughput", options.throughputThreshold, "Minimum throughput threshold"); + app.add_option("-o,--output", options.outputFile, "Output image file path"); + + // Background color (3 floats) + std::vector bgColor(3, 0.0f); + app.add_option("--bg-color", bgColor, "Background/sky color (r g b)") + ->expected(3); + + // Camera parameters + std::vector camPos(3, 0.0f); + app.add_option("--cam-pos", camPos, "Camera position (x y z)") + ->expected(3); + std::vector camTarget(3, 0.0f); + app.add_option("--cam-target", camTarget, "Camera target (x y z)") + ->expected(3); + app.add_option("--cam-focal", options.camFocalLength, "Focal length"); + app.add_option("--cam-aperture", options.camAperture, "Aperture size"); + app.add_option("--cam-distance", options.camDistance, "Subject distance S_O"); + std::vector camSensor(2, 0.0f); + app.add_option("--cam-sensor", camSensor, "Sensor size (w h)") + ->expected(2); + + // Camera shading parameters + std::vector fogEmission(3, 0.0f); + app.add_option("--fog-emission", fogEmission, "Fog emission color (r g b)") + ->expected(3); + app.add_option("--specular-coeff", options.specularCoeff, "Specular coefficient ks"); + app.add_option("--ambient-coeff", options.ambientCoeff, "Ambient coefficient ka"); + + // Light parameters - using TakeAll policy to allow multiple occurrences + std::vector> dirLightArgs; + app.add_option("--dir-light", dirLightArgs, "Add directional light (dx dy dz r g b)") + ->expected(6) + ->multi_option_policy(::CLI::MultiOptionPolicy::TakeAll); + std::vector> pointLightArgs; + app.add_option("--point-light", pointLightArgs, "Add point light (x y z r g b attenuation)") + ->expected(7) + ->multi_option_policy(::CLI::MultiOptionPolicy::TakeAll); + + // Parse arguments + bool helpRequested = false; + try { - args.push_back(argv[i]); + app.parse(argc, argv); + } + catch (const ::CLI::CallForHelp& e) + { + helpRequested = true; + } + catch (const ::CLI::CallForAllHelp& e) + { + helpRequested = true; + } + catch (const ::CLI::ParseError& e) + { + std::cerr << e.what() << std::endl; + return options; } - size_t i = 0; - while (i < args.size()) + // Check if help was requested + if (helpRequested || app.get_help_ptr()->count() > 0) { - const std::string& arg = args[i]; + options.showHelp = true; + printHelp(); + return options; + } - if (arg == "--help") + // Process background color + if (bgColor.size() == 3) + { + options.backgroundColor = glm::vec3(bgColor[0], bgColor[1], bgColor[2]); + } + + // Process camera position + if (camPos.size() == 3) + { + options.camPosition = glm::vec3(camPos[0], camPos[1], camPos[2]); + } + + // Process camera target + if (camTarget.size() == 3) + { + options.camTarget = glm::vec3(camTarget[0], camTarget[1], camTarget[2]); + } + + // Process camera sensor size + if (camSensor.size() == 2) + { + options.camSensorSize = glm::vec2(camSensor[0], camSensor[1]); + } + + // Process fog emission + if (fogEmission.size() == 3) + { + options.fogEmission = glm::vec3(fogEmission[0], fogEmission[1], fogEmission[2]); + } + + // Process directional lights + for (const auto& args : dirLightArgs) + { + if (args.size() == 6) { - 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"); + light.direction = glm::vec3(args[0], args[1], args[2]); + light.color = glm::vec3(args[3], args[4], args[5]); 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; + // Process point lights + for (const auto& args : pointLightArgs) + { + if (args.size() == 7) + { + PointLight light; + light.position = glm::vec3(args[0], args[1], args[2]); + light.color = glm::vec3(args[3], args[4], args[5]); + light.attenuation = args[6]; + options.pointLights.push_back(light); + } } return options; diff --git a/src/util/CLI.h b/src/util/CLI.h index c6efb2a..9bb10f9 100644 --- a/src/util/CLI.h +++ b/src/util/CLI.h @@ -36,7 +36,7 @@ struct CLIOptions bool showHelp = false; }; -namespace CLI +namespace AppCLI { // Parse command-line arguments and return CLIOptions CLIOptions parseArgs(int argc, const char* argv[]); diff --git a/vcpkg.json b/vcpkg.json index 18f2bce..1195396 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -5,6 +5,7 @@ "features": [ "glfw-binding", "opengl3-binding", "metal-binding" ] }, "assimp", + "cli11", "ktx", "glfw3", "glew", @@ -12,4 +13,4 @@ "fmt", "shader-slang" ] -} \ No newline at end of file +}