Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2f67926
feat(String): add split predicate overload, to_string_view, and to_st…
csparker247 Mar 25, 2026
d1b53bd
conductor: update mesh-io track plan for String.hpp API changes
csparker247 Mar 25, 2026
e818d3b
feat(MeshIO): Phase 1 — detection traits and test infrastructure (mes…
csparker247 Mar 25, 2026
9442655
chore: mark Phase 1 complete (mesh-io_20260323)
csparker247 Mar 25, 2026
0a4efdd
feat(MeshIO): Phase 2 — OBJ IO read/write (mesh-io_20260323)
csparker247 Mar 25, 2026
2f6612d
chore: mark Phase 2 complete (mesh-io_20260323)
csparker247 Mar 25, 2026
ea14fbf
feat(MeshIO): Phase 3 — PLY IO, expand_at_seams, MeshUtils (mesh-io_2…
csparker247 Mar 25, 2026
57bb3b0
chore: mark Phase 3 complete (mesh-io_20260323)
csparker247 Mar 25, 2026
1d0e3fc
feat(MeshIO): Phase 4 — read_mesh/write_mesh convenience facade (mesh…
csparker247 Mar 25, 2026
3788b89
chore: mark Phase 4 and track complete (mesh-io_20260323)
csparker247 Mar 25, 2026
dd97cb7
fix(MeshIO): Phase 5 — security, perf, arch, and test fixes (mesh-io_…
csparker247 Mar 25, 2026
6ced8c7
chore: mark Phase 5 tasks 5.1-5.5 complete (mesh-io_20260323)
csparker247 Mar 25, 2026
da1ac94
fix(charconv): per-type from/to_chars probes and independent fallbacks
csparker247 Mar 25, 2026
4c51c7c
fix(MeshIO): support all Color types in color_to_rgb and color_to_u8c3
Copilot Mar 26, 2026
658cdb5
fix(MeshIO_OBJ): remove duplicate 'using Vertex' declaration in write…
Copilot Mar 26, 2026
28f3ec3
fix(MeshIO): add stream error checks, bounds validation, case-insensi…
Copilot Mar 26, 2026
cba7923
style: restore section separators lost during edit
Copilot Mar 26, 2026
ca18909
Remove detail/MeshTraits
csparker247 Mar 26, 2026
b0b63d4
Rename split_into -> split and define new vector versions in terms of…
csparker247 Mar 26, 2026
1d12044
String: docs and tests
csparker247 Mar 26, 2026
149d3f0
OBJ: Review
csparker247 Apr 6, 2026
e52159b
Fix regressions
csparker247 Apr 6, 2026
1fa2b71
Handle TODOs
csparker247 Apr 6, 2026
b841b72
Merge branch 'develop' into feature/mesh-io_20260323
csparker247 Apr 6, 2026
f53d54f
obj_io: Error reporting. math: fix init list hack
csparker247 Apr 9, 2026
0c1b94a
Cleanup and comments
csparker247 Apr 9, 2026
f03e73a
refactor(ply): replace manual \\r-strip with trim_right_in_place (mes…
csparker247 Apr 21, 2026
087491a
chore: mark task 6.3 complete (mesh-io_20260323)
csparker247 Apr 21, 2026
fb757a6
perf(ply): replace per-property string comparison with role-indexed d…
csparker247 Apr 21, 2026
066da48
chore: mark task 5.6 complete (mesh-io_20260323)
csparker247 Apr 21, 2026
cb5ee97
perf(ply): batch-read full vertex record into stack buffer in binary …
csparker247 Apr 22, 2026
e1abe9a
chore: mark task 5.7 complete (mesh-io_20260323)
csparker247 Apr 22, 2026
6b9d7f5
refactor(ply): extract read_ply_face_binary/ascii helpers from read_p…
csparker247 Apr 22, 2026
a069c58
chore: mark task 5.8 complete (mesh-io_20260323)
csparker247 Apr 22, 2026
44bc4fa
chore: mark Phase 6 carry-forward tasks 5.7 and 5.8 complete (mesh-io…
csparker247 Apr 22, 2026
929c1e0
docs(ply,obj): add Doxygen comments to detail types and fix broken refs
csparker247 Apr 22, 2026
ab3e4fe
chore: mark Doxygen verification complete (mesh-io_20260323)
csparker247 Apr 22, 2026
a5ebb00
chore: mark track mesh-io_20260323 complete
csparker247 Apr 22, 2026
ed9dcde
docs: sync product, tech-stack, and README for Mesh IO track completion
csparker247 Apr 22, 2026
4cf40fc
claude: Harden
csparker247 Apr 23, 2026
7d6254f
Fix open review comments on MeshIO PR (#18)
Copilot Apr 23, 2026
3b00d82
Tweak README
csparker247 Apr 23, 2026
9047703
style(MeshIO): short-circuit false paths to reduce indentation
csparker247 Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ docs/html/

# CLion
cmake-build*/

.cache/

# Agents
.claude/settings.local.json
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

# Modules
include(FetchContent)
include(CheckCXXSourceCompiles)
include(CMakeDependentOption)

# Get Git hash
Expand All @@ -22,13 +21,16 @@ else()
endif()

# Extra compile definitions
include(CheckToNumericFP)
include(CheckCharconvFP)

# Build library
set(public_hdrs
include/educelab/core.hpp
include/educelab/core/Version.hpp
include/educelab/core/io/ImageIO.hpp
include/educelab/core/io/MeshIO.hpp
include/educelab/core/io/MeshIO_OBJ.hpp
include/educelab/core/io/MeshIO_PLY.hpp
include/educelab/core/types/Color.hpp
include/educelab/core/types/Image.hpp
include/educelab/core/types/Mat.hpp
Expand All @@ -38,6 +40,7 @@ set(public_hdrs
include/educelab/core/types/Uuid.hpp
include/educelab/core/types/Vec.hpp
include/educelab/core/utils/Caching.hpp
include/educelab/core/utils/MeshUtils.hpp
include/educelab/core/utils/Filesystem.hpp
include/educelab/core/utils/Iteration.hpp
include/educelab/core/utils/LinearAlgebra.hpp
Expand All @@ -62,6 +65,9 @@ target_include_directories(core
$<INSTALL_INTERFACE:include>
)
target_compile_features(core PUBLIC cxx_std_17)
if(EDUCE_CORE_CHARCONV_DEFS)
target_compile_definitions(core PUBLIC ${EDUCE_CORE_CHARCONV_DEFS})
endif()
set_target_properties(core
PROPERTIES
PUBLIC_HEADER "${public_hdrs}"
Expand Down
136 changes: 110 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ following files can be installed in this way:
- `types/UVMap.hpp`
- Requires:
- `types/Vec.hpp`
- `utils/MeshUtils.hpp`
- Requires:
- `types/Mesh.hpp`
- `types/UVMap.hpp`
- `io/MeshIO_OBJ.hpp`
- Requires:
- `types/Mesh.hpp`
- `types/UVMap.hpp`
- `utils/String.hpp`
- `io/MeshIO_PLY.hpp`
- Requires:
- `types/Mesh.hpp`
- `types/UVMap.hpp`
- `utils/String.hpp`
- `io/MeshIO.hpp`
- Convenience facade; includes `MeshIO_OBJ.hpp` and `MeshIO_PLY.hpp`


## Usage
Expand Down Expand Up @@ -199,6 +215,50 @@ std::cout << charted.get_coordinate(0, 0).chart << "\n"; // 1

See [examples/MeshExample.cpp](examples/MeshExample.cpp) for more usage examples.

### Mesh IO

Read and write OBJ and PLY files using the convenience facade:

```c++
#include "educelab/core/io/MeshIO.hpp"

Mesh<float, 3> mesh;

// Read — format detected from file extension (.obj or .ply)
read_mesh("model.obj", mesh);

// Write
write_mesh("output.ply", mesh);
```

Read and write with UV maps and texture paths:

```c++
#include "educelab/core/io/MeshIO.hpp"

Mesh<float, 3> mesh;
UVMap<float, 2> uvmap;
std::vector<std::filesystem::path> texture_paths;

// Read UV coords and texture path references
read_mesh("model.obj", mesh, uvmap, texture_paths);

// Write with a single texture path (OBJ emits .mtl; PLY emits comment TextureFile)
write_mesh("output.obj", mesh, uvmap, texture_paths[0]);
```

Vertex traits are detected at compile time — normals and colors are read/written
automatically when the vertex type includes them:

```c++
struct MyTraits : traits::WithNormal<float, 3>, traits::WithColor {};
using RichMesh = Mesh<float, 3, MyTraits>;

RichMesh mesh;
read_mesh("model.ply", mesh); // normals and colors populated if present in file
write_mesh("output.ply", mesh); // normals and colors included in output
```

### Image class

```c++
Expand Down Expand Up @@ -258,49 +318,73 @@ std::cout << trim_left(" left") << "\n"; // "left"
std::cout << trim_right("right ") << "\n"; // "right"
std::cout << trim(" center ") << "\n"; // "center"

// Conversion to numeric types
// Split on whitespace (any run treated as one delimiter)
auto tokens = split(" v 1.0\t2.0 3.0 "); // {"v", "1.0", "2.0", "3.0"}

// Split with a predicate (single-pass, no allocation)
auto parts = split("1/2/3", [](char c) { return c == '/'; }); // {"1", "2", "3"}

// Conversion from string to numeric types
std::cout << to_numeric<int>("3.14") << "\n"; // 3
std::cout << to_numeric<float>("3.14") << "\n"; // 3.14

// Conversion from numeric to string (locale-independent)
std::cout << to_string(3.14f) << "\n"; // "3.14"

// Buffer-reusing variant for hot paths (no heap allocation)
std::array<char, 128> buf;
file << to_string_view(buf, x) << ' ' << to_string_view(buf, y) << '\n';
```

See [examples/StringExample.cpp](examples/StringExample.cpp) for more usage
examples.å

#### A note on `to_numeric` compilation
The default `to_numeric` implementation relies upon `std::from_chars`.
However, many compilers do not provide implementations for this function for
floating point types. In these circumstances, you may fall back to a
`std::sto*`-based `to_numeric` implementation by adding the
`EDUCE_CORE_NEED_TO_NUMERIC_FP` compiler definition. When using this project
with CMake, this definition will automatically be added when you link against
the `educelab::core` target.

**(v0.2.1 and later)** If not linking against the target (i.e. when using the
library as header-only), you may alternatively check the result of the
`EDUCE_CORE_NEED_TO_NUMERIC_FP` CMake cache variable and set the definition
manually in your own project.
examples.

#### A note on `to_numeric` and `to_string[_view]` compilation

`to_numeric` uses `std::from_chars` for string-to-number conversion.
`to_string` and `to_string_view` use `std::to_chars` for number-to-string
conversion. Both were introduced in C++17 but floating-point support arrived
later and is gated on the runtime library version (e.g. macOS 13.3+).

CMake probes for both at configure time via `CheckCharconvFP.cmake` and reports
the results as compile definitions (only emitted when the corresponding
function+type combination is absent):

- **`from_chars` fallbacks** — `to_numeric` falls back to `std::stof` /
`std::stod` / `std::stold` for any type whose definition is set:
- `EDUCE_CORE_NEED_FROM_CHARS_FLOAT`
- `EDUCE_CORE_NEED_FROM_CHARS_DOUBLE`
- `EDUCE_CORE_NEED_FROM_CHARS_LONG_DOUBLE`
- **`to_chars` fallbacks** — `to_string` / `to_string_view` fall back to
`std::snprintf` for any type whose definition is set:
- `EDUCE_CORE_NEED_TO_CHARS_FLOAT`
- `EDUCE_CORE_NEED_TO_CHARS_DOUBLE`
- `EDUCE_CORE_NEED_TO_CHARS_LONG_DOUBLE`

These definitions are attached to the `educelab::core` target as `PUBLIC`
compile definitions, so they propagate automatically to any target that links
against it:

```cmake
# Import libcore
FetchContent_Declare(
libcore
GIT_REPOSITORY https://github.com/educelab/libcore.git
GIT_TAG v0.2.1
GIT_TAG v0.3.0
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(libcore)

# Add an executable which has access to the libcore headers
# Definitions are propagated automatically — no extra step needed.
add_executable(foo foo.cpp)
target_include_directories(foo
PUBLIC
$<BUILD_INTERFACE:${libcore_SOURCE_DIR}/include>
)
target_link_libraries(foo PRIVATE educelab::core)
```

# Conditionally add the to_numeric compiler definition
if(EDUCE_CORE_NEED_TO_NUMERIC_FP)
target_compile_definitions(foo PRIVATE EDUCE_CORE_NEED_TO_NUMERIC_FP)
endif()
If using the headers without linking against the CMake target, manually set
your required definitions:

```cmake
target_compile_definitions(foo PRIVATE EDUCE_CORE_NEED_FROM_CHARS_LONG_DOUBLE)
```

### Data caching
Expand Down
87 changes: 87 additions & 0 deletions cmake/CheckCharconvFP.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Check floating-point support in std::from_chars and std::to_chars,
# probed independently for float, double, and long double.
#
# Both were added in C++17 but compiler/platform support arrived at different
# times and varies per type. Apple Clang / libc++ added float/double support
# in macOS 13.3; long double availability differs further.
#
# For each (function, type) combination a separate compile-time probe is run.
# When unavailable, a per-type preprocessor definition is appended to the
# EDUCE_CORE_CHARCONV_DEFS list so the caller can attach it to the library
# target via target_compile_definitions.
#
# Definitions collected into EDUCE_CORE_CHARCONV_DEFS (only when the
# corresponding function+type is absent):
# EDUCE_CORE_NEED_FROM_CHARS_FLOAT
# EDUCE_CORE_NEED_FROM_CHARS_DOUBLE
# EDUCE_CORE_NEED_FROM_CHARS_LONG_DOUBLE
# EDUCE_CORE_NEED_TO_CHARS_FLOAT
# EDUCE_CORE_NEED_TO_CHARS_DOUBLE
# EDUCE_CORE_NEED_TO_CHARS_LONG_DOUBLE

include(CMakePushCheckState)
include(CheckCXXSourceCompiles)

cmake_push_check_state(RESET)

if(MSVC)
set(CMAKE_REQUIRED_FLAGS "/std:c++17")
else()
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
endif()

# ---------------------------------------------------------------------------
# Helper macro: probe one (function, type) combination.
#
# Usage:
# _charconv_probe(FROM_CHARS float "float result; std::from_chars(s,s+3,result);" NEED_FROM_CHARS_FLOAT)
# ---------------------------------------------------------------------------
macro(_charconv_probe _direction _typename _snippet _defname)
set(_probe_code "
#include <charconv>
#include <array>
int main() {
const char src[] = \"5.0\";
${_snippet}
return 0;
}
")
check_cxx_source_compiles("${_probe_code}" _CXX_CHARCONV_${_defname})
if(NOT _CXX_CHARCONV_${_defname})
list(APPEND EDUCE_CORE_CHARCONV_DEFS EDUCE_CORE_${_defname})
endif()
endmacro()

# --- from_chars ---------------------------------------------------------------
_charconv_probe(
"from_chars" "float"
"float r{}; std::from_chars(src, src+3, r);"
NEED_FROM_CHARS_FLOAT)

_charconv_probe(
"from_chars" "double"
"double r{}; std::from_chars(src, src+3, r);"
NEED_FROM_CHARS_DOUBLE)

_charconv_probe(
"from_chars" "long double"
"long double r{}; std::from_chars(src, src+3, r);"
NEED_FROM_CHARS_LONG_DOUBLE)

# --- to_chars -----------------------------------------------------------------
_charconv_probe(
"to_chars" "float"
"std::array<char,32> buf{}; float v{5.f}; std::to_chars(buf.data(), buf.data()+buf.size(), v);"
NEED_TO_CHARS_FLOAT)

_charconv_probe(
"to_chars" "double"
"std::array<char,32> buf{}; double v{5.0}; std::to_chars(buf.data(), buf.data()+buf.size(), v);"
NEED_TO_CHARS_DOUBLE)

_charconv_probe(
"to_chars" "long double"
"std::array<char,32> buf{}; long double v{5.0L}; std::to_chars(buf.data(), buf.data()+buf.size(), v);"
NEED_TO_CHARS_LONG_DOUBLE)

cmake_pop_check_state()
20 changes: 0 additions & 20 deletions cmake/CheckToNumericFP.cmake

This file was deleted.

3 changes: 2 additions & 1 deletion conductor/product.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ EduceLab developers building C++ applications and libraries.
## Key Goals

1. Provide a stable, reusable foundation for all EduceLab C++ projects
2. Offer well-tested, header-friendly types (Vec, Mat, Image, Mesh, etc.)
2. Offer well-tested, header-friendly types (Vec, Mat, Image, Mesh, UVMap, etc.)
3. Minimize external dependencies while maximizing utility
4. Provide standard Mesh IO (OBJ and PLY read/write) including UV maps and texture paths

## Current Version

Expand Down
5 changes: 5 additions & 0 deletions conductor/tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
| Mat | `types/Mat.hpp` |
| Image | `types/Image.hpp` |
| Mesh | `types/Mesh.hpp` |
| UVMap | `types/UVMap.hpp` |
| Color | `types/Color.hpp` |
| Signals | `types/Signals.hpp` |
| Uuid | `types/Uuid.hpp` |
Expand All @@ -36,7 +37,11 @@
| Filesystem | `utils/Filesystem.hpp` |
| Caching | `utils/Caching.hpp` |
| Iteration | `utils/Iteration.hpp` |
| MeshUtils | `utils/MeshUtils.hpp` |
| ImageIO | `io/ImageIO.hpp` |
| MeshIO | `io/MeshIO.hpp` |
| MeshIO_OBJ | `io/MeshIO_OBJ.hpp` |
| MeshIO_PLY | `io/MeshIO_PLY.hpp` |

## Distribution

Expand Down
2 changes: 1 addition & 1 deletion conductor/tracks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
| ------ | -------- | ----- | ------- | ------- |
| [x] | mesh-redesign_20260320 | Mesh Class Redesign | 2026-03-20 | 2026-03-23 |
| [x] | uv-map_20260323 | UVMap | 2026-03-23 | 2026-03-24 |
| [!] | mesh-io_20260323 | Mesh IO (blocked) | 2026-03-23 | 2026-03-23 |
| [x] | mesh-io_20260323 | Mesh IO | 2026-03-23 | 2026-04-21 |

<!-- Tracks registered by /conductor:new-track -->
13 changes: 6 additions & 7 deletions conductor/tracks/mesh-io_20260323/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
"id": "mesh-io_20260323",
"title": "Mesh IO",
"type": "feature",
"status": "blocked",
"status": "complete",
"created": "2026-03-23T00:00:00Z",
"updated": "2026-03-23T00:00:00Z",
"blockedBy": ["mesh-redesign_20260320", "uv-map_20260323"],
"updated": "2026-04-21T00:00:00Z",
"phases": {
"total": 3,
"completed": 0
"total": 6,
"completed": 6
},
"tasks": {
"total": 14,
"completed": 0
"total": 36,
"completed": 36
}
}
Loading
Loading