openscad-cpp-evaluator 1.49.8__tar.gz → 1.50.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/CLAUDE.md +37 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/CMakeLists.txt +2 -1
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/PKG-INFO +1 -1
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/pyproject.toml +1 -1
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/CMakeLists.txt +7 -0
- openscad_cpp_evaluator-1.50.0/src/builtins/alpha_wrap.cpp +593 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/builtins.hpp +5 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/function_builtins.cpp +1 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/modifiers.cpp +7 -1
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/registry.cpp +3 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/csg_resolve.cpp +4 -1
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/CMakeLists.txt +1 -0
- openscad_cpp_evaluator-1.50.0/tests/test_alpha_wrap.cpp +120 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_booleans.cpp +32 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_feature_detection.cpp +1 -1
- openscad_cpp_evaluator-1.50.0/third_party/predicates/predicates.c +4130 -0
- openscad_cpp_evaluator-1.50.0/third_party/predicates/predicates.h +22 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.github/workflows/wheels.yml +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.gitignore +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/.gitmodules +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/LICENSE +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/README.md +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/bindings/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/bindings/ast_to_py.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/bindings/module.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/cmake/embed_font.cmake +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/cmake/patch_manifold.cmake +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/eval_perf_benchmark.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/manifold_cache_reuse.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/examples/minimal_debugger.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/.github/workflows/ci.yml +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/.gitignore +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/CLAUDE.md +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/LICENSE +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/README.md +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/api.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/ast_node.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/declarations.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/expression.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/module_instantiation.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/scope_builder.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast/vector_element.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/ast.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/comments.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/position.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/pretty_print.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/scope_table.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/serialization.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/include/openscad_cpp_parser/source_map.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/.DS_Store +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/api.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/ast_node.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/declarations.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/expression.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/format_utils.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/module_instantiation.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/scope_builder.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/ast/vector_element.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/comment_attach_internal.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/comments.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/driver.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/driver.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/lexer.l +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/lexer_api.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/oscad_location.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/grammar/parser.y +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/inline_comment_attach.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/position.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/scope.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/serialization/json_io.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/serialization/yaml_io.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/src/source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_assignments.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_ast_generation.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_comments_and_files.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_concurrent_parse.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_const_borrow.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_control.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_expressions.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_functions.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_inline_comments.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_lexical.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_modules.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_node_str.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_pretty_print.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_profile_time.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_render_expression.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_scope.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_serialization.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_smoke.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_source_map.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_strict_commas.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_use_include.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tests/test_vectors.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/external/openscad_cpp_parser/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bound_args.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bytecode.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bytecode_compiler.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/bytecode_vm.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/call_args.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/colored_body.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/coverage.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/csg_node.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/css_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/debug_hooks.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/debug_repl.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/dispatch.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/dxf_svg_import.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/eval_context.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/eval_error.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/eval_use.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/evaluator.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/export.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/font_match.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/format_closure.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/freetype_font_provider.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/function_builtins.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/import_builtin.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/manifold_cache.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/mesh_check.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/mesh_import.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/osc_range.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/profile.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/reassign.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/scope_trail.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/segments.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/surface_load.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/text_metrics.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/utf8.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/value.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/include/openscad_cpp_evaluator/zip_stored.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/python/openscad_cpp_evaluator/__init__.py +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LICENSE +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-Bold.ttf +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-BoldItalic.ttf +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-Italic.ttf +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/resources/fonts/LiberationSans-Regular.ttf +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/booleans.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/call_args.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/color.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/control.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/dxf_dim.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/extrude.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/import.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/primitives_2d.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/primitives_3d.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/profile_time.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/roof.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/segments.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/surface.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/text.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/topology.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/builtins/transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/bytecode_vm.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/colored_body.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/csg_generate.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/css_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/debug/debug_repl.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/debug_profile.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/eval_error.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/eval_use.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/export.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/format_closure.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/dxf_import.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/mesh_import.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/scene_import.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/surface_load.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/import/svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/measure_geometry.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/stmt_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/text/font_match.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/text/freetype_font_provider.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/text/text_metrics.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/user_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/utf8.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/value.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/xkcd_colors.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/xkcd_colors.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/src/zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_bytecode_compiler.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_children_separate.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_cli.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_control_flow.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_coverage.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_csg_tree.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_debug_hooks.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_discretizer.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_dxf_svg_import.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_eval_context.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_export.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_expr_eval.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_extrude_roof.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_function_builtins.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_helpers.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_import_export.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_include_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_manifold_cache.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_mesh_check.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_multi_color_merge.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_openscad_parity.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_primitives.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_profile_time.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_profiling.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_python_bindings.py +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_render_expr.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_scope_trail.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_scoping.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_surface.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_tail_calls.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_text.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_transforms.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_undef_args.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_unexpected_args.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_use.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_value.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_viewport_params.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tests/test_zip_stored.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/CMakeLists.txt +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/cli_lib.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/cli_lib.hpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/tools/cli/main.cpp +0 -0
- {openscad_cpp_evaluator-1.49.8 → openscad_cpp_evaluator-1.50.0}/uv.lock +0 -0
|
@@ -1970,3 +1970,40 @@ is evaluated before the clock starts. Times under 1 ms print in microseconds (`f
|
|
|
1970
1970
|
after the body, which a tail step would not wait for.
|
|
1971
1971
|
- `PROFILE:` lines go straight to `echoFn_`, not through `emitWarning`, so the ManifoldCache never
|
|
1972
1972
|
stores or replays them (a replay would print a stale time as if it were current).
|
|
1973
|
+
|
|
1974
|
+
## `alpha_wrap()`
|
|
1975
|
+
|
|
1976
|
+
`alpha_wrap(alpha, offset) { ... }` shrink-wraps its children in a closed, manifold skin
|
|
1977
|
+
(BelfrySCAD #941; `supported_feature("alpha-wrap")` is 1). The skin follows any concavity a ball
|
|
1978
|
+
of radius `alpha` can enter and bridges any gap or hole narrower than that; every point of it lies
|
|
1979
|
+
`offset` outside the input. Defaults are CGAL's: alpha 1/20 of the bounding-box diagonal, offset
|
|
1980
|
+
alpha/30. All Normal 3D children are wrapped together into one body taking the first one's colour,
|
|
1981
|
+
**including an open `polyhedron()`'s raw triangles** (`ColoredBody::rawMesh`) -- wrapping geometry
|
|
1982
|
+
too broken for Manifold is half the point. 2D children and `%`/`#` bodies pass through (2D with a
|
|
1983
|
+
warning). A non-positive or non-numeric `alpha`/`offset` warns and leaves the children as they were.
|
|
1984
|
+
|
|
1985
|
+
### Implementation notes
|
|
1986
|
+
|
|
1987
|
+
- `src/builtins/alpha_wrap.cpp`, written **from the paper** (Portaneri et al., "Alpha Wrapping with
|
|
1988
|
+
an Offset", SIGGRAPH 2022) and CGAL's user manual -- **not** from CGAL's `Alpha_wrap_3` source,
|
|
1989
|
+
which is GPL and cannot be carried into this MIT project. Keep it that way: do not paste from CGAL.
|
|
1990
|
+
- Delaunay is our own Bowyer-Watson insertion over **Shewchuk's exact predicates**
|
|
1991
|
+
(`third_party/predicates/`, public domain, the original file minus its `random()` test helpers,
|
|
1992
|
+
which MSVC lacks). They are K&R C, so the project enables `C`; `predicates.c` is compiled with
|
|
1993
|
+
warnings off and must never see fast-math. `exactinit()` runs once (`std::call_once`).
|
|
1994
|
+
- Four far "super" vertices stand in for the infinite vertex; a cell using one is outside and never
|
|
1995
|
+
entered. The loose box's 8 corners are jittered by 1e-9 of the diagonal so no four are
|
|
1996
|
+
cospherical; Steiner points are not jittered, and a tie (`insphere == 0`) counts as no conflict.
|
|
1997
|
+
- Gates are ordered by the smallest empty sphere through the face (its centre on the dual Voronoi
|
|
1998
|
+
edge, nearest the face's circumcentre). Rule 1 finds where the Voronoi edge first meets the offset
|
|
1999
|
+
surface by **sphere tracing** -- the distance to the input is 1-Lipschitz, so stepping by
|
|
2000
|
+
`distance - offset` cannot overshoot. Rule 2 projects the circumcentre of a cell that overlaps the
|
|
2001
|
+
input (separating-axis tet/triangle test over a BVH) onto the offset surface.
|
|
2002
|
+
- The skin is built as `MeshGL64`: rounding Steiner points to float can fold the thin triangles a
|
|
2003
|
+
small offset produces. Input is read from each body's float `MeshGL`.
|
|
2004
|
+
- `checkCancel()` every 256 gates; a 5M-Steiner-point runaway guard warns and returns the children.
|
|
2005
|
+
- Measured: a 242-triangle bracket wraps in 5 ms at alpha 3 (362 faces) and 137 ms at alpha 0.5
|
|
2006
|
+
(11.9k faces); extruded text, 2,468 triangles, 0.7 s at alpha 0.5 and 4 s at alpha 0.2 (102k
|
|
2007
|
+
faces). Time follows OUTPUT size. Not done: CGAL's manifold post-pass for very thin features
|
|
2008
|
+
(no failure seen yet), and the same input scaled can differ by a near-tie or two (same shape,
|
|
2009
|
+
volume within 0.2%), which is why `DefaultsScaleWithTheModel` compares within tolerances.
|
|
@@ -11,7 +11,8 @@ if(NOT _oscad_pyproject MATCHES "\nversion = \"([0-9]+)\.([0-9]+)\.([0-9]+)\"")
|
|
|
11
11
|
endif()
|
|
12
12
|
set(OSCAD_EVAL_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# C for third_party/predicates (Shewchuk's robust predicates, used by alpha_wrap).
|
|
15
|
+
project(openscad_cpp_evaluator VERSION ${OSCAD_EVAL_VERSION} LANGUAGES C CXX)
|
|
15
16
|
|
|
16
17
|
# On Apple Silicon, warn (don't fail -- a universal or intentionally-x86_64
|
|
17
18
|
# build is legitimate) if the effective target architecture is x86_64 while
|
|
@@ -47,6 +47,8 @@ add_library(openscad_cpp_evaluator STATIC
|
|
|
47
47
|
builtins/function_builtins.cpp
|
|
48
48
|
builtins/import.cpp
|
|
49
49
|
builtins/topology.cpp
|
|
50
|
+
builtins/alpha_wrap.cpp
|
|
51
|
+
../third_party/predicates/predicates.c
|
|
50
52
|
builtins/extrude.cpp
|
|
51
53
|
builtins/roof.cpp
|
|
52
54
|
builtins/surface.cpp
|
|
@@ -73,6 +75,11 @@ target_compile_options(openscad_cpp_evaluator PRIVATE
|
|
|
73
75
|
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall;-Wextra>
|
|
74
76
|
)
|
|
75
77
|
|
|
78
|
+
# Shewchuk's predicates are 1996 K&R C, kept as published: quiet, and never
|
|
79
|
+
# with fast-math, which would break their exact arithmetic.
|
|
80
|
+
set_source_files_properties(../third_party/predicates/predicates.c PROPERTIES
|
|
81
|
+
COMPILE_OPTIONS "$<IF:$<C_COMPILER_ID:MSVC>,/w,-w>")
|
|
82
|
+
|
|
76
83
|
# -Wall -Wextra above is about OUR code, but two TUs pull a vendored header
|
|
77
84
|
# in wholesale and inherit its warnings: 27 from Boost.Polygon's voronoi
|
|
78
85
|
# headers and 9 from stb_image_write's implementation, against 5 that were
|
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
// alpha_wrap(alpha, offset) -- shrink-wrap the children in a closed,
|
|
2
|
+
// manifold skin. A BelfrySCAD extension (BelfrySCAD #941).
|
|
3
|
+
//
|
|
4
|
+
// The skin follows any concavity a ball of radius `alpha` can enter and
|
|
5
|
+
// bridges any gap or hole narrower than that, and every point of it lies
|
|
6
|
+
// `offset` outside the input. So it is also a robust rounded outward offset,
|
|
7
|
+
// and a way to wrap geometry too broken for Manifold to accept: an open
|
|
8
|
+
// polyhedron()'s raw triangles are wrapped like any other.
|
|
9
|
+
//
|
|
10
|
+
// Written from Portaneri, Rouxel-Labbe, Hemmer, Cohen-Steiner and Alliez,
|
|
11
|
+
// "Alpha Wrapping with an Offset" (ACM TOG 41(4), SIGGRAPH 2022) and CGAL's
|
|
12
|
+
// user manual for its Alpha_wrap_3 package -- NOT from CGAL's source, which
|
|
13
|
+
// is GPL and could not be carried into this MIT project. The predicates are
|
|
14
|
+
// Shewchuk's, which are public domain (third_party/predicates).
|
|
15
|
+
//
|
|
16
|
+
// The algorithm, as the paper gives it:
|
|
17
|
+
// - A 3D Delaunay triangulation of a loose box around the input. Its cells
|
|
18
|
+
// start "inside"; the region beyond the box is "outside".
|
|
19
|
+
// - A gate is a face between an outside cell and an inside one. It is
|
|
20
|
+
// traversable when the smallest empty sphere through it -- whose centre
|
|
21
|
+
// lies on the Voronoi edge dual to the face -- is wider than alpha.
|
|
22
|
+
// Gates are taken widest first.
|
|
23
|
+
// - Crossing a gate into an inside cell either inserts a Steiner point on
|
|
24
|
+
// the offset surface (rule 1: where the Voronoi edge first meets it;
|
|
25
|
+
// rule 2: when the cell overlaps the input, its circumcentre projected
|
|
26
|
+
// onto it), whose new cells are inside, or marks the cell outside.
|
|
27
|
+
// - When no gate is left, the faces between inside and outside are the skin.
|
|
28
|
+
//
|
|
29
|
+
// Four far "super" vertices stand in for the triangulation's infinite
|
|
30
|
+
// vertex: a cell using one is "infinite", always outside, never entered.
|
|
31
|
+
|
|
32
|
+
#include "builtins.hpp"
|
|
33
|
+
|
|
34
|
+
#include "openscad_cpp_evaluator/call_args.hpp"
|
|
35
|
+
#include "openscad_cpp_evaluator/evaluator.hpp"
|
|
36
|
+
|
|
37
|
+
#include "../../third_party/predicates/predicates.h"
|
|
38
|
+
|
|
39
|
+
#include <algorithm>
|
|
40
|
+
#include <array>
|
|
41
|
+
#include <cmath>
|
|
42
|
+
#include <cstring>
|
|
43
|
+
#include <mutex>
|
|
44
|
+
#include <queue>
|
|
45
|
+
#include <unordered_map>
|
|
46
|
+
#include <vector>
|
|
47
|
+
|
|
48
|
+
namespace oscadeval {
|
|
49
|
+
|
|
50
|
+
namespace {
|
|
51
|
+
|
|
52
|
+
// CGAL's suggested defaults: alpha 1/20 of the bounding-box diagonal, and
|
|
53
|
+
// the offset 1/30 of alpha -- small, so sharp features survive.
|
|
54
|
+
constexpr double kDefaultAlphaFraction = 1.0 / 20.0;
|
|
55
|
+
constexpr double kDefaultOffsetFraction = 1.0 / 30.0;
|
|
56
|
+
// A runaway guard, not a quality knob: a tiny alpha on a big model asks for
|
|
57
|
+
// tens of millions of points, which would exhaust memory long before ending.
|
|
58
|
+
constexpr size_t kMaxSteinerPoints = 5'000'000;
|
|
59
|
+
|
|
60
|
+
using V3 = std::array<double, 3>;
|
|
61
|
+
V3 operator+(V3 a, V3 b) { return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; }
|
|
62
|
+
V3 operator-(V3 a, V3 b) { return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; }
|
|
63
|
+
V3 operator*(V3 a, double s) { return {a[0] * s, a[1] * s, a[2] * s}; }
|
|
64
|
+
double dot(V3 a, V3 b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
|
|
65
|
+
V3 cross(V3 a, V3 b) { return {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]}; }
|
|
66
|
+
double len(V3 a) { return std::sqrt(dot(a, a)); }
|
|
67
|
+
|
|
68
|
+
struct Tri { V3 a, b, c; };
|
|
69
|
+
|
|
70
|
+
// Closest point on a triangle (Ericson, Real-Time Collision Detection 5.1.5).
|
|
71
|
+
V3 closestOnTri(V3 p, const Tri& t) {
|
|
72
|
+
V3 ab = t.b - t.a, ac = t.c - t.a, ap = p - t.a;
|
|
73
|
+
double d1 = dot(ab, ap), d2 = dot(ac, ap);
|
|
74
|
+
if (d1 <= 0 && d2 <= 0) return t.a;
|
|
75
|
+
V3 bp = p - t.b;
|
|
76
|
+
double d3 = dot(ab, bp), d4 = dot(ac, bp);
|
|
77
|
+
if (d3 >= 0 && d4 <= d3) return t.b;
|
|
78
|
+
double vc = d1 * d4 - d3 * d2;
|
|
79
|
+
if (vc <= 0 && d1 >= 0 && d3 <= 0) return t.a + ab * (d1 / (d1 - d3));
|
|
80
|
+
V3 cp = p - t.c;
|
|
81
|
+
double d5 = dot(ab, cp), d6 = dot(ac, cp);
|
|
82
|
+
if (d6 >= 0 && d5 <= d6) return t.c;
|
|
83
|
+
double vb = d5 * d2 - d1 * d6;
|
|
84
|
+
if (vb <= 0 && d2 >= 0 && d6 <= 0) return t.a + ac * (d2 / (d2 - d6));
|
|
85
|
+
double va = d3 * d6 - d5 * d4;
|
|
86
|
+
if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0)
|
|
87
|
+
return t.b + (t.c - t.b) * ((d4 - d3) / ((d4 - d3) + (d5 - d6)));
|
|
88
|
+
double den = 1.0 / (va + vb + vc);
|
|
89
|
+
return t.a + ab * (vb * den) + ac * (vc * den);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Separating-axis test: does the tetrahedron overlap the triangle?
|
|
93
|
+
bool tetTriOverlap(const V3 q[4], const Tri& t) {
|
|
94
|
+
const V3 tp[3] = {t.a, t.b, t.c};
|
|
95
|
+
auto separated = [&](V3 axis) {
|
|
96
|
+
if (dot(axis, axis) < 1e-30) return false;
|
|
97
|
+
double a0 = 1e300, a1 = -1e300, b0 = 1e300, b1 = -1e300;
|
|
98
|
+
for (int i = 0; i < 4; ++i) { double d = dot(axis, q[i]); a0 = std::min(a0, d); a1 = std::max(a1, d); }
|
|
99
|
+
for (int i = 0; i < 3; ++i) { double d = dot(axis, tp[i]); b0 = std::min(b0, d); b1 = std::max(b1, d); }
|
|
100
|
+
return a1 < b0 || b1 < a0;
|
|
101
|
+
};
|
|
102
|
+
static const int F[4][3] = {{1, 2, 3}, {0, 2, 3}, {0, 1, 3}, {0, 1, 2}};
|
|
103
|
+
for (const auto& f : F)
|
|
104
|
+
if (separated(cross(q[f[1]] - q[f[0]], q[f[2]] - q[f[0]]))) return false;
|
|
105
|
+
if (separated(cross(t.b - t.a, t.c - t.a))) return false;
|
|
106
|
+
static const int E[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}};
|
|
107
|
+
const V3 te[3] = {t.b - t.a, t.c - t.b, t.a - t.c};
|
|
108
|
+
for (const auto& e : E)
|
|
109
|
+
for (const auto& d : te)
|
|
110
|
+
if (separated(cross(q[e[1]] - q[e[0]], d))) return false;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Bounding-volume hierarchy over the input triangles: nearest point, and
|
|
115
|
+
// whether a tetrahedron overlaps any of them.
|
|
116
|
+
class Oracle {
|
|
117
|
+
public:
|
|
118
|
+
explicit Oracle(std::vector<Tri> t) : tris_(std::move(t)) { build(0, static_cast<int>(tris_.size())); }
|
|
119
|
+
|
|
120
|
+
V3 lo() const { return nodes_[0].lo; }
|
|
121
|
+
V3 hi() const { return nodes_[0].hi; }
|
|
122
|
+
|
|
123
|
+
V3 closest(V3 p) const {
|
|
124
|
+
double best = 1e300;
|
|
125
|
+
V3 bp{};
|
|
126
|
+
std::vector<int> st{0};
|
|
127
|
+
while (!st.empty()) {
|
|
128
|
+
const Node& n = nodes_[st.back()];
|
|
129
|
+
st.pop_back();
|
|
130
|
+
if (boxDist2(n, p) >= best) continue;
|
|
131
|
+
if (n.left < 0) {
|
|
132
|
+
for (int k = n.first; k < n.first + n.count; ++k) {
|
|
133
|
+
V3 c = closestOnTri(p, tris_[k]);
|
|
134
|
+
double d = dot(c - p, c - p);
|
|
135
|
+
if (d < best) { best = d; bp = c; }
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
st.push_back(n.left);
|
|
139
|
+
st.push_back(n.right);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return bp;
|
|
143
|
+
}
|
|
144
|
+
double dist(V3 p) const { return len(closest(p) - p); }
|
|
145
|
+
|
|
146
|
+
bool overlapsTet(const V3 q[4]) const {
|
|
147
|
+
V3 lo = q[0], hi = q[0];
|
|
148
|
+
for (int i = 1; i < 4; ++i)
|
|
149
|
+
for (int k = 0; k < 3; ++k) { lo[k] = std::min(lo[k], q[i][k]); hi[k] = std::max(hi[k], q[i][k]); }
|
|
150
|
+
std::vector<int> st{0};
|
|
151
|
+
while (!st.empty()) {
|
|
152
|
+
const Node& n = nodes_[st.back()];
|
|
153
|
+
st.pop_back();
|
|
154
|
+
bool apart = false;
|
|
155
|
+
for (int k = 0; k < 3; ++k)
|
|
156
|
+
if (n.hi[k] < lo[k] || hi[k] < n.lo[k]) apart = true;
|
|
157
|
+
if (apart) continue;
|
|
158
|
+
if (n.left < 0) {
|
|
159
|
+
for (int k = n.first; k < n.first + n.count; ++k)
|
|
160
|
+
if (tetTriOverlap(q, tris_[k])) return true;
|
|
161
|
+
} else {
|
|
162
|
+
st.push_back(n.left);
|
|
163
|
+
st.push_back(n.right);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private:
|
|
170
|
+
struct Node { V3 lo, hi; int left = -1, right = -1, first = 0, count = 0; };
|
|
171
|
+
std::vector<Tri> tris_;
|
|
172
|
+
std::vector<Node> nodes_;
|
|
173
|
+
|
|
174
|
+
int build(int first, int count) {
|
|
175
|
+
Node n;
|
|
176
|
+
n.lo = {1e300, 1e300, 1e300};
|
|
177
|
+
n.hi = {-1e300, -1e300, -1e300};
|
|
178
|
+
for (int i = first; i < first + count; ++i)
|
|
179
|
+
for (const V3& p : {tris_[i].a, tris_[i].b, tris_[i].c})
|
|
180
|
+
for (int k = 0; k < 3; ++k) { n.lo[k] = std::min(n.lo[k], p[k]); n.hi[k] = std::max(n.hi[k], p[k]); }
|
|
181
|
+
const int id = static_cast<int>(nodes_.size());
|
|
182
|
+
nodes_.push_back(n);
|
|
183
|
+
if (count <= 4) {
|
|
184
|
+
nodes_[id].first = first;
|
|
185
|
+
nodes_[id].count = count;
|
|
186
|
+
return id;
|
|
187
|
+
}
|
|
188
|
+
int axis = 0;
|
|
189
|
+
V3 ext = n.hi - n.lo;
|
|
190
|
+
if (ext[1] > ext[axis]) axis = 1;
|
|
191
|
+
if (ext[2] > ext[axis]) axis = 2;
|
|
192
|
+
const int mid = first + count / 2;
|
|
193
|
+
std::nth_element(tris_.begin() + first, tris_.begin() + mid, tris_.begin() + first + count,
|
|
194
|
+
[axis](const Tri& x, const Tri& y) {
|
|
195
|
+
return x.a[axis] + x.b[axis] + x.c[axis] < y.a[axis] + y.b[axis] + y.c[axis];
|
|
196
|
+
});
|
|
197
|
+
const int l = build(first, mid - first);
|
|
198
|
+
const int r = build(mid, first + count - mid);
|
|
199
|
+
nodes_[id].left = l;
|
|
200
|
+
nodes_[id].right = r;
|
|
201
|
+
return id;
|
|
202
|
+
}
|
|
203
|
+
static double boxDist2(const Node& n, V3 p) {
|
|
204
|
+
double d = 0;
|
|
205
|
+
for (int k = 0; k < 3; ++k) {
|
|
206
|
+
double e = std::max({n.lo[k] - p[k], 0.0, p[k] - n.hi[k]});
|
|
207
|
+
d += e * e;
|
|
208
|
+
}
|
|
209
|
+
return d;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
struct Cell {
|
|
214
|
+
int v[4];
|
|
215
|
+
int n[4]; // neighbour across the face opposite v[i]; -1: none
|
|
216
|
+
bool alive = true, outside = false, infinite = false;
|
|
217
|
+
unsigned gen = 0; // bumped when the slot is reused, so a stale gate is recognised
|
|
218
|
+
V3 cc{}; // circumcentre
|
|
219
|
+
double r2 = 0; // squared circumradius
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
class Wrapper {
|
|
223
|
+
public:
|
|
224
|
+
Wrapper(const Oracle& oracle, double alpha, double offset, Evaluator& ev)
|
|
225
|
+
: oracle_(oracle), alpha2_(alpha * alpha), offset_(offset), ev_(ev) {}
|
|
226
|
+
|
|
227
|
+
// False if the runaway guard stopped it; the skin is then incomplete.
|
|
228
|
+
bool run(V3 lo, V3 hi);
|
|
229
|
+
manifold::MeshGL64 skin() const;
|
|
230
|
+
size_t steinerCount() const { return steiner_; }
|
|
231
|
+
|
|
232
|
+
private:
|
|
233
|
+
struct Gate {
|
|
234
|
+
double prio; int cell, face; unsigned gen;
|
|
235
|
+
bool operator<(const Gate& o) const { return prio < o.prio; }
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const Oracle& oracle_;
|
|
239
|
+
double alpha2_, offset_;
|
|
240
|
+
Evaluator& ev_;
|
|
241
|
+
std::vector<V3> pts_;
|
|
242
|
+
std::vector<Cell> cells_;
|
|
243
|
+
std::vector<int> free_;
|
|
244
|
+
std::priority_queue<Gate> queue_;
|
|
245
|
+
int hint_ = 0;
|
|
246
|
+
size_t steiner_ = 0;
|
|
247
|
+
|
|
248
|
+
const double* P(int i) const { return pts_[i].data(); }
|
|
249
|
+
|
|
250
|
+
void setGeometry(Cell& c) {
|
|
251
|
+
c.infinite = c.v[0] < 4 || c.v[1] < 4 || c.v[2] < 4 || c.v[3] < 4;
|
|
252
|
+
V3 a = pts_[c.v[0]], b = pts_[c.v[1]] - a, d = pts_[c.v[2]] - a, e = pts_[c.v[3]] - a;
|
|
253
|
+
const double den = 2 * dot(b, cross(d, e));
|
|
254
|
+
V3 o = (cross(d, e) * dot(b, b) + cross(e, b) * dot(d, d) + cross(b, d) * dot(e, e)) * (1.0 / den);
|
|
255
|
+
c.cc = a + o;
|
|
256
|
+
c.r2 = dot(o, o);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
int newCell(const int v[4]) {
|
|
260
|
+
int id;
|
|
261
|
+
if (!free_.empty()) { id = free_.back(); free_.pop_back(); }
|
|
262
|
+
else { id = static_cast<int>(cells_.size()); cells_.emplace_back(); }
|
|
263
|
+
Cell& c = cells_[id];
|
|
264
|
+
const unsigned g = c.gen + 1;
|
|
265
|
+
c = Cell();
|
|
266
|
+
c.gen = g;
|
|
267
|
+
std::memcpy(c.v, v, sizeof c.v);
|
|
268
|
+
std::fill(std::begin(c.n), std::end(c.n), -1);
|
|
269
|
+
setGeometry(c);
|
|
270
|
+
return id;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// The cell containing p, by walking from the last insertion.
|
|
274
|
+
int locate(V3 p) const {
|
|
275
|
+
int t = hint_;
|
|
276
|
+
while (!cells_[t].alive) ++t;
|
|
277
|
+
for (size_t steps = 0; steps < cells_.size() + 16; ++steps) {
|
|
278
|
+
const Cell& c = cells_[t];
|
|
279
|
+
int next = -1;
|
|
280
|
+
for (int f = 0; f < 4 && next < 0; ++f) {
|
|
281
|
+
const double* q[4] = {P(c.v[0]), P(c.v[1]), P(c.v[2]), P(c.v[3])};
|
|
282
|
+
q[f] = p.data();
|
|
283
|
+
if (orient3d(q[0], q[1], q[2], q[3]) < 0 && c.n[f] >= 0) next = c.n[f];
|
|
284
|
+
}
|
|
285
|
+
if (next < 0) return t;
|
|
286
|
+
t = next;
|
|
287
|
+
}
|
|
288
|
+
return t;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Bowyer-Watson insertion of p; returns the cells made.
|
|
292
|
+
std::vector<int> insert(V3 p) {
|
|
293
|
+
const int c0 = locate(p);
|
|
294
|
+
const int pi = static_cast<int>(pts_.size());
|
|
295
|
+
pts_.push_back(p);
|
|
296
|
+
std::vector<int> cavity{c0};
|
|
297
|
+
std::vector<char> inCavity(cells_.size(), 0);
|
|
298
|
+
inCavity[c0] = 1;
|
|
299
|
+
for (size_t k = 0; k < cavity.size(); ++k) {
|
|
300
|
+
const Cell& c = cells_[cavity[k]];
|
|
301
|
+
for (int f = 0; f < 4; ++f) {
|
|
302
|
+
const int nb = c.n[f];
|
|
303
|
+
if (nb < 0 || inCavity[nb]) continue;
|
|
304
|
+
const Cell& N = cells_[nb];
|
|
305
|
+
if (insphere(P(N.v[0]), P(N.v[1]), P(N.v[2]), P(N.v[3]), p.data()) > 0) {
|
|
306
|
+
inCavity[nb] = 1;
|
|
307
|
+
cavity.push_back(nb);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
struct Boundary { int cell, face, outer; std::array<int, 4> v; };
|
|
312
|
+
std::vector<Boundary> bound;
|
|
313
|
+
for (int c : cavity)
|
|
314
|
+
for (int f = 0; f < 4; ++f) {
|
|
315
|
+
const int nb = cells_[c].n[f];
|
|
316
|
+
if (nb >= 0 && inCavity[nb]) continue;
|
|
317
|
+
Boundary b{c, f, nb, {}};
|
|
318
|
+
std::memcpy(b.v.data(), cells_[c].v, sizeof cells_[c].v);
|
|
319
|
+
b.v[f] = pi; // keeps the cell's orientation
|
|
320
|
+
bound.push_back(b);
|
|
321
|
+
}
|
|
322
|
+
for (int c : cavity) { cells_[c].alive = false; free_.push_back(c); }
|
|
323
|
+
|
|
324
|
+
std::vector<int> made;
|
|
325
|
+
std::unordered_map<uint64_t, std::pair<int, int>> open; // edge -> (cell, face)
|
|
326
|
+
for (const Boundary& b : bound) {
|
|
327
|
+
const int id = newCell(b.v.data());
|
|
328
|
+
made.push_back(id);
|
|
329
|
+
cells_[id].n[b.face] = b.outer;
|
|
330
|
+
if (b.outer >= 0) {
|
|
331
|
+
Cell& O = cells_[b.outer];
|
|
332
|
+
for (int f = 0; f < 4; ++f)
|
|
333
|
+
if (O.n[f] == b.cell && !std::count(b.v.begin(), b.v.end(), O.v[f])) { O.n[f] = id; break; }
|
|
334
|
+
}
|
|
335
|
+
for (int f = 0; f < 4; ++f) {
|
|
336
|
+
if (f == b.face) continue;
|
|
337
|
+
int a = -1, e = -1;
|
|
338
|
+
for (int i = 0; i < 4; ++i)
|
|
339
|
+
if (i != f && i != b.face) (a < 0 ? a : e) = cells_[id].v[i];
|
|
340
|
+
if (a > e) std::swap(a, e);
|
|
341
|
+
const uint64_t key = static_cast<uint64_t>(static_cast<uint32_t>(a)) << 32 | static_cast<uint32_t>(e);
|
|
342
|
+
auto it = open.find(key);
|
|
343
|
+
if (it == open.end()) {
|
|
344
|
+
open[key] = {id, f};
|
|
345
|
+
} else {
|
|
346
|
+
cells_[id].n[f] = it->second.first;
|
|
347
|
+
cells_[it->second.first].n[it->second.second] = id;
|
|
348
|
+
open.erase(it);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!made.empty()) hint_ = made[0];
|
|
353
|
+
return made;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Squared radius of the smallest empty sphere through face f of cell c.
|
|
357
|
+
double minEmptyR2(int c, int f) const {
|
|
358
|
+
const Cell& C = cells_[c];
|
|
359
|
+
int idx[3], k = 0;
|
|
360
|
+
for (int i = 0; i < 4; ++i)
|
|
361
|
+
if (i != f) idx[k++] = C.v[i];
|
|
362
|
+
V3 a = pts_[idx[0]], b = pts_[idx[1]] - a, d = pts_[idx[2]] - a;
|
|
363
|
+
V3 nrm = cross(b, d);
|
|
364
|
+
V3 o = (cross(nrm, b) * dot(d, d) + cross(d, nrm) * dot(b, b)) * (1.0 / (2 * dot(nrm, nrm)));
|
|
365
|
+
const V3 fc = a + o;
|
|
366
|
+
const double fr2 = dot(o, o);
|
|
367
|
+
const int nb = C.n[f];
|
|
368
|
+
if (nb < 0) return fr2;
|
|
369
|
+
V3 c1 = C.cc, dir = cells_[nb].cc - c1;
|
|
370
|
+
const double s = dot(fc - c1, dir) / std::max(dot(dir, dir), 1e-300);
|
|
371
|
+
if (s >= 0 && s <= 1) return fr2;
|
|
372
|
+
return s < 0 ? C.r2 : cells_[nb].r2;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
void pushGatesOf(int c) {
|
|
376
|
+
const Cell& C = cells_[c];
|
|
377
|
+
if (!C.outside) return;
|
|
378
|
+
for (int f = 0; f < 4; ++f) {
|
|
379
|
+
const int nb = C.n[f];
|
|
380
|
+
if (nb < 0 || cells_[nb].outside || cells_[nb].infinite) continue;
|
|
381
|
+
const double r2 = minEmptyR2(c, f);
|
|
382
|
+
if (r2 > alpha2_) queue_.push({r2, c, f, C.gen});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Rule 1: the first point from a towards b at the offset distance. The
|
|
387
|
+
// distance is 1-Lipschitz, so stepping by (distance - offset) cannot
|
|
388
|
+
// overshoot the surface.
|
|
389
|
+
bool firstOffsetHit(V3 a, V3 b, V3& out) const {
|
|
390
|
+
V3 d = b - a;
|
|
391
|
+
const double L = len(d);
|
|
392
|
+
if (L <= 0) return false;
|
|
393
|
+
d = d * (1.0 / L);
|
|
394
|
+
const double eps = offset_ * 1e-6;
|
|
395
|
+
double s = 0;
|
|
396
|
+
for (int it = 0; it < 100000 && s <= L; ++it) {
|
|
397
|
+
const V3 p = a + d * s;
|
|
398
|
+
const double g = oracle_.dist(p) - offset_;
|
|
399
|
+
if (g <= eps) {
|
|
400
|
+
if (s == 0) return false; // a is itself within the offset
|
|
401
|
+
out = p;
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
s += std::max(g, eps);
|
|
405
|
+
}
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
bool steinerPoint(int from, int into, V3& sp) const {
|
|
410
|
+
const Cell& N = cells_[into];
|
|
411
|
+
if (oracle_.dist(N.cc) < offset_ && firstOffsetHit(cells_[from].cc, N.cc, sp)) return true;
|
|
412
|
+
V3 q[4];
|
|
413
|
+
for (int i = 0; i < 4; ++i) q[i] = pts_[N.v[i]];
|
|
414
|
+
if (oracle_.overlapsTet(q)) {
|
|
415
|
+
const V3 c = oracle_.closest(N.cc), dir = N.cc - c;
|
|
416
|
+
const double l = len(dir);
|
|
417
|
+
if (l < 1e-12) return false;
|
|
418
|
+
sp = c + dir * (offset_ / l);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
bool Wrapper::run(V3 lo, V3 hi) {
|
|
426
|
+
const V3 ctr = (lo + hi) * 0.5;
|
|
427
|
+
const double R = len(hi - lo) * 1000;
|
|
428
|
+
pts_ = {ctr + V3{R, R, R}, ctr + V3{-R, -R, R}, ctr + V3{-R, R, -R}, ctr + V3{R, -R, -R}};
|
|
429
|
+
int v0[4] = {0, 1, 2, 3};
|
|
430
|
+
if (orient3d(P(0), P(1), P(2), P(3)) < 0) std::swap(v0[2], v0[3]);
|
|
431
|
+
newCell(v0);
|
|
432
|
+
// The loose box's corners, jittered a hair so no four are cospherical.
|
|
433
|
+
unsigned seed = 12345;
|
|
434
|
+
auto jitter = [&] { seed = seed * 1103515245u + 12345u; return ((seed >> 8) % 1000) * 1e-9; };
|
|
435
|
+
const double diag = len(hi - lo);
|
|
436
|
+
for (int i = 0; i < 8; ++i) {
|
|
437
|
+
V3 p{i & 1 ? hi[0] : lo[0], i & 2 ? hi[1] : lo[1], i & 4 ? hi[2] : lo[2]};
|
|
438
|
+
insert(p + V3{jitter(), jitter(), jitter()} * diag);
|
|
439
|
+
}
|
|
440
|
+
for (Cell& c : cells_)
|
|
441
|
+
if (c.alive) c.outside = c.infinite;
|
|
442
|
+
for (size_t c = 0; c < cells_.size(); ++c)
|
|
443
|
+
if (cells_[c].alive) pushGatesOf(static_cast<int>(c));
|
|
444
|
+
|
|
445
|
+
size_t gates = 0;
|
|
446
|
+
while (!queue_.empty()) {
|
|
447
|
+
if ((++gates & 0xFF) == 0) ev_.checkCancel();
|
|
448
|
+
const Gate g = queue_.top();
|
|
449
|
+
queue_.pop();
|
|
450
|
+
const Cell& C = cells_[g.cell];
|
|
451
|
+
if (!C.alive || C.gen != g.gen || !C.outside) continue;
|
|
452
|
+
const int into = C.n[g.face];
|
|
453
|
+
if (into < 0 || cells_[into].outside || cells_[into].infinite) continue;
|
|
454
|
+
V3 sp;
|
|
455
|
+
if (steinerPoint(g.cell, into, sp)) {
|
|
456
|
+
if (++steiner_ > kMaxSteinerPoints) return false;
|
|
457
|
+
const std::vector<int> made = insert(sp);
|
|
458
|
+
for (int c : made) cells_[c].outside = cells_[c].infinite; // new cells are inside
|
|
459
|
+
// New gates can only touch the new cells: theirs, and their
|
|
460
|
+
// outside neighbours' faces onto them.
|
|
461
|
+
for (int c : made) {
|
|
462
|
+
pushGatesOf(c);
|
|
463
|
+
for (int f = 0; f < 4; ++f) {
|
|
464
|
+
const int nb = cells_[c].n[f];
|
|
465
|
+
if (nb >= 0 && cells_[nb].outside) pushGatesOf(nb);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
cells_[into].outside = true;
|
|
470
|
+
pushGatesOf(into);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// MeshGL64: the Steiner points are placed in double precision, and rounding
|
|
477
|
+
// them to float could fold the thin triangles a small offset produces.
|
|
478
|
+
manifold::MeshGL64 Wrapper::skin() const {
|
|
479
|
+
manifold::MeshGL64 mesh;
|
|
480
|
+
mesh.numProp = 3;
|
|
481
|
+
std::unordered_map<int, uint64_t> remap;
|
|
482
|
+
auto vert = [&](int v) {
|
|
483
|
+
auto [it, fresh] = remap.try_emplace(v, static_cast<uint64_t>(remap.size()));
|
|
484
|
+
if (fresh)
|
|
485
|
+
for (int k = 0; k < 3; ++k) mesh.vertProperties.push_back(pts_[v][k]);
|
|
486
|
+
return it->second;
|
|
487
|
+
};
|
|
488
|
+
for (size_t c = 0; c < cells_.size(); ++c) {
|
|
489
|
+
const Cell& C = cells_[c];
|
|
490
|
+
if (!C.alive || C.outside) continue;
|
|
491
|
+
for (int f = 0; f < 4; ++f) {
|
|
492
|
+
const int nb = C.n[f];
|
|
493
|
+
if (nb >= 0 && !cells_[nb].outside) continue;
|
|
494
|
+
// Wound so the normal points away from v[f], out of the inside cell.
|
|
495
|
+
int r[3], k = 0;
|
|
496
|
+
for (int i = 0; i < 4; ++i)
|
|
497
|
+
if (i != f) r[k++] = C.v[i];
|
|
498
|
+
if (orient3d(P(r[0]), P(r[1]), P(r[2]), P(C.v[f])) < 0) std::swap(r[1], r[2]);
|
|
499
|
+
for (int v : r) mesh.triVerts.push_back(vert(v));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return mesh;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
void appendTriangles(const manifold::MeshGL& m, std::vector<Tri>& out) {
|
|
506
|
+
const size_t np = m.numProp;
|
|
507
|
+
auto at = [&](uint32_t v) {
|
|
508
|
+
return V3{m.vertProperties[v * np], m.vertProperties[v * np + 1], m.vertProperties[v * np + 2]};
|
|
509
|
+
};
|
|
510
|
+
for (size_t i = 0; i + 2 < m.triVerts.size(); i += 3)
|
|
511
|
+
out.push_back({at(m.triVerts[i]), at(m.triVerts[i + 1]), at(m.triVerts[i + 2])});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
} // namespace
|
|
515
|
+
|
|
516
|
+
CSGParams resolveAlphaWrap(Evaluator& ev, const oscad::ModularCall& node, EvalContext& ctx) {
|
|
517
|
+
auto [args, effCtx] = resolveCallArgs(ev, node.arguments, ctx);
|
|
518
|
+
CSGParams params;
|
|
519
|
+
params["alpha"] = getArg(args, 0, "alpha", Value{});
|
|
520
|
+
params["offset"] = getArg(args, 1, "offset", Value{});
|
|
521
|
+
EvalContext blockCtx = ev.blockScope(effCtx);
|
|
522
|
+
ev.evalChildren(node.children, blockCtx);
|
|
523
|
+
return params;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
std::vector<ColoredBody> generateAlphaWrap(Evaluator& ev, const CSGParams& params,
|
|
527
|
+
const std::vector<std::unique_ptr<CSGNode>>& children,
|
|
528
|
+
const oscad::ASTNode& node) {
|
|
529
|
+
const std::vector<ColoredBody> bodies = flattenCsgTree(children);
|
|
530
|
+
if (bodies.empty()) return {};
|
|
531
|
+
|
|
532
|
+
// Background/highlight geometry and 2D sections pass through untouched;
|
|
533
|
+
// every other solid, including an open polyhedron()'s raw triangles, is
|
|
534
|
+
// wrapped together.
|
|
535
|
+
std::vector<Tri> tris;
|
|
536
|
+
std::vector<ColoredBody> out;
|
|
537
|
+
std::optional<std::array<float, 4>> color;
|
|
538
|
+
bool colorTaken = false, sawSection = false;
|
|
539
|
+
for (const ColoredBody& b : bodies) {
|
|
540
|
+
if (b.role != BodyRole::Normal || (!b.body && !b.rawMesh)) {
|
|
541
|
+
sawSection = sawSection || (b.role == BodyRole::Normal && b.section);
|
|
542
|
+
out.push_back(b);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const size_t before = tris.size();
|
|
546
|
+
if (b.rawMesh) appendTriangles(*b.rawMesh, tris);
|
|
547
|
+
else if (!b.body->IsEmpty()) appendTriangles(b.body->GetMeshGL(), tris);
|
|
548
|
+
if (!colorTaken && tris.size() > before) { color = b.color; colorTaken = true; }
|
|
549
|
+
}
|
|
550
|
+
if (sawSection) ev.warn("alpha_wrap: 2D children are not wrapped", &node.position());
|
|
551
|
+
if (tris.empty()) return out;
|
|
552
|
+
|
|
553
|
+
Oracle oracle(std::move(tris));
|
|
554
|
+
const V3 d = oracle.hi() - oracle.lo();
|
|
555
|
+
const double diag = len(d);
|
|
556
|
+
|
|
557
|
+
auto number = [&](const char* name, double fallback) -> std::optional<double> {
|
|
558
|
+
const Value& v = params.at(name);
|
|
559
|
+
if (std::holds_alternative<std::monostate>(v)) return fallback;
|
|
560
|
+
if (std::holds_alternative<double>(v) && std::get<double>(v) > 0 && std::isfinite(std::get<double>(v)))
|
|
561
|
+
return std::get<double>(v);
|
|
562
|
+
ev.warn(std::string("alpha_wrap: ") + name + " must be a positive number", &node.position());
|
|
563
|
+
return std::nullopt;
|
|
564
|
+
};
|
|
565
|
+
const std::optional<double> alpha = number("alpha", kDefaultAlphaFraction * diag);
|
|
566
|
+
if (!alpha) return bodies;
|
|
567
|
+
const std::optional<double> offset = number("offset", kDefaultOffsetFraction * *alpha);
|
|
568
|
+
if (!offset) return bodies;
|
|
569
|
+
|
|
570
|
+
static std::once_flag predicatesReady;
|
|
571
|
+
std::call_once(predicatesReady, [] { exactinit(); });
|
|
572
|
+
|
|
573
|
+
// The box must leave room for the skin, which sits `offset` out, and for
|
|
574
|
+
// a ball of radius alpha to pass all round it.
|
|
575
|
+
const double margin = 2 * (*alpha + *offset);
|
|
576
|
+
Wrapper wrap(oracle, *alpha, *offset, ev);
|
|
577
|
+
if (!wrap.run(oracle.lo() - V3{margin, margin, margin}, oracle.hi() + V3{margin, margin, margin})) {
|
|
578
|
+
ev.warn("alpha_wrap: stopped after " + std::to_string(kMaxSteinerPoints) +
|
|
579
|
+
" points; alpha is too small for this model",
|
|
580
|
+
&node.position());
|
|
581
|
+
return bodies;
|
|
582
|
+
}
|
|
583
|
+
manifold::Manifold body(wrap.skin());
|
|
584
|
+
if (body.Status() != manifold::Manifold::Error::NoError) {
|
|
585
|
+
ev.warn("alpha_wrap: the skin is not manifold", &node.position());
|
|
586
|
+
}
|
|
587
|
+
ColoredBody cb = ev.tagGenerated(std::move(body), node, Value{});
|
|
588
|
+
cb.color = color;
|
|
589
|
+
out.insert(out.begin(), std::move(cb));
|
|
590
|
+
return out;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
} // namespace oscadeval
|