-
Notifications
You must be signed in to change notification settings - Fork 1
Np 1250 create emscripten binding #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
f8473f0
Implement Emscripten support with JavaScript bindings for `UvulaJS`
saumyaj3 1737a91
Update GitHub workflow naming: `conan-package.yml` → `package.yml`
saumyaj3 d3f77fe
Fix workflow path trigger in package.yml to include correct location
saumyaj3 77e4a0d
Refactor UvulaJS JavaScript bindings for TypeScript integration
saumyaj3 02cb395
Expand GitHub Actions path triggers in `package.yml` to include `Uvul…
saumyaj3 3288882
Refactor `UvulaJS` bindings to enhance TypeScript compatibility
saumyaj3 2422f59
Add Emscripten-specific threading configuration and refactor memory a…
saumyaj3 d46eaba
Simplify bindings
casperlamboo 8c483b0
Transpose camera matrix
casperlamboo 229a2da
Merge branch 'main' into NP-1250-create-emscripten-binding-II
saumyaj3 63a340d
Merge remote-tracking branch 'origin/NP-1250-create-emscripten-bindin…
casperlamboo 7081897
Merge pull request #6 from Ultimaker/NP-1250-create-emscripten-bindin…
casperlamboo 202d95c
Construct `Float32Array` to return uv coords
casperlamboo 2516aad
Move vertices in `Geometry` container
casperlamboo a315f3a
Merge pull request #8 from Ultimaker/NP-1250-create-emscripten-bindin…
casperlamboo 6332d8b
Fix js bindings so we can provide a `const Geometry&` in `project` fu…
casperlamboo cf3190e
Merge branch 'refs/heads/NP-1250-create-emscripten-binding-III' into …
casperlamboo 35aed92
Fix Python binding
wawanbreton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| add_executable(uvula_js UvulaJS.cpp) | ||
| target_link_options(uvula_js | ||
| PUBLIC | ||
| "SHELL:-s USE_ES6_IMPORT_META=1" | ||
| "SHELL:-s FORCE_FILESYSTEM=1" | ||
| "SHELL:-s EXPORT_NAME=uvula" | ||
| "SHELL:-s MODULARIZE=1" | ||
| "SHELL:-s EXPORT_ES6=1" | ||
| "SHELL:-s SINGLE_FILE=1" | ||
| "SHELL:-s ALLOW_MEMORY_GROWTH=1" | ||
| "SHELL:-s ERROR_ON_UNDEFINED_SYMBOLS=0" | ||
| "SHELL:--bind" | ||
| "SHELL:-l embind" | ||
| "SHELL: --emit-tsd uvula_js.d.ts" | ||
| ) | ||
| target_link_libraries(uvula_js PUBLIC libuvula) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,260 @@ | ||
| // (c) 2025, UltiMaker -- see LICENCE for details | ||
|
|
||
| #include <emscripten/bind.h> | ||
| #include <emscripten/val.h> | ||
| #include <vector> | ||
|
|
||
| #include "Face.h" | ||
| #include "Matrix44F.h" | ||
| #include "Point2F.h" | ||
| #include "Point3F.h" | ||
| #include "Vector3F.h" | ||
| #include "project.h" | ||
| #include "unwrap.h" | ||
|
|
||
| using namespace emscripten; | ||
|
|
||
| // TypeScript type aliases for better type safety | ||
| EMSCRIPTEN_DECLARE_VAL_TYPE(Float32Array); | ||
| EMSCRIPTEN_DECLARE_VAL_TYPE(Uint32Array); | ||
| EMSCRIPTEN_DECLARE_VAL_TYPE(Int32Array); | ||
| EMSCRIPTEN_DECLARE_VAL_TYPE(UInt32Array); | ||
| EMSCRIPTEN_DECLARE_VAL_TYPE(PolygonArray); | ||
|
|
||
| // Return type for unwrap function | ||
| struct UnwrapResult | ||
| { | ||
| Float32Array uvCoordinates = Float32Array{emscripten::val::array()}; | ||
| uint32_t textureWidth; | ||
| uint32_t textureHeight; | ||
| }; | ||
|
|
||
| struct Geometry | ||
| { | ||
| std::vector<Point3F> vertices; | ||
| std::vector<Face> indices; | ||
| std::vector<Point2F> uvs; | ||
| std::vector<FaceSigned> connectivity; | ||
|
|
||
| Geometry(const Float32Array& vertices_js, const Uint32Array& indices_js, const Float32Array& uvs_js, const Int32Array& connectivity_js) | ||
| { | ||
| // Convert vertices | ||
| auto vertices_length = vertices_js["length"].as<int>(); | ||
| vertices.reserve(vertices_length / 3); | ||
| for (int i = 0; i < vertices_length; i += 3) { | ||
| auto x = vertices_js[i].as<float>(); | ||
| auto y = vertices_js[i + 1].as<float>(); | ||
| auto z = vertices_js[i + 2].as<float>(); | ||
| vertices.emplace_back(x, y, z); | ||
| } | ||
|
|
||
| // Convert indices | ||
| auto indices_length = indices_js["length"].as<int>(); | ||
| indices.reserve(indices_length / 3); | ||
| for (int i = 0; i < indices_length; i += 3) | ||
| { | ||
| auto i1 = indices_js[i].as<uint32_t>(); | ||
| auto i2 = indices_js[i + 1].as<uint32_t>(); | ||
| auto i3 = indices_js[i + 2].as<uint32_t>(); | ||
| indices.push_back({i1, i2, i3}); | ||
| } | ||
|
|
||
| // Convert UVs | ||
| auto uvs_length = uvs_js["length"].as<int>(); | ||
| uvs.reserve(uvs_length / 2); | ||
| for (int i = 0; i < uvs_length; i += 2) { | ||
| auto u = uvs_js[i].as<float>(); | ||
| auto v = uvs_js[i + 1].as<float>(); | ||
| uvs.emplace_back(u, v); | ||
| } | ||
|
|
||
| // Convert connectivity | ||
| auto connectivity_length = connectivity_js["length"].as<int>(); | ||
| connectivity.reserve(connectivity_length / 3); | ||
| for (int i = 0; i < connectivity_length; i += 3) { | ||
| auto i1 = connectivity_js[i].as<int32_t>(); | ||
| auto i2 = connectivity_js[i + 1].as<int32_t>(); | ||
| auto i3 = connectivity_js[i + 2].as<int32_t>(); | ||
| connectivity.push_back({i1, i2, i3}); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| std::string get_uvula_info() | ||
| { | ||
| return { UVULA_VERSION }; | ||
| } | ||
|
|
||
| // Typed wrapper functions for better TypeScript generation | ||
| UnwrapResult unwrap(const Float32Array& vertices_js, const Uint32Array& indices_js) | ||
| { | ||
| // Convert JavaScript arrays to C++ vectors | ||
| std::vector<Point3F> vertex_points; | ||
| std::vector<Face> face_indices; | ||
|
|
||
| // Get array lengths | ||
| unsigned vertices_length = vertices_js["length"].as<unsigned>(); | ||
| unsigned indices_length = indices_js["length"].as<unsigned>(); | ||
|
|
||
| // Convert vertices (expecting flat array of [x1, y1, z1, x2, y2, z2, ...]) | ||
| vertex_points.reserve(vertices_length / 3); | ||
| for (unsigned i = 0; i < vertices_length; i += 3) { | ||
| float x = vertices_js[i].as<float>(); | ||
| float y = vertices_js[i + 1].as<float>(); | ||
| float z = vertices_js[i + 2].as<float>(); | ||
| vertex_points.emplace_back(x, y, z); | ||
| } | ||
|
|
||
| // Convert indices (expecting flat array of [i1, i2, i3, i4, i5, i6, ...]) | ||
| face_indices.reserve(indices_length / 3); | ||
| for (unsigned i = 0; i < indices_length; i += 3) | ||
| { | ||
| uint32_t i1 = indices_js[i].as<uint32_t>(); | ||
| uint32_t i2 = indices_js[i + 1].as<uint32_t>(); | ||
| uint32_t i3 = indices_js[i + 2].as<uint32_t>(); | ||
| face_indices.push_back({i1, i2, i3}); | ||
| } | ||
|
|
||
| // Prepare output | ||
| std::vector<Point2F> uv_coords(vertex_points.size(), {0.0f, 0.0f}); | ||
| uint32_t texture_width; | ||
| uint32_t texture_height; | ||
|
|
||
| // Perform unwrapping | ||
| bool success = smartUnwrap(vertex_points, face_indices, uv_coords, texture_width, texture_height); | ||
|
|
||
| if (!success) | ||
| { | ||
| throw std::runtime_error("Couldn't unwrap UVs!"); | ||
| } | ||
|
|
||
| // Convert result to structured return type | ||
| emscripten::val uv_array = emscripten::val::global("Float32Array").new_(uv_coords.size() * 2); | ||
| for (size_t i = 0; i < uv_coords.size(); ++i) | ||
| { | ||
| uv_array.set(i * 2, uv_coords[i].x); | ||
| uv_array.set(i * 2 + 1, uv_coords[i].y); | ||
| } | ||
|
|
||
| return UnwrapResult{ | ||
| .uvCoordinates = Float32Array{uv_array}, | ||
| .textureWidth = texture_width, | ||
| .textureHeight = texture_height | ||
| }; | ||
| } | ||
|
|
||
| PolygonArray project( | ||
| const Float32Array& stroke_polygon_js, | ||
| const Geometry& geometry, | ||
| uint32_t texture_width, | ||
| uint32_t texture_height, | ||
| const Float32Array& camera_projection_matrix_js, | ||
| bool is_camera_perspective, | ||
| uint32_t viewport_width, | ||
| uint32_t viewport_height, | ||
| const Vector3F& camera_normal, | ||
| uint32_t face_id | ||
| ) | ||
| { | ||
| std::vector<Point2F> stroke_points; | ||
| auto stroke_length = stroke_polygon_js["length"].as<int>(); | ||
| stroke_points.reserve(stroke_length / 2); | ||
| for (int i = 0; i < stroke_length; i += 2) { | ||
| auto x = stroke_polygon_js[i].as<float>(); | ||
| auto y = stroke_polygon_js[i + 1].as<float>(); | ||
| stroke_points.push_back({x, y}); | ||
| } | ||
|
|
||
| // Convert camera projection matrix (4x4 matrix as flat array) | ||
| float matrix_data[4][4]; | ||
| for (int i = 0; i < 16; ++i) { | ||
| matrix_data[i % 4][i / 4] = camera_projection_matrix_js[i].as<float>(); | ||
| } | ||
| Matrix44F projection_matrix(matrix_data); | ||
|
|
||
| // Call the projection function | ||
| std::vector<Polygon> result = doProject( | ||
| stroke_points, | ||
| geometry.vertices, | ||
| geometry.indices, | ||
| geometry.uvs, | ||
| geometry.connectivity, | ||
| texture_width, | ||
| texture_height, | ||
| projection_matrix, | ||
| is_camera_perspective, | ||
| viewport_width, | ||
| viewport_height, | ||
| camera_normal, | ||
| face_id | ||
| ); | ||
|
|
||
| // Convert result to structured return type | ||
| emscripten::val result_polygons = emscripten::val::array(); | ||
|
|
||
| for (size_t i = 0; i < result.size(); ++i) | ||
| { | ||
| emscripten::val polygon_array = emscripten::val::array(); | ||
| const auto& polygon = result[i]; | ||
| for (size_t j = 0; j < polygon.size(); ++j) | ||
| { | ||
| polygon_array.set(j, polygon[j]); | ||
| } | ||
|
|
||
| result_polygons.set(i, polygon_array); | ||
| } | ||
|
|
||
| return PolygonArray{ result_polygons }; | ||
| } | ||
|
|
||
| EMSCRIPTEN_BINDINGS(uvula) | ||
| { | ||
| // Register TypeScript-style typed arrays | ||
| emscripten::register_type<Float32Array>("Float32Array"); | ||
| emscripten::register_type<Uint32Array>("Uint32Array"); | ||
| emscripten::register_type<Int32Array>("Int32Array"); | ||
| emscripten::register_type<PolygonArray>("Point2F[][]"); | ||
|
|
||
| // Register structured return types | ||
| value_object<UnwrapResult>("UnwrapResult") | ||
| .field("uvCoordinates", &UnwrapResult::uvCoordinates) | ||
| .field("textureWidth", &UnwrapResult::textureWidth) | ||
| .field("textureHeight", &UnwrapResult::textureHeight); | ||
|
|
||
| // Main typed functions with proper TypeScript signatures | ||
| function("unwrap", &unwrap); | ||
| function("project", &project); | ||
|
|
||
| function("uvula_info", &get_uvula_info); | ||
|
|
||
| class_<Geometry>("Geometry") | ||
| .constructor<const Float32Array&, const Uint32Array&, const Float32Array&, const Int32Array&>(); | ||
|
|
||
| // Utility classes for direct access if needed | ||
| class_<Point2F>("Point2F") | ||
| .constructor<>() | ||
| .property("x", &Point2F::x) | ||
| .property("y", &Point2F::y); | ||
|
|
||
| class_<Point3F>("Point3F") | ||
| .constructor<float, float, float>() | ||
| .function("x", &Point3F::x) | ||
| .function("y", &Point3F::y) | ||
| .function("z", &Point3F::z); | ||
|
|
||
| class_<Vector3F>("Vector3F") | ||
| .constructor<float, float, float>() | ||
| .function("x", &Vector3F::x) | ||
| .function("y", &Vector3F::y) | ||
| .function("z", &Vector3F::z); | ||
|
|
||
| value_object<Face>("Face") | ||
| .field("i1", &Face::i1) | ||
| .field("i2", &Face::i2) | ||
| .field("i3", &Face::i3); | ||
|
|
||
| value_object<FaceSigned>("FaceSigned") | ||
| .field("i1", &FaceSigned::i1) | ||
| .field("i2", &FaceSigned::i2) | ||
| .field("i3", &FaceSigned::i3); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.