1
0

feat: parse wavefront as string lines

This commit is contained in:
2025-07-20 18:29:21 +03:00
parent 58a84ade0d
commit 753c21ef11
7 changed files with 195 additions and 464 deletions

53
build.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -euo pipefail
# Determine absolute project root (scripts parent directory)
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Helper to configure & build
# $1 = build directory name under PROJECT_ROOT/build
# $2 = CMake build type (arbitrary string; flags are driven by -DCMAKE_CXX_FLAGS)
# $3 = CXX flags
cmake_build() {
local name=$1
local type=$2
local flags=$3
local build_dir="${PROJECT_ROOT}/build/${name}"
echo "=== Configuring ${name} (${flags}) ==="
rm -rf "${build_dir}"
mkdir -p "${build_dir}"
pushd "${build_dir}" >/dev/null
cmake \
-DCMAKE_BUILD_TYPE="${type}" \
-DCMAKE_CXX_FLAGS="${flags}" \
"${PROJECT_ROOT}"
make -j$(nproc)
popd >/dev/null
}
# 1) Debug: -g
cmake_build "Debug" "Debug" "-g"
# 2) Release: -O3
cmake_build "Release" "Release" "-O3"
# 3) RelWithDebugInfo: -g -O3
cmake_build "RelWithDebugInfo" "RelWithDebInfo" "-g -O3"
# 4) Generate verbose assembly (.S) for all translation units in RelWithDebugInfo
echo "=== Generating .S assembly in build/RelWithDebugInfo ==="
ASM_OUT_DIR="${PROJECT_ROOT}/build/RelWithDebugInfo/asm"
mkdir -p "${ASM_OUT_DIR}"
# adjust the sourcefile patterns as needed (e.g. .cc, .cxx)
find "${PROJECT_ROOT}" -type f \( -name '*.cpp' -o -name '*.cc' \) | while read -r src; do
fname="$(basename "${src}")"
base="${fname%.*}"
g++ -g -O3 -masm=intel -fno-verbose-asm -std=c++20 -S "${src}" \
-o "${ASM_OUT_DIR}/${base}.S"
done
echo "All builds complete."

View File

@@ -12,4 +12,14 @@ constexpr std::array<T, N> array_fill(const T& value) {
return array;
}
template<typename F, std::size_t... I>
constexpr void _static_for_impl(F &&f, std::index_sequence<I...>) {
(f(std::integral_constant<std::size_t, I>{}), ...);
}
template<std::size_t N, typename F>
constexpr void static_for(F &&f) {
_static_for_impl(std::forward<F>(f), std::make_index_sequence<N>{});
}
#endif // APP_HELPER_H

View File

@@ -13,7 +13,6 @@
#include <vector>
#include "wavefront.hpp"
#include "output.hpp"
namespace fs = std::filesystem;
@@ -109,31 +108,22 @@ int main(int argc, char *argv[]) {
// printUsage();
// return 1;
// }
WaveFront::Settings wavefront_settings(
selected_objects.empty() ? std::nullopt : std::make_optional(selected_objects),
selected_groups.empty() ? std::nullopt : std::make_optional(selected_groups)
);
WaveFront wavefront_parser(wavefront_settings);
// WaveFrontFilter wavefront_filter(
// selected_objects.empty() ? std::nullopt : std::make_optional(selected_objects),
// selected_groups.empty() ? std::nullopt : std::make_optional(selected_groups)
// );
try {
if (input_file_path == "-") {
wavefront_parser.parse(std::cin);
} else {
std::ifstream input_file(input_file_path);
if (!input_file) {
std::cerr << "Failed to open input file: " << input_file_path << std::endl;
return 1;
}
wavefront_parser.parse(input_file);
}
} catch (const WaveFront::parse_error& e) {
std::cerr << input_file_path << ":" << e.line_number() << ": " << e.what() << std::endl;
return 1;
std::ifstream input_file;
std::istream &input_stream = (input_file_path == "")
? std::cin
: (input_file.open(input_file_path), input_file);
if (!input_stream) {
std::cerr << "Failed to open file: " << input_file_path << std::endl;
std::exit(1);
}
Output output;
output.compile(wavefront_parser);
auto wavefront_lines = parse_wavefront(input_stream);
return 0;
}

View File

@@ -1,93 +0,0 @@
#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <sstream>
#include "output.hpp"
#include "helper.hpp"
#include "numset.hpp"
Output::Output() {
float_data_.push_back(std::numeric_limits<float>::quiet_NaN());
vector_2d_data_.push_back(array_fill<2, std::uint32_t>(0));
vector_3d_data_.push_back(array_fill<3, std::uint32_t>(0));
triangle_data_.push_back(array_fill<3, uint32_t>(0));
}
void Output::compile(const WaveFront &input) {
compile_float_data(input);
}
static const std::array<std::string, 3> attribute_name = {
"position",
"texture",
"normal"
};
template<std::size_t N>
void ensure_dims(std::size_t num_dimensions, std::uint32_t attribute_index) {
if (num_dimensions != 3) {
std::stringstream message;
message << "Unexpected unaligned " << attribute_name[attribute_index] << " size: expected exactly " << N << " dimensions, got " << num_dimensions;
throw std::runtime_error(message.str());
}
}
void Output::compile_float_data(const WaveFront &input) {
using get_data_reference_func_t = std::function<const std::vector<std::array<float, 3>> &()>;
static const std::array<get_data_reference_func_t, 3> get_data_reference{{
[&]() -> const std::vector<std::array<float, 3>> & {
return input.obj_v();
},
[&]() -> const std::vector<std::array<float, 3>> & {
return input.obj_vt();
},
[&]() -> const std::vector<std::array<float, 3>> & {
return input.obj_vn();
},
}};
std::set<float, less_with_nan_first_and_nearly_equal<float>> float_set;
std::size_t texture_size = 0;
using check_attribute_dimensions_func_t = std::function<void(std::uint32_t, std::uint32_t)>;
std::array<check_attribute_dimensions_func_t, 3> check_attribute_dimensions{{
ensure_dims<3>,
[&](std::uint32_t num_dimensions, std::uint32_t attribute_index) {
texture_size = num_dimensions;
check_attribute_dimensions[1] = [&](std::uint32_t num_dimensions, std::uint32_t attribute_index) {
if (texture_size != num_dimensions) {
std::stringstream message;
message << "Unexpected unaligned texture size: expected exactly " << texture_size << " dimensions, got " << num_dimensions;
throw std::runtime_error(message.str());
}
};
},
ensure_dims<3>,
}};
for (const auto &triangle : input.obj_f()) {
for (const auto &vertex : triangle) {
for (std::uint32_t attribute_index = 0; attribute_index < 3; ++attribute_index) {
const auto &attribute_reference = vertex[attribute_index];
if (attribute_reference == 0) {
continue;
}
const auto &container = get_data_reference[attribute_index]();
assert(attribute_reference < container.size());
std::uint32_t dim_count = 0;
for (const auto &value : container.at(attribute_reference)) {
if (std::isnan(value)) {
break;
}
++dim_count;
float_set.insert(value);
}
assert(dim_count > 0);
check_attribute_dimensions[attribute_index](dim_count, attribute_index);
}
}
}
float_data_.insert(float_data_.end(), float_set.begin(), float_set.end());
int j = 0;
}

View File

@@ -1,32 +0,0 @@
#ifndef APP_MODEL_HPP
#define APP_MODEL_HPP
#include <cstdint>
#include <variant>
#include <vector>
#include "wavefront.hpp"
class Output {
std::vector<float> float_data_;
std::vector<std::array<std::uint32_t, 2>> vector_2d_data_;
std::vector<std::array<std::uint32_t, 3>> vector_3d_data_;
std::variant<
uint32_t,
std::array<uint32_t, 2>,
std::array<uint32_t, 3>
> vertex_data_;
std::vector<std::array<uint32_t, 3>> triangle_data_;
public:
Output();
~Output() = default;
private:
void compile_float_data(const WaveFront &input);
public:
void compile(const WaveFront &input);
};
#endif // APP_MODEL_HPP

View File

@@ -1,278 +1,120 @@
#include "wavefront.hpp"
#include "helper.hpp"
#include <algorithm>
#include <limits>
WaveFront::Settings::Settings(
std::optional<std::vector<std::string>> selected_objects,
std::optional<std::vector<std::string>> selected_groups
) :
selected_objects_(selected_objects),
selected_groups_(selected_groups)
{
#include "wavefront.hpp"
WaveFrontLine::WaveFrontLine(const std::size_t &line_number, const std::string &type, const std::string &content)
: line_number_(line_number), type_(type), content_(content) {
}
WaveFront::parse_error::parse_error(std::size_t line_number, const std::string& message) :
std::runtime_error(message),
line_number_(line_number)
{
}
std::size_t WaveFront::parse_error::line_number() const noexcept {
const std::size_t &WaveFrontLine::line_number() {
return line_number_;
}
const std::map<std::string, WaveFront::_parse_line_t> WaveFront::_parse_line_{
{ "v", &WaveFront::parse_v },
{ "vt", &WaveFront::parse_vt },
{ "vn", &WaveFront::parse_vn },
{ "f", &WaveFront::parse_f },
{ "o", &WaveFront::parse_o },
{ "g", &WaveFront::parse_g },
};
WaveFront::WaveFront(
const Settings& settings
) :
settings_(settings),
num_attributes_(0),
num_texture_coordinates_(0),
current_line_(0)
{
obj_v_.push_back(array_fill<3>(std::numeric_limits<float>::quiet_NaN()));
obj_vt_.push_back(array_fill<3>(std::numeric_limits<float>::quiet_NaN()));
obj_vn_.push_back(array_fill<3>(std::numeric_limits<float>::quiet_NaN()));
obj_f_.push_back(array_fill<3>(array_fill<3>(uint32_t(0))));
const std::string &WaveFrontLine::type() {
return type_;
}
const std::string &WaveFrontLine::content() {
return content_;
}
void WaveFront::parse(std::istream &input_stream) {
std::string line;
const WaveFrontFilter WaveFrontFilter::no_filter{std::nullopt, std::nullopt};
while(std::getline(input_stream, line)) {
++current_line_;
if (line.empty() || line[0] == '#') {
continue; // Skip empty lines and comments
}
std::istringstream line_parser(line);
std::string token;
std::getline(line_parser, token, ' ');
if (token.empty()) {
continue;
}
auto token_parser_iterator = _parse_line_.find(token);
if (token_parser_iterator != _parse_line_.end()) {
(this->*(token_parser_iterator->second))(line_parser);
}
}
inline std::string trim(const std::string &source) {
static const auto if_space = [](auto source_char){
return std::isspace(source_char);
};
auto left = std::find_if_not(source.begin(), source.end(), if_space);
auto right = std::find_if_not(source.rbegin(), source.rend(), if_space).base();
return std::string(left, right);
}
void WaveFront::parse_o(std::istringstream &line_parser) {
std::string object_name;
std::getline(line_parser, object_name);
if (object_name.empty()) {
current_object_.reset();
inline void assign_if_not_empty(std::optional<std::string> &context, const std::string &name) {
auto trimmed_name = trim(name);
if (trimmed_name.empty()) {
context.reset();
} else {
current_object_ = object_name;
context = name;
}
}
void WaveFront::parse_g(std::istringstream &line_parser) {
std::string group_name;
std::getline(line_parser, group_name);
if (group_name.empty()) {
current_group_.reset();
} else {
current_group_ = group_name;
}
}
void WaveFront::parse_v(std::istringstream &line_parser) {
std::array<float, 3> data = array_fill<3>(std::numeric_limits<float>::quiet_NaN());
std::string element;
std::size_t element_count = 0;
while (std::getline(line_parser, element, ' ')) {
if (element_count >= 3) {
throw parse_error(current_line_, "Type \"v\" with more than 3 components is not supported.");
}
if (element.empty()) {
break;
}
try {
data[element_count++] = std::stof(element);
} catch (const std::invalid_argument&) {
throw parse_error(current_line_, "Invalid float value in vertex definition.");
} catch (const std::out_of_range&) {
throw parse_error(current_line_, "Float value out of range in vertex definition.");
}
}
if (element_count < 3) {
throw parse_error(current_line_, "Type \"v\" must have exactly 3 components.");
}
obj_v_.push_back(data);
}
void WaveFront::parse_vn(std::istringstream &line_parser) {
std::array<float, 3> data = array_fill<3>(std::numeric_limits<float>::quiet_NaN());
std::string element;
std::size_t element_count = 0;
while (std::getline(line_parser, element, ' ')) {
if (element_count >= 3) {
throw parse_error(current_line_, "Type \"vn\" with more than 3 components is not supported.");
}
if (element.empty()) {
break;
}
try {
data[element_count++] = std::stof(element);
} catch (const std::invalid_argument&) {
throw parse_error(current_line_, "Invalid float value in vertex normal definition.");
} catch (const std::out_of_range&) {
throw parse_error(current_line_, "Float value out of range in vertex normal definition.");
}
}
if (element_count < 3) {
throw parse_error(current_line_, "Type \"vn\" with have exactly 3 components.");
}
obj_vn_.push_back(data);
}
void WaveFront::parse_vt(std::istringstream &line_parser) {
std::array<float, 3> data = array_fill<3>(std::numeric_limits<float>::quiet_NaN());
std::string element;
std::size_t element_count = 0;
while (std::getline(line_parser, element, ' ')) {
if (element_count >= 3) {
throw parse_error(current_line_, "Type \"vt\" with more than 3 components is not supported.");
std::map<std::string, std::vector<WaveFrontLine>> parse_wavefront(
std::istream &input_stream,
const WaveFrontFilter &filter
) {
std::map<std::string, std::vector<WaveFrontLine>> line_data;
std::optional<std::string> current_object, current_group;
std::size_t current_line_number = 0;
bool select_face = true;
using check_filter_func_t = std::function<bool()>;
static const auto return_true = []()->bool { return true; };
check_filter_func_t check_object = return_true;
check_filter_func_t check_group = return_true;
if (filter.selected_objects.has_value()) {
check_object = [&]()->bool {
auto selected = filter.selected_objects.value();
auto begin = selected.begin();
auto end = selected.end();
return std::find(begin, end, current_object.value_or("")) == end;
};
if (element.empty()) {
break;
}
try {
data[element_count++] = std::stof(element);
} catch (const std::invalid_argument&) {
throw parse_error(current_line_, "Invalid float value in texture coordinate definition.");
} catch (const std::out_of_range&) {
throw parse_error(current_line_, "Float value out of range in texture coordinate definition.");
}
}
if (element_count < 1) {
throw parse_error(current_line_, "Type \"vt\" must have at least 1 component.");
if (filter.selected_groups.has_value()) {
check_group = [&]()->bool {
auto selected = filter.selected_objects.value();
auto begin = selected.begin();
auto end = selected.end();
return std::find(begin, end, current_object.value_or("")) == end;
};
}
using line_processor_func_t = std::function<void(const std::string &type, const std::string &content)>;
line_processor_func_t insert_line = [&](const std::string &type, const std::string &content) {
line_data[type].push_back(WaveFrontLine(
current_line_number,
type,
content
));
};
static const std::map<std::string, line_processor_func_t> line_post_processor_map{
{ "o", [&](const std::string &type, const std::string &content) {
assign_if_not_empty(current_object, content);
}},
{ "g", [&](const std::string &type, const std::string &content) {
assign_if_not_empty(current_group, content);
}},
{ "f", [&](const std::string &type, const std::string &content) {
if (!check_object() || !check_group()) {
return;
}
insert_line(type, content);
}},
{ "v", insert_line },
{ "vt", insert_line },
{ "vn", insert_line },
};
obj_vt_.push_back(data);
}
{
std::string line;
while(std::getline(input_stream, line)) {
++current_line_number;
void WaveFront::parse_f(std::istringstream &line_parser) {
if (settings_.selected_objects_.has_value()) {
if (!current_object_.has_value()) {
return;
}
auto &selected_objects = settings_.selected_objects_.value();
if (std::find(selected_objects.begin(), selected_objects.end(), current_object_.value()) == selected_objects.end()) {
return;
}
}
if (settings_.selected_groups_) {
if (!current_group_.has_value()) {
return;
}
auto selected_begin = settings_.selected_groups_->begin();
auto selected_end = settings_.selected_groups_->end();
auto current_value = current_group_.value();
if (std::find(selected_begin, selected_end, current_value) == selected_end) {
return;
}
}
if (line.empty() || line[0] == '#') {
continue; // Skip empty lines and comments
}
std::size_t vertex_count = 0;
std::array<std::array<uint32_t, 3>, 3> triangle;
std::string token;
std::istringstream line_parser(line);
std::string line_type;
while (std::getline(line_parser, token, ' ')) {
if (token.empty()) {
break;
}
auto vertex_indices = parse_vertex(token);
if (vertex_count < 3) {
triangle[vertex_count] = vertex_indices;
} else {
obj_f_.push_back(triangle);
triangle[0] = triangle[1];
triangle[1] = triangle[2];
triangle[2] = vertex_indices;
}
++vertex_count;
}
}
std::getline(line_parser, line_type, ' ');
if (line_type.empty()) {
continue;
}
std::array<uint32_t, 3> WaveFront::parse_vertex(std::string &token) {
std::istringstream token_parser(token);
std::array<uint32_t, 3> vertex_result{0, 0, 0};
std::string vertex_token;
std::size_t attribute_count = 0;
static const std::array<std::vector<std::array<float, 3>>, 3> data_reference = {obj_v_, obj_vt_, obj_vn_};
while (std::getline(token_parser, vertex_token, '/')) {
if (attribute_count >= 3) {
throw parse_error(current_line_, "Face vertex with more than 3 components is not supported.");
}
int64_t attribute_value;
if (vertex_token.empty()) {
attribute_value = 0;
} else {
try {
attribute_value = std::stoll(vertex_token);
} catch (const std::invalid_argument&) {
std::stringstream message;
message << "Invalid face vertex index: " << vertex_token;
throw parse_error(current_line_, message.str());
} catch (const std::out_of_range&) {
std::stringstream message;
message << "Face vertex index out of range: " << vertex_token;
throw parse_error(current_line_, message.str());
auto preprocessor_iterator = line_post_processor_map.find(line_type);
if (preprocessor_iterator != line_post_processor_map.end()) {
std::ostringstream line_content;
line_content << line_parser.rdbuf();
(preprocessor_iterator->second)(line_type, line_content.str());
}
}
if (attribute_value < 0) {
attribute_value = data_reference[attribute_count].size() + attribute_value;
if (attribute_value < 1 || attribute_value > data_reference[attribute_count].size()) {
std::stringstream message;
message << "Face vertex index out of range: " << vertex_token;
throw parse_error(current_line_, message.str());
}
} else if (attribute_value >= static_cast<int64_t>(std::numeric_limits<uint32_t>::max())) {
std::stringstream message;
message << "Face vertex index out of range: " << vertex_token;
throw parse_error(current_line_, message.str());
}
vertex_result[attribute_count] = attribute_value;
++attribute_count;
}
return vertex_result;
}
const std::vector<std::array<float, 3>> &WaveFront::obj_v() const noexcept {
return obj_v_;
}
const std::vector<std::array<float, 3>> &WaveFront::obj_vt() const noexcept {
return obj_vt_;
}
const std::vector<std::array<float, 3>> &WaveFront::obj_vn() const noexcept {
return obj_vn_;
}
const std::vector<std::array<std::array<std::uint32_t, 3>, 3>> &WaveFront::obj_f() const noexcept {
return obj_f_;
return line_data;
}

View File

@@ -1,80 +1,41 @@
#ifndef APP_WAVEFRONT_HPP
#define APP_WAVEFRONT_HPP
#include <array>
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
class WaveFront;
class WaveFront {
public:
enum class AttributeIndex : uint32_t {
POSITION = 0,
TEXTURE = 1,
NORMAL = 2
};
public:
class Settings {
friend class WaveFront;
private:
std::optional<std::vector<std::string>> selected_objects_;
std::optional<std::vector<std::string>> selected_groups_;
public:
Settings() = default;
Settings(
std::optional<std::vector<std::string>> selected_objects,
std::optional<std::vector<std::string>> selected_groups
);
Settings(const Settings&) = default;
Settings(Settings&&) = default;
~Settings() = default;
};
class parse_error : public std::runtime_error {
public:
parse_error(std::size_t line_number, const std::string& message);
std::size_t line_number() const noexcept;
private:
std::size_t line_number_;
};
private:
using _parse_line_t = void (WaveFront::*)(std::istringstream&);
Settings settings_;
std::vector<std::array<float, 3>> obj_v_, obj_vt_, obj_vn_;
std::vector<std::array<std::array<std::uint32_t, 3>, 3>> obj_f_;
std::size_t num_attributes_, num_texture_coordinates_, current_line_;
std::optional<std::string> current_object_, current_group_;
static const std::map<std::string, _parse_line_t> _parse_line_;
private:
void parse_o(std::istringstream &line_parser);
void parse_g(std::istringstream &line_parser);
void parse_v(std::istringstream &line_parser);
void parse_vn(std::istringstream &line_parser);
void parse_vt(std::istringstream &line_parser);
void parse_f(std::istringstream &line_parser);
std::array<uint32_t, 3> parse_vertex(std::string &line_parser);
class WaveFrontLine {
std::size_t line_number_;
std::string type_;
std::string content_;
public:
const std::vector<std::array<float, 3>> &obj_v() const noexcept;
const std::vector<std::array<float, 3>> &obj_vt() const noexcept;
const std::vector<std::array<float, 3>> &obj_vn() const noexcept;
const std::vector<std::array<std::array<std::uint32_t, 3>, 3>> &obj_f() const noexcept;
WaveFrontLine(const std::size_t &line_number, const std::string &type, const std::string &content);
~WaveFrontLine() = default;
WaveFrontLine(const WaveFrontLine &) = default;
WaveFrontLine(WaveFrontLine &&) = default;
public:
explicit WaveFront(const Settings &settings = Settings(std::nullopt, std::nullopt));
~WaveFront() = default;
void parse(std::istream &input_string);
const std::size_t &line_number();
const std::string &type();
const std::string &content();
};
class WaveFrontFilter {
public:
std::optional<std::vector<std::string>> selected_objects;
std::optional<std::vector<std::string>> selected_groups;
public:
static const WaveFrontFilter no_filter;
};
std::map<std::string, std::vector<WaveFrontLine>> parse_wavefront(
std::istream &input_stream,
const WaveFrontFilter &filter = WaveFrontFilter::no_filter
);
#endif // APP_WAVEFRONT_HPP