agent: Write a CLI interface for the Ray Tracer Application Iteration 1
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <numbers>
|
||||
|
||||
namespace CLI
|
||||
{
|
||||
@@ -60,6 +62,30 @@ namespace CLI
|
||||
parseFloat(tokens[offset + 1], argName + " y")
|
||||
);
|
||||
}
|
||||
|
||||
RendererType parseRendererType(const std::string& str)
|
||||
{
|
||||
if (str == "cpu" || str == "CPU")
|
||||
return RendererType::CPU;
|
||||
if (str == "gpu" || str == "GPU" || str == "vulkan" || str == "Vulkan")
|
||||
return RendererType::GPU;
|
||||
if (str == "metal" || str == "Metal")
|
||||
return RendererType::Metal;
|
||||
|
||||
std::cerr << "Warning: Unknown renderer type '" << str << "', using CPU renderer\n";
|
||||
return RendererType::CPU;
|
||||
}
|
||||
}
|
||||
|
||||
const char* rendererTypeToString(RendererType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case RendererType::CPU: return "CPU";
|
||||
case RendererType::GPU: return "GPU (Vulkan)";
|
||||
case RendererType::Metal: return "Metal";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void printHelp()
|
||||
@@ -95,7 +121,18 @@ Light Parameters:
|
||||
--point-light <x> <y> <z> <r> <g> <b> <att>
|
||||
Add point light (position + color + attenuation)
|
||||
|
||||
Model Parameters:
|
||||
--model <file> Model file path (OBJ, FBX, GLB, etc.)
|
||||
--model-scale <x> <y> <z> Model scale (default: 1 1 1)
|
||||
--model-translate <x> <y> <z>
|
||||
Model translation (default: 0 0 0)
|
||||
--model-rotate <x> <y> <z> Model rotation in degrees (default: 0 0 0)
|
||||
|
||||
Renderer Selection:
|
||||
--renderer <type> Renderer type: cpu, gpu, metal (default: cpu)
|
||||
|
||||
Other:
|
||||
--headless Run without GUI (render and save to file)
|
||||
--help Show this help message
|
||||
|
||||
Examples:
|
||||
@@ -105,6 +142,8 @@ Examples:
|
||||
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
|
||||
RayTracer --model models/bunny.obj --renderer cpu --headless -o bunny.pgm
|
||||
RayTracer --model models/scene.fbx --model-scale 0.5 0.5 0.5 --renderer gpu
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -304,6 +343,58 @@ Examples:
|
||||
options.pointLights.push_back(light);
|
||||
i += 7;
|
||||
}
|
||||
else if (arg == "--model")
|
||||
{
|
||||
if (i + 1 >= args.size())
|
||||
{
|
||||
std::cerr << "Error: --model requires a file path\n";
|
||||
return options;
|
||||
}
|
||||
options.modelPath = args[++i];
|
||||
}
|
||||
else if (arg == "--model-scale")
|
||||
{
|
||||
if (i + 3 >= args.size())
|
||||
{
|
||||
std::cerr << "Error: --model-scale requires 3 values (x y z)\n";
|
||||
return options;
|
||||
}
|
||||
options.modelScale = parseVec3(args, i + 1, "model-scale");
|
||||
i += 3;
|
||||
}
|
||||
else if (arg == "--model-translate")
|
||||
{
|
||||
if (i + 3 >= args.size())
|
||||
{
|
||||
std::cerr << "Error: --model-translate requires 3 values (x y z)\n";
|
||||
return options;
|
||||
}
|
||||
options.modelTranslation = parseVec3(args, i + 1, "model-translate");
|
||||
i += 3;
|
||||
}
|
||||
else if (arg == "--model-rotate")
|
||||
{
|
||||
if (i + 3 >= args.size())
|
||||
{
|
||||
std::cerr << "Error: --model-rotate requires 3 values (x y z)\n";
|
||||
return options;
|
||||
}
|
||||
options.modelRotation = parseVec3(args, i + 1, "model-rotate");
|
||||
i += 3;
|
||||
}
|
||||
else if (arg == "--renderer")
|
||||
{
|
||||
if (i + 1 >= args.size())
|
||||
{
|
||||
std::cerr << "Error: --renderer requires a type (cpu, gpu, metal)\n";
|
||||
return options;
|
||||
}
|
||||
options.rendererType = parseRendererType(args[++i]);
|
||||
}
|
||||
else if (arg == "--headless")
|
||||
{
|
||||
options.headless = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Warning: Unknown argument: " << arg << "\n";
|
||||
@@ -350,4 +441,67 @@ Examples:
|
||||
renderer->addPointLight(pointLight);
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 getModelTransform(const CLIOptions& opts)
|
||||
{
|
||||
// Build transformation matrix: translate * rotate * scale
|
||||
glm::mat4 scale = glm::scale(glm::mat4(1.0f), opts.modelScale);
|
||||
glm::mat4 rotationX = glm::rotate(glm::mat4(1.0f), glm::radians(opts.modelRotation.x), glm::vec3(1, 0, 0));
|
||||
glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(opts.modelRotation.y), glm::vec3(0, 1, 0));
|
||||
glm::mat4 rotationZ = glm::rotate(glm::mat4(1.0f), glm::radians(opts.modelRotation.z), glm::vec3(0, 0, 1));
|
||||
glm::mat4 rotation = rotationX * rotationY * rotationZ;
|
||||
glm::mat4 translation = glm::translate(glm::mat4(1.0f), opts.modelTranslation);
|
||||
|
||||
return translation * rotation * scale;
|
||||
}
|
||||
|
||||
bool saveImagePGM(const std::vector<float>& imageData, uint32_t width, uint32_t height, const std::string& filepath)
|
||||
{
|
||||
if (imageData.empty() || width == 0 || height == 0)
|
||||
{
|
||||
std::cerr << "Error: Cannot save empty image\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (imageData.size() < width * height * 3)
|
||||
{
|
||||
std::cerr << "Error: Image data size mismatch. Expected " << (width * height * 3)
|
||||
<< " floats, got " << imageData.size() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
std::cerr << "Error: Cannot open file for writing: " << filepath << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// PGM P5 format header
|
||||
file << "P5\n";
|
||||
file << "#" << filepath << "\n"; // Comment line
|
||||
file << width << " " << height << "\n";
|
||||
file << "255\n"; // Max val
|
||||
|
||||
// Write pixel data
|
||||
for (uint32_t i = 0; i < width * height * 3; ++i)
|
||||
{
|
||||
// Convert float [0, 1] to uint8_t [0, 255]
|
||||
uint8_t val = static_cast<uint8_t>(std::clamp(imageData[i], 0.0f, 1.0f) * 255.0f);
|
||||
file.put(val);
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (file.good())
|
||||
{
|
||||
std::cout << "Image saved to: " << filepath << "\n";
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: Failed to write image to: " << filepath << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
enum class RendererType
|
||||
{
|
||||
CPU,
|
||||
GPU, // Vulkan
|
||||
Metal
|
||||
};
|
||||
|
||||
struct CLIOptions
|
||||
{
|
||||
// Render parameters
|
||||
@@ -33,6 +40,19 @@ struct CLIOptions
|
||||
std::vector<DirectionalLight> directionalLights;
|
||||
std::vector<PointLight> pointLights;
|
||||
|
||||
// Model parameters
|
||||
std::string modelPath;
|
||||
glm::vec3 modelScale = glm::vec3(1, 1, 1);
|
||||
glm::vec3 modelTranslation = glm::vec3(0, 0, 0);
|
||||
glm::vec3 modelRotation = glm::vec3(0, 0, 0); // Euler angles in degrees
|
||||
|
||||
// Renderer selection
|
||||
RendererType rendererType = RendererType::CPU;
|
||||
|
||||
// Headless mode (no GUI, render and exit)
|
||||
bool headless = false;
|
||||
|
||||
// Help flag
|
||||
bool showHelp = false;
|
||||
};
|
||||
|
||||
@@ -52,4 +72,13 @@ namespace CLI
|
||||
|
||||
// Apply lights from CLIOptions to renderer
|
||||
void applyLights(Renderer* renderer, const CLIOptions& opts);
|
||||
|
||||
// Generate model transform matrix from CLI options
|
||||
glm::mat4 getModelTransform(const CLIOptions& opts);
|
||||
|
||||
// Save image buffer to PGM file
|
||||
bool saveImagePGM(const std::vector<float>& imageData, uint32_t width, uint32_t height, const std::string& filepath);
|
||||
|
||||
// Get renderer type as string
|
||||
const char* rendererTypeToString(RendererType type);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user