diff --git a/Cargo.lock b/Cargo.lock index a23863c93f..9dd1bc2b4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,15 +55,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -174,23 +165,6 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "arrayref" version = "0.3.9" @@ -232,7 +206,7 @@ dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand 0.9.2", + "rand", "raw-window-handle", "serde", "serde_repr", @@ -408,29 +382,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "av1-grain" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" -dependencies = [ - "arrayvec", -] - [[package]] name = "aws-lc-rs" version = "1.16.0" @@ -498,12 +449,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - [[package]] name = "bitflags" version = "1.3.2" @@ -519,12 +464,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "bitstream-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" - [[package]] name = "blending-nodes" version = "0.1.0" @@ -606,12 +545,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "built" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" - [[package]] name = "bumpalo" version = "3.19.0" @@ -822,16 +755,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.3" @@ -1148,8 +1071,8 @@ dependencies = [ "parley", "petgraph 0.7.1", "polycool", - "rand 0.9.2", - "rand_chacha 0.9.0", + "rand", + "rand_chacha", "rustc-hash 2.1.1", "serde", "serde_json", @@ -1208,7 +1131,7 @@ dependencies = [ "ciborium", "clap", "criterion-plot", - "itertools 0.13.0", + "itertools", "num-traits", "oorandom", "plotters", @@ -1227,7 +1150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", - "itertools 0.13.0", + "itertools", ] [[package]] @@ -1650,26 +1573,6 @@ dependencies = [ "log", ] -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1732,21 +1635,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "exr" -version = "1.73.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - [[package]] name = "fastnoise-lite" version = "1.1.1" @@ -2148,16 +2036,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "gif" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gimli" version = "0.31.1" @@ -2186,12 +2064,6 @@ dependencies = [ "serde", ] -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "glow" version = "0.16.0" @@ -2280,7 +2152,6 @@ dependencies = [ "iai-callgrind", "js-sys", "log", - "path-bool-nodes", "pretty_assertions", "raster-nodes", "rendering", @@ -2439,7 +2310,7 @@ dependencies = [ "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "open", - "rand 0.9.2", + "rand", "rfd", "ron", "serde", @@ -2971,28 +2842,11 @@ dependencies = [ "bytemuck", "byteorder-lite", "color_quant", - "exr", - "gif 0.13.3", - "image-webp", + "gif", "num-traits", - "png 0.17.16", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core 0.4.12", - "zune-jpeg 0.4.20", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", + "png", + "zune-core", + "zune-jpeg", ] [[package]] @@ -3001,12 +2855,6 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" -[[package]] -name = "imgref" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" - [[package]] name = "include_dir" version = "0.7.4" @@ -3078,17 +2926,6 @@ dependencies = [ "libc", ] -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "interpreted-executor" version = "0.1.0" @@ -3104,7 +2941,6 @@ dependencies = [ "iai-callgrind", "log", "once_cell", - "path-bool-nodes", "preprocessor", "serde", "wgpu-executor", @@ -3162,15 +2998,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -3242,12 +3069,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" - [[package]] name = "js-sys" version = "0.3.77" @@ -3323,12 +3144,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libbz2-rs-sys" version = "0.2.2" @@ -3341,16 +3156,6 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" -[[package]] -name = "libfuzzer-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" -dependencies = [ - "arbitrary", - "cc", -] - [[package]] name = "libloading" version = "0.8.8" @@ -3441,15 +3246,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -3520,7 +3316,7 @@ dependencies = [ "log", "math-parser", "node-macro", - "rand 0.9.2", + "rand", "vector-types", ] @@ -3546,16 +3342,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - [[package]] name = "memchr" version = "2.7.5" @@ -3601,12 +3387,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3642,7 +3422,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation 0.3.2", "once_cell", - "png 0.17.16", + "png", "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -3786,22 +3566,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - [[package]] name = "notify" version = "8.2.0" @@ -3835,16 +3599,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -3880,17 +3634,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -4279,40 +4022,18 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "path-bool" -version = "0.1.0" -dependencies = [ - "criterion", - "glam", - "glob", - "image", - "lyon_geom", - "regex", - "resvg", - "roots", - "rustc-hash 2.1.1", - "slotmap", - "smallvec", - "svg", -] - [[package]] name = "path-bool-nodes" version = "0.1.0" dependencies = [ "core-types", - "dyn-any", "glam", "graphic-types", "linesweeper", "log", "node-macro", - "serde", "smallvec", - "tsify", "vector-types", - "wasm-bindgen", ] [[package]] @@ -4566,19 +4287,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.11.0", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - [[package]] name = "polling" version = "3.10.0" @@ -4724,28 +4432,6 @@ name = "profiling" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] [[package]] name = "qrcodegen" @@ -4753,12 +4439,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quick-xml" version = "0.37.5" @@ -4807,7 +4487,7 @@ dependencies = [ "bytes", "getrandom 0.3.3", "lru-slab", - "rand 0.9.2", + "rand", "ring", "rustc-hash 2.1.1", "rustls", @@ -4848,35 +4528,14 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "rand_chacha", + "rand_core", ] [[package]] @@ -4886,16 +4545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", + "rand_core", ] [[package]] @@ -4930,8 +4580,8 @@ dependencies = [ "node-macro", "num-traits", "num_enum", - "rand 0.9.2", - "rand_chacha 0.9.0", + "rand", + "rand_chacha", "raster-nodes-shaders", "raster-types", "serde", @@ -4977,56 +4627,6 @@ dependencies = [ "wgpu", ] -[[package]] -name = "rav1e" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" -dependencies = [ - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools 0.12.1", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "once_cell", - "paste", - "profiling", - "rand 0.8.5", - "rand_chacha 0.3.1", - "simd_helpers", - "system-deps", - "thiserror 1.0.69", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.11.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - [[package]] name = "raw-string" version = "0.3.5" @@ -5229,23 +4829,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "resvg" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be183ad6a216aa96f33e4c8033b0988b8b3ea6fd2359d19af5bac4643fd8e81" -dependencies = [ - "gif 0.14.1", - "image-webp", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia 0.12.0", - "usvg", - "zune-jpeg 0.5.12", -] - [[package]] name = "rfd" version = "0.15.4" @@ -5270,15 +4853,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "rgb" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" -dependencies = [ - "bytemuck", -] - [[package]] name = "ring" version = "0.17.14" @@ -5307,12 +4881,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "roots" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082f11ffa03bbef6c2c6ea6bea1acafaade2fd9050ae0234ab44a2153742b058" - [[package]] name = "roxmltree" version = "0.20.0" @@ -5561,7 +5129,7 @@ dependencies = [ "log", "memmap2", "smithay-client-toolkit", - "tiny-skia 0.11.4", + "tiny-skia", ] [[package]] @@ -5807,15 +5375,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - [[package]] name = "simplecss" version = "0.2.2" @@ -6090,12 +5649,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "svg" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94afda9cd163c04f6bee8b4bf2501c91548deae308373c436f36aeff3cf3c4a3" - [[package]] name = "svg_fmt" version = "0.4.5" @@ -6186,19 +5739,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml 0.8.23", - "version-compare", -] - [[package]] name = "tar" version = "0.4.44" @@ -6210,12 +5750,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tempfile" version = "3.21.0" @@ -6327,17 +5861,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "time" version = "0.3.41" @@ -6383,21 +5906,6 @@ dependencies = [ "tiny-skia-path 0.11.4", ] -[[package]] -name = "tiny-skia" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png 0.18.1", - "tiny-skia-path 0.12.0", -] - [[package]] name = "tiny-skia-path" version = "0.11.4" @@ -6672,7 +6180,7 @@ dependencies = [ "glam", "graphic-types", "node-macro", - "rand 0.9.2", + "rand", "serde", "vector-types", ] @@ -6915,17 +6423,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "v_frame" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" @@ -6946,7 +6443,7 @@ dependencies = [ "log", "node-macro", "qrcodegen", - "rand 0.9.2", + "rand", "repeat-nodes", "rustc-hash 2.1.1", "serde", @@ -6990,7 +6487,7 @@ dependencies = [ "futures-intrusive", "log", "peniko", - "png 0.17.16", + "png", "skrifa 0.40.0", "static_assertions", "thiserror 2.0.18", @@ -7025,12 +6522,6 @@ dependencies = [ "vello_encoding", ] -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.5" @@ -8446,37 +7937,13 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" -[[package]] -name = "zune-core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - [[package]] name = "zune-jpeg" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" dependencies = [ - "zune-core 0.4.12", -] - -[[package]] -name = "zune-jpeg" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" -dependencies = [ - "zune-core 0.5.1", + "zune-core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9c58c7cd19..118f8d0208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ members = [ "editor", "frontend/wasm", "libraries/dyn-any", - "libraries/path-bool", "libraries/math-parser", "node-graph/libraries/*", "node-graph/nodes/*", @@ -33,7 +32,6 @@ default-members = [ "editor", "frontend/wasm", "libraries/dyn-any", - "libraries/path-bool", "libraries/math-parser", "node-graph/graph-craft", "node-graph/interpreted-executor", @@ -67,7 +65,6 @@ dyn-any = { path = "libraries/dyn-any", features = [ ] } preprocessor = { path = "node-graph/preprocessor" } math-parser = { path = "libraries/math-parser" } -path-bool = { path = "libraries/path-bool" } graphene-application-io = { path = "node-graph/libraries/application-io" } core-types = { path = "node-graph/libraries/core-types" } no-std-types = { path = "node-graph/libraries/no-std-types" } diff --git a/editor/src/messages/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/menu_bar/menu_bar_message_handler.rs index 6dae07fc0f..8d7adeb73a 100644 --- a/editor/src/messages/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/menu_bar/menu_bar_message_handler.rs @@ -3,7 +3,7 @@ use crate::messages::input_mapper::utility_types::macros::action_shortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType}; use crate::messages::prelude::*; -use graphene_std::path_bool::BooleanOperation; +use graphene_std::vector::misc::BooleanOperation; #[derive(Debug, Clone, Default, ExtractField)] pub struct MenuBarMessageHandler { diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index e9c8ba2f11..8cdb18e959 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -29,7 +29,7 @@ use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork}; use graphene_std::math::quad::Quad; -use graphene_std::path_bool::boolean_intersect; +use graphene_std::path_bool_nodes::boolean_intersect; use graphene_std::raster::BlendMode; use graphene_std::raster_types::Raster; use graphene_std::render_node::wgpu_available; diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index 8ebde695b5..2768b875ca 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -76,7 +76,7 @@ pub enum GraphOperationMessage { }, NewBooleanOperationLayer { id: NodeId, - operation: graphene_std::path_bool::BooleanOperation, + operation: graphene_std::vector::misc::BooleanOperation, parent: LayerNodeIdentifier, insert_index: usize, }, diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 16c70b119b..f74fb67c15 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -142,7 +142,7 @@ impl<'a> ModifyInputsContext<'a> { LayerNodeIdentifier::new(new_id, self.network_interface) } - pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) { + pub fn insert_boolean_data(&mut self, operation: graphene_std::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) { let boolean = resolve_network_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([ Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)), Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)), diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 06c64faa64..b7db96ec65 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -1783,7 +1783,7 @@ fn document_node_definitions() -> HashMap), 0), NodeInput::import(concrete!(vector::style::Fill), 1)], - implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(path_bool_nodes::boolean_operation::IDENTIFIER), call_argument: generic!(T), ..Default::default() }, @@ -1802,7 +1802,7 @@ fn document_node_definitions() -> HashMap { @@ -454,10 +455,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ ], }, // ================================ - // path-bool + // path bool // ================================ NodeReplacement { - node: graphene_std::path_bool::boolean_operation::IDENTIFIER, + node: graphene_std::path_bool_nodes::boolean_operation::IDENTIFIER, aliases: &["graphene_path_bool::BooleanOperationNode", "graphene_std::vector::BooleanOperationNode"], }, // ================================ diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 2ddff084e5..b8c6751565 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -23,11 +23,11 @@ use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage}; use glam::DMat2; use graph_craft::document::NodeId; -use graphene_std::path_bool::BooleanOperation; use graphene_std::renderer::Quad; use graphene_std::renderer::Rect; use graphene_std::subpath::Subpath; use graphene_std::transform::ReferencePoint; +use graphene_std::vector::misc::BooleanOperation; use std::fmt; #[derive(Default, ExtractField)] diff --git a/libraries/path-bool/.gitignore b/libraries/path-bool/.gitignore deleted file mode 100644 index 73bdcbf49f..0000000000 --- a/libraries/path-bool/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target/ -test-results/ diff --git a/libraries/path-bool/Cargo.toml b/libraries/path-bool/Cargo.toml deleted file mode 100644 index 4d8fd4daa6..0000000000 --- a/libraries/path-bool/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "path-bool" -version = "0.1.0" -rust-version = "1.85" -authors = ["Graphite Authors ", "Adam Platkevič"] -edition = "2024" -keywords = [ - "bezier", - "curve", - "boolean", - "path", - "geometry", - "computational geometry", - "vector graphics", - "2d", - "graphics", -] -categories = ["graphics", "mathematics"] -license = "MIT OR Apache-2.0" - -[features] -logging = ["parsing"] -parsing = [] -default = ["parsing"] - -[dependencies] -glam = "0.29.0" -regex = "1.10.6" -slotmap = "1.0.7" -lyon_geom = "1.0" -roots = "0.0.8" -rustc-hash = "2.0.0" -smallvec = "1.13.2" - -[dev-dependencies] -glob = "0.3" -svg = "0.18" -resvg = "0.47" -image = "0.25" - -# Required dependencies -criterion = { workspace = true } - -# Benchmarks -[[bench]] -name = "painted_dreams" -harness = false -[[bench]] -name = "path_segment_intersection" -harness = false diff --git a/libraries/path-bool/LICENSE-APACHE b/libraries/path-bool/LICENSE-APACHE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/libraries/path-bool/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/libraries/path-bool/LICENSE-MIT b/libraries/path-bool/LICENSE-MIT deleted file mode 100644 index 826be3fb67..0000000000 --- a/libraries/path-bool/LICENSE-MIT +++ /dev/null @@ -1,10 +0,0 @@ -MIT License - -Copyright (c) 2024 Adam Platkevič -Copyright (c) 2024 Graphite Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libraries/path-bool/NOTICE b/libraries/path-bool/NOTICE deleted file mode 100644 index 80e631b181..0000000000 --- a/libraries/path-bool/NOTICE +++ /dev/null @@ -1,15 +0,0 @@ -NOTICE - -Rust port and modifications are (c) 2024 Graphite Authors. - -This library is derived from software originally developed by Adam Platkevič which is licensed under the MIT License reproduced below: - -MIT License - -Copyright (c) 2024 Adam Platkevič - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libraries/path-bool/README.md b/libraries/path-bool/README.md deleted file mode 100644 index 0293bb1bc8..0000000000 --- a/libraries/path-bool/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Path Bool - -A Rust library for performing boolean operations on SVG paths. - -Path Bool is a port of [PathBool.js](https://github.com/r-flash/PathBool.js), providing low-level functionality for boolean operations on complex 2D paths. It handles paths with multiple subpaths, self-intersections, and different fill rules. - -## Features - -- Supports multiple boolean operations: Union, Intersection, Difference, Exclusion, Division, and Fracture. -- Handles both `NonZero` and `EvenOdd` fill rules. -- Works with paths containing lines, cubic Bézier curves, quadratic Bézier curves, and elliptical arcs. -- Provides utilities for parsing and generating SVG path data. - -## Installation - -Add this to your `Cargo.toml`: - -```toml -[dependencies] -path-bool = "0.1.0" -``` - -## Usage - -Here's a basic example of performing an intersection operation on two paths: - -```rust -use path_bool::{path_boolean, FillRule, PathBooleanOperation, path_from_path_data, path_to_path_data}; - -fn main() { - let path_a = path_from_path_data("M 10 10 L 50 10 L 30 40 Z").unwrap(); - let path_b = path_from_path_data("M 20 30 L 60 30 L 60 50 L 20 50 Z").unwrap(); - - let result = path_boolean( - &path_a, - FillRule::NonZero, - &path_b, - FillRule::NonZero, - PathBooleanOperation::Intersection - ).unwrap(); - - let result_data = path_to_path_data(&result[0], 0.001); - println!("Result: {}", result_data); -} -``` - -## Algorithm - -The boolean operations are implemented using a graph-based approach. After the parsing the input, self-intersecting cubic beziers curves are simplified. Then the intersection points between all edges are calculated. These are then turned into a graph representation where every intersection becomes a new vertex. We then apply edge contractions to remove vertices with a degree of 2 to compute the [graph minor](https://en.wikipedia.org/wiki/Graph_minor). At this stage, identical edges are deduplicated. Because we are ultimately interested in the faces of the graph to decide if they should be included in the final output, we then compute the dual graph in which the faces become vertices and vertices become the new faces. That dual structure is then used to determine which faces (dual vertices) should be included in the final output. - -## Development status - -This project is a port of PathBool.js which is still in early stages of development. Contributions, bug reports, and feedback are welcome. - -Future work includes: - -- Comprehensive test suite -- Performance optimizations -- Additional examples and documentation -- Support for path builder tool features - -## License and acknowledgements - -This library is a Rust port of [PathBool.js](https://github.com/r-flash/PathBool.js) by Adam Platkevič. - -It is dual-licensed under the MIT License or Apache-2.0 License. You may opt to comply with either license. - -Copyright © 2024 Adam Platkevič -Copyright © 2024 Graphite Authors diff --git a/libraries/path-bool/benches/painted_dreams.rs b/libraries/path-bool/benches/painted_dreams.rs deleted file mode 100644 index 3513fc6ec7..0000000000 --- a/libraries/path-bool/benches/painted_dreams.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::hint::black_box; - -use criterion::{Criterion, criterion_group, criterion_main}; -use path_bool::*; - -pub fn criterion_benchmark(c: &mut Criterion) { - let path_a = - path_from_path_data("M0,340C161.737914,383.575765 107.564182,490.730587 273,476 C419,463 481.741198,514.692273 481.333333,768 C481.333333,768 -0,768 -0,768 C-0,768 0,340 0,340 Z").unwrap(); - let path_b = path_from_path_data( - "M458.370270,572.165771C428.525848,486.720093 368.618805,467.485992 273,476 C107.564178,490.730591 161.737915,383.575775 0,340 C0,340 0,689 0,689 C56,700 106.513901,779.342590 188,694.666687 C306.607422,571.416260 372.033966,552.205139 458.370270,572.165771 Z", - ).unwrap(); - c.bench_function("painted_dreams_diff", |b| { - b.iter(|| path_boolean(black_box(&path_a), FillRule::NonZero, black_box(&path_b), FillRule::NonZero, PathBooleanOperation::Difference)) - }); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/libraries/path-bool/benches/path_segment_intersection.rs b/libraries/path-bool/benches/path_segment_intersection.rs deleted file mode 100644 index 25c422bf10..0000000000 --- a/libraries/path-bool/benches/path_segment_intersection.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::hint::black_box; - -use criterion::{Criterion, criterion_group, criterion_main}; -use glam::DVec2; -use path_bool::*; - -pub fn criterion_benchmark(crit: &mut Criterion) { - crit.bench_function("intersect 1", |bench| bench.iter(|| path_segment_intersection(black_box(&a()), black_box(&b()), true, &EPS))); - crit.bench_function("intersect 2", |bench| bench.iter(|| path_segment_intersection(black_box(&c()), black_box(&d()), true, &EPS))); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); - -fn a() -> PathSegment { - PathSegment::Cubic( - DVec2::new(458.37027, 572.165771), - DVec2::new(428.525848, 486.720093), - DVec2::new(368.618805, 467.485992), - DVec2::new(273., 476.), - ) -} -fn b() -> PathSegment { - PathSegment::Cubic(DVec2::new(273., 476.), DVec2::new(419., 463.), DVec2::new(481.741198, 514.692273), DVec2::new(481.333333, 768.)) -} -fn c() -> PathSegment { - PathSegment::Cubic(DVec2::new(273., 476.), DVec2::new(107.564178, 490.730591), DVec2::new(161.737915, 383.575775), DVec2::new(0., 340.)) -} -fn d() -> PathSegment { - PathSegment::Cubic(DVec2::new(0., 340.), DVec2::new(161.737914, 383.575765), DVec2::new(107.564182, 490.730587), DVec2::new(273., 476.)) -} diff --git a/libraries/path-bool/src/lib.rs b/libraries/path-bool/src/lib.rs deleted file mode 100644 index e80a57fa49..0000000000 --- a/libraries/path-bool/src/lib.rs +++ /dev/null @@ -1,333 +0,0 @@ -#![expect(clippy::needless_doctest_main)] -#![doc = include_str!("../README.md")] -mod path_boolean; -// #[cfg(feature = "parsing")] -mod parsing { - pub(crate) mod path_command; - pub(crate) mod path_data; -} -mod util { - pub(crate) mod aabb; - pub(crate) mod epsilons; - pub(crate) mod grid; - pub(crate) mod math; -} -mod path; -#[cfg(test)] -mod visual_tests; - -#[cfg(feature = "parsing")] -pub(crate) use parsing::*; -pub(crate) use path::*; -pub(crate) use util::*; - -pub use intersection_path_segment::path_segment_intersection; -#[cfg(feature = "parsing")] -pub use parsing::path_data::{path_from_path_data, path_to_path_data}; -pub use path_boolean::{BooleanError, EPS, FillRule, PathBooleanOperation, path_boolean}; -pub use path_segment::PathSegment; - -#[cfg(test)] -mod test { - use crate::path_boolean::{self, FillRule, PathBooleanOperation}; - use crate::path_data::{path_from_path_data, path_to_path_data}; - use path_boolean::path_boolean; - - #[test] - fn square() { - let a = path_from_path_data("M 10 10 L 50 10 L 30 40 Z").unwrap(); - let b = path_from_path_data("M 20 30 L 60 30 L 60 50 L 20 50 Z").unwrap(); - let union = path_boolean( - &a, - path_boolean::FillRule::NonZero, - &b, - path_boolean::FillRule::NonZero, - path_boolean::PathBooleanOperation::Intersection, - ) - .unwrap(); - dbg!(path_to_path_data(&union[0], 0.001)); - assert!(!union[0].is_empty()); - } - - #[test] - fn nesting_01() { - let a = path_from_path_data("M 47,24 A 23,23 0 0 1 24,47 23,23 0 0 1 1,24 23,23 0 0 1 24,1 23,23 0 0 1 47,24 Z").unwrap(); - let b = path_from_path_data( - "M 37.909023,24 A 13.909023,13.909023 0 0 1 24,37.909023 13.909023,13.909023 0 0 1 10.090978,24 13.909023,13.909023 0 0 1 24,10.090978 13.909023,13.909023 0 0 1 37.909023,24 Z", - ) - .unwrap(); - let union = path_boolean(&a, path_boolean::FillRule::NonZero, &b, path_boolean::FillRule::NonZero, path_boolean::PathBooleanOperation::Union).unwrap(); - dbg!(path_to_path_data(&union[0], 0.001)); - assert!(!union[0].is_empty()); - } - #[test] - fn semi_circle_join() { - let a = path_from_path_data( - "M 0.000000000000,0.000000000000 C 0.000000000000,0.000000000000 48.487723000000,-68.116546000000 51.950617000000,-96.987654000000 C 53.354187000000,-108.689605000000 5.171145000000,-241.394513000000 -0.000000000000,-254.901235000000 Z M 0.000000000000,0.000000000000 L -0.000000000000,-254.901235000000 Z" - ).unwrap(); - let b = path_from_path_data( - "M -0.000000000000,0.484328000000 C -0.000000000000,0.484328000000 -48.487723000000,-67.632218000000 -51.950617000000,-96.503326000000 C -53.354187000000,-108.205277000000 -5.171145000000,-240.910185000000 -0.000000000000,-254.416907000000 Z M -0.000000000000,0.484328000000 L -0.000000000000,-254.416907000000 Z", - ) - .unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn nesting_02() { - let a = path_from_path_data("M 0.99999994,31.334457 C 122.61195,71.81859 -79.025816,-5.5803326 47,32.253367 V 46.999996 H 0.99999994 Z").unwrap(); - let b = path_from_path_data("m 25.797222,29.08718 c 0,1.292706 -1.047946,2.340652 -2.340652,2.340652 -1.292707,0 -2.340652,-1.047946 -2.340652,-2.340652 0,-1.292707 1.047945,-2.340652 2.340652,-2.340652 1.292706,0 2.340652,1.047945 2.340652,2.340652 z M 7.5851073,28.332212 c 1e-7,1.292706 -1.0479456,2.340652 -2.3406521,2.340652 -1.2927063,-1e-6 -2.3406518,-1.047946 -2.3406517,-2.340652 -10e-8,-1.292707 1.0479454,-2.340652 2.3406517,-2.340652 1.2927065,-1e-6 2.3406522,1.047945 2.3406521,2.340652 z").unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn nesting_03() { - let a = path_from_path_data("m 21.829117,3.5444345 h 4.341766 V 16.502158 H 21.829117 Z M 47,24 A 23,23 0 0 1 24,47 23,23 0 0 1 1,24 23,23 0 0 1 24,1 23,23 0 0 1 47,24 Z").unwrap(); - let b = path_from_path_data("M 24 6.4960938 A 17.504802 17.504802 0 0 0 6.4960938 24 A 17.504802 17.504802 0 0 0 24 41.503906 A 17.504802 17.504802 0 0 0 41.503906 24 A 17.504802 17.504802 0 0 0 24 6.4960938 z M 24 12.193359 A 11.805881 11.805881 0 0 1 35.806641 24 A 11.805881 11.805881 0 0 1 24 35.806641 A 11.805881 11.805881 0 0 1 12.193359 24 A 11.805881 11.805881 0 0 1 24 12.193359 z ").unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - // Add more specific assertions about the resulting path if needed - let path_string = dbg!(path_to_path_data(&result[0], 0.001)); - assert_eq!(path_string.chars().filter(|c| c == &'M').count(), 1, "More than one path returned"); - assert!(!result[0].is_empty()); - } - #[test] - fn simple_07() { - let a = path_from_path_data("M 37.671452,24 C 52.46888,31.142429 42.887716,37.358779 24,37.671452 16.4505,37.796429 10.328548,31.550534 10.328548,24 c 0,-7.550534 6.120918,-13.671452 13.671452,-13.671452 7.550534,0 6.871598,10.389295 13.671452,13.671452 z", - ).unwrap(); - let b = path_from_path_data("M 37.671452,24 C 33.698699,53.634887 29.50935,49.018306 24,37.671452 20.7021,30.879219 10.328548,31.550534 10.328548,24 c 0,-7.550534 6.120918,-13.671452 13.671452,-13.671452 7.550534,0 14.674677,6.187863 13.671452,13.671452 z").unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - // Add more specific assertions about the resulting path if needed - dbg!(path_to_path_data(&result[0], 0.001)); - assert!(!result[0].is_empty()); - } - #[test] - fn rect_ellipse() { - let a = path_from_path_data("M0,0C0,0 100,0 100,0 C100,0 100,100 100,100 C100,100 0,100 0,100 C0,100 0,0 0,0 Z").unwrap(); - let b = path_from_path_data("M50,0C77.589239,0 100,22.410761 100,50 C100,77.589239 77.589239,100 50,100 C22.410761,100 0,77.589239 0,50 C0,22.410761 22.410761,0 50,0 Z").unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - assert!(!result[0].is_empty()); - // Add more specific assertions about the resulting path if needed - } - #[test] - fn red_dress_loop() { - let a = path_from_path_data("M969.000000,0.000000C969.000000,0.000000 1110.066898,76.934393 1085.000000,181.000000 C1052.000000,318.000000 1199.180581,334.301571 1277.000000,319.000000 C1455.000000,284.000000 1586.999985,81.000000 1418.000000,0.000000 C1418.000000,0.000000 969.000000,0.000000 969.000000,0.000000").unwrap(); - let b = path_from_path_data( - "M1211.000000,0.000000C1211.000000,0.000000 1255.000000,78.000000 1536.000000,95.000000 C1536.000000,95.000000 1536.000000,0.000000 1536.000000,0.000000 C1536.000000,0.000000 1211.000000,0.000000 1211.000000,0.000000 Z", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Intersection).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_1() { - let a = path_from_path_data("M969.000000,0.000000C969.000000,0.000000 1110.066898,76.934393 1085.000000,181.000000 C1052.000000,318.000000 1199.180581,334.301571 1277.000000,319.000000 C1455.000000,284.000000 1586.999985,81.000000 1418.000000,0.000000 C1418.000000,0.000000 969.000000,0.000000 969.000000,0.000000 Z").unwrap(); - let b = path_from_path_data( - "M763.000000,0.000000C763.000000,0.000000 1536.000000,0.000000 1536.000000,0.000000 C1536.000000,0.000000 1536.000000,254.000000 1536.000000,254.000000 C1536.000000,254.000000 1462.000000,93.000000 1271.000000,199.000000 C1149.163056,266.616314 976.413656,188.510842 908.000000,134.000000 C839.586344,79.489158 763.000000,0.000000 763.000000,0.000000 Z", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Intersection).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_2() { - let a = path_from_path_data("M0,340C161.737914,383.575765 107.564182,490.730587 273,476 C419,463 481.741198,514.692273 481.333333,768 C481.333333,768 -0,768 -0,768 C-0,768 0,340 0,340 Z ") - .unwrap(); - let b = path_from_path_data( - "M458.370270,572.165771C428.525848,486.720093 368.618805,467.485992 273,476 C107.564178,490.730591 161.737915,383.575775 0,340 C0,340 0,689 0,689 C56,700 106.513901,779.342590 188,694.666687 C306.607422,571.416260 372.033966,552.205139 458.370270,572.165771 Z", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_3() { - let a = path_from_path_data("M889,0C889,0 889,21 898,46 C909.595887,78.210796 872.365858,104.085306 869,147 C865,198 915,237 933,273 C951,309 951.703704,335.407407 923,349 C898.996281,360.366922 881,367 902,394 C923,421 928.592593,431.407407 898,468 C912.888889,472.888889 929.333333,513.333333 896,523 C896,523 876,533.333333 886,572 C896.458810,612.440732 873.333333,657.777778 802.666667,656.444444 C738.670245,655.236965 689,643 655,636 C621,629 604,623 585,666 C566,709 564,768 564,768 C564,768 0,768 0,768 C0,768 0,0 0,0 C0,0 889,0 889,0 Z ").unwrap(); - let b = path_from_path_data( - "M552,768C552,768 993,768 993,768 C993,768 1068.918039,682.462471 1093,600 C1126,487 1007.352460,357.386071 957,324 C906.647540,290.613929 842,253 740,298 C638,343 491.342038,421.999263 491.342038,506.753005 C491.342038,641.999411 552,768 552,768 Z ", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Difference).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_4() { - let a = path_from_path_data("M458.370270,572.165771C372.033966,552.205139 306.607422,571.416260 188.000000,694.666687 C106.513901,779.342590 56.000000,700.000000 0.000000,689.000000 C0.000000,689.000000 0.000000,768.000000 0.000000,768.000000 C0.000000,768.000000 481.333344,768.000000 481.333344,768.000000 C481.474091,680.589417 474.095154,617.186768 458.370270,572.165771 Z ").unwrap(); - let b = path_from_path_data( - "M364.000000,768.000000C272.000000,686.000000 294.333333,468.666667 173.333333,506.666667 C110.156241,526.507407 0.000000,608.000000 0.000000,608.000000 L -0.000000,768.000000 L 364.000000,768.000000 Z", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Difference).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_5() { - let a = path_from_path_data("M889.000000,0.000000C889.000000,0.000000 889.000000,21.000000 898.000000,46.000000 C909.595887,78.210796 872.365858,104.085306 869.000000,147.000000 C865.000000,198.000000 915.000000,237.000000 933.000000,273.000000 C951.000000,309.000000 951.703704,335.407407 923.000000,349.000000 C898.996281,360.366922 881.000000,367.000000 902.000000,394.000000 C923.000000,421.000000 928.592593,431.407407 898.000000,468.000000 C912.888889,472.888889 929.333333,513.333333 896.000000,523.000000 C896.000000,523.000000 876.000000,533.333333 886.000000,572.000000 C896.458810,612.440732 873.333333,657.777778 802.666667,656.444444 C738.670245,655.236965 689.000000,643.000000 655.000000,636.000000 C621.000000,629.000000 604.000000,623.000000 585.000000,666.000000 C566.000000,709.000000 564.000000,768.000000 564.000000,768.000000 C564.000000,768.000000 0.000000,768.000000 0.000000,768.000000 C0.000000,768.000000 0.000000,0.000000 0.000000,0.000000 C0.000000,0.000000 889.000000,0.000000 889.000000,0.000000 Z" - ).unwrap(); - let b = path_from_path_data( - "M891.555556,569.382716C891.555556,569.382716 883.555556,577.777778 879.111111,595.851852 C874.666667,613.925926 857.185185,631.407407 830.814815,633.777778 C804.444444,636.148148 765.629630,637.925926 708.148148,616.296296 C650.666667,594.666667 560.666667,568.000000 468.000000,487.333333 C375.333333,406.666667 283.333333,354.666667 283.333333,354.666667 C332.000000,330.666667 373.407788,298.323579 468.479950,219.785706 C495.739209,197.267187 505.084065,165.580817 514.452332,146.721008 C525.711584,124.054345 577.519713,94.951389 589.958848,64.658436 C601.152263,37.399177 601.175694,0.000010 601.175694,0.000000 C601.175694,0.000000 0.000000,0.000000 0.000000,0.000000 C0.000000,0.000000 0.000000,768.000000 0.000000,768.000000 C0.000000,768.000000 891.555556,768.000000 891.555556,768.000000 C891.555556,768.000000 891.555556,569.382716 891.555556,569.382716 Z", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Intersection).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_6() { - let a = path_from_path_data( - "M 969.000000000000,0.000000000000 C 969.000000000000,0.000000000000 1110.066900000000,76.934400000000 1085.000000000000,181.000000000000 C 1052.000000000000,318.000000000000 1199.180600000000,334.301600000000 1277.000000000000,319.000000000000 C 1455.000000000000,284.000000000000 1587.000000000000,81.000000000000 1418.000000000000,0.000000000000 C 1418.000000000000,0.000000000000 969.000000000000,0.000000000000 969.000000000000,0.000000000000 L 969.000000000000,0.000000000000" - ).unwrap(); - let b = path_from_path_data( - "M 763.000000000000,0.000000000000 C 763.000000000000,0.000000000000 1536.000000000000,0.000000000000 1536.000000000000,0.000000000000 C 1536.000000000000,0.000000000000 1536.000000000000,254.000000000000 1536.000000000000,254.000000000000 C 1536.000000000000,254.000000000000 1462.000000000000,93.000000000000 1271.000000000000,199.000000000000 C 1149.163100000000,266.616300000000 976.413700000000,188.510800000000 908.000000000000,134.000000000000 C 839.586300000000,79.489200000000 763.000000000000,0.000000000000 763.000000000000,0.000000000000 L 763.000000000000,0.000000000000", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Intersection).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn painted_dreams_7() { - let a = path_from_path_data( - "M 989.666700000000,768.000000000000 C 989.666700000000,768.000000000000 1011.111100000000,786.399400000000 1011.111100000000,786.399400000000 C 1011.111100000000,786.399400000000 1299.306500000000,786.399400000000 1299.306500000000,786.399400000000 C 1299.306500000000,786.399400000000 1318.000000000000,768.000000000000 1318.000000000000,768.000000000000 C 1293.666700000000,681.000000000000 1173.363200000000,625.103600000000 1094.162400000000,594.296600000000 C 1094.162400000000,594.296600000000 1058.747200000000,687.805800000000 989.666700000000,768.000000000000" - ).unwrap(); - let b = path_from_path_data( - "M 983.155000000000,775.589300000000 L 1004.599400000000,793.988700000000 L 1007.409000000000,796.399400000000 L 1011.111100000000,796.399400000000 L 1299.306500000000,796.399400000000 L 1303.402200000000,796.399400000000 L 1306.321200000000,793.526300000000 L 1325.014800000000,775.126900000000 L 1329.236900000000,770.971200000000 L 1327.630400000000,765.306400000000 C 1302.280700000000,675.920800000000 1179.503900000000,617.211200000000 1097.787500000000,584.976800000000 L 1088.418100000000,581.280900000000 L 1084.806400000000,590.765700000000 C 1084.117400000000,592.575300000000 1049.449700000000,683.516200000000 982.090100000000,761.473400000000 L 975.539200000000,769.055000000000 L 983.155000000000,775.589300000000 M 1003.696800000000,766.861600000000 C 1068.901100000000,687.878900000000 1102.806400000000,599.696700000000 1103.497000000000,597.883400000000 L 1090.537200000000,603.616300000000 C 1165.521500000000,632.344400000000 1279.846400000000,683.736400000000 1306.585700000000,765.203400000000 L 1295.210700000000,776.399400000000 L 1014.813100000000,776.399400000000 L 1003.696800000000,766.861600000000", - ).unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Difference).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn blobs() { - let a = path_from_path_data( - "m658.03348 118.4966c7.85928 4.83645 114.84582 7.8304 127.89652 6.52531 20.97932-2.09799 43.06722-24.79623 43.06722-24.79623 0 0-96.43723-26.02101-108.97311-28.54836-20.22849-4.07832-78.95651 36.37872-61.99063 46.81928z - m658.03348 115.88649c40.45718-30.01653 82.213-45.24662 103.10032-31.32163 7.83037 5.2203-3.58567 22.51547 13.05064 39.152 3.91519 3.9152-129.49099 2.06705-116.15096-7.83037z - m680.87214 56.0165c2.20775-9.60391 62.6449-29.65403 101.79518-30.01652 17.61846-0.16312 119.39605 40.30737 130.50668 54.8128 5.8045 7.57806-76.88558 29.08762-91.35464 31.32162-15.28899 2.36056-144.20983-41.92525-140.94722-56.1179z" - ).unwrap(); - let b = path_from_path_data("").unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn shared_line() { - let a = path_from_path_data( - "m 658.03348,118.4966 c 7.85928,4.83645 114.84582,7.8304 127.89652,6.52531 20.97932,-2.09799 43.06722,-24.79623 43.06722,-24.79623 0,0 -96.43723,-26.02101 -108.97311,-28.54836 -20.22849,-4.07832 -78.95651,36.37872 -61.99063,46.81928 Z" - ).unwrap(); - let b = path_from_path_data( - "m 658.03348,115.88649 c 40.45718,-30.01653 82.213,-45.24662 103.10032,-31.32163 7.83037,5.2203 -3.58567,22.51547 13.05064,39.152 3.91519,3.9152 -129.49099,2.06705 -116.15096,-7.83037 z", - ) - .unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } - #[test] - fn leaf() { - let a = path_from_path_data( - "M 0.000000000000,0.000000000000 C 0.000000000000,0.000000000000 30.204085760000,16.549763300000 72.719386340000,26.932261410000 C 72.719386340000,26.932261410000 70.254313510000,17.656445030000 64.511458520000,11.328808640000 C 64.511458520000,11.328808640000 87.851933730000,7.175523990000 112.835205650000,14.952290640000 C 112.835205650000,14.952290640000 85.410272600000,-0.944114870000 126.097112900000,-8.575894450000 C 118.337466460000,-27.271823700000 151.641256810000,-26.435638610000 157.566270790000,-37.101186050000 C 157.566270790000,-37.101186050000 140.392289790000,-20.685582680000 114.877703390000,-41.337145860000 C 99.535241580000,-45.054042580000 93.348771040000,-46.907137740000 91.251365300000,-51.920903790000 C 88.782358670000,-57.822967180000 120.444444440000,-82.851851850000 141.777777780000,-86.604938270000 C 141.777777780000,-86.604938270000 123.802469140000,-82.654320990000 114.716049380000,-90.160493830000 C 105.629629630000,-97.666666670000 112.345679010000,-115.000000000000 125.382716050000,-124.679012350000 C 125.382716050000,-124.679012350000 112.740740740000,-117.962962960000 115.506172840000,-132.382716050000 C 118.271604940000,-146.802469140000 109.740281350000,-157.864197530000 136.839506170000,-177.617283950000 C 115.506172840000,-165.567901230000 97.333333330000,-150.358024690000 92.395061730000,-161.419753090000 C 81.925925930000,-147.987654320000 66.518518520000,-140.481481480000 66.320987650000,-154.308641980000 C 59.703703700000,-139.000000000000 54.074074070000,-137.518518520000 49.135802470000,-136.333333330000 C 47.555555560000,-141.864197530000 43.456790120000,-172.333333330000 63.802469140000,-190.901234570000 C 48.888888890000,-178.259259260000 20.444444440000,-172.037037040000 28.444444440000,-210.456790120000 C 23.506172840000,-203.938271600000 13.234567900000,-196.432098770000 0.000000000000,-231.000000000000 L 0.000000000000,0.000000000000 Z" - ).unwrap(); - let b = path_from_path_data( - "M 0.100000000000,0.000000000000 C 0.100000000000,0.000000000000 -30.104085760000,16.549763300000 -72.619386340000,26.932261410000 C -72.619386340000,26.932261410000 -70.154313510000,17.656445030000 -64.411458520000,11.328808640000 C -64.411458520000,11.328808640000 -87.751933730000,7.175523990000 -112.735205650000,14.952290640000 C -112.735205650000,14.952290640000 -85.310272600000,-0.944114870000 -125.997112900000,-8.575894450000 C -118.237466460000,-27.271823700000 -151.541256810000,-26.435638610000 -157.466270790000,-37.101186050000 C -157.466270790000,-37.101186050000 -140.292289790000,-20.685582680000 -114.777703390000,-41.337145860000 C -99.435241580000,-45.054042580000 -93.248771040000,-46.907137740000 -91.151365300000,-51.920903790000 C -88.682358670000,-57.822967180000 -120.344444440000,-82.851851850000 -141.677777780000,-86.604938270000 C -141.677777780000,-86.604938270000 -123.702469140000,-82.654320990000 -114.616049380000,-90.160493830000 C -105.529629630000,-97.666666670000 -112.245679010000,-115.000000000000 -125.282716050000,-124.679012350000 C -125.282716050000,-124.679012350000 -112.640740740000,-117.962962960000 -115.406172840000,-132.382716050000 C -118.171604940000,-146.802469140000 -109.640281350000,-157.864197530000 -136.739506170000,-177.617283950000 C -115.406172840000,-165.567901230000 -97.233333330000,-150.358024690000 -92.295061730000,-161.419753090000 C -81.825925930000,-147.987654320000 -66.418518520000,-140.481481480000 -66.220987650000,-154.308641980000 C -59.603703700000,-139.000000000000 -53.974074070000,-137.518518520000 -49.035802470000,-136.333333330000 C -47.455555560000,-141.864197530000 -43.356790120000,-172.333333330000 -63.702469140000,-190.901234570000 C -48.788888890000,-178.259259260000 -20.344444440000,-172.037037040000 -28.344444440000,-210.456790120000 C -23.406172840000,-203.938271600000 -13.134567900000,-196.432098770000 0.100000000000,-231.000000000000 L 0.100000000000,0.000000000000 Z", - ) - .unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert_eq!(result.len(), 1); - assert!(!result[0].is_empty()); - } - #[test] - fn real_01() { - let a = path_from_path_data( - "M 212.67152,105 A 64.171516,64.171516 0 0 1 148.5,169.17152 64.171516,64.171516 0 0 1 84.328484,105 64.171516,64.171516 0 0 1 148.5,40.828484 64.171516,64.171516 0 0 1 212.67152,105 Z", - ) - .unwrap(); - let b = path_from_path_data( - "m 83.755387,112.6962 h -7.62 v 37.0332 h 7.62 z m 22.097973,9.144 c -3.4544,0 -5.892801,1.4732 -7.620001,4.5212 v -4.064 H 91.12136 v 38.5064 h 7.111999 v -14.3256 c 1.7272,3.048 4.165601,4.4704 7.620001,4.4704 6.604,0 11.4808,-6.1976 11.4808,-14.5288 0,-8.0772 -4.3688,-14.5796 -11.4808,-14.5796 z m -1.6256,5.9436 c 3.6068,0 5.9944,3.5052 5.9944,8.7376 0,4.9784 -2.4892,8.4836 -5.9944,8.4836 -3.556,0 -5.994401,-3.4544 -5.994401,-8.5852 0,-5.1308 2.438401,-8.636 5.994401,-8.636 z m 23.62201,13.97 h -6.9596 c 0.2032,5.9944 4.6228,9.144 12.954,9.144 9.6012,0 11.9888,-5.4864 11.9888,-9.2964 0,-3.556 -1.778,-5.842 -5.3848,-6.9088 l -8.9916,-2.5908 c -1.9812,-0.6096 -2.4892,-1.016 -2.4892,-2.1336 0,-1.524 1.6256,-2.54 4.1148,-2.54 3.4036,0 5.08,1.2192 5.1308,3.7084 h 6.858 c -0.1016,-5.7912 -4.572,-9.2964 -11.938,-9.2964 -6.9596,0 -11.2776,3.5052 -11.2776,9.144 0,5.3848 4.4196,6.2484 5.9944,6.7564 l 8.4836,2.6416 c 1.778,0.5588 2.3876,1.1176 2.3876,2.2352 0,1.6764 -1.9812,2.6924 -5.2832,2.6924 -4.4704,0 -5.1816,-1.6764 -5.588,-3.556 z m 47.59959,7.9756 v -27.432 h -7.112 v 17.1704 c 0,3.2512 -2.286,5.3848 -5.7404,5.3848 -3.048,0 -4.572,-1.6256 -4.572,-4.9276 v -17.6276 h -7.112 v 19.1008 c 0,6.0452 3.3528,9.4996 9.1948,9.4996 3.7084,0 6.1976,-1.3716 8.2296,-4.4196 v 3.2512 z m 6.60404,-27.432 v 27.432 h 7.112 v -16.4592 c 0,-3.3528 1.8288,-5.3848 4.8768,-5.3848 2.3876,0 3.8608,1.3716 3.8608,3.556 v 18.288 h 7.112 v -16.4592 c 0,-3.3528 1.8288,-5.3848 4.8768,-5.3848 2.3876,0 3.8608,1.3716 3.8608,3.556 v 18.288 h 7.112 v -19.4056 c 0,-5.334 -3.2512,-8.4836 -8.7376,-8.4836 -3.4544,0 -5.8928,1.2192 -8.0264,4.064 -1.3208,-2.5908 -4.064,-4.064 -7.4676,-4.064 -3.1496,0 -5.1816,1.0668 -7.5184,3.8608 v -3.4036 z M 81.012192,49.196201 h -7.62 v 37.0332 h 25.3492 v -6.35 h -17.7292 z m 35.305978,9.144 c -8.382,0 -13.5128,5.5372 -13.5128,14.5288 0,9.0424 5.1308,14.5288 13.5636,14.5288 8.3312,0 13.5636,-5.5372 13.5636,-14.3256 0,-9.2964 -5.0292,-14.732 -13.6144,-14.732 z m 0.0508,5.7404 c 3.9116,0 6.4516,3.5052 6.4516,8.89 0,5.1308 -2.6416,8.6868 -6.4516,8.6868 -3.8608,0 -6.4516,-3.556 -6.4516,-8.7884 0,-5.2324 2.5908,-8.7884 6.4516,-8.7884 z m 18.89761,-5.2832 v 27.432 h 7.112 v -14.5796 c 0,-4.1656 2.0828,-6.2484 6.2484,-6.2484 0.762,0 1.27,0.0508 2.2352,0.2032 v -7.2136 c -0.4064,-0.0508 -0.6604,-0.0508 -0.8636,-0.0508 -3.2512,0 -6.096,2.1336 -7.62,5.842 v -5.3848 z m 31.34362,-0.4572 c -7.874,0 -12.7,5.6896 -12.7,14.8844 0,8.7884 4.7752,14.1732 12.5476,14.1732 6.14679,0 11.12519,-3.5052 12.69999,-8.89 h -7.0104 c -0.8636,2.1844 -2.8448,3.4544 -5.43559,3.4544 -4.7752,0 -5.5372,-3.5052 -5.6896,-7.2136 h 18.38959 c 0.0508,-0.6096 0.0508,-0.8636 0.0508,-1.2192 0,-12.1412 -7.366,-15.1892 -12.85239,-15.1892 z m 5.43559,11.684 H 161.1238 c 0.4572,-4.1656 2.2352,-6.2484 5.3848,-6.2484 3.30199,0 5.23239,2.2352 5.53719,6.2484 z m 12.75081,-11.2268 v 27.432 h 7.112 v -16.4592 c 0,-3.3528 1.8288,-5.3848 4.8768,-5.3848 2.3876,0 3.8608,1.3716 3.8608,3.556 v 18.288 h 7.112 v -16.4592 c 0,-3.3528 1.8288,-5.3848 4.8768,-5.3848 2.3876,0 3.8608,1.3716 3.8608,3.556 v 18.288 h 7.112 v -19.4056 c 0,-5.334 -3.2512,-8.4836 -8.7376,-8.4836 -3.4544,0 -5.8928,1.2192 -8.0264,4.064 -1.3208,-2.5908 -4.064,-4.064 -7.4676,-4.064 -3.1496,0 -5.1816,1.0668 -7.5184,3.8608 v -3.4036 z", - ) - .unwrap(); - - let result = path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, PathBooleanOperation::Union).unwrap(); - - // Add assertions here based on expected results - assert_eq!(result.len(), 1, "Expected 1 resulting path for Union operation"); - dbg!(path_to_path_data(&result[0], 0.001)); - // Add more specific assertions about the resulting path if needed - assert!(!result[0].is_empty()); - } -} diff --git a/libraries/path-bool/src/parsing/path_command.rs b/libraries/path-bool/src/parsing/path_command.rs deleted file mode 100644 index 6721900529..0000000000 --- a/libraries/path-bool/src/parsing/path_command.rs +++ /dev/null @@ -1,124 +0,0 @@ -use glam::DVec2; - -#[derive(Clone, Debug)] -pub enum AbsolutePathCommand { - H(f64), - V(f64), - M(DVec2), - L(DVec2), - C(DVec2, DVec2, DVec2), - S(DVec2, DVec2), - Q(DVec2, DVec2), - T(DVec2), - A(f64, f64, f64, bool, bool, DVec2), - Z, -} - -#[derive(Clone, Debug)] -pub enum RelativePathCommand { - H(f64), - V(f64), - M(f64, f64), - L(f64, f64), - C(f64, f64, f64, f64, f64, f64), - S(f64, f64, f64, f64), - Q(f64, f64, f64, f64), - T(f64, f64), - A(f64, f64, f64, bool, bool, f64, f64), -} - -#[derive(Clone, Debug)] -pub enum PathCommand { - Absolute(AbsolutePathCommand), - Relative(RelativePathCommand), -} - -pub fn to_absolute_commands(commands: I) -> impl Iterator -where - I: IntoIterator, -{ - let mut last_point = DVec2::ZERO; - let mut first_point = last_point; - - commands.into_iter().flat_map(move |cmd| match cmd { - PathCommand::Absolute(abs_cmd) => { - match abs_cmd { - AbsolutePathCommand::H(x) => { - last_point.x = x; - } - AbsolutePathCommand::V(y) => { - last_point.y = y; - } - AbsolutePathCommand::M(point) => { - last_point = point; - first_point = point; - } - AbsolutePathCommand::L(point) => { - last_point = point; - } - AbsolutePathCommand::C(_, _, end) => { - last_point = end; - } - AbsolutePathCommand::S(_, end) => { - last_point = end; - } - AbsolutePathCommand::Q(_, end) => { - last_point = end; - } - AbsolutePathCommand::T(end) => { - last_point = end; - } - AbsolutePathCommand::A(_, _, _, _, _, end) => { - last_point = end; - } - AbsolutePathCommand::Z => { - last_point = first_point; - } - } - vec![abs_cmd] - } - PathCommand::Relative(rel_cmd) => match rel_cmd { - RelativePathCommand::H(dx) => { - last_point.x += dx; - vec![AbsolutePathCommand::L(last_point)] - } - RelativePathCommand::V(dy) => { - last_point.y += dy; - vec![AbsolutePathCommand::L(last_point)] - } - RelativePathCommand::M(dx, dy) => { - last_point += DVec2::new(dx, dy); - first_point = last_point; - vec![AbsolutePathCommand::M(last_point)] - } - RelativePathCommand::L(dx, dy) => { - last_point += DVec2::new(dx, dy); - vec![AbsolutePathCommand::L(last_point)] - } - RelativePathCommand::C(dx1, dy1, dx2, dy2, dx, dy) => { - let c1 = last_point + DVec2::new(dx1, dy1); - let c2 = last_point + DVec2::new(dx2, dy2); - last_point += DVec2::new(dx, dy); - vec![AbsolutePathCommand::C(c1, c2, last_point)] - } - RelativePathCommand::S(dx2, dy2, dx, dy) => { - let c2 = last_point + DVec2::new(dx2, dy2); - last_point += DVec2::new(dx, dy); - vec![AbsolutePathCommand::S(c2, last_point)] - } - RelativePathCommand::Q(dx1, dy1, dx, dy) => { - let control = last_point + DVec2::new(dx1, dy1); - last_point += DVec2::new(dx, dy); - vec![AbsolutePathCommand::Q(control, last_point)] - } - RelativePathCommand::T(dx, dy) => { - last_point += DVec2::new(dx, dy); - vec![AbsolutePathCommand::T(last_point)] - } - RelativePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, dx, dy) => { - last_point += DVec2::new(dx, dy); - vec![AbsolutePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, last_point)] - } - }, - }) -} diff --git a/libraries/path-bool/src/parsing/path_data.rs b/libraries/path-bool/src/parsing/path_data.rs deleted file mode 100644 index 076f424795..0000000000 --- a/libraries/path-bool/src/parsing/path_data.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::BooleanError; -use crate::path::{Path, path_from_commands, path_to_commands}; -use crate::path_command::{AbsolutePathCommand, PathCommand, RelativePathCommand}; -use glam::DVec2; -use regex::Regex; - -pub fn commands_from_path_data(d: &str) -> Result, BooleanError> { - let re_float = Regex::new(r"^\s*,?\s*(-?\d*(?:\d\.|\.\d|\d)\d*(?:[eE][+\-]?\d+)?)").unwrap(); - let re_cmd = Regex::new(r"^\s*([MLCSQTAZHVmlhvcsqtaz])").unwrap(); - let re_bool = Regex::new(r"^\s*,?\s*([01])").unwrap(); - - let mut i = 0; - let mut last_cmd = 'M'; - let mut commands = Vec::new(); - - let get_cmd = |i: &mut usize, last_cmd: char| -> Option { - if *i >= d.len() - 1.min(d.len()) { - return None; - } - - if let Some(cap) = re_cmd.captures(&d[*i..]) { - *i += cap[0].len(); - Some(cap[1].chars().next().unwrap()) - } else { - match last_cmd { - 'M' => Some('L'), - 'm' => Some('l'), - 'z' | 'Z' => None, - _ => Some(last_cmd), - } - } - }; - - let get_float = |i: &mut usize| -> f64 { - if let Some(cap) = re_float.captures(&d[*i..]) { - *i += cap[0].len(); - cap[1].parse().unwrap() - } else { - panic!("Invalid path data. Expected a number at index {}, got {}", i, &d[*i..]); - } - }; - - let get_bool = |i: &mut usize| -> bool { - if let Some(cap) = re_bool.captures(&d[*i..]) { - *i += cap[0].len(); - &cap[1] == "1" - } else { - panic!("Invalid path data. Expected a flag at index {i}"); - } - }; - - while let Some(cmd) = get_cmd(&mut i, last_cmd) { - last_cmd = cmd; - match cmd { - 'M' => commands.push(PathCommand::Absolute(AbsolutePathCommand::M(DVec2::new(get_float(&mut i), get_float(&mut i))))), - 'L' => commands.push(PathCommand::Absolute(AbsolutePathCommand::L(DVec2::new(get_float(&mut i), get_float(&mut i))))), - 'C' => commands.push(PathCommand::Absolute(AbsolutePathCommand::C( - DVec2::new(get_float(&mut i), get_float(&mut i)), - DVec2::new(get_float(&mut i), get_float(&mut i)), - DVec2::new(get_float(&mut i), get_float(&mut i)), - ))), - 'S' => commands.push(PathCommand::Absolute(AbsolutePathCommand::S( - DVec2::new(get_float(&mut i), get_float(&mut i)), - DVec2::new(get_float(&mut i), get_float(&mut i)), - ))), - 'Q' => commands.push(PathCommand::Absolute(AbsolutePathCommand::Q( - DVec2::new(get_float(&mut i), get_float(&mut i)), - DVec2::new(get_float(&mut i), get_float(&mut i)), - ))), - 'T' => commands.push(PathCommand::Absolute(AbsolutePathCommand::T(DVec2::new(get_float(&mut i), get_float(&mut i))))), - 'A' => commands.push(PathCommand::Absolute(AbsolutePathCommand::A( - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_bool(&mut i), - get_bool(&mut i), - DVec2::new(get_float(&mut i), get_float(&mut i)), - ))), - 'Z' | 'z' => commands.push(PathCommand::Absolute(AbsolutePathCommand::Z)), - 'H' => commands.push(PathCommand::Absolute(AbsolutePathCommand::H(get_float(&mut i)))), - 'V' => commands.push(PathCommand::Absolute(AbsolutePathCommand::V(get_float(&mut i)))), - 'm' => commands.push(PathCommand::Relative(RelativePathCommand::M(get_float(&mut i), get_float(&mut i)))), - 'l' => commands.push(PathCommand::Relative(RelativePathCommand::L(get_float(&mut i), get_float(&mut i)))), - 'h' => commands.push(PathCommand::Relative(RelativePathCommand::H(get_float(&mut i)))), - 'v' => commands.push(PathCommand::Relative(RelativePathCommand::V(get_float(&mut i)))), - 'c' => commands.push(PathCommand::Relative(RelativePathCommand::C( - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - ))), - 's' => commands.push(PathCommand::Relative(RelativePathCommand::S( - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - ))), - 'q' => commands.push(PathCommand::Relative(RelativePathCommand::Q( - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - ))), - 't' => commands.push(PathCommand::Relative(RelativePathCommand::T(get_float(&mut i), get_float(&mut i)))), - 'a' => commands.push(PathCommand::Relative(RelativePathCommand::A( - get_float(&mut i), - get_float(&mut i), - get_float(&mut i), - get_bool(&mut i), - get_bool(&mut i), - get_float(&mut i), - get_float(&mut i), - ))), - _ => return Err(BooleanError::InvalidPathCommand(cmd)), - } - } - - Ok(commands) -} - -pub fn path_from_path_data(d: &str) -> Result { - Ok(path_from_commands(commands_from_path_data(d)?).collect()) -} - -pub fn path_to_path_data(path: &Path, eps: f64) -> String { - path_to_commands(path.iter(), eps) - .map(|cmd| match cmd { - PathCommand::Absolute(abs_cmd) => match abs_cmd { - AbsolutePathCommand::H(dx) => format!("H {dx:.12}"), - AbsolutePathCommand::V(dy) => format!("V {dy:.12}"), - AbsolutePathCommand::M(p) => format!("M {:.12},{:.12}", p.x, p.y), - AbsolutePathCommand::L(p) => format!("L {:.12},{:.12}", p.x, p.y), - AbsolutePathCommand::C(p1, p2, p3) => format!("C {:.12},{:.12} {:.12},{:.12} {:.12},{:.12}", p1.x, p1.y, p2.x, p2.y, p3.x, p3.y), - AbsolutePathCommand::S(p1, p2) => { - format!("S {:.12},{:.12} {:.12},{:.12}", p1.x, p1.y, p2.x, p2.y) - } - AbsolutePathCommand::Q(p1, p2) => { - format!("Q {:.12},{:.12} {:.12},{:.12}", p1.x, p1.y, p2.x, p2.y) - } - AbsolutePathCommand::T(p) => format!("T {:.12},{:.12}", p.x, p.y), - AbsolutePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, p) => { - format!("A {:.12} {:.12} {:.12} {} {} {:.12},{:.12}", rx, ry, x_axis_rotation, large_arc_flag as u8, sweep_flag as u8, p.x, p.y) - } - AbsolutePathCommand::Z => "Z".to_string(), - }, - PathCommand::Relative(rel_cmd) => match rel_cmd { - RelativePathCommand::M(dx, dy) => format!("m {dx:.12},{dy:.12}"), - RelativePathCommand::L(dx, dy) => format!("l {dx:.12},{dy:.12}"), - RelativePathCommand::H(dx) => format!("h {dx:.12}"), - RelativePathCommand::V(dy) => format!("v {dy:.12}"), - RelativePathCommand::C(dx1, dy1, dx2, dy2, dx, dy) => format!("c{dx1:.12},{dy1:.12} {dx2:.12},{dy2:.12} {dx:.12},{dy:.12}"), - RelativePathCommand::S(dx2, dy2, dx, dy) => { - format!("s {dx2:.12},{dy2:.12} {dx:.12},{dy:.12}") - } - RelativePathCommand::Q(dx1, dy1, dx, dy) => { - format!("q {dx1:.12},{dy1:.12} {dx:.12},{dy:.12}") - } - RelativePathCommand::T(dx, dy) => format!("t{dx:.12},{dy:.12}"), - RelativePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, dx, dy) => { - format!("a {:.12} {:.12} {:.12} {} {} {:.12},{:.12}", rx, ry, x_axis_rotation, large_arc_flag as u8, sweep_flag as u8, dx, dy) - } - }, - }) - .collect::>() - .join(" ") -} diff --git a/libraries/path-bool/src/path.rs b/libraries/path-bool/src/path.rs deleted file mode 100644 index 283f22e280..0000000000 --- a/libraries/path-bool/src/path.rs +++ /dev/null @@ -1,140 +0,0 @@ -pub(crate) mod intersection_path_segment; -pub(crate) mod line_segment; -pub(crate) mod line_segment_aabb; -pub(crate) mod path_cubic_segment_self_intersection; -pub(crate) mod path_segment; - -use glam::DVec2; - -#[cfg(feature = "parsing")] -use crate::path_command::{AbsolutePathCommand, PathCommand, to_absolute_commands}; -use crate::path_segment::PathSegment; - -pub type Path = Vec; - -fn reflect_control_point(point: DVec2, control_point: DVec2) -> DVec2 { - point * 2. - control_point -} - -#[cfg(feature = "parsing")] -pub fn path_from_commands(commands: I) -> impl Iterator -where - I: IntoIterator, -{ - let mut first_point: Option = None; - let mut last_point: Option = None; - let mut last_control_point: Option = None; - - to_absolute_commands(commands).filter_map(move |cmd| match cmd { - AbsolutePathCommand::M(point) => { - last_point = Some(point); - first_point = Some(point); - last_control_point = None; - None - } - AbsolutePathCommand::L(point) => { - let start = last_point.unwrap(); - last_point = Some(point); - last_control_point = None; - Some(PathSegment::Line(start, point)) - } - AbsolutePathCommand::H(x) => { - let start = last_point.unwrap(); - let point = DVec2::new(x, start.y); - last_point = Some(point); - last_control_point = None; - Some(PathSegment::Line(start, point)) - } - AbsolutePathCommand::V(y) => { - let start = last_point.unwrap(); - let point = DVec2::new(start.x, y); - last_point = Some(point); - last_control_point = None; - Some(PathSegment::Line(start, point)) - } - AbsolutePathCommand::C(c1, c2, end) => { - let start = last_point.unwrap(); - last_point = Some(end); - last_control_point = Some(c2); - Some(PathSegment::Cubic(start, c1, c2, end)) - } - AbsolutePathCommand::S(c2, end) => { - let start = last_point.unwrap(); - let c1 = reflect_control_point(start, last_control_point.unwrap_or(start)); - last_point = Some(end); - last_control_point = Some(c2); - Some(PathSegment::Cubic(start, c1, c2, end)) - } - AbsolutePathCommand::Q(c, end) => { - let start = last_point.unwrap(); - last_point = Some(end); - last_control_point = Some(c); - Some(PathSegment::Quadratic(start, c, end)) - } - AbsolutePathCommand::T(end) => { - let start = last_point.unwrap(); - let c = reflect_control_point(start, last_control_point.unwrap_or(start)); - last_point = Some(end); - last_control_point = Some(c); - Some(PathSegment::Quadratic(start, c, end)) - } - AbsolutePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, end) => { - let start = last_point.unwrap(); - last_point = Some(end); - last_control_point = None; - Some(PathSegment::Arc(start, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, end)) - } - AbsolutePathCommand::Z => { - let start = last_point.unwrap(); - let end = first_point.unwrap(); - last_point = Some(end); - last_control_point = None; - Some(PathSegment::Line(start, end)) - } - }) -} - -#[cfg(feature = "parsing")] -pub fn path_to_commands<'a, I>(segments: I, eps: f64) -> impl Iterator + 'a -where - I: IntoIterator + 'a, -{ - let mut last_point: Option = None; - - segments - .into_iter() - .flat_map(move |seg| { - let start = seg.start(); - let mut commands = Vec::new(); - - if last_point.is_none_or(|lp| !start.abs_diff_eq(lp, eps)) { - if last_point.is_some() { - commands.push(PathCommand::Absolute(AbsolutePathCommand::Z)); - } - - commands.push(PathCommand::Absolute(AbsolutePathCommand::M(start))); - } - - match seg { - PathSegment::Line(_, end) => { - commands.push(PathCommand::Absolute(AbsolutePathCommand::L(*end))); - last_point = Some(*end); - } - PathSegment::Cubic(_, c1, c2, end) => { - commands.push(PathCommand::Absolute(AbsolutePathCommand::C(*c1, *c2, *end))); - last_point = Some(*end); - } - PathSegment::Quadratic(_, c, end) => { - commands.push(PathCommand::Absolute(AbsolutePathCommand::Q(*c, *end))); - last_point = Some(*end); - } - PathSegment::Arc(_, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, end) => { - commands.push(PathCommand::Absolute(AbsolutePathCommand::A(*rx, *ry, *x_axis_rotation, *large_arc_flag, *sweep_flag, *end))); - last_point = Some(*end); - } - } - - commands - }) - .chain(std::iter::once(PathCommand::Absolute(AbsolutePathCommand::Z))) -} diff --git a/libraries/path-bool/src/path/intersection_path_segment.rs b/libraries/path-bool/src/path/intersection_path_segment.rs deleted file mode 100644 index 1ef931b868..0000000000 --- a/libraries/path-bool/src/path/intersection_path_segment.rs +++ /dev/null @@ -1,313 +0,0 @@ -use crate::aabb::{Aabb, bounding_box_max_extent, bounding_boxes_overlap}; -use crate::epsilons::Epsilons; -use crate::line_segment::{line_segment_intersection, line_segments_intersect}; -use crate::line_segment_aabb::line_segment_aabb_intersect; -use crate::math::lerp; -use crate::path_segment::PathSegment; -use glam::DVec2; -use lyon_geom::{CubicBezierSegment, Point}; - -/// Convert PathSegment::Cubic to lyon_geom::CubicBezierSegment -fn path_segment_cubic_to_lyon(start: DVec2, ctrl1: DVec2, ctrl2: DVec2, end: DVec2) -> CubicBezierSegment { - CubicBezierSegment { - from: Point::new(start.x, start.y), - ctrl1: Point::new(ctrl1.x, ctrl1.y), - ctrl2: Point::new(ctrl2.x, ctrl2.y), - to: Point::new(end.x, end.y), - } -} - -#[derive(Clone)] -struct IntersectionSegment { - seg: PathSegment, - start_param: f64, - end_param: f64, - bounding_box: Aabb, -} - -#[inline(never)] -fn subdivide_intersection_segment(int_seg: &IntersectionSegment) -> [IntersectionSegment; 2] { - let (seg0, seg1) = int_seg.seg.split_at(0.5); - let mid_param = (int_seg.start_param + int_seg.end_param) / 2.; - [ - IntersectionSegment { - seg: seg0, - start_param: int_seg.start_param, - end_param: mid_param, - bounding_box: seg0.approx_bounding_box(), - }, - IntersectionSegment { - seg: seg1, - start_param: mid_param, - end_param: int_seg.end_param, - bounding_box: seg1.approx_bounding_box(), - }, - ] -} - -#[inline(never)] -fn path_segment_to_line_segment(seg: &PathSegment) -> [DVec2; 2] { - match seg { - PathSegment::Line(start, end) => [*start, *end], - PathSegment::Cubic(start, _, _, end) => [*start, *end], - PathSegment::Quadratic(start, _, end) => [*start, *end], - PathSegment::Arc(start, _, _, _, _, _, end) => [*start, *end], - } -} - -#[inline(never)] -fn intersection_segments_overlap(seg0: &IntersectionSegment, seg1: &IntersectionSegment) -> bool { - match (&seg0.seg, &seg1.seg) { - (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) => { - line_segments_intersect([*start0, *end0], [*start1, *end1], 1e-6) // TODO: configurable - } - (PathSegment::Line(start, end), _) => line_segment_aabb_intersect([*start, *end], &seg1.bounding_box), - (_, PathSegment::Line(start, end)) => line_segment_aabb_intersect([*start, *end], &seg0.bounding_box), - _ => bounding_boxes_overlap(&seg0.bounding_box, &seg1.bounding_box), - } -} - -#[inline(never)] -pub fn segments_equal(seg0: &PathSegment, seg1: &PathSegment, point_epsilon: f64) -> bool { - match (*seg0, *seg1) { - (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) => start0.abs_diff_eq(start1, point_epsilon) && end0.abs_diff_eq(end1, point_epsilon), - (PathSegment::Cubic(p00, p01, p02, p03), PathSegment::Cubic(p10, p11, p12, p13)) => { - let start_and_end_equal = p00.abs_diff_eq(p10, point_epsilon) && p03.abs_diff_eq(p13, point_epsilon); - - let parameter_equal = p01.abs_diff_eq(p11, point_epsilon) && p02.abs_diff_eq(p12, point_epsilon); - let direction1 = seg0.sample_at(0.1); - let direction2 = seg1.sample_at(0.1); - let angles_equal = (direction1 - p00).angle_to(direction2 - p00).abs() < point_epsilon * 4.; - - start_and_end_equal && (parameter_equal || angles_equal) - } - (PathSegment::Quadratic(p00, p01, p02), PathSegment::Quadratic(p10, p11, p12)) => { - p00.abs_diff_eq(p10, point_epsilon) && p01.abs_diff_eq(p11, point_epsilon) && p02.abs_diff_eq(p12, point_epsilon) - } - (PathSegment::Arc(p00, rx0, ry0, angle0, large_arc0, sweep0, p01), PathSegment::Arc(p10, rx1, ry1, angle1, large_arc1, sweep1, p11)) => { - p00.abs_diff_eq(p10, point_epsilon) && - (rx0 - rx1).abs() < point_epsilon && - (ry0 - ry1).abs() < point_epsilon && - (angle0 - angle1).abs() < point_epsilon && // TODO: Phi can be anything if rx = ry. Also, handle rotations by Pi/2. - large_arc0 == large_arc1 && - sweep0 == sweep1 && - p01.abs_diff_eq(p11, point_epsilon) - } - _ => false, - } -} - -pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoints: bool, eps: &Epsilons) -> Vec<[f64; 2]> { - match (seg0, seg1) { - (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) => { - if let Some(st) = line_segment_intersection([*start0, *end0], [*start1, *end1], eps.param) { - if !endpoints && (st.0 < eps.param || st.0 > 1. - eps.param) && (st.1 < eps.param || st.1 > 1. - eps.param) { - return vec![]; - } - return vec![st.into()]; - } - } - (PathSegment::Cubic(s1, c11, c21, e1), PathSegment::Cubic(s2, c12, c22, e2)) => { - let path1 = path_segment_cubic_to_lyon(*s1, *c11, *c21, *e1); - let path2 = path_segment_cubic_to_lyon(*s2, *c12, *c22, *e2); - - let intersections = path1.cubic_intersections_t(&path2); - let intersections: Vec<_> = intersections.into_iter().map(|(s, t)| [s, t]).collect(); - return intersections; - } - _ => (), - }; - - // Fallback for quadratics and arc segments - // https://math.stackexchange.com/questions/20321/how-can-i-tell-when-two-cubic-b%C3%A9zier-curves-intersect - - let mut pairs = vec![( - IntersectionSegment { - seg: *seg0, - start_param: 0., - end_param: 1., - bounding_box: seg0.approx_bounding_box(), - }, - IntersectionSegment { - seg: *seg1, - start_param: 0., - end_param: 1., - bounding_box: seg1.approx_bounding_box(), - }, - )]; - let mut next_pairs = Vec::new(); - - let mut params = Vec::new(); - let mut subdivided0 = Vec::new(); - let mut subdivided1 = Vec::new(); - - // Check if start and end points are on the other bezier curves. If so, add an intersection. - - while !pairs.is_empty() { - next_pairs.clear(); - - if pairs.len() > 256 { - return calculate_overlap_intersections(seg0, seg1, eps); - } - - for (seg0, seg1) in pairs.iter() { - if segments_equal(&seg0.seg, &seg1.seg, eps.point) { - // TODO: move this outside of this loop? - continue; // TODO: what to do? - } - - let is_linear0 = bounding_box_max_extent(&seg0.bounding_box) <= eps.linear || (seg0.end_param - seg0.start_param).abs() < eps.param; - let is_linear1 = bounding_box_max_extent(&seg1.bounding_box) <= eps.linear || (seg1.end_param - seg1.start_param).abs() < eps.param; - - if is_linear0 && is_linear1 { - let line_segment0 = path_segment_to_line_segment(&seg0.seg); - let line_segment1 = path_segment_to_line_segment(&seg1.seg); - if let Some(st) = line_segment_intersection(line_segment0, line_segment1, eps.param) { - params.push([lerp(seg0.start_param, seg0.end_param, st.0), lerp(seg1.start_param, seg1.end_param, st.1)]); - } - } else { - subdivided0.clear(); - subdivided1.clear(); - if is_linear0 { - subdivided0.push(seg0.clone()) - } else { - subdivided0.extend_from_slice(&subdivide_intersection_segment(seg0)) - }; - if is_linear1 { - subdivided1.push(seg1.clone()) - } else { - subdivided1.extend_from_slice(&subdivide_intersection_segment(seg1)) - }; - - for seg0 in &subdivided0 { - for seg1 in &subdivided1 { - if intersection_segments_overlap(seg0, seg1) { - next_pairs.push((seg0.clone(), seg1.clone())); - } - } - } - } - } - - std::mem::swap(&mut pairs, &mut next_pairs); - } - - params -} - -fn calculate_overlap_intersections(seg0: &PathSegment, seg1: &PathSegment, eps: &Epsilons) -> Vec<[f64; 2]> { - let start0 = seg0.start(); - let end0 = seg0.end(); - let start1 = seg1.start(); - let end1 = seg1.end(); - - let mut intersections = Vec::new(); - - // Check start0 against seg1 - if let Some(t1) = find_point_on_segment(seg1, start0, eps) { - intersections.push([0., t1]); - } - - // Check end0 against seg1 - if let Some(t1) = find_point_on_segment(seg1, end0, eps) { - intersections.push([1., t1]); - } - - // Check start1 against seg0 - if let Some(t0) = find_point_on_segment(seg0, start1, eps) { - intersections.push([t0, 0.]); - } - - // Check end1 against seg0 - if let Some(t0) = find_point_on_segment(seg0, end1, eps) { - intersections.push([t0, 1.]); - } - - // Remove duplicates and sort intersections - intersections.sort_unstable_by(|a, b| a[0].partial_cmp(&b[0]).unwrap()); - intersections.dedup_by(|a, b| DVec2::from(*a).abs_diff_eq(DVec2::from(*b), eps.param)); - - // Handle special cases - if intersections.is_empty() { - // Check if segments are identical - if (start0.abs_diff_eq(start1, eps.point)) && end0.abs_diff_eq(end1, eps.point) { - return vec![[0., 0.], [1., 1.]]; - } - } else if intersections.len() > 2 { - // Keep only the first and last intersection points - intersections = vec![intersections[0], intersections[intersections.len() - 1]]; - } - - intersections -} - -fn find_point_on_segment(seg: &PathSegment, point: DVec2, eps: &Epsilons) -> Option { - let start = 0.; - let end = 1.; - let mut t = 0.5; - - for _ in 0..32 { - // Limit iterations to prevent infinite loops - let current_point = seg.sample_at(t); - - if current_point.abs_diff_eq(point, eps.point) { - return Some(t); - } - - let start_point = seg.sample_at(start); - let end_point = seg.sample_at(end); - - let dist_start = (point - start_point).length_squared(); - let dist_end = (point - end_point).length_squared(); - let dist_current = (point - current_point).length_squared(); - - if dist_current < dist_start && dist_current < dist_end { - return Some(t); - } - - if dist_start < dist_end { - t = (start + t) / 2.; - } else { - t = (t + end) / 2.; - } - - if (end - start) < eps.param { - break; - } - } - - None -} - -#[cfg(test)] -mod test { - use super::*; - use glam::DVec2; - - #[test] - fn intersect_cubic_slow_first() { - path_segment_intersection(&a(), &b(), true, &crate::EPS); - } - #[test] - fn intersect_cubic_slow_second() { - path_segment_intersection(&c(), &d(), true, &crate::EPS); - } - - fn a() -> PathSegment { - PathSegment::Cubic( - DVec2::new(458.37027, 572.165771), - DVec2::new(428.525848, 486.720093), - DVec2::new(368.618805, 467.485992), - DVec2::new(273., 476.), - ) - } - fn b() -> PathSegment { - PathSegment::Cubic(DVec2::new(273., 476.), DVec2::new(419., 463.), DVec2::new(481.741198, 514.692273), DVec2::new(481.333333, 768.)) - } - fn c() -> PathSegment { - PathSegment::Cubic(DVec2::new(273., 476.), DVec2::new(107.564178, 490.730591), DVec2::new(161.737915, 383.575775), DVec2::new(0., 340.)) - } - fn d() -> PathSegment { - PathSegment::Cubic(DVec2::new(0., 340.), DVec2::new(161.737914, 383.575765), DVec2::new(107.564182, 490.730587), DVec2::new(273., 476.)) - } -} diff --git a/libraries/path-bool/src/path/line_segment.rs b/libraries/path-bool/src/path/line_segment.rs deleted file mode 100644 index 6b76a09f71..0000000000 --- a/libraries/path-bool/src/path/line_segment.rs +++ /dev/null @@ -1,29 +0,0 @@ -use glam::DVec2; - -pub type LineSegment = [DVec2; 2]; - -const COLLINEAR_EPS: f64 = f64::EPSILON * 64.; - -#[inline(never)] -pub fn line_segment_intersection([p1, p2]: LineSegment, [p3, p4]: LineSegment, eps: f64) -> Option<(f64, f64)> { - // https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments - - let a = p2 - p1; - let b = p3 - p4; - let c = p3 - p1; - - let denom = a.x * b.y - a.y * b.x; - - if denom.abs() < COLLINEAR_EPS { - return None; - } - - let s = (c.x * b.y - c.y * b.x) / denom; - let t = (a.x * c.y - a.y * c.x) / denom; - - if (-eps..=1. + eps).contains(&s) && (-eps..=1. + eps).contains(&t) { Some((s, t)) } else { None } -} - -pub fn line_segments_intersect(seg1: LineSegment, seg2: LineSegment, eps: f64) -> bool { - line_segment_intersection(seg1, seg2, eps).is_some() -} diff --git a/libraries/path-bool/src/path/line_segment_aabb.rs b/libraries/path-bool/src/path/line_segment_aabb.rs deleted file mode 100644 index 38fbdf7d98..0000000000 --- a/libraries/path-bool/src/path/line_segment_aabb.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::aabb::Aabb; -use crate::line_segment::LineSegment; - -const INSIDE: u8 = 0; -const LEFT: u8 = 1; -const RIGHT: u8 = 1 << 1; -const BOTTOM: u8 = 1 << 2; -const TOP: u8 = 1 << 3; - -fn out_code(x: f64, y: f64, bounding_box: &Aabb) -> u8 { - let mut code = INSIDE; - - if x < bounding_box.left() { - code |= LEFT; - } else if x > bounding_box.right() { - code |= RIGHT; - } - - if y < bounding_box.top() { - code |= BOTTOM; - } else if y > bounding_box.bottom() { - code |= TOP; - } - - code -} - -pub(crate) fn line_segment_aabb_intersect(seg: LineSegment, bounding_box: &Aabb) -> bool { - let [mut p0, mut p1] = seg; - - let mut outcode0 = out_code(p0.x, p0.y, bounding_box); - let mut outcode1 = out_code(p1.x, p1.y, bounding_box); - - loop { - if (outcode0 | outcode1) == 0 { - // bitwise OR is 0: both points inside window; trivially accept and exit loop - return true; - } else if (outcode0 & outcode1) != 0 { - // bitwise AND is not 0: both points share an outside zone (LEFT, RIGHT, TOP, - // or BOTTOM), so both must be outside window; exit loop (accept is false) - return false; - } else { - // failed both tests, so calculate the line segment to clip - // from an outside point to an intersection with clip edge - let mut x = 0.; - let mut y = 0.; - - // At least one endpoint is outside the clip rectangle; pick it. - let outcode_out = if outcode1 > outcode0 { outcode1 } else { outcode0 }; - - // Now find the intersection point; - // use formulas: - // slope = (y1 - y0) / (x1 - x0) - // x = x0 + (1 / slope) * (ym - y0), where ym is ymin or ymax - // y = y0 + slope * (xm - x0), where xm is xmin or xmax - // No need to worry about divide-by-zero because, in each case, the - // outcode bit being tested guarantees the denominator is non-zero - if (outcode_out & TOP) != 0 { - // point is above the clip window - x = p0.x + (p1.x - p0.x) * (bounding_box.bottom() - p0.y) / (p1.y - p0.y); - y = bounding_box.bottom(); - } else if (outcode_out & BOTTOM) != 0 { - // point is below the clip window - x = p0.x + (p1.x - p0.x) * (bounding_box.top() - p0.y) / (p1.y - p0.y); - y = bounding_box.top(); - } else if (outcode_out & RIGHT) != 0 { - // point is to the right of clip window - y = p0.y + (p1.y - p0.y) * (bounding_box.right() - p0.x) / (p1.x - p0.x); - x = bounding_box.right(); - } else if (outcode_out & LEFT) != 0 { - // point is to the left of clip window - y = p0.y + (p1.y - p0.y) * (bounding_box.left() - p0.x) / (p1.x - p0.x); - x = bounding_box.left(); - } - - // Now we move outside point to intersection point to clip - // and get ready for next pass. - if outcode_out == outcode0 { - p0.x = x; - p0.y = y; - outcode0 = out_code(p0.x, p0.y, bounding_box); - } else { - p1.x = x; - p1.y = y; - outcode1 = out_code(p1.x, p1.y, bounding_box); - } - } - } -} diff --git a/libraries/path-bool/src/path/path_cubic_segment_self_intersection.rs b/libraries/path-bool/src/path/path_cubic_segment_self_intersection.rs deleted file mode 100644 index fba671f9c2..0000000000 --- a/libraries/path-bool/src/path/path_cubic_segment_self_intersection.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::path_segment::PathSegment; - -const EPS: f64 = 1e-12; - -pub fn path_cubic_segment_self_intersection(seg: &PathSegment) -> Option<[f64; 2]> { - // https://math.stackexchange.com/questions/3931865/self-intersection-of-a-cubic-bezier-interpretation-of-the-solution - - if let PathSegment::Cubic(p1, p2, p3, p4) = seg { - let ax = -p1.x + 3. * p2.x - 3. * p3.x + p4.x; - let ay = -p1.y + 3. * p2.y - 3. * p3.y + p4.y; - let bx = 3. * p1.x - 6. * p2.x + 3. * p3.x; - let by = 3. * p1.y - 6. * p2.y + 3. * p3.y; - let cx = -3. * p1.x + 3. * p2.x; - let cy = -3. * p1.y + 3. * p2.y; - - let m = ay * bx - ax * by; - let n = ax * cy - ay * cx; - - let k = (-3. * ax * ax * cy * cy + 6. * ax * ay * cx * cy + 4. * ax * bx * by * cy - 4. * ax * by * by * cx - 3. * ay * ay * cx * cx - 4. * ay * bx * bx * cy + 4. * ay * bx * by * cx) - / (ax * ax * by * by - 2. * ax * ay * bx * by + ay * ay * bx * bx); - - if k < 0. { - return None; - } - - let t1 = (n / m + k.sqrt()) / 2.; - let t2 = (n / m - k.sqrt()) / 2.; - - if (EPS..=1. - EPS).contains(&t1) && (EPS..=1. - EPS).contains(&t2) { - let mut result = [t1, t2]; - result.sort_by(|a, b| a.partial_cmp(b).unwrap()); - Some(result) - } else { - None - } - } else { - None - } -} diff --git a/libraries/path-bool/src/path/path_segment.rs b/libraries/path-bool/src/path/path_segment.rs deleted file mode 100644 index eb0b6443ec..0000000000 --- a/libraries/path-bool/src/path/path_segment.rs +++ /dev/null @@ -1,729 +0,0 @@ -//! Defines the `PathSegment` enum and related functionality for representing and -//! manipulating path segments in 2D space. -//! -//! This module provides implementations for various types of path segments including -//! lines, cubic and quadratic Bézier curves, and elliptical arcs. It also includes -//! utility functions for operations such as bounding box calculation, segment splitting, -//! and arc-to-cubic conversion. -//! -//! The implementations in this module closely follow the SVG path specification, -//! making it suitable for use in vector graphics applications. - -use crate::EPS; -use crate::aabb::{Aabb, bounding_box_around_point, expand_bounding_box, extend_bounding_box, merge_bounding_boxes}; -use crate::math::{lerp, vector_angle}; -use glam::{DMat2, DMat3, DVec2}; -use std::f64::consts::{PI, TAU}; - -/// Represents a segment of a path in a 2D space, based on the SVG path specification. -/// -/// This enum closely follows the path segment types defined in the SVG 2 specification. -/// For more details, see: -/// -/// Each variant of this enum corresponds to a different type of path segment: -/// - Line: A straight line between two points. -/// - Cubic: A cubic Bézier curve. -/// - Quadratic: A quadratic Bézier curve. -/// - Arc: An elliptical arc. -/// -/// # Examples -/// -/// Creating a line segment: -/// ``` -/// use path_bool::PathSegment; -/// use glam::DVec2; -/// -/// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(1., 1.)); -/// ``` -/// -/// Creating a cubic Bézier curve: -/// ``` -/// use path_bool::PathSegment; -/// use glam::DVec2; -/// -/// let cubic = PathSegment::Cubic( -/// DVec2::new(0., 0.), -/// DVec2::new(1., 0.), -/// DVec2::new(1., 1.), -/// DVec2::new(2., 1.) -/// ); -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PathSegment { - /// A line segment from the first point to the second. - /// Corresponds to the SVG "L" command. - Line(DVec2, DVec2), - - /// A cubic Bézier curve with start point, two control points, and end point. - /// Corresponds to the SVG "C" command. - Cubic(DVec2, DVec2, DVec2, DVec2), - - /// A quadratic Bézier curve with start point, control point, and end point. - /// Corresponds to the SVG "Q" command. - Quadratic(DVec2, DVec2, DVec2), - - /// An elliptical arc. - /// Corresponds to the SVG "A" command. - /// - /// Parameters: - /// - Start point - /// - X-axis radius - /// - Y-axis radius - /// - X-axis rotation (in radians) - /// - Large arc flag (true if the arc should be greater than or equal to 180 degrees) - /// - Sweep flag (true if the arc should be drawn in a "positive-angle" direction) - /// - End point - Arc(DVec2, f64, f64, f64, bool, bool, DVec2), -} - -impl PathSegment { - /// Calculates the angle of the tangent at the start point of the segment. - /// - /// This method computes the angle (in radians) of the tangent vector at the - /// beginning of the path segment. The angle is measured clockwise - /// from the positive x-axis. - /// - /// # Returns - /// - /// A float representing the angle in radians, normalized to the range [0, 2π). - /// - /// # Examples - /// - /// ``` - /// use path_bool::PathSegment; - /// use glam::DVec2; - /// use std::f64::consts::{TAU, FRAC_PI_4}; - /// - /// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(1., 1.)); - /// assert_eq!(line.start_angle(), TAU - (FRAC_PI_4)); - /// ``` - pub fn start_angle(&self) -> f64 { - let angle = match *self { - PathSegment::Line(start, end) => (end - start).angle_to(DVec2::X), - PathSegment::Cubic(start, control1, control2, _) => { - let diff = control1 - start; - if diff.abs_diff_eq(DVec2::ZERO, EPS.point) { - // if this diff were empty too, the segments would have been converted to a line - (control2 - start).angle_to(DVec2::X) - } else { - diff.angle_to(DVec2::X) - } - } - // Apply same logic as for cubic bezier - PathSegment::Quadratic(start, control, _) => (control - start).to_angle(), - PathSegment::Arc(..) => self.arc_segment_to_cubics(0.001)[0].start_angle(), - }; - use std::f64::consts::TAU; - (angle + TAU) % TAU - } - - /// Computes the curvature at the start point of the segment. - /// - /// The curvature is a measure of how sharply a curve bends. A straight line - /// has a curvature of 0, while a tight curve has a higher curvature value. - /// - /// # Returns - /// - /// A float representing the curvature. Positive values indicate a left - /// curve, while negative values indicate a right curve. - /// - /// # Examples - /// - /// ``` - /// use path_bool::PathSegment; - /// use glam::DVec2; - /// - /// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(1., 1.)); - /// assert_eq!(line.start_curvature(), 0.); - /// - /// let curve = PathSegment::Cubic( - /// DVec2::new(0., 0.), - /// DVec2::new(0., 1.), - /// DVec2::new(1., 1.), - /// DVec2::new(1., 0.) - /// ); - /// assert!(curve.start_curvature() < 0.); - /// ``` - pub fn start_curvature(&self) -> f64 { - match *self { - PathSegment::Line(_, _) => 0., - PathSegment::Cubic(start, control1, control2, _) => { - let a = control1 - start; - let a = 3. * a; - let b = start - 2. * control1 + control2; - let b = 6. * b; - let numerator = a.x * b.y - a.y * b.x; - let denominator = a.length_squared() * a.length(); - if denominator == 0. { 0. } else { numerator / denominator } - } - PathSegment::Quadratic(start, control, end) => { - // First derivative - let a = 2. * (control - start); - // Second derivative - let b = 2. * (start - 2. * control + end); - let numerator = a.x * b.y - a.y * b.x; - let denominator = a.length_squared() * a.length(); - if denominator == 0. { 0. } else { numerator / denominator } - } - PathSegment::Arc(..) => self.arc_segment_to_cubics(0.001)[0].start_curvature(), - } - } - /// Converts the segment to a cubic Bézier curve representation. - /// - /// This method provides a uniform representation of all segment types as - /// cubic Bézier curves. For segments that are not naturally cubic Bézier - /// curves (like lines or quadratic Bézier curves), an equivalent cubic - /// Bézier representation is computed. - /// - /// # Returns - /// - /// An array of four `DVec2` points representing the cubic Bézier curve: - /// [start point, first control point, second control point, end point] - /// - /// # Examples - /// - /// ``` - /// use path_bool::PathSegment; - /// use glam::DVec2; - /// - /// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(1., 1.)); - /// let cubic = line.to_cubic(); - /// assert_eq!(cubic[0], DVec2::new(0., 0.)); - /// assert_eq!(cubic[3], DVec2::new(1., 1.)); - /// ``` - /// - /// # Panics - /// - /// This method is not implemented for `PathSegment::Arc`. Attempting to call - /// `to_cubic()` on an `Arc` segment will result in a panic. - pub fn to_cubic(&self) -> [DVec2; 4] { - match *self { - PathSegment::Line(start, end) => [start, start, end, end], - PathSegment::Cubic(s, c1, c2, e) => [s, c1, c2, e], - PathSegment::Quadratic(start, control, end) => { - // C0 = Q0 - // C1 = Q0 + (2/3) (Q1 - Q0) - // C2 = Q2 + (2/3) (Q1 - Q2) - // C3 = Q2 - let d1 = control - start; - let d2 = control - end; - [start, start + (2. / 3.) * d1, end + (2. / 3.) * d2, end] - } - PathSegment::Arc(..) => unimplemented!(), - } - } - - #[must_use] - /// Retrieves the start point of a path segment. - pub fn start(&self) -> DVec2 { - match self { - PathSegment::Line(start, _) => *start, - PathSegment::Cubic(start, _, _, _) => *start, - PathSegment::Quadratic(start, _, _) => *start, - PathSegment::Arc(start, _, _, _, _, _, _) => *start, - } - } - - #[must_use] - /// Retrieves the end point of a path segment. - pub fn end(&self) -> DVec2 { - match self { - PathSegment::Line(_, end) => *end, - PathSegment::Cubic(_, _, _, end) => *end, - PathSegment::Quadratic(_, _, end) => *end, - PathSegment::Arc(_, _, _, _, _, _, end) => *end, - } - } - - #[must_use] - /// Reverses the direction of the path segment. - /// - /// This method creates a new `PathSegment` that represents the same geometric shape - /// but in the opposite direction. - /// - /// # Examples - /// - /// ``` - /// use path_bool::PathSegment; - /// use glam::DVec2; - /// - /// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(1., 1.)); - /// let reversed = line.reverse(); - /// assert_eq!(reversed.start(), DVec2::new(1., 1.)); - /// assert_eq!(reversed.end(), DVec2::new(0., 0.)); - /// ``` - pub fn reverse(&self) -> PathSegment { - match *self { - PathSegment::Line(start, end) => PathSegment::Line(end, start), - PathSegment::Cubic(p1, p2, p3, p4) => PathSegment::Cubic(p4, p3, p2, p1), - PathSegment::Quadratic(p1, p2, p3) => PathSegment::Quadratic(p3, p2, p1), - PathSegment::Arc(start, rx, ry, phi, fa, fs, end) => PathSegment::Arc(end, rx, ry, phi, fa, !fs, start), - } - } - - #[must_use] - /// Converts an arc segment to its center parameterization. - /// - /// This method is only meaningful for `Arc` segments. For other segment types, - /// it returns `None`. - /// - /// # Returns - /// - /// An `Option` containing `PathArcSegmentCenterParametrization` if the segment - /// is an `Arc`, or `None` otherwise. - pub fn arc_segment_to_center(&self) -> Option { - if let PathSegment::Arc(xy1, rx, ry, phi, fa, fs, xy2) = *self { - if rx == 0. || ry == 0. { - return None; - } - - let rotation_matrix = DMat2::from_angle(-phi.to_radians()); - let xy1_prime = rotation_matrix * (xy1 - xy2) * 0.5; - - let mut rx2 = rx * rx; - let mut ry2 = ry * ry; - let x1_prime2 = xy1_prime.x * xy1_prime.x; - let y1_prime2 = xy1_prime.y * xy1_prime.y; - - let mut rx = rx.abs(); - let mut ry = ry.abs(); - let lambda = x1_prime2 / rx2 + y1_prime2 / ry2 + 1e-12; - if lambda > 1. { - let lambda_sqrt = lambda.sqrt(); - rx *= lambda_sqrt; - ry *= lambda_sqrt; - let lambda_abs = lambda.abs(); - rx2 *= lambda_abs; - ry2 *= lambda_abs; - } - - let sign = if fa == fs { -1. } else { 1. }; - let multiplier = ((rx2 * ry2 - rx2 * y1_prime2 - ry2 * x1_prime2) / (rx2 * y1_prime2 + ry2 * x1_prime2)).sqrt(); - let cx_prime = sign * multiplier * ((rx * xy1_prime.y) / ry); - let cy_prime = sign * multiplier * ((-ry * xy1_prime.x) / rx); - - let cxy = rotation_matrix.transpose() * DVec2::new(cx_prime, cy_prime) + (xy1 + xy2) * 0.5; - - let vec1 = DVec2::new((xy1_prime.x - cx_prime) / rx, (xy1_prime.y - cy_prime) / ry); - let theta1 = vector_angle(DVec2::new(1., 0.), vec1); - let mut delta_theta = vector_angle(vec1, DVec2::new((-xy1_prime.x - cx_prime) / rx, (-xy1_prime.y - cy_prime) / ry)); - - if !fs && delta_theta > 0. { - delta_theta -= TAU; - } else if fs && delta_theta < 0. { - delta_theta += TAU; - } - - Some(PathArcSegmentCenterParametrization { - center: cxy, - theta1, - delta_theta, - rx, - ry, - phi, - }) - } else { - None - } - } - - #[must_use] - /// Samples a point on the path segment at a given parameter value. - /// - /// # Arguments - /// - /// * `t` - A value between 0. and 1. representing the position along the segment. - /// - /// # Examples - /// - /// ``` - /// use path_bool::PathSegment; - /// use glam::DVec2; - /// - /// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(2., 2.)); - /// assert_eq!(line.sample_at(0.5), DVec2::new(1., 1.)); - /// ``` - pub fn sample_at(&self, t: f64) -> DVec2 { - match *self { - PathSegment::Line(start, end) => start.lerp(end, t), - PathSegment::Cubic(p1, p2, p3, p4) => { - let p01 = p1.lerp(p2, t); - let p12 = p2.lerp(p3, t); - let p23 = p3.lerp(p4, t); - let p012 = p01.lerp(p12, t); - let p123 = p12.lerp(p23, t); - p012.lerp(p123, t) - } - PathSegment::Quadratic(p1, p2, p3) => { - let p01 = p1.lerp(p2, t); - let p12 = p2.lerp(p3, t); - p01.lerp(p12, t) - } - PathSegment::Arc(start, rx, ry, phi, _, _, end) => { - if let Some(center_param) = self.arc_segment_to_center() { - let theta = center_param.theta1 + t * center_param.delta_theta; - let p = DVec2::new(rx * theta.cos(), ry * theta.sin()); - let rotation_matrix = DMat2::from_angle(phi); - rotation_matrix * p + center_param.center - } else { - start.lerp(end, t) - } - } - } - } - - #[must_use] - /// Approximates an arc segment with a series of cubic Bézier curves. - /// - /// This method is primarily used for `Arc` segments, converting them into - /// a series of cubic Bézier curves for easier rendering or manipulation. - /// For non-`Arc` segments, it returns a vector containing only the original segment. - /// - /// # Arguments - /// - /// * `max_delta_theta` - The maximum angle (in radians) that each cubic Bézier - /// curve approximation should span. - /// - /// # Returns - /// - /// A vector of `PathSegment::Cubic` approximating the original segment. - pub fn arc_segment_to_cubics(&self, max_delta_theta: f64) -> Vec { - if let PathSegment::Arc(start, rx, ry, phi, _, _, end) = *self { - if let Some(center_param) = self.arc_segment_to_center() { - let count = ((center_param.delta_theta.abs() / max_delta_theta).ceil() as usize).max(1); - - let from_unit = DMat3::from_translation(center_param.center) * DMat3::from_angle(phi.to_radians()) * DMat3::from_scale(DVec2::new(rx, ry)); - - let theta = center_param.delta_theta / count as f64; - let k = (4. / 3.) * (theta / 4.).tan(); - let sin_theta = theta.sin(); - let cos_theta = theta.cos(); - - (0..count) - .map(|i| { - let start = DVec2::new(1., 0.); - let control1 = DVec2::new(1., k); - let control2 = DVec2::new(cos_theta + k * sin_theta, sin_theta - k * cos_theta); - let end = DVec2::new(cos_theta, sin_theta); - - let matrix = DMat3::from_angle(center_param.theta1 + i as f64 * theta) * from_unit; - let start = (matrix * start.extend(1.)).truncate(); - let control1 = (matrix * control1.extend(1.)).truncate(); - let control2 = (matrix * control2.extend(1.)).truncate(); - let end = (matrix * end.extend(1.)).truncate(); - - PathSegment::Cubic(start, control1, control2, end) - }) - .collect() - } else { - vec![PathSegment::Line(start, end)] - } - } else { - vec![*self] - } - } -} - -/// Represents the center parameterization of an elliptical arc. -/// -/// This struct is used internally to perform calculations on arc segments. -pub struct PathArcSegmentCenterParametrization { - center: DVec2, - theta1: f64, - delta_theta: f64, - rx: f64, - ry: f64, - phi: f64, -} - -/// Converts the center parameterization back to an arc segment. -/// -/// # Arguments -/// -/// * `start` - Optional start point of the arc. If `None`, the start point is calculated. -/// * `end` - Optional end point of the arc. If `None`, the end point is calculated. -/// -/// # Returns -/// -/// A `PathSegment::Arc` representing the arc described by this parameterization. -impl PathArcSegmentCenterParametrization { - #[must_use] - pub fn arc_segment_from_center(&self, start: Option, end: Option) -> PathSegment { - let rotation_matrix = DMat2::from_angle(self.phi); - - let mut xy1 = rotation_matrix * DVec2::new(self.rx * self.theta1.cos(), self.ry * self.theta1.sin()) + self.center; - - let mut xy2 = rotation_matrix * DVec2::new(self.rx * (self.theta1 + self.delta_theta).cos(), self.ry * (self.theta1 + self.delta_theta).sin()) + self.center; - - let fa = self.delta_theta.abs() > PI; - let fs = self.delta_theta > 0.; - xy1 = start.unwrap_or(xy1); - xy2 = end.unwrap_or(xy2); - - PathSegment::Arc(xy1, self.rx, self.ry, self.phi, fa, fs, xy2) - } -} - -/// Evaluates a 1D cubic Bézier curve at a given parameter value. -/// -/// # Arguments -/// -/// * `p0`, `p1`, `p2`, `p3` - Control points of the cubic Bézier curve. -/// * `t` - Parameter value between 0 and 1. -/// -/// # Returns -/// -/// The value of the Bézier curve at parameter `t`. -fn eval_cubic_1d(p0: f64, p1: f64, p2: f64, p3: f64, t: f64) -> f64 { - let p01 = lerp(p0, p1, t); - let p12 = lerp(p1, p2, t); - let p23 = lerp(p2, p3, t); - let p012 = lerp(p01, p12, t); - let p123 = lerp(p12, p23, t); - lerp(p012, p123, t) -} - -/// Computes the bounding interval of a 1D cubic Bézier curve. -/// -/// This function finds the minimum and maximum values of a cubic Bézier curve -/// over the interval [0, 1]. -/// -/// # Arguments -/// -/// * `p0`, `p1`, `p2`, `p3` - Control points of the cubic Bézier curve. -/// -/// # Returns -/// -/// A tuple `(min, max)` representing the bounding interval. -fn cubic_bounding_interval(p0: f64, p1: f64, p2: f64, p3: f64) -> (f64, f64) { - let mut min = p0.min(p3); - let mut max = p0.max(p3); - - let a = 3. * (-p0 + 3. * p1 - 3. * p2 + p3); - let b = 6. * (p0 - 2. * p1 + p2); - let c = 3. * (p1 - p0); - let d = b * b - 4. * a * c; - - if d < 0. || a == 0. { - // TODO: if a=0, solve linear - return (min, max); - } - - let sqrt_d = d.sqrt(); - - let t0 = (-b - sqrt_d) / (2. * a); - if 0. < t0 && t0 < 1. { - let x0 = eval_cubic_1d(p0, p1, p2, p3, t0); - min = min.min(x0); - max = max.max(x0); - } - - let t1 = (-b + sqrt_d) / (2. * a); - if 0. < t1 && t1 < 1. { - let x1 = eval_cubic_1d(p0, p1, p2, p3, t1); - min = min.min(x1); - max = max.max(x1); - } - - (min, max) -} - -/// Evaluates a 1D quadratic Bézier curve at a given parameter value. -/// -/// # Arguments -/// -/// * `p0`, `p1`, `p2` - Control points of the quadratic Bézier curve. -/// * `t` - Parameter value between 0 and 1. -/// -/// # Returns -/// -/// The value of the Bézier curve at parameter `t`. -fn eval_quadratic_1d(p0: f64, p1: f64, p2: f64, t: f64) -> f64 { - let p01 = lerp(p0, p1, t); - let p12 = lerp(p1, p2, t); - lerp(p01, p12, t) -} - -/// Computes the bounding interval of a 1D quadratic Bézier curve. -/// -/// This function finds the minimum and maximum values of a quadratic Bézier curve -/// over the interval [0, 1]. -/// -/// # Arguments -/// -/// * `p0`, `p1`, `p2` - Control points of the quadratic Bézier curve. -/// -/// # Returns -/// -/// A tuple `(min, max)` representing the bounding interval. -fn quadratic_bounding_interval(p0: f64, p1: f64, p2: f64) -> (f64, f64) { - let mut min = p0.min(p2); - let mut max = p0.max(p2); - - let denominator = p0 - 2. * p1 + p2; - - if denominator == 0. { - return (min, max); - } - - let t = (p0 - p1) / denominator; - if (0.0..=1.).contains(&t) { - let x = eval_quadratic_1d(p0, p1, p2, t); - min = min.min(x); - max = max.max(x); - } - - (min, max) -} - -fn in_interval(x: f64, x0: f64, x1: f64) -> bool { - (x0..=x1).contains(&x) -} - -impl PathSegment { - /// Computes the bounding box of the path segment. - /// - /// # Returns - /// - /// An [`Aabb`] representing the axis-aligned bounding box of the segment. - pub(crate) fn bounding_box(&self) -> Aabb { - match *self { - PathSegment::Line(start, end) => Aabb::new(start.x.min(end.x), start.y.min(end.y), start.x.max(end.x), start.y.max(end.y)), - PathSegment::Cubic(p1, p2, p3, p4) => { - let (left, right) = cubic_bounding_interval(p1.x, p2.x, p3.x, p4.x); - let (top, bottom) = cubic_bounding_interval(p1.y, p2.y, p3.y, p4.y); - Aabb::new(left, top, right, bottom) - } - PathSegment::Quadratic(p1, p2, p3) => { - let (left, right) = quadratic_bounding_interval(p1.x, p2.x, p3.x); - let (top, bottom) = quadratic_bounding_interval(p1.y, p2.y, p3.y); - Aabb::new(left, top, right, bottom) - } - PathSegment::Arc(start, rx, ry, phi, _, _, end) => { - if let Some(center_param) = self.arc_segment_to_center() { - let theta2 = center_param.theta1 + center_param.delta_theta; - let mut bounding_box = extend_bounding_box(Some(bounding_box_around_point(start, 0.)), end); - - if phi == 0. || rx == ry { - // TODO: Fix the fact that the following gives false positives, resulting in larger boxes - if in_interval(-PI, center_param.theta1, theta2) || in_interval(PI, center_param.theta1, theta2) { - bounding_box = extend_bounding_box(Some(bounding_box), DVec2::new(center_param.center.x - rx, center_param.center.y)); - } - if in_interval(-PI / 2., center_param.theta1, theta2) || in_interval(3. * PI / 2., center_param.theta1, theta2) { - bounding_box = extend_bounding_box(Some(bounding_box), DVec2::new(center_param.center.x, center_param.center.y - ry)); - } - if in_interval(0., center_param.theta1, theta2) || in_interval(2. * PI, center_param.theta1, theta2) { - bounding_box = extend_bounding_box(Some(bounding_box), DVec2::new(center_param.center.x + rx, center_param.center.y)); - } - if in_interval(PI / 2., center_param.theta1, theta2) || in_interval(5. * PI / 2., center_param.theta1, theta2) { - bounding_box = extend_bounding_box(Some(bounding_box), DVec2::new(center_param.center.x, center_param.center.y + ry)); - } - expand_bounding_box(&bounding_box, 1e-11) // TODO: Get rid of expansion - } else { - // TODO: Don't convert to cubics - let cubics = self.arc_segment_to_cubics(PI / 16.); - let mut bounding_box = bounding_box_around_point(start, 0.); - for cubic_seg in cubics { - bounding_box = merge_bounding_boxes(&bounding_box, &cubic_seg.bounding_box()); - } - bounding_box - } - } else { - extend_bounding_box(Some(bounding_box_around_point(start, 0.)), end) - } - } - } - } - - /// Computes a loose bounding box that surrounds all anchors, but also the handles of cubic and quadratic segments. - /// This will usually be larger than the actual bounding box, but is faster to compute because it does not have to find where each curve reaches its maximum and minimum. - pub(crate) fn approx_bounding_box(&self) -> Aabb { - match *self { - PathSegment::Cubic(p1, p2, p3, p4) => { - // Use the control points to create a bounding box - let left = p1.x.min(p2.x).min(p3.x).min(p4.x); - let right = p1.x.max(p2.x).max(p3.x).max(p4.x); - let top = p1.y.min(p2.y).min(p3.y).min(p4.y); - let bottom = p1.y.max(p2.y).max(p3.y).max(p4.y); - Aabb::new(left, top, right, bottom) - } - PathSegment::Quadratic(p1, p2, p3) => { - // Use the control points to create a bounding box - let left = p1.x.min(p2.x).min(p3.x); - let right = p1.x.max(p2.x).max(p3.x); - let top = p1.y.min(p2.y).min(p3.y); - let bottom = p1.y.max(p2.y).max(p3.y); - Aabb::new(left, top, right, bottom) - } - seg => seg.bounding_box(), - } - } - - /// Splits the path segment at a given parameter value. - /// - /// # Arguments - /// - /// * `t` - A value between 0. and 1. representing the split point along the segment. - /// - /// # Returns - /// - /// A tuple of two `PathSegment`s representing the parts before and after the split point. - /// - /// # Examples - /// - /// ``` - /// use path_bool::PathSegment; - /// use glam::DVec2; - /// - /// let line = PathSegment::Line(DVec2::new(0., 0.), DVec2::new(2., 2.)); - /// let (first_half, second_half) = line.split_at(0.5); - /// assert_eq!(first_half.end(), DVec2::new(1., 1.)); - /// assert_eq!(second_half.start(), DVec2::new(1., 1.)); - /// ``` - pub fn split_at(&self, t: f64) -> (PathSegment, PathSegment) { - match *self { - PathSegment::Line(start, end) => { - let p = start.lerp(end, t); - (PathSegment::Line(start, p), PathSegment::Line(p, end)) - } - PathSegment::Cubic(p0, p1, p2, p3) => { - let p01 = p0.lerp(p1, t); - let p12 = p1.lerp(p2, t); - let p23 = p2.lerp(p3, t); - let p012 = p01.lerp(p12, t); - let p123 = p12.lerp(p23, t); - let p = p012.lerp(p123, t); - - (PathSegment::Cubic(p0, p01, p012, p), PathSegment::Cubic(p, p123, p23, p3)) - } - PathSegment::Quadratic(p0, p1, p2) => { - let p01 = p0.lerp(p1, t); - let p12 = p1.lerp(p2, t); - let p = p01.lerp(p12, t); - - (PathSegment::Quadratic(p0, p01, p), PathSegment::Quadratic(p, p12, p2)) - } - PathSegment::Arc(start, _, _, _, _, _, end) => { - if let Some(center_param) = self.arc_segment_to_center() { - let mid_delta_theta = center_param.delta_theta * t; - let seg1 = PathArcSegmentCenterParametrization { - delta_theta: mid_delta_theta, - ..center_param - } - .arc_segment_from_center(Some(start), None); - let seg2 = PathArcSegmentCenterParametrization { - theta1: center_param.theta1 + mid_delta_theta, - delta_theta: center_param.delta_theta - mid_delta_theta, - ..center_param - } - .arc_segment_from_center(None, Some(end)); - (seg1, seg2) - } else { - // https://svgwg.org/svg2-draft/implnote.html#ArcCorrectionOutOfRangeRadii - let p = start.lerp(end, t); - (PathSegment::Line(start, p), PathSegment::Line(p, end)) - } - } - } - } -} diff --git a/libraries/path-bool/src/path_boolean.rs b/libraries/path-bool/src/path_boolean.rs deleted file mode 100644 index 34d178559e..0000000000 --- a/libraries/path-bool/src/path_boolean.rs +++ /dev/null @@ -1,1969 +0,0 @@ -//! Implements boolean operations on paths using graph-based algorithms. -//! -//! This module uses concepts from graph theory to efficiently perform boolean -//! operations on complex paths. The main algorithms involve creating a graph -//! representation of the paths, simplifying this graph, and then working with -//! its dual graph to determine the result of the boolean operation. -//! -//! ## Graph Minor -//! -//! A graph minor is a simplified version of a graph, obtained by contracting edges -//! (merging connected vertices) and removing isolated vertices. In the context of -//! path boolean operations, we use a graph minor to simplify the initial graph -//! representation of the paths. This simplification involves: -//! -//! 1. Merging collinear segments into single edges. -//! 2. Removing vertices that don't represent significant features (like intersections -//! or endpoints). -//! -//! The resulting graph minor preserves the topological structure of the paths while -//! reducing computational complexity. -//! -//! For more information on graph minors, see: -//! -//! -//! ## Dual Graph -//! -//! The dual graph is a graph derived from another graph (the primal graph). In the -//! context of path boolean operations, we construct the dual graph as follows: -//! -//! 1. Each face (region) in the primal graph becomes a vertex in the dual graph. -//! 2. Each edge in the primal graph becomes an edge in the dual graph, connecting -//! the vertices that represent the faces on either side of the original edge. -//! -//! The dual graph allows us to efficiently determine which regions are inside or -//! outside the original paths, which is crucial for performing boolean operations. -//! -//! For more information on dual graphs, see: -//! -//! -//! ## Algorithm Overview -//! -//! The boolean operation algorithm follows these main steps: -//! -//! 1. Create a graph representation of both input paths (MajorGraph). -//! 2. Simplify this graph to create a graph minor (MinorGraph). -//! 3. Construct the dual graph of the MinorGraph. -//! 4. Use the dual graph to determine which regions should be included in the result, -//! based on the specific boolean operation being performed. -//! 5. Reconstruct the resulting path(s) from the selected regions. -//! -//! This approach allows for efficient and accurate boolean operations, even on -//! complex paths with many intersections or self-intersections. - -new_key_type! { - pub struct MajorVertexKey; - pub struct MajorEdgeKey; - pub struct MinorVertexKey; - pub struct MinorEdgeKey; - pub struct DualVertexKey; - pub struct DualEdgeKey; -} -// Copyright 2024 Adam Platkevič -// -// SPDX-License-Identifier: MIT - -use crate::aabb::{Aabb, bounding_box_max_extent, extend_bounding_box, merge_bounding_boxes}; -use crate::epsilons::Epsilons; -use crate::grid::{BitVec, Grid}; -use crate::intersection_path_segment::{path_segment_intersection, segments_equal}; -use crate::path::Path; -use crate::path_cubic_segment_self_intersection::path_cubic_segment_self_intersection; -use crate::path_segment::PathSegment; -#[cfg(feature = "logging")] -use crate::path_to_path_data; - -use glam::{BVec2, DVec2, I64Vec2}; -use roots::{Roots, find_roots_cubic}; -use rustc_hash::FxHashMap as HashMap; -use rustc_hash::FxHashSet as HashSet; -use slotmap::{SlotMap, new_key_type}; -use smallvec::SmallVec; -use std::cmp::Ordering; -use std::collections::VecDeque; -use std::fmt::Display; - -fn new_hash_map(capacity: usize) -> HashMap { - HashMap::with_capacity_and_hasher(capacity, Default::default()) -} - -/// Represents the types of boolean operations that can be performed on paths. -#[derive(Debug, Clone, Copy)] -pub enum PathBooleanOperation { - /// Computes the union of two paths. - /// - /// The result contains all areas that are inside either path A or path B (or both). - /// This operation is useful for combining shapes or creating complex outlines. - Union, - - /// Computes the difference between two paths (A minus B). - /// - /// The result contains all areas that are inside path A but not inside path B. - /// This operation is useful for cutting holes or subtracting shapes from each other. - Difference, - - /// Computes the intersection of two paths. - /// - /// The result contains only the areas that are inside both path A and path B. - /// This operation is useful for finding overlapping regions between shapes. - Intersection, - - /// Computes the symmetric difference (exclusive or) of two paths. - /// - /// The result contains areas that are inside either path A or path B, but not in both. - /// This operation is useful for creating non-overlapping regions or finding boundaries. - Exclusion, - - /// Divides the first path using the second path as a "knife". - /// - /// This operation splits path A wherever it intersects with path B, but keeps all - /// parts of path A. It's useful for creating segments or partitioning shapes. - Division, - - /// Breaks both paths into separate pieces where they intersect. - /// - /// This operation splits both path A and path B at their intersection points, - /// resulting in all possible non-overlapping segments from both paths. - /// It's useful for creating detailed breakdowns of overlapping shapes. - Fracture, -} - -/// Specifies how to determine the "inside" of a path for filling. -#[derive(Debug, Clone, Copy)] -pub enum FillRule { - /// A point is inside if a ray from the point to infinity crosses an odd number of path segments. - NonZero, - /// A point is inside if a ray from the point to infinity crosses an even number of path segments. - EvenOdd, -} - -pub const EPS: Epsilons = Epsilons { - point: 1e-5, - linear: 1e-4, - param: 1e-8, -}; - -type MajorGraphEdgeStage1 = (PathSegment, u8); -type MajorGraphEdgeStage2 = (PathSegment, u8, Aabb); - -#[derive(Debug, Clone)] -pub struct MajorGraphEdge { - seg: PathSegment, - parent: u8, - incident_vertices: [MajorVertexKey; 2], - direction_flag: Direction, - twin: Option, -} - -#[derive(Debug, Clone, Default)] -pub struct MajorGraphVertex { - #[cfg_attr(not(feature = "logging"), expect(dead_code))] - pub point: DVec2, - outgoing_edges: SmallVec<[MajorEdgeKey; 4]>, -} - -/// Represents the initial graph structure used in boolean operations. -/// -/// This graph contains all segments from both input paths. -#[derive(Debug, Clone)] -struct MajorGraph { - edges: SlotMap, - vertices: SlotMap, -} - -#[derive(Debug, Clone, PartialEq)] -struct MinorGraphEdge { - segments: SmallVec<[PathSegment; 4]>, - parent: u8, - incident_vertices: [MinorVertexKey; 2], - direction_flag: Direction, - twin: Option, -} - -impl MinorGraphEdge { - fn start_segment(&self) -> PathSegment { - let segment = self.segments[0]; - match self.direction_flag { - Direction::Forward => segment, - Direction::Backwards => segment.reverse(), - } - } -} - -// Compares Segments based on their derivative at the start. If the derivative -// is equal, check the curvature instead. This should correctly sort most instances. -fn compare_segments(a: &PathSegment, b: &PathSegment) -> Ordering { - let angle_a = a.start_angle(); - let angle_b = b.start_angle(); - - // Normalize angles to [0, 2π) - let angle_a = (angle_a * 1000.).round() / 1000.; - let angle_b = (angle_b * 1000.).round() / 1000.; - - // Compare angles first - match angle_b.partial_cmp(&angle_a) { - Some(Ordering::Equal) => { - // If angles are equal (or very close), compare curvatures - let curvature_a = a.start_curvature(); - let curvature_b = b.start_curvature(); - curvature_a.partial_cmp(&curvature_b).unwrap_or(Ordering::Equal) - } - Some(ordering) => ordering, - None => Ordering::Equal, // Handle NaN cases - } -} - -impl PartialOrd for MinorGraphEdge { - fn partial_cmp(&self, other: &Self) -> Option { - Some(compare_segments(&self.start_segment(), &other.start_segment())) - } -} - -#[derive(Debug, Clone, Default)] -struct MinorGraphVertex { - outgoing_edges: SmallVec<[MinorEdgeKey; 8]>, -} - -#[derive(Debug, Clone)] -struct MinorGraphCycle { - segments: Vec, - parent: u8, - direction_flag: Direction, -} - -/// Represents a simplified graph structure derived from the MajorGraph. -/// -/// This graph combines collinear segments and removes unnecessary vertices. -#[derive(Debug, Clone)] -struct MinorGraph { - edges: SlotMap, - vertices: SlotMap, - cycles: Vec, -} - -#[derive(Debug, Clone, PartialEq)] -struct DualGraphHalfEdge { - segments: Vec, - parent: u8, - incident_vertex: DualVertexKey, - direction_flag: Direction, - twin: Option, -} - -impl DualGraphHalfEdge { - fn start_segment(&self) -> PathSegment { - let segment = self.segments[0]; - match self.direction_flag { - Direction::Forward => segment, - Direction::Backwards => segment.reverse(), - } - } - - fn outer_boundnig_box(&self) -> Aabb { - self.segments - .iter() - .map(|seg| seg.approx_bounding_box()) - .fold(Default::default(), |old, new| merge_bounding_boxes(&old, &new)) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct DualGraphVertex { - incident_edges: Vec, -} - -/// Represents a component in the dual graph. -/// -/// A component is a connected subset of the dual graph, typically corresponding -/// to a distinct region in the original paths. -#[derive(Debug, Clone)] -struct DualGraphComponent { - edges: Vec, - vertices: Vec, - outer_face: Option, - inner_bb: Aabb, - outer_bb: Aabb, -} - -/// Represents the dual graph of the MinorGraph. -/// -/// In this graph, faces of the MinorGraph become vertices, and edges represent -/// adjacency between faces. This structure is crucial for determining the -/// inside/outside regions of the paths. -#[derive(Debug, Clone)] -struct DualGraph { - components: Vec, - edges: SlotMap, - vertices: SlotMap, -} - -/// Represents the hierarchical nesting of regions in the paths. -/// -/// This tree structure captures how different regions of the paths are contained -/// within each other -#[derive(Debug, Clone)] -struct NestingTree { - component: DualGraphComponent, - outgoing_edges: HashMap>, -} - -#[cfg(feature = "logging")] -fn major_graph_to_dot(graph: &MajorGraph) -> String { - let mut dot = String::from("digraph {\n"); - for (vertex_key, vertex) in &graph.vertices { - dot.push_str(&format!(" {:?} [label=\"{:.1},{:.1}\"]\n", (vertex_key.0.as_ffi() & 0xFF), vertex.point.x, vertex.point.y)); - } - for (_, edge) in &graph.edges { - dot.push_str(&format!( - " {:?} -> {:?}: {:0b}\n", - (edge.incident_vertices[0].0.as_ffi() & 0xFF), - (edge.incident_vertices[1].0.as_ffi() & 0xFF), - edge.parent - )); - } - dot.push_str("}\n"); - dot -} - -#[cfg(feature = "logging")] -fn minor_graph_to_dot(edges: &SlotMap) -> String { - let mut dot = String::from("digraph {\n"); - for edge in edges.values() { - dot.push_str(&format!( - " {:?} -> {:?}: {:0b}\n", - (edge.incident_vertices[0].0.as_ffi() & 0xFF), - (edge.incident_vertices[1].0.as_ffi() & 0xFF), - edge.parent - )); - } - dot.push_str("}\n"); - dot -} - -#[cfg(feature = "logging")] -fn dual_graph_to_dot(components: &[DualGraphComponent], edges: &SlotMap) -> String { - let mut dot = String::from("strict graph {\n"); - for component in components { - for &edge_key in &component.edges { - let edge = &edges[edge_key]; - dot.push_str(&format!( - " {:?} -- {:?}\n", - (edge.incident_vertex.0.as_ffi() & 0xFF), - (edges[edge.twin.unwrap()].incident_vertex.0.as_ffi() & 0xFF) - )); - } - } - dot.push_str("}\n"); - dot -} - -fn segment_to_edge(parent: u8) -> impl Fn(&PathSegment) -> Option { - move |seg| { - if bounding_box_max_extent(&seg.bounding_box()) < EPS.point / 2. { - return None; - } - - match seg { - // Convert Line Segments expressed as cubic beziers to proper line segments - PathSegment::Cubic(start, _, _, end) => { - let direction = seg.sample_at(0.1); - if (*end - *start).angle_to(direction - *start).abs() < EPS.point * 4. { - Some((PathSegment::Line(*start, *end), parent)) - } else { - Some((*seg, parent)) - } - } - seg => Some((*seg, parent)), - } - } -} - -fn split_at_self_intersections(edges: &mut Vec) { - let mut new_edges = Vec::new(); - for (seg, parent) in edges.iter_mut() { - if let PathSegment::Cubic(..) = seg { - if let Some(intersection) = path_cubic_segment_self_intersection(seg) { - let mut intersection = intersection; - if intersection[0] > intersection[1] { - intersection.swap(0, 1); - } - let [t1, t2] = intersection; - if (t1 - t2).abs() < EPS.param { - let (seg1, seg2) = seg.split_at(t1); - *seg = seg1; - new_edges.push((seg2, *parent)); - } else { - let (seg1, tmp_seg) = seg.split_at(t1); - let (seg2, seg3) = &tmp_seg.split_at((t2 - t1) / (1. - t1)); - *seg = seg1; - new_edges.push((*seg2, *parent)); - new_edges.push((*seg3, *parent)); - } - } - } - } - edges.extend(new_edges); -} - -/// Splits path segments at their intersections with other segments. -/// -/// This function performs the following steps: -/// 1. Computes bounding boxes for all input edges. -/// 2. Creates a spatial index (quad tree) of edges for efficient intersection checks. -/// 3. For each edge: -/// a. Finds potential intersecting edges using the spatial index. -/// b. Computes precise intersections with these candidates. -/// c. Records the intersection points as split locations. -/// 4. Splits the original edges at the recorded intersection points. -/// 5. Returns the split edges along with an overall bounding box. -/// -/// The function uses an epsilon value to handle floating-point imprecision -/// when determining if intersections occur at endpoints. -/// -/// # Arguments -/// -/// * `edges` - A slice of initial path segments (MajorGraphEdgeStage1). -/// -/// # Returns -/// -/// A tuple containing: -/// * A vector of split edges (MajorGraphEdgeStage2). -/// * An optional overall bounding box (AaBb) for all edges. -fn split_at_intersections(edges: &[MajorGraphEdgeStage1]) -> Vec { - // Step 1: Add bounding boxes to edges - let with_bounding_box: Vec = edges.iter().map(|(seg, parent)| (*seg, *parent, seg.approx_bounding_box())).collect(); - // Step 2: Calculate total bounding box - let total_bounding_box = with_bounding_box.iter().fold(Default::default(), |acc, (_, _, bb)| merge_bounding_boxes(&acc, bb)); - - let max_extent = bounding_box_max_extent(&total_bounding_box); - let cell_size = max_extent / (edges.len() as f64).sqrt(); - - // Step 3: Create grid for efficient intersection checks - let mut grid = Grid::new(cell_size, edges.len()); - - // Step 3: Create edge tree for efficient intersection checks - // let mut edge_tree = QuadTree::new(total_bounding_box, INTERSECTION_TREE_DEPTH, 16); - // let mut rtree = crate::util::rtree::RTree::new(24); - - let mut splits_per_edge: Vec> = vec![Vec::new(); edges.len()]; - - fn add_split(splits_per_edge: &mut [Vec], i: usize, t: f64) { - splits_per_edge[i].push(t); - } - // let mut candidates = Vec::with_capacity(8); - let mut candidates = BitVec::new(edges.len()); - - // Step 4: Find intersections and record split points - for (i, edge) in with_bounding_box.iter().enumerate() { - // let candidates = edge_tree.find(&edge.2); - // let mut quad_candidates: Vec<_> = quad_candidates.into_iter().collect(); - // quad_candidates.sort_unstable(); - // let mut candidates = rtree.query(&edge.2); - // candidates.sort_unstable(); - // assert_eq!(candidates, quad_candidates); - candidates.clear(); - grid.query(&edge.2, &mut candidates); - - for j in candidates.iter_set_bits() { - let candidate: &(PathSegment, u8) = &edges[j]; - let include_endpoints = edge.1 != candidate.1 || !(candidate.0.end().abs_diff_eq(edge.0.start(), EPS.point) || candidate.0.start().abs_diff_eq(edge.0.end(), EPS.point)); - let intersection = path_segment_intersection(&edge.0, &candidate.0, include_endpoints, &EPS); - for [t0, t1] in intersection { - add_split(&mut splits_per_edge, i, t0); - add_split(&mut splits_per_edge, j, t1); - } - } - grid.insert(&edge.2, i); - // edge_tree.insert(edge.2, i); - // rtree.insert(edge.2, i); - } - - // Step 5: Apply splits to create new edges - let mut new_edges = Vec::new(); - - for (i, (seg, parent, _)) in with_bounding_box.into_iter().enumerate() { - if let Some(splits) = splits_per_edge.get(i) { - let mut splits = splits.clone(); - splits.sort_by(|a, b| a.partial_cmp(b).unwrap()); - let mut tmp_seg = seg; - let mut prev_t = 0.; - for &t in splits.iter() { - if t > 1. - EPS.param { - break; - } - let tt = (t - prev_t) / (1. - prev_t); - prev_t = t; - if tt < EPS.param { - continue; - } - if tt > 1. - EPS.param { - continue; - } - let (seg1, seg2) = tmp_seg.split_at(tt); - new_edges.push((seg1, parent)); - tmp_seg = seg2; - } - new_edges.push((tmp_seg, parent)); - } else { - new_edges.push((seg, parent)); - } - } - - new_edges -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Direction { - Forward, - Backwards, -} - -impl std::ops::Neg for Direction { - type Output = Self; - - fn neg(self) -> Self::Output { - match self { - Self::Forward => Self::Backwards, - Self::Backwards => Self::Forward, - } - } -} -impl std::ops::Not for Direction { - type Output = Self; - - fn not(self) -> Self::Output { - match self { - Self::Forward => Self::Backwards, - Self::Backwards => Self::Forward, - } - } -} - -impl Direction { - pub fn forward(self) -> bool { - self == Self::Forward - } -} - -const ROUNDING_FACTOR: f64 = 1.0 / (2. * EPS.point); - -fn round_point(point: DVec2) -> I64Vec2 { - (point * ROUNDING_FACTOR).round().as_i64vec2() -} - -type Edges = SmallVec<[(PathSegment, u8, MajorEdgeKey, MajorEdgeKey); 2]>; - -fn find_vertices(edges: &[MajorGraphEdgeStage1]) -> MajorGraph { - let mut graph = MajorGraph { - edges: SlotMap::with_capacity_and_key(edges.len() * 2), - vertices: SlotMap::with_capacity_and_key(edges.len()), - }; - - let mut vertex_pair_id_to_edges: HashMap<_, Edges> = new_hash_map(edges.len()); - let mut vertex_hashmap: HashMap = new_hash_map(edges.len() * 2); - - for (seg, parent) in edges { - let mut get_vertex = |point: DVec2| -> MajorVertexKey { - let rounded = round_point(point); - for dx in -1..=1 { - for dy in -1..=1 { - let offset = I64Vec2::new(dx, dy); - if let Some(&vertex) = vertex_hashmap.get(&(rounded + offset)) { - return vertex; - } - } - } - - let vertex_key = graph.vertices.insert(MajorGraphVertex { - point, - outgoing_edges: SmallVec::new(), - }); - vertex_hashmap.insert(rounded, vertex_key); - vertex_key - }; - // we should subtract the center instead here - let start_vertex = get_vertex(seg.start()); - let end_vertex = get_vertex(seg.end()); - - if start_vertex == end_vertex { - match seg { - PathSegment::Line(..) => continue, - PathSegment::Cubic(_, c1, c2, _) => { - if c1.abs_diff_eq(*c2, EPS.point) { - continue; - } - } - PathSegment::Quadratic(_, c, _) => { - if seg.start().abs_diff_eq(*c, EPS.point) { - continue; - } - } - PathSegment::Arc(_, _, _, _, _, false, _) => continue, - _ => {} - } - } - - let vertex_pair_id = (start_vertex.min(end_vertex), start_vertex.max(end_vertex)); - if let Some(existing_edges) = vertex_pair_id_to_edges.get(&vertex_pair_id) { - if let Some(existing_edge) = existing_edges - .iter() - .find(|(other_seg, ..)| segments_equal(seg, other_seg, EPS.point) || segments_equal(&seg.reverse(), other_seg, EPS.point)) - { - if existing_edge.1 != *parent { - graph.edges[existing_edge.2].parent = 0b11; - graph.edges[existing_edge.3].parent = 0b11; - } - continue; - } - } - - let fwd_edge_key = graph.edges.insert(MajorGraphEdge { - seg: *seg, - parent: *parent, - incident_vertices: [start_vertex, end_vertex], - direction_flag: Direction::Forward, - twin: None, - }); - - let bwd_edge_key = graph.edges.insert(MajorGraphEdge { - seg: *seg, - parent: *parent, - incident_vertices: [end_vertex, start_vertex], - direction_flag: Direction::Backwards, - twin: Some(fwd_edge_key), - }); - - graph.edges[fwd_edge_key].twin = Some(bwd_edge_key); - - graph.vertices[start_vertex].outgoing_edges.push(fwd_edge_key); - graph.vertices[end_vertex].outgoing_edges.push(bwd_edge_key); - - vertex_pair_id_to_edges.entry(vertex_pair_id).or_default().push((*seg, *parent, fwd_edge_key, bwd_edge_key)); - } - - graph -} - -fn get_order(vertex: &MajorGraphVertex) -> usize { - vertex.outgoing_edges.len() -} - -/// Computes the minor graph from the major graph. -/// -/// This function simplifies the graph structure by performing the following steps: -/// 1. Iterates through vertices of the major graph. -/// 2. For vertices with exactly two edges (degree 2): -/// a. Combines the two edges into a single edge if they have the same parent. -/// b. Updates the endpoints of the new edge to skip the current vertex. -/// 3. For vertices with degree != 2: -/// a. Creates a new vertex in the minor graph. -/// b. Creates new edges in the minor graph for each outgoing edge. -/// 4. Handles any cyclic components (closed loops with no high-degree vertices). -/// -/// The resulting minor graph preserves the topological structure of the paths -/// while reducing the number of vertices and edges. -/// -/// # Arguments -/// -/// * `major_graph` - A reference to the MajorGraph. -/// -/// # Returns -/// -/// A new MinorGraph representing the simplified structure. -fn compute_minor(major_graph: &MajorGraph) -> MinorGraph { - let vertex_count = major_graph.vertices.len(); - let edge_count = major_graph.edges.len(); - let mut new_edges = SlotMap::with_capacity_and_key(edge_count / 2); - let mut new_vertices = SlotMap::with_capacity_and_key(vertex_count.ilog2() as usize + 2); - let mut to_minor_vertex = new_hash_map(vertex_count); - let mut id_to_edge = new_hash_map(edge_count); - // merge with to_minor_vertex - let mut visited = HashSet::with_capacity_and_hasher(vertex_count, Default::default()); - - // Handle components that are not cycles - for (major_vertex_key, vertex) in &major_graph.vertices { - // Edges are contracted - if get_order(vertex) == 2 { - continue; - } - let start_vertex = *to_minor_vertex - .entry(major_vertex_key) - .or_insert_with(|| new_vertices.insert(MinorGraphVertex { outgoing_edges: SmallVec::new() })); - - for &start_edge_key in &vertex.outgoing_edges { - let mut segments = SmallVec::new(); - let mut edge_key = start_edge_key; - let mut edge = &major_graph.edges[edge_key]; - - while edge.parent == major_graph.edges[start_edge_key].parent - && edge.direction_flag == major_graph.edges[start_edge_key].direction_flag - && get_order(&major_graph.vertices[edge.incident_vertices[1]]) == 2 - { - segments.push(edge.seg); - visited.insert(edge.incident_vertices[1]); - let next_vertex = &major_graph.vertices[edge.incident_vertices[1]]; - // Choose the edge which is not our twin so we can make progress - edge_key = *next_vertex.outgoing_edges.iter().find(|&&e| Some(e) != edge.twin).unwrap(); - edge = &major_graph.edges[edge_key]; - } - segments.push(edge.seg); - - let end_vertex = *to_minor_vertex - .entry(edge.incident_vertices[1]) - .or_insert_with(|| new_vertices.insert(MinorGraphVertex { outgoing_edges: SmallVec::new() })); - assert!(major_graph.edges[start_edge_key].twin.is_some()); - assert!(edge.twin.is_some()); - - let edge_id = (start_edge_key, edge_key); - let twin_id = (edge.twin.unwrap(), major_graph.edges[start_edge_key].twin.unwrap()); - - let twin_key = id_to_edge.get(&twin_id); - - let new_edge_key = new_edges.insert(MinorGraphEdge { - segments, - parent: major_graph.edges[start_edge_key].parent, - incident_vertices: [start_vertex, end_vertex], - direction_flag: major_graph.edges[start_edge_key].direction_flag, - twin: twin_key.copied(), - }); - if let Some(&twin_key) = twin_key { - new_edges[twin_key].twin = Some(new_edge_key); - } - id_to_edge.insert(edge_id, new_edge_key); - new_vertices[start_vertex].outgoing_edges.push(new_edge_key); - } - } - - // Handle cyclic components (if any) - let mut cycles = Vec::new(); - for (major_vertex_key, vertex) in &major_graph.vertices { - if vertex.outgoing_edges.len() != 2 || visited.contains(&major_vertex_key) { - continue; - } - let mut edge_key = vertex.outgoing_edges[0]; - let mut edge = &major_graph.edges[edge_key]; - let mut cycle = MinorGraphCycle { - segments: Vec::with_capacity(4), - parent: edge.parent, - direction_flag: edge.direction_flag, - }; - loop { - cycle.segments.push(edge.seg); - visited.insert(edge.incident_vertices[0]); - assert_eq!(major_graph.vertices[edge.incident_vertices[1]].outgoing_edges.len(), 2, "Found an unvisited vertex of order != 2."); - let next_vertex = &major_graph.vertices[edge.incident_vertices[1]]; - edge_key = *next_vertex.outgoing_edges.iter().find(|&&e| Some(e) != edge.twin).unwrap(); - edge = &major_graph.edges[edge_key]; - if edge.incident_vertices[0] == major_vertex_key { - break; - } - } - cycles.push(cycle); - } - - MinorGraph { - edges: new_edges, - vertices: new_vertices, - cycles, - } -} - -fn remove_dangling_edges(graph: &mut MinorGraph) { - // Basically DFS for each parent with BFS number - fn walk(parent: u8, graph: &MinorGraph) -> HashSet { - // merge - let vertex_count = graph.vertices.len(); - let mut kept_vertices = HashSet::with_capacity_and_hasher(vertex_count, Default::default()); - let mut vertex_to_level = new_hash_map(vertex_count); - - fn visit( - vertex: MinorVertexKey, - incoming_edge: Option, - level: usize, - graph: &MinorGraph, - vertex_to_level: &mut HashMap, - kept_vertices: &mut HashSet, - parent: u8, - ) -> usize { - if let Some(&existing_level) = vertex_to_level.get(&vertex) { - return existing_level; - } - vertex_to_level.insert(vertex, level); - - let mut min_level = usize::MAX; - for &edge_key in &graph.vertices[vertex].outgoing_edges { - let edge = &graph.edges[edge_key]; - if edge.parent & parent != 0 && Some(edge_key) != incoming_edge { - min_level = min_level.min(visit(edge.incident_vertices[1], edge.twin, level + 1, graph, vertex_to_level, kept_vertices, parent)); - } - } - - if min_level <= level { - kept_vertices.insert(vertex); - } - - min_level - } - - for edge in graph.edges.values() { - if edge.parent & parent != 0 { - visit(edge.incident_vertices[0], None, 0, graph, &mut vertex_to_level, &mut kept_vertices, parent); - } - } - - kept_vertices - } - - let kept_vertices_a = walk(1, graph); - let kept_vertices_b = walk(2, graph); - - graph.vertices.retain(|k, _| kept_vertices_a.contains(&k) || kept_vertices_b.contains(&k)); - - for vertex in graph.vertices.values_mut() { - vertex.outgoing_edges.retain(|edge_key| { - let edge = &graph.edges[*edge_key]; - (edge.parent & 1 == 1 && kept_vertices_a.contains(&edge.incident_vertices[0]) && kept_vertices_a.contains(&edge.incident_vertices[1])) - || (edge.parent & 2 == 2 && kept_vertices_b.contains(&edge.incident_vertices[0]) && kept_vertices_b.contains(&edge.incident_vertices[1])) - }); - } - // TODO(@TrueDoctor): merge - graph.edges.retain(|_, edge| { - (edge.parent & 1 == 1 && kept_vertices_a.contains(&edge.incident_vertices[0]) && kept_vertices_a.contains(&edge.incident_vertices[1])) - || (edge.parent & 2 == 2 && kept_vertices_b.contains(&edge.incident_vertices[0]) && kept_vertices_b.contains(&edge.incident_vertices[1])) - }); -} - -fn sort_outgoing_edges_by_angle(graph: &mut MinorGraph) { - for (vertex_key, vertex) in graph.vertices.iter_mut() { - if vertex.outgoing_edges.len() > 2 { - vertex.outgoing_edges.sort_by(|&a, &b| graph.edges[a].partial_cmp(&graph.edges[b]).unwrap()); - if cfg!(feature = "logging") { - eprintln!("Outgoing edges for {vertex_key:?}:"); - for &edge_key in &vertex.outgoing_edges { - let edge = &graph.edges[edge_key]; - let angle = edge.start_segment().start_angle(); - eprintln!("{:?}: {}°", edge_key.0, angle.to_degrees()) - } - } - } - } -} - -#[cfg(feature = "logging")] -fn face_to_polygon(face: &DualGraphVertex, edges: &SlotMap) -> Vec { - const CNT: usize = 3; - - face.incident_edges - .iter() - .flat_map(|&edge_key| { - let edge = &edges[edge_key]; - edge.segments.iter().flat_map(move |seg| { - (0..CNT).map(move |i| { - let t0 = i as f64 / CNT as f64; - let t = if edge.direction_flag.forward() { t0 } else { 1. - t0 }; - seg.sample_at(t) - }) - }) - }) - .collect() -} - -fn interval_crosses_point(a: f64, b: f64, p: f64) -> bool { - let dy1 = a >= p; - let dy2 = b < p; - dy1 == dy2 -} - -fn line_segment_intersects_horizontal_ray(a: DVec2, b: DVec2, point: DVec2) -> bool { - if !interval_crosses_point(a.y, b.y, point.y) { - return false; - } - let x = crate::math::lin_map(point.y, a.y, b.y, a.x, b.x); - x >= point.x -} - -#[cfg(feature = "logging")] -fn compute_point_winding(polygon: &[DVec2], tested_point: DVec2) -> i32 { - if polygon.len() <= 2 { - return 0; - } - let mut prev_point = polygon[polygon.len() - 1]; - let mut winding = 0; - for &point in polygon { - if line_segment_intersects_horizontal_ray(prev_point, point, tested_point) { - winding += if point.y > prev_point.y { -1 } else { 1 }; - } - prev_point = point; - } - winding -} - -#[cfg(feature = "logging")] -fn compute_winding(face: &DualGraphVertex, edges: &SlotMap) -> Option { - let polygon = face_to_polygon(face, edges); - - for i in 0..polygon.len() { - let a = polygon[i]; - let b = polygon[(i + 1) % polygon.len()]; - let c = polygon[(i + 2) % polygon.len()]; - let center = (a + b + c) / 3.; - let winding = compute_point_winding(&polygon, center); - if winding != 0 { - return Some(winding); - } - } - - None -} - -fn compute_signed_area(face: &DualGraphVertex, edges: &SlotMap) -> f64 { - const CNT: usize = 3; - let mut area = 0.0; - let mut prev_point: Option = None; - let mut start_point = None; - let mut point_count = 0; - - for &edge_key in &face.incident_edges { - let edge = &edges[edge_key]; - for seg in &edge.segments { - for i in 0..CNT { - let t0 = i as f64 / CNT as f64; - let t = if edge.direction_flag.forward() { t0 } else { 1. - t0 }; - let current_point = seg.sample_at(t); - - if let Some(prev) = prev_point { - area += prev.x * current_point.y; - area -= current_point.x * prev.y; - } else { - start_point = Some(current_point); - } - - prev_point = Some(current_point); - point_count += 1; - } - } - } - - if point_count <= 4 { - return -f64::EPSILON; - } - - // Close the polygon - if let (Some(start), Some(end)) = (start_point, prev_point) { - area += end.x * start.y; - area -= start.x * end.y; - } - - #[cfg(feature = "logging")] - { - eprintln!("vertex: {:?}", face); - eprintln!("winding: {}", area); - } - - area -} - -/// Computes the dual graph from the minor graph. -/// -/// This function creates the dual graph by following these steps: -/// 1. Initializes empty structures for dual graph vertices and edges. -/// 2. For each edge in the minor graph: -/// a. Creates a new face (dual vertex) if not already created. -/// b. Traverses around the face, creating dual edges for each minor edge. -/// c. Connects dual edges to their twins (edges representing the same minor edge). -/// 3. Handles special cases like isolated cycles. -/// 4. Groups dual graph elements into connected components. -/// 5. Determines the outer face for each component. -/// -/// The dual graph represents faces of the minor graph as vertices and adjacencies -/// between faces as edges, effectively flipping the concepts of vertices and faces. -/// -/// # Arguments -/// -/// * `minor_graph` - A reference to the MinorGraph. -/// -/// # Returns -/// -/// A Result containing either the computed DualGraph or a BooleanError if the -/// operation cannot be completed successfully. -fn compute_dual(minor_graph: &MinorGraph) -> Result { - let mut new_vertices: Vec = Vec::new(); - let edge_count = minor_graph.edges.len(); - let mut minor_to_dual_edge: HashMap = new_hash_map(edge_count); - let mut dual_edges = SlotMap::with_capacity_and_key(edge_count); - let mut dual_vertices = SlotMap::with_key(); - - for (start_edge_key, start_edge) in &minor_graph.edges { - #[cfg(feature = "logging")] - eprintln!("Processing start edge: {}", (start_edge_key.0.as_ffi() & 0xFF)); - if minor_to_dual_edge.contains_key(&start_edge_key) { - continue; - } - - let face_key = dual_vertices.insert(DualGraphVertex { incident_edges: Vec::new() }); - - let mut edge_key = start_edge_key; - let mut edge = start_edge; - - loop { - #[cfg(feature = "logging")] - eprintln!("Processing edge: {}", (edge_key.0.as_ffi() & 0xFF)); - let twin = edge.twin.expect("Edge doesn't have a twin"); - let twin_dual_key = minor_to_dual_edge.get(&twin).copied(); - - let new_edge_key = dual_edges.insert(DualGraphHalfEdge { - segments: edge.segments.to_vec(), - parent: edge.parent, - incident_vertex: face_key, - direction_flag: edge.direction_flag, - twin: twin_dual_key, - }); - - if let Some(twin_key) = twin_dual_key { - dual_edges[twin_key].twin = Some(new_edge_key); - } - - minor_to_dual_edge.insert(edge_key, new_edge_key); - - dual_vertices[face_key].incident_edges.push(new_edge_key); - - edge_key = get_next_edge(edge_key, minor_graph); - #[cfg(feature = "logging")] - eprintln!("Next edge: {}", (edge_key.0.as_ffi() & 0xFF)); - edge = &minor_graph.edges[edge_key]; - - if edge.incident_vertices[0] == start_edge.incident_vertices[0] { - break; - } - } - - new_vertices.push(face_key); - } - - for cycle in &minor_graph.cycles { - let inner_face_key = dual_vertices.insert(DualGraphVertex { incident_edges: Vec::new() }); - let outer_face_key = dual_vertices.insert(DualGraphVertex { incident_edges: Vec::new() }); - - let inner_half_edge_key = dual_edges.insert(DualGraphHalfEdge { - segments: cycle.segments.clone(), - parent: cycle.parent, - incident_vertex: inner_face_key, - direction_flag: cycle.direction_flag, - twin: None, - }); - - let outer_half_edge_key = dual_edges.insert(DualGraphHalfEdge { - segments: cycle.segments.iter().cloned().rev().collect(), - parent: cycle.parent, - incident_vertex: outer_face_key, - direction_flag: !cycle.direction_flag, - twin: Some(inner_half_edge_key), - }); - - dual_edges[inner_half_edge_key].twin = Some(outer_half_edge_key); - dual_vertices[inner_face_key].incident_edges.push(inner_half_edge_key); - dual_vertices[outer_face_key].incident_edges.push(outer_half_edge_key); - new_vertices.push(inner_face_key); - new_vertices.push(outer_face_key); - } - - let mut components = Vec::with_capacity(12); - let mut visited_vertices = HashSet::with_capacity_and_hasher(dual_vertices.len(), Default::default()); - let mut visited_edges = HashSet::with_capacity_and_hasher(edge_count, Default::default()); - - if cfg!(feature = "logging") { - eprintln!("faces: {}, dual-edges: {}, cycles: {}", new_vertices.len(), dual_edges.len(), minor_graph.cycles.len()) - } - - // This can be very useful for debugging: - // Copy the face outlines to a file called faces_combined.csv and then use this gnuplot command: - // ``` - // plot 'faces_combined.csv' i 0:99 w l, 'faces_combined.csv' index 0 w l lc 'red' - // ``` - // The first part of the command plots all faces to the graph and the second comand plots one surface, - // specified by the index, in red. This allows you to check if all surfaces are closed paths and can - // be used in conjunction with the flag debugging to identify issues later down the line as well. - #[cfg(feature = "logging")] - for (vertex_key, vertex) in &dual_vertices { - eprintln!("\n\n#{:?}", vertex_key.0); - let polygon = face_to_polygon(vertex, &dual_edges); - for point in polygon.iter() { - eprintln!("{}, {}", point.x, point.y); - } - eprintln!("{}, {}", polygon[0].x, polygon[0].y); - } - - for &start_vertex_key in &new_vertices { - if visited_vertices.contains(&start_vertex_key) { - continue; - } - - let mut component_vertices = Vec::new(); - let mut component_edges = Vec::new(); - - let mut stack = vec![start_vertex_key]; - while let Some(vertex_key) = stack.pop() { - if visited_vertices.insert(vertex_key) { - component_vertices.push(vertex_key); - } - - for &edge_key in &dual_vertices[vertex_key].incident_edges { - if !visited_edges.insert(edge_key) { - continue; - } - - let edge = &dual_edges[edge_key]; - let twin_key = edge.twin.expect("Edge doesn't have a twin."); - component_edges.push(edge_key); - component_edges.push(twin_key); - visited_edges.insert(twin_key); - stack.push(dual_edges[twin_key].incident_vertex); - } - } - #[cfg(feature = "logging")] - eprintln!("component_vertices: {}", component_vertices.len()); - - #[cfg(feature = "logging")] - let windings: Option> = component_vertices - .iter() - .map(|face_key| compute_winding(&dual_vertices[*face_key], &dual_edges).map(|w| (face_key, w))) - .collect(); - #[cfg(feature = "logging")] - let Some(_) = windings else { - return Err(BooleanError::NoEarInPolygon); - }; - - #[cfg(feature = "logging")] - let areas: Vec<_> = component_vertices - .iter() - .map(|face_key| (face_key, compute_signed_area(&dual_vertices[*face_key], &dual_edges))) - .collect(); - #[cfg(feature = "logging")] - dbg!(&areas); - - #[cfg(feature = "logging")] - if cfg!(feature = "logging") { - eprintln!( - "{}", - dual_graph_to_dot( - &[DualGraphComponent { - vertices: component_vertices.clone(), - edges: component_edges.clone(), - outer_face: None, - inner_bb: Default::default(), - outer_bb: Default::default(), - }], - &dual_edges, - ) - ); - } - - let areas: Vec<_> = component_vertices - .iter() - .map(|face_key| (face_key, (compute_signed_area(&dual_vertices[*face_key], &dual_edges) * 1000.) as i64)) - .collect(); - #[cfg(feature = "logging")] - eprintln!("Found multiple outer faces: {areas:?}, falling back to area calculation"); - let (&outer_face_key, _) = *areas - .iter() - .max_by(|(_, a1), (_, a2)| match a1.abs().cmp(&a2.abs()) { - Ordering::Equal => a1.cmp(a2), - ord => ord, - }) - .unwrap(); - let inner_bb = dual_vertices[outer_face_key] - .incident_edges - .iter() - .map(|edge_key| dual_edges[*edge_key].start_segment().start()) - .fold(Default::default(), |bbox, point| extend_bounding_box(Some(bbox), point)); - let outer_bb = dual_vertices[outer_face_key] - .incident_edges - .iter() - .map(|edge_key| dual_edges[*edge_key].outer_boundnig_box()) - .reduce(|acc, new| merge_bounding_boxes(&acc, &new)) - .unwrap_or_default(); - #[cfg(feature = "logging")] - dbg!(outer_face_key); - - components.push(DualGraphComponent { - vertices: component_vertices, - edges: component_edges, - outer_face: Some(outer_face_key), - inner_bb, - outer_bb, - }); - } - - Ok(DualGraph { - vertices: dual_vertices, - edges: dual_edges, - components, - }) -} - -fn get_next_edge(edge_key: MinorEdgeKey, graph: &MinorGraph) -> MinorEdgeKey { - let edge = &graph.edges[edge_key]; - let vertex = &graph.vertices[edge.incident_vertices[1]]; - #[cfg(feature = "logging")] - eprintln!("{edge_key:?}, twin: {:?}, {:?}", edge.twin, vertex.outgoing_edges); - let index = vertex.outgoing_edges.iter().position(|&e| Some(edge_key) == graph.edges[e].twin).unwrap(); - vertex.outgoing_edges[(index + 1) % vertex.outgoing_edges.len()] -} - -fn test_inclusion(a: &DualGraphComponent, b: &DualGraphComponent, edges: &SlotMap, vertices: &SlotMap) -> Option { - if a.inner_bb.min().cmpge(b.outer_bb.min()) & a.inner_bb.max().cmple(b.outer_bb.max()) != BVec2::TRUE { - return None; - } - let tested_point = edges[a.edges[0]].segments[0].start(); - for (face_key, face) in b.vertices.iter().map(|&key| (key, &vertices[key])) { - if Some(face_key) == b.outer_face { - continue; - } - let mut count = 0; - for &edge_key in &face.incident_edges { - let edge = &edges[edge_key]; - for seg in &edge.segments { - count += path_segment_horizontal_ray_intersection_count(seg, tested_point); - } - } - if count % 2 == 1 { - return Some(face_key); - } - } - None -} -fn bounding_box_intersects_horizontal_ray(bounding_box: &Aabb, point: DVec2) -> bool { - bounding_box.right() >= point[0] && (bounding_box.top()..bounding_box.bottom()).contains(&point[1]) -} - -#[derive(Copy, Clone)] -struct IntersectionSegment { - bounding_box: Aabb, - seg: PathSegment, -} - -pub fn path_segment_horizontal_ray_intersection_count(orig_seg: &PathSegment, point: DVec2) -> usize { - let total_bounding_box = orig_seg.approx_bounding_box(); - if !bounding_box_intersects_horizontal_ray(&total_bounding_box, point) { - return 0; - } - - match orig_seg { - PathSegment::Cubic(..) => cubic_bezier_horizontal_ray_intersection_count(orig_seg, point), - _ => fallback_intersection_count(orig_seg, total_bounding_box, point), - } -} - -fn cubic_bezier_horizontal_ray_intersection_count(cubic: &PathSegment, point: DVec2) -> usize { - let y = point.y; - let PathSegment::Cubic(p0, p1, p2, p3) = cubic else { unreachable!() }; - - // Transform the curve so that the horizontal line is at y = 0 - let a = -p0.y + 3.0 * p1.y - 3.0 * p2.y + p3.y; - let b = 3.0 * p0.y - 6.0 * p1.y + 3.0 * p2.y; - let c = -3.0 * p0.y + 3.0 * p1.y; - let d = p0.y - y; - - let roots = find_roots_cubic(a, b, c, d); - - let mut count = 0; - match roots { - Roots::Three(roots) => { - for &t in roots.iter() { - if (0.0..=1.0).contains(&t) { - let x = cubic.sample_at(t).x; - if x > point.x { - count += 1; - } - } - } - } - Roots::Two(roots) => { - for &t in roots.iter() { - if (0.0..=1.0).contains(&t) { - let x = cubic.sample_at(t).x; - if x > point.x { - count += 1; - } - } - } - } - Roots::One(roots) => { - for &t in roots.iter() { - if (0.0..=1.0).contains(&t) { - let x = cubic.sample_at(t).x; - if x > point.x { - count += 1; - } - } - } - } - _ => {} - } - - count -} - -fn fallback_intersection_count(orig_seg: &PathSegment, bounding_box: Aabb, point: DVec2) -> usize { - // Existing implementation for non-cubic segments - let mut segments = vec![IntersectionSegment { bounding_box, seg: *orig_seg }]; - let mut count = 0; - let mut next_segments = Vec::new(); - - while !segments.is_empty() { - next_segments.clear(); - for segment in segments.iter() { - if bounding_box_max_extent(&segment.bounding_box) < EPS.linear { - if line_segment_intersects_horizontal_ray(segment.seg.start(), segment.seg.end(), point) { - count += 1; - } - } else { - let split = &segment.seg.split_at(0.5); - let bounding_box0 = split.0.bounding_box(); - let bounding_box1 = split.1.bounding_box(); - if bounding_box_intersects_horizontal_ray(&bounding_box0, point) { - next_segments.push(IntersectionSegment { - bounding_box: bounding_box0, - seg: split.0, - }); - } - if bounding_box_intersects_horizontal_ray(&bounding_box1, point) { - next_segments.push(IntersectionSegment { - bounding_box: bounding_box1, - seg: split.1, - }); - } - } - } - std::mem::swap(&mut next_segments, &mut segments); - } - count -} - -/// Computes the nesting tree of the dual graph components. -/// -/// This function builds a hierarchical structure representing how the components -/// of the dual graph are nested within each other. It does this by: -/// 1. Initializing an empty list of top-level nesting trees. -/// 2. For each component in the dual graph: -/// a. Tests for inclusion against existing nesting trees. -/// b. If included in an existing tree, recursively inserts it at the appropriate level. -/// c. If not included, creates a new top-level tree. -/// d. Checks if any existing trees should become children of the new tree. -/// 3. Continues this process until all components are placed in the nesting structure. -/// -/// The resulting nesting tree captures the containment relationships between -/// different regions of the original paths. -/// -/// # Arguments -/// -/// * `dual_graph` - A reference to the DualGraph. -/// -/// # Returns -/// -/// A vector of NestingTree structures representing the top-level components and their nested subcomponents. -fn compute_nesting_tree(DualGraph { components, vertices, edges }: &mut DualGraph) -> Vec { - let mut nesting_trees = Vec::with_capacity(4); - - for component in components.drain(..) { - insert_component(&mut nesting_trees, component, edges, vertices); - } - - nesting_trees -} - -fn insert_component(trees: &mut Vec, component: DualGraphComponent, edges: &SlotMap, vertices: &SlotMap) { - for tree in trees.iter_mut() { - if let Some(face_key) = test_inclusion(&component, &tree.component, edges, vertices) { - if let Some(children) = tree.outgoing_edges.get_mut(&face_key) { - insert_component(children, component, edges, vertices); - } else { - tree.outgoing_edges.insert( - face_key, - vec![NestingTree { - component, - outgoing_edges: HashMap::default(), - }], - ); - } - return; - } - } - - let mut new_tree = NestingTree { - component, - outgoing_edges: HashMap::default(), - }; - - let mut i = 0; - while i < trees.len() { - if let Some(face_key) = test_inclusion(&trees[i].component, &new_tree.component, edges, vertices) { - let tree = trees.swap_remove(i); - new_tree.outgoing_edges.entry(face_key).or_default().push(tree); - } else { - i += 1; - } - } - - trees.push(new_tree); -} - -fn get_flag(count: i32, fill_rule: FillRule) -> u8 { - match fill_rule { - FillRule::NonZero => { - if count == 0 { - 0 - } else { - 1 - } - } - FillRule::EvenOdd => (count % 2).unsigned_abs() as u8, - } -} - -/// Determines which faces should be included in the result based on the boolean operation. -/// -/// This function applies the specified boolean operation and fill rules to decide -/// which regions of the dual graph should be part of the resulting path. -fn flag_faces( - nesting_trees: &[NestingTree], - a_fill_rule: FillRule, - b_fill_rule: FillRule, - edges: &SlotMap, - vertices: &SlotMap, - flags: &mut HashMap, -) { - let mut visited_faces = HashSet::default(); - let mut face_stack = VecDeque::new(); - for tree in nesting_trees.iter() { - let mut tree_stack = vec![(tree, 0, 0)]; - - while let Some((current_tree, a_running_count, b_running_count)) = tree_stack.pop() { - // TODO: Test if clearing is faster - visited_faces.clear(); - face_stack.clear(); - - let outer_face_key = current_tree.component.outer_face.expect("Component doesn't have an outer face."); - face_stack.push_back((outer_face_key, a_running_count, b_running_count)); - - while let Some((face_key, a_count, b_count)) = face_stack.pop_front() { - if visited_faces.contains(&face_key) { - continue; - } - visited_faces.insert(face_key); - - let a_flag = get_flag(a_count, a_fill_rule); - let b_flag = get_flag(b_count, b_fill_rule); - *flags.entry(face_key).or_default() = a_flag | (b_flag << 1); - - for edge_key in &vertices[face_key].incident_edges { - let edge = &edges[*edge_key]; - let twin_key = edge.twin.expect("Edge doesn't have a twin"); - #[cfg(feature = "logging")] - eprintln!("Processing edge: {:?} to: {:?}", edge_key.0, edges[twin_key].incident_vertex.0); - let mut next_a_count = a_count; - if edge.parent & 1 != 0 { - next_a_count += if edge.direction_flag.forward() { 1 } else { -1 }; - } - let mut next_b_count = b_count; - if edge.parent & 2 != 0 { - next_b_count += if edge.direction_flag.forward() { 1 } else { -1 }; - } - #[cfg(feature = "logging")] - eprintln!("next_count a: {}, b:{}", next_a_count, next_b_count); - face_stack.push_back((edges[twin_key].incident_vertex, next_a_count, next_b_count)); - } - - // Collect subtrees to be processed later - if let Some(subtrees) = current_tree.outgoing_edges.get(&face_key) { - for subtree in subtrees { - tree_stack.push((subtree, a_count, b_count)); - } - } - } - } - } -} - -fn get_selected_faces<'a>(predicate: &'a impl Fn(u8) -> bool, flags: &'a HashMap) -> impl Iterator + 'a { - flags.iter().filter_map(|(key, &flag)| predicate(flag).then_some(*key)) -} - -fn walk_faces<'a>(faces: &'a [DualVertexKey], edges: &SlotMap, vertices: &SlotMap) -> impl Iterator + use<'a> { - let face_set: HashSet<_> = faces.iter().copied().collect(); - // TODO: Try using a binary search to avoid the hashset construction - let is_removed_edge = |edge: &DualGraphHalfEdge| face_set.contains(&edge.incident_vertex) == face_set.contains(&edges[edge.twin.unwrap()].incident_vertex); - - let mut edge_to_next = HashMap::with_capacity_and_hasher(edges.len(), Default::default()); - for face_key in faces { - let face = &vertices[*face_key]; - let mut prev_edge = *face.incident_edges.last().unwrap(); - for &edge in &face.incident_edges { - edge_to_next.insert(prev_edge, edge); - prev_edge = edge; - } - } - - let mut visited_edges = HashSet::with_capacity_and_hasher(edges.len(), Default::default()); - let mut result = Vec::with_capacity(edges.len()); - - for &face_key in faces { - let face = &vertices[face_key]; - for &start_edge in &face.incident_edges { - if is_removed_edge(&edges[start_edge]) || visited_edges.contains(&start_edge) { - continue; - } - let mut edge = start_edge; - loop { - let current_edge = &edges[edge]; - if current_edge.direction_flag.forward() { - result.extend(current_edge.segments.iter().cloned()); - } else { - result.extend(current_edge.segments.iter().map(PathSegment::reverse)); - } - visited_edges.insert(edge); - edge = *edge_to_next.get(&edge).unwrap(); - while is_removed_edge(&edges[edge]) { - edge = *edge_to_next.get(&edges[edge].twin.unwrap()).unwrap(); - } - if edge == start_edge { - break; - } - } - } - } - - result.into_iter() -} - -/// Reconstructs the resulting path(s) from the selected faces of the dual graph. -/// -/// This function takes the faces that were flagged for inclusion and reconstructs -/// the path segments that form the boundaries of these faces, resulting in the -/// final output of the boolean operation. -fn dump_faces( - nesting_trees: &[NestingTree], - predicate: impl Fn(u8) -> bool + Copy, - edges: &SlotMap, - vertices: &SlotMap, - flags: &HashMap, -) -> Vec { - let mut paths = Vec::new(); - - fn visit( - tree: &NestingTree, - predicate: impl Fn(u8) -> bool + Copy, - paths: &mut Vec, - edges: &SlotMap, - vertices: &SlotMap, - flags: &HashMap, - ) { - for &face_key in tree.component.vertices.iter() { - let face = &vertices[face_key]; - let flag = flags[&face_key]; - if !predicate(flag) || Some(face_key) == tree.component.outer_face { - continue; - } - - let mut path = Vec::new(); - - for &edge_key in &face.incident_edges { - let edge = &edges[edge_key]; - if edge.direction_flag.forward() { - path.extend(edge.segments.iter().cloned()); - } else { - path.extend(edge.segments.iter().map(PathSegment::reverse)); - } - } - - // Poke holes in the face - if let Some(subtrees) = tree.outgoing_edges.get(&face_key) { - for subtree in subtrees { - let outer_face_key = subtree.component.outer_face.unwrap(); - for &edge_key in &vertices[outer_face_key].incident_edges { - let edge = &edges[edge_key]; - if edge.direction_flag.forward() { - path.extend(edge.segments.iter().cloned()); - } else { - path.extend(edge.segments.iter().map(PathSegment::reverse)); - } - } - } - } - - paths.push(path); - } - - for subtrees in tree.outgoing_edges.values() { - for subtree in subtrees { - visit(subtree, predicate, paths, edges, vertices, flags); - } - } - } - - for tree in nesting_trees { - visit(tree, predicate, &mut paths, edges, vertices, flags); - } - - paths -} - -const OPERATION_PREDICATES: [fn(u8) -> bool; 6] = [ - |flag: u8| flag > 0, // Union - |flag: u8| flag == 1, // Difference - |flag: u8| flag == 0b11, // Intersection - |flag: u8| flag == 1 || flag == 2, // Exclusion - |flag: u8| (flag & 1) == 1, // Division - |flag: u8| flag > 0, // Fracture -]; - -/// Represents errors that can occur during boolean operations on paths. -#[derive(Debug)] -pub enum BooleanError { - /// Indicates that multiple outer faces were found where only one was expected. - MultipleOuterFaces, - /// Indicates that no valid ear was found in a polygon during triangulation. - NoEarInPolygon, - InvalidPathCommand(char), -} - -impl Display for BooleanError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::MultipleOuterFaces => f.write_str("Found multiple candidates for the outer face in a connected component of the dual graph."), - Self::NoEarInPolygon => f.write_str("Failed to compute winding order for one of the faces, this usually happens when the polygon is malformed."), - Self::InvalidPathCommand(cmd) => f.write_fmt(format_args!("Encountered a '{cmd}' while parsing the svg data which was not recognized")), - } - } -} - -/// Performs boolean operations on two paths. -/// -/// Takes two paths, applies specified fill rules, and performs a boolean operation, -/// returning the resulting path(s). -/// -/// # Examples -/// -/// ``` -/// use path_bool::{path_boolean, FillRule, PathBooleanOperation, path_from_path_data, path_to_path_data}; -/// -/// let path_a = path_from_path_data("M 10 10 L 50 10 L 30 40 Z").unwrap(); -/// let path_b = path_from_path_data("M 20 30 L 60 30 L 60 50 L 20 50 Z").unwrap(); -/// -/// let result = path_boolean( -/// &path_a, -/// FillRule::NonZero, -/// &path_b, -/// FillRule::NonZero, -/// PathBooleanOperation::Intersection -/// ).unwrap(); -/// -/// let result_data = path_to_path_data(&result[0], 0.001); -/// assert_eq!(result_data, "M 36.666666666667,30.000000000000 L 23.333333333333,30.000000000000 L 30.000000000000,40.000000000000 L 36.666666666667,30.000000000000 Z"); -/// ``` -/// -/// # Operations -/// -/// The function supports various boolean operations: -/// - Union -/// - Difference -/// - Intersection -/// - Exclusion -/// - Division -/// - Fracture -/// -/// See [`PathBooleanOperation`] for more details on each operation. -/// -/// # Algorithm -/// -/// The boolean operation is performed in several steps: -/// -/// 1. Preprocessing: Convert input paths to edges and split at intersections. -/// 2. Graph Construction: Build a graph representation of path segments. -/// 3. Intersection Analysis: Compute intersections between path segments. -/// 4. Graph Transformation: Convert the initial graph into the graph minor using edge contractions. -/// 5. Nesting Analysis: Determine nesting relationships between path parts. -/// 6. Boolean Evaluation: Apply the specified operation based on nesting. -/// 7. Result Construction: Generate final path(s) based on the operation result. -/// -/// # Errors -/// -/// Returns a [`BooleanError`] if: -/// - Input paths are invalid or cannot be processed. -/// - The operation encounters an unsolvable geometric configuration. -/// - Issues arise in determining the nesting structure of the paths. -#[inline(never)] -pub fn path_boolean(a: &Path, a_fill_rule: FillRule, b: &Path, b_fill_rule: FillRule, op: PathBooleanOperation) -> Result, BooleanError> { - let mut unsplit_edges: Vec = a.iter().map(segment_to_edge(1)).chain(b.iter().map(segment_to_edge(2))).flatten().collect(); - - if unsplit_edges.is_empty() { - return Ok(Vec::new()); - } - split_at_self_intersections(&mut unsplit_edges); - - let split_edges = split_at_intersections(&unsplit_edges); - - #[cfg(feature = "logging")] - for (edge, _) in split_edges.iter() { - eprintln!("{}", path_to_path_data(&vec![*edge], 0.001)); - } - - let major_graph = find_vertices(&split_edges); - - #[cfg(feature = "logging")] - eprintln!("Major graph:"); - #[cfg(feature = "logging")] - eprintln!("{}", major_graph_to_dot(&major_graph)); - - let mut minor_graph = compute_minor(&major_graph); - - #[cfg(feature = "logging")] - eprintln!("Minor graph:"); - #[cfg(feature = "logging")] - eprintln!("{}", minor_graph_to_dot(&minor_graph.edges)); - - remove_dangling_edges(&mut minor_graph); - #[cfg(feature = "logging")] - eprintln!("After removing dangling edges:"); - #[cfg(feature = "logging")] - eprintln!("{}", minor_graph_to_dot(&minor_graph.edges)); - - #[cfg(feature = "logging")] - for (key, edge) in minor_graph.edges.iter() { - eprintln!("{key:?}:\n{}", path_to_path_data(&edge.segments.to_vec(), 0.001)); - } - #[cfg(feature = "logging")] - for vertex in minor_graph.vertices.values() { - eprintln!("{vertex:?}"); - } - sort_outgoing_edges_by_angle(&mut minor_graph); - #[cfg(feature = "logging")] - for vertex in minor_graph.vertices.values() { - eprintln!("{vertex:?}"); - } - - for (edge_key, edge) in &minor_graph.edges { - assert!(minor_graph.vertices.contains_key(edge.incident_vertices[0]), "Edge {edge_key:?} has invalid start vertex"); - assert!(minor_graph.vertices.contains_key(edge.incident_vertices[1]), "Edge {edge_key:?} has invalid end vertex"); - assert!(edge.twin.is_some(), "Edge {edge_key:?} should have a twin"); - let twin = &minor_graph.edges[edge.twin.unwrap()]; - assert_eq!(twin.twin.unwrap(), edge_key, "Twin relationship should be symmetrical for edge {edge_key:?}"); - } - - let mut dual_graph = compute_dual(&minor_graph)?; - - let nesting_trees = compute_nesting_tree(&mut dual_graph); - - #[cfg(feature = "logging")] - for tree in &nesting_trees { - eprintln!("nesting_trees: {tree:?}"); - } - - let DualGraph { edges, vertices, .. } = &dual_graph; - - #[cfg(feature = "logging")] - eprintln!("Dual Graph:"); - #[cfg(feature = "logging")] - eprintln!("{}", dual_graph_to_dot(&dual_graph.components, edges)); - - let mut flags = new_hash_map(vertices.len()); - flag_faces(&nesting_trees, a_fill_rule, b_fill_rule, edges, vertices, &mut flags); - - #[cfg(feature = "logging")] - for (face, flag) in &flags { - eprintln!("{:?}: {:b}", face.0, flag); - } - - let predicate = OPERATION_PREDICATES[op as usize]; - - match op { - PathBooleanOperation::Division | PathBooleanOperation::Fracture => Ok(dump_faces(&nesting_trees, predicate, edges, vertices, &flags)), - _ => { - let mut selected_faces: Vec = get_selected_faces(&predicate, &flags).collect(); - selected_faces.sort_unstable(); - Ok(vec![walk_faces(&selected_faces, edges, vertices).collect()]) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use glam::DVec2; - use std::f64::consts::TAU; // Assuming DVec2 is defined in your crate - - #[test] - fn test_split_at_intersections() { - let unsplit_edges = unsplit_edges(); - let split_edges = split_at_intersections(&unsplit_edges); - - // Check that we have more edges after splitting (due to intersections) - assert!(split_edges.len() >= unsplit_edges.len()); - - // You might want to add more specific checks based on the expected behavior - // of your split_at_intersections function - } - - fn unsplit_edges() -> Vec<(PathSegment, u8)> { - let unsplit_edges = vec![ - (PathSegment::Arc(DVec2::new(39., 20.), 19., 19., 0., false, true, DVec2::new(20., 39.)), 1), - (PathSegment::Arc(DVec2::new(20., 39.), 19., 19., 0., false, true, DVec2::new(1., 20.)), 1), - (PathSegment::Arc(DVec2::new(1., 20.), 19., 19., 0., false, true, DVec2::new(20., 1.)), 1), - (PathSegment::Arc(DVec2::new(20., 1.), 19., 19., 0., false, true, DVec2::new(39., 20.)), 1), - (PathSegment::Arc(DVec2::new(47., 28.), 19., 19., 0., false, true, DVec2::new(28., 47.)), 2), - (PathSegment::Arc(DVec2::new(28., 47.), 19., 19., 0., false, true, DVec2::new(9., 28.)), 2), - (PathSegment::Arc(DVec2::new(9., 28.), 19., 19., 0., false, true, DVec2::new(28., 9.)), 2), - (PathSegment::Arc(DVec2::new(28., 9.), 19., 19., 0., false, true, DVec2::new(47., 28.)), 2), - ]; - unsplit_edges - } - - #[test] - fn test_compute_minor() { - // Set up the initial graph - let unsplit_edges = unsplit_edges(); - let split_edges = split_at_intersections(&unsplit_edges); - let major_graph = find_vertices(&split_edges); - - // Compute minor graph - let minor_graph = compute_minor(&major_graph); - - // Print minor graph state - eprintln!("Minor Graph:"); - print_minor_graph_state(&minor_graph); - - // Assertions - assert_eq!(minor_graph.edges.len(), 8, "Expected 8 edges in minor graph"); - assert_eq!(minor_graph.vertices.len(), 2, "Expected 2 vertices in minor graph"); - assert!(minor_graph.cycles.is_empty(), "Expected no cycles in minor graph"); - - // Check that each vertex has 4 outgoing edges - for (vertex_key, vertex) in &minor_graph.vertices { - assert_eq!(vertex.outgoing_edges.len(), 4, "Vertex {:?} should have 4 outgoing edges", vertex_key); - } - - // Check that all edges have valid incident vertices and twins - for (edge_key, edge) in &minor_graph.edges { - assert!(minor_graph.vertices.contains_key(edge.incident_vertices[0]), "Edge {:?} has invalid start vertex", edge_key); - assert!(minor_graph.vertices.contains_key(edge.incident_vertices[1]), "Edge {:?} has invalid end vertex", edge_key); - assert!(edge.twin.is_some(), "Edge {:?} should have a twin", edge_key); - let twin = &minor_graph.edges[edge.twin.unwrap()]; - assert_eq!(twin.twin.unwrap(), edge_key, "Twin relationship should be symmetrical for edge {:?}", edge_key); - } - - // Check that parents are correctly assigned - assert_eq!(minor_graph.edges.values().filter(|e| e.parent == 1).count(), 4, "Expected 4 edges with parent 1"); - assert_eq!(minor_graph.edges.values().filter(|e| e.parent == 2).count(), 4, "Expected 4 edges with parent 2"); - } - - fn print_minor_graph_state(graph: &MinorGraph) { - eprintln!(" Vertices: {}", graph.vertices.len()); - eprintln!(" Edges: {}", graph.edges.len()); - eprintln!(" Cycles: {}", graph.cycles.len()); - - for (vertex_key, vertex) in &graph.vertices { - eprintln!(" Vertex {:?}: {} outgoing edges", vertex_key, vertex.outgoing_edges.len()); - } - - for (edge_key, edge) in &graph.edges { - eprintln!(" Edge {:?}:", edge_key); - eprintln!(" Parent: {}", edge.parent); - eprintln!(" Twin: {:?}", edge.twin); - eprintln!(" Incident vertices: {:?}", edge.incident_vertices); - } - } - - #[test] - fn test_sort_outgoing_edges_by_angle() { - // Set up the initial graph - let unsplit_edges = unsplit_edges(); - let split_edges = split_at_intersections(&unsplit_edges); - let major_graph = find_vertices(&split_edges); - let mut minor_graph = compute_minor(&major_graph); - - // Print initial state - eprintln!("Initial Minor Graph:"); - print_minor_graph_state(&minor_graph); - - // Store initial edge order - let initial_edge_order: HashMap> = minor_graph.vertices.iter().map(|(k, v)| (k, v.outgoing_edges.clone())).collect(); - - // Apply sort_outgoing_edges_by_angle - sort_outgoing_edges_by_angle(&mut minor_graph); - - // Print final state - eprintln!("\nAfter sort_outgoing_edges_by_angle:"); - print_minor_graph_state(&minor_graph); - - // Assertions - assert_eq!(minor_graph.edges.len(), 8, "Number of edges should remain unchanged"); - assert_eq!(minor_graph.vertices.len(), 2, "Number of vertices should remain unchanged"); - assert!(minor_graph.cycles.is_empty(), "Expected no cycles"); - - // Check that each vertex still has 4 outgoing edges - for (vertex_key, vertex) in &minor_graph.vertices { - assert_eq!(vertex.outgoing_edges.len(), 4, "Vertex {:?} should have 4 outgoing edges", vertex_key); - } - - // Check that the edges are sorted by angle - for (vertex_key, vertex) in &minor_graph.vertices { - let angles: Vec = vertex.outgoing_edges.iter().map(|&edge_key| get_incidence_angle(&minor_graph.edges[edge_key])).collect(); - - // Check if angles are in ascending order - for i in 1..angles.len() { - assert!(angles[i] >= angles[i - 1], "Edges for vertex {:?} are not sorted by angle {} {}", vertex_key, angles[i], angles[i - 1]); - } - - // Check that the set of edges is the same as before, just in different order - let initial_edges: HashSet<_> = initial_edge_order[&vertex_key].iter().collect(); - let sorted_edges: HashSet<_> = vertex.outgoing_edges.iter().collect(); - assert_eq!(initial_edges, sorted_edges, "Set of edges for vertex {:?} changed after sorting", vertex_key); - } - - // Check that all edges still have valid incident vertices and twins - for (edge_key, edge) in &minor_graph.edges { - assert!(minor_graph.vertices.contains_key(edge.incident_vertices[0]), "Edge {:?} has invalid start vertex", edge_key); - assert!(minor_graph.vertices.contains_key(edge.incident_vertices[1]), "Edge {:?} has invalid end vertex", edge_key); - assert!(edge.twin.is_some(), "Edge {:?} should have a twin", edge_key); - let twin = &minor_graph.edges[edge.twin.unwrap()]; - assert_eq!(twin.twin.unwrap(), edge_key, "Twin relationship should be symmetrical for edge {:?}", edge_key); - } - } - - fn get_incidence_angle(edge: &MinorGraphEdge) -> f64 { - let seg = &edge.segments[0]; // First segment is always the incident one in both fwd and bwd - let (p0, p1) = if edge.direction_flag.forward() { - (seg.sample_at(0.), seg.sample_at(0.1)) - } else { - (seg.sample_at(1.), seg.sample_at(1. - 0.1)) - }; - ((p1.y - p0.y).atan2(p1.x - p0.x) + TAU) % TAU - } - - #[test] - fn test_path_segment_horizontal_ray_intersection_count() { - let orig_seg = PathSegment::Arc(DVec2::new(24., 10.090978), 13.909023, 13.909023, 0., false, true, DVec2::new(47., 24.)); - - let point = DVec2::new(37.99, 24.); - - eprintln!("Starting test with segment: {:?}", orig_seg); - eprintln!("Test point: {:?}", point); - - let count = path_segment_horizontal_ray_intersection_count(&orig_seg, point); - - eprintln!("Final intersection count: {}", count); - - let expected_count = 1; - assert_eq!(count, expected_count, "Intersection count mismatch"); - } - - #[test] - fn test_bounding_box_intersects_horizontal_ray() { - let bbox = Aabb::new(20., 10., 40., 30.); - - assert!(bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(0., 29.))); - assert!(bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(20., 29.))); - assert!(bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(10., 20.))); - assert!(!bounding_box_intersects_horizontal_ray(&bbox, DVec2::new(30., 40.))); - } -} diff --git a/libraries/path-bool/src/util/aabb.rs b/libraries/path-bool/src/util/aabb.rs deleted file mode 100644 index 7d68cfd084..0000000000 --- a/libraries/path-bool/src/util/aabb.rs +++ /dev/null @@ -1,92 +0,0 @@ -use glam::{BVec2, DVec2}; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct Aabb { - min: DVec2, - max: DVec2, -} - -impl Default for Aabb { - fn default() -> Self { - Self { - min: DVec2::INFINITY, - max: DVec2::NEG_INFINITY, - } - } -} - -impl Aabb { - #[inline] - pub(crate) fn min(&self) -> DVec2 { - self.min - } - #[inline] - pub(crate) fn max(&self) -> DVec2 { - self.max - } - - pub(crate) const fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self { - Aabb { - min: DVec2::new(left, top), - max: DVec2::new(right, bottom), - } - } - #[inline] - pub(crate) fn top(&self) -> f64 { - self.min.y - } - #[inline] - pub(crate) fn left(&self) -> f64 { - self.min.x - } - #[inline] - pub(crate) fn right(&self) -> f64 { - self.max.x - } - #[inline] - pub(crate) fn bottom(&self) -> f64 { - self.max.y - } -} - -#[inline] -pub(crate) fn bounding_boxes_overlap(a: &Aabb, b: &Aabb) -> bool { - (a.min.cmple(b.max) & b.min.cmple(a.max)) == BVec2::TRUE -} - -#[inline] -pub(crate) fn merge_bounding_boxes(a: &Aabb, b: &Aabb) -> Aabb { - Aabb { - min: a.min.min(b.min), - max: a.max.max(b.max), - } -} - -#[inline] -pub(crate) fn extend_bounding_box(bounding_box: Option, point: DVec2) -> Aabb { - match bounding_box { - Some(bb) => Aabb { - min: bb.min.min(point), - max: bb.max.max(point), - }, - None => Aabb { min: point, max: point }, - } -} - -pub(crate) fn bounding_box_max_extent(bounding_box: &Aabb) -> f64 { - (bounding_box.max - bounding_box.min).max_element() -} - -pub(crate) fn bounding_box_around_point(point: DVec2, padding: f64) -> Aabb { - Aabb { - min: point - DVec2::splat(padding), - max: point + DVec2::splat(padding), - } -} - -pub(crate) fn expand_bounding_box(bounding_box: &Aabb, padding: f64) -> Aabb { - Aabb { - min: bounding_box.min - DVec2::splat(padding), - max: bounding_box.max + DVec2::splat(padding), - } -} diff --git a/libraries/path-bool/src/util/epsilons.rs b/libraries/path-bool/src/util/epsilons.rs deleted file mode 100644 index aed8ca2770..0000000000 --- a/libraries/path-bool/src/util/epsilons.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[derive(Clone, Copy, Debug)] -pub struct Epsilons { - pub point: f64, - pub linear: f64, - pub param: f64, -} diff --git a/libraries/path-bool/src/util/grid.rs b/libraries/path-bool/src/util/grid.rs deleted file mode 100644 index 4cb38c477b..0000000000 --- a/libraries/path-bool/src/util/grid.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::aabb::Aabb; -use glam::{DVec2, IVec2}; -use rustc_hash::FxHashMap; -use smallvec::SmallVec; - -pub(crate) struct Grid { - cell_factor: f64, - cells: FxHashMap>, -} - -impl Grid { - pub(crate) fn new(cell_size: f64, edges: usize) -> Self { - Grid { - cell_factor: cell_size.recip(), - cells: FxHashMap::with_capacity_and_hasher(edges, Default::default()), - } - } - - pub(crate) fn insert(&mut self, bbox: &Aabb, index: usize) { - let min_cell = self.point_to_cell_floor(bbox.min()); - let max_cell = self.point_to_cell_ceil(bbox.max()); - - for i in min_cell.x..=max_cell.x { - for j in min_cell.y..=max_cell.y { - self.cells.entry((i, j).into()).or_default().push(index); - } - } - } - - pub(crate) fn query(&self, bbox: &Aabb, result: &mut BitVec) { - let min_cell = self.point_to_cell_floor(bbox.min()); - let max_cell = self.point_to_cell_ceil(bbox.max()); - - for i in min_cell.x..=max_cell.x { - for j in min_cell.y..=max_cell.y { - if let Some(indices) = self.cells.get(&(i, j).into()) { - for &index in indices { - result.set(index); - } - } - } - } - // result.sort_unstable(); - // result.dedup(); - } - - fn point_to_cell_ceil(&self, point: DVec2) -> IVec2 { - (point * self.cell_factor).ceil().as_ivec2() - } - fn point_to_cell_floor(&self, point: DVec2) -> IVec2 { - (point * self.cell_factor).floor().as_ivec2() - } -} - -pub struct BitVec { - data: Vec, -} - -impl BitVec { - pub fn new(capacity: usize) -> Self { - let num_words = capacity.div_ceil(64); - BitVec { data: vec![0; num_words] } - } - - pub fn set(&mut self, index: usize) { - let word_index = index / 64; - let bit_index = index % 64; - self.data[word_index] |= 1u64 << bit_index; - } - - pub fn clear(&mut self) { - self.data.fill(0); - } - - pub fn iter_set_bits(&self) -> BitVecIterator<'_> { - BitVecIterator { - bit_vec: self, - current_word: self.data[0], - word_index: 0, - } - } -} - -pub struct BitVecIterator<'a> { - bit_vec: &'a BitVec, - current_word: u64, - word_index: usize, -} - -impl<'a> Iterator for BitVecIterator<'a> { - type Item = usize; - - fn next(&mut self) -> Option { - loop { - if self.current_word == 0 { - self.word_index += 1; - if self.word_index == self.bit_vec.data.len() { - return None; - } - self.current_word = self.bit_vec.data[self.word_index]; - continue; - } - let tz = self.current_word.trailing_zeros() as usize; - self.current_word ^= 1 << tz; - - let result = self.word_index * 64 + tz; - - return Some(result); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bitvec() { - let mut bv = BitVec::new(200); - bv.set(5); - bv.set(64); - bv.set(128); - bv.set(199); - - let set_bits: Vec = bv.iter_set_bits().collect(); - assert_eq!(set_bits, vec![5, 64, 128, 199]); - } -} diff --git a/libraries/path-bool/src/util/math.rs b/libraries/path-bool/src/util/math.rs deleted file mode 100644 index 6d099a276c..0000000000 --- a/libraries/path-bool/src/util/math.rs +++ /dev/null @@ -1,23 +0,0 @@ -use glam::{DVec2, FloatExt}; -pub use std::f64::consts::PI; - -pub fn lin_map(value: f64, in_min: f64, in_max: f64, out_min: f64, out_max: f64) -> f64 { - ((value - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min -} - -pub fn lerp(a: f64, b: f64, t: f64) -> f64 { - a.lerp(b, t) -} - -pub fn vector_angle(u: DVec2, v: DVec2) -> f64 { - const EPS: f64 = 1e-12; - - let sign = u.x * v.y - u.y * v.x; - - if sign.abs() < EPS && (u + v).length_squared() < EPS * EPS { - // TODO: `u` can be scaled - return PI; - } - - sign.signum() * (u.dot(v) / (u.length() * v.length())).acos() -} diff --git a/libraries/path-bool/src/visual_tests.rs b/libraries/path-bool/src/visual_tests.rs deleted file mode 100644 index e765768aa0..0000000000 --- a/libraries/path-bool/src/visual_tests.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::path_boolean::{self, FillRule, PathBooleanOperation}; -use crate::path_data::{path_from_path_data, path_to_path_data}; -use core::panic; -use glob::glob; -use image::{DynamicImage, GenericImageView, RgbaImage}; -use resvg::render; -use resvg::tiny_skia::Transform; -use resvg::usvg::{Options, Tree}; -use std::fs; -use std::path::PathBuf; -use svg::parser::Event; - -const TOLERANCE: u8 = 84; - -fn get_fill_rule(fill_rule: &str) -> FillRule { - match fill_rule { - "evenodd" => FillRule::EvenOdd, - _ => FillRule::NonZero, - } -} - -#[test] -fn visual_tests() { - let ops = [ - ("union", PathBooleanOperation::Union), - ("difference", PathBooleanOperation::Difference), - ("intersection", PathBooleanOperation::Intersection), - ("exclusion", PathBooleanOperation::Exclusion), - ("division", PathBooleanOperation::Division), - ("fracture", PathBooleanOperation::Fracture), - ]; - - let folders: Vec<(String, PathBuf, &str, PathBooleanOperation)> = glob("visual-tests/*/") - .expect("Failed to read glob pattern") - .flat_map(|entry| { - let dir = entry.expect("Failed to get directory entry"); - ops.iter() - .map(move |(op_name, op)| (dir.file_name().unwrap().to_string_lossy().into_owned(), dir.clone(), *op_name, *op)) - }) - .collect(); - - let mut failure = false; - - for (name, dir, op_name, op) in folders { - let test_name = format!("{} {}", name, op_name); - println!("Running test: {}", test_name); - - fs::create_dir_all(dir.join("test-results")).expect("Failed to create test-results directory"); - - let original_path = dir.join("original.svg"); - - let mut content = String::new(); - let svg_tree = svg::open(&original_path, &mut content).expect("Failed to parse SVG"); - - let mut paths = Vec::new(); - let mut first_path_attributes = String::new(); - let mut width = String::new(); - let mut height = String::new(); - let mut view_box = String::new(); - let mut transform = String::new(); - for event in svg_tree { - match event { - Event::Tag("svg", svg::node::element::tag::Type::Start, attributes) => { - width = attributes.get("width").map(|s| s.to_string()).unwrap_or_default(); - height = attributes.get("height").map(|s| s.to_string()).unwrap_or_default(); - view_box = attributes.get("viewBox").map(|s| s.to_string()).unwrap_or_default(); - } - Event::Tag("g", svg::node::element::tag::Type::Start, attributes) => { - if let Some(transform_attr) = attributes.get("transform") { - transform = transform_attr.to_string(); - } - } - Event::Tag("path", svg::node::element::tag::Type::Empty, attributes) => { - let data = attributes.get("d").map(|s| s.to_string()).expect("Path data not found"); - let fill_rule = attributes.get("fill-rule").map(|v| v.to_string()).unwrap_or_else(|| "nonzero".to_string()); - paths.push((data, fill_rule)); - - // Store attributes of the first path - if first_path_attributes.is_empty() { - for (key, value) in attributes.iter() { - if key != "d" && key != "id" { - first_path_attributes.push_str(&format!("{}=\"{}\" ", key, value)); - } - } - } - } - _ => {} - } - } - - if (width.is_empty() || height.is_empty()) && !view_box.is_empty() { - let vb: Vec<&str> = view_box.split_whitespace().collect(); - if vb.len() == 4 { - width = vb[2].to_string(); - height = vb[3].to_string(); - } - } - - if width.is_empty() || height.is_empty() { - panic!("Failed to extract width and height from SVG"); - } - - let a_node = paths[0].clone(); - let b_node = paths[1].clone(); - - let a = path_from_path_data(&a_node.0).unwrap(); - let b = path_from_path_data(&b_node.0).unwrap(); - - let a_fill_rule = get_fill_rule(&a_node.1); - let b_fill_rule = get_fill_rule(&b_node.1); - - let result = path_boolean::path_boolean(&a, a_fill_rule, &b, b_fill_rule, op).unwrap(); - - // Create the result SVG with correct dimensions and transform - let mut result_svg = format!("", width, height, view_box); - if !transform.is_empty() { - result_svg.push_str(&format!("", transform)); - } - for path in &result { - result_svg.push_str(&format!("", path_to_path_data(path, 1e-4), first_path_attributes)); - } - if !transform.is_empty() { - result_svg.push_str(""); - } - result_svg.push_str(""); - - // Save the result SVG - let destination_path = dir.join("test-results").join(format!("{}-ours.svg", op_name)); - fs::write(&destination_path, &result_svg).expect("Failed to write result SVG"); - - // Render and compare images - let ground_truth_path = dir.join(format!("{}.svg", op_name)); - let ground_truth_svg = fs::read_to_string(&ground_truth_path).expect("Failed to read ground truth SVG"); - - let ours_image = render_svg(&result_svg); - let ground_truth_image = render_svg(&ground_truth_svg); - - let ours_png_path = dir.join("test-results").join(format!("{}-ours.png", op_name)); - ours_image.save(&ours_png_path).expect("Failed to save our PNG"); - - let ground_truth_png_path = dir.join("test-results").join(format!("{}.png", op_name)); - ground_truth_image.save(&ground_truth_png_path).expect("Failed to save ground truth PNG"); - - failure |= compare_images(&ours_image, &ground_truth_image, TOLERANCE); - - // Check the number of paths - let result_path_count = result.len(); - let ground_truth_path_count = ground_truth_svg.matches(" DynamicImage { - let opts = Options::default(); - let tree = Tree::from_str(svg_code, &opts).unwrap(); - let pixmap_size = tree.size(); - let (width, height) = (pixmap_size.width() as u32, pixmap_size.height() as u32); - let mut pixmap = resvg::tiny_skia::Pixmap::new(width, height).unwrap(); - let mut pixmap_mut = pixmap.as_mut(); - render(&tree, Transform::default(), &mut pixmap_mut); - DynamicImage::ImageRgba8(RgbaImage::from_raw(width, height, pixmap.data().to_vec()).unwrap()) -} - -fn compare_images(img1: &DynamicImage, img2: &DynamicImage, tolerance: u8) -> bool { - assert_eq!(img1.dimensions(), img2.dimensions(), "Image dimensions do not match"); - - for (x, y, pixel1) in img1.pixels() { - let pixel2 = img2.get_pixel(x, y); - for i in 0..4 { - let difference = (pixel1[i] as i32 - pixel2[i] as i32).unsigned_abs() as u8; - if difference > tolerance { - println!("Difference {} larger than tolerance {} at [{}, {}], channel {}.", difference, tolerance, x, y, i); - return true; - } - - assert!( - difference <= tolerance, - "Difference {} larger than tolerance {} at [{}, {}], channel {}.", - difference, - tolerance, - x, - y, - i - ); - } - } - false -} diff --git a/libraries/path-bool/visual-tests/dangling-01/difference.svg b/libraries/path-bool/visual-tests/dangling-01/difference.svg deleted file mode 100644 index 8bd2508868..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-01/division.svg b/libraries/path-bool/visual-tests/dangling-01/division.svg deleted file mode 100644 index 3f59874513..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-01/exclusion.svg b/libraries/path-bool/visual-tests/dangling-01/exclusion.svg deleted file mode 100644 index b0949e0f92..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-01/fracture.svg b/libraries/path-bool/visual-tests/dangling-01/fracture.svg deleted file mode 100644 index 142cccbcb6..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-01/intersection.svg b/libraries/path-bool/visual-tests/dangling-01/intersection.svg deleted file mode 100644 index 5595e7d7b2..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-01/original.svg b/libraries/path-bool/visual-tests/dangling-01/original.svg deleted file mode 100644 index 73668dee93..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-01/union.svg b/libraries/path-bool/visual-tests/dangling-01/union.svg deleted file mode 100644 index 4e4df455ca..0000000000 --- a/libraries/path-bool/visual-tests/dangling-01/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/difference.svg b/libraries/path-bool/visual-tests/dangling-02/difference.svg deleted file mode 100644 index 8bd2508868..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/division.svg b/libraries/path-bool/visual-tests/dangling-02/division.svg deleted file mode 100644 index b95b4e41ba..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/exclusion.svg b/libraries/path-bool/visual-tests/dangling-02/exclusion.svg deleted file mode 100644 index 665045430b..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/fracture.svg b/libraries/path-bool/visual-tests/dangling-02/fracture.svg deleted file mode 100644 index b1a44e6acb..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/intersection.svg b/libraries/path-bool/visual-tests/dangling-02/intersection.svg deleted file mode 100644 index 21cf4ef874..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/original.svg b/libraries/path-bool/visual-tests/dangling-02/original.svg deleted file mode 100644 index 24bb0df75e..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-02/union.svg b/libraries/path-bool/visual-tests/dangling-02/union.svg deleted file mode 100644 index 5ce5269aeb..0000000000 --- a/libraries/path-bool/visual-tests/dangling-02/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/difference.svg b/libraries/path-bool/visual-tests/dangling-03/difference.svg deleted file mode 100644 index 6db8be9723..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/division.svg b/libraries/path-bool/visual-tests/dangling-03/division.svg deleted file mode 100644 index d962fd3f94..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/exclusion.svg b/libraries/path-bool/visual-tests/dangling-03/exclusion.svg deleted file mode 100644 index 6db8be9723..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/fracture.svg b/libraries/path-bool/visual-tests/dangling-03/fracture.svg deleted file mode 100644 index d962fd3f94..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/intersection.svg b/libraries/path-bool/visual-tests/dangling-03/intersection.svg deleted file mode 100644 index 6db8be9723..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/original.svg b/libraries/path-bool/visual-tests/dangling-03/original.svg deleted file mode 100644 index a25711e02f..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-03/union.svg b/libraries/path-bool/visual-tests/dangling-03/union.svg deleted file mode 100644 index 6db8be9723..0000000000 --- a/libraries/path-bool/visual-tests/dangling-03/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/difference.svg b/libraries/path-bool/visual-tests/dangling-04/difference.svg deleted file mode 100644 index bae74eb65f..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/division.svg b/libraries/path-bool/visual-tests/dangling-04/division.svg deleted file mode 100644 index 201715a33a..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/exclusion.svg b/libraries/path-bool/visual-tests/dangling-04/exclusion.svg deleted file mode 100644 index b3569edeee..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/fracture.svg b/libraries/path-bool/visual-tests/dangling-04/fracture.svg deleted file mode 100644 index 33de261fe1..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/intersection.svg b/libraries/path-bool/visual-tests/dangling-04/intersection.svg deleted file mode 100644 index 73f4c7da4c..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/original.svg b/libraries/path-bool/visual-tests/dangling-04/original.svg deleted file mode 100644 index 7d32ceef25..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/dangling-04/union.svg b/libraries/path-bool/visual-tests/dangling-04/union.svg deleted file mode 100644 index 89fe9b6363..0000000000 --- a/libraries/path-bool/visual-tests/dangling-04/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/establish-ground-truth.sh b/libraries/path-bool/visual-tests/establish-ground-truth.sh deleted file mode 100644 index 557a2b645c..0000000000 --- a/libraries/path-bool/visual-tests/establish-ground-truth.sh +++ /dev/null @@ -1,5 +0,0 @@ -for dir in */; do - for fn in difference division exclusion fracture intersection union; do - cp "${dir}test-results/$fn-ours.svg" "$dir$fn.svg" - done -done diff --git a/libraries/path-bool/visual-tests/generate-ground-truth.sh b/libraries/path-bool/visual-tests/generate-ground-truth.sh deleted file mode 100644 index 5471a1e105..0000000000 --- a/libraries/path-bool/visual-tests/generate-ground-truth.sh +++ /dev/null @@ -1,10 +0,0 @@ -INKSCAPE_CMD=inkscape -OPS=(union difference intersection exclusion division fracture) - -for dir in */; do - for op in "${OPS[@]}"; do - if [ ! -e "$dir/$op.svg" ]; then - $INKSCAPE_CMD --actions="select-all; path-$op; export-filename:$dir/$op.svg; export-plain-svg; export-do; file-close" "$dir/original.svg" - fi - done -done diff --git a/libraries/path-bool/visual-tests/nesting-01/difference.svg b/libraries/path-bool/visual-tests/nesting-01/difference.svg deleted file mode 100644 index 5cb447ebf2..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/difference.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-01/division.svg b/libraries/path-bool/visual-tests/nesting-01/division.svg deleted file mode 100644 index 0e86f1788b..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/division.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-01/exclusion.svg b/libraries/path-bool/visual-tests/nesting-01/exclusion.svg deleted file mode 100644 index 5cb447ebf2..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/exclusion.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-01/fracture.svg b/libraries/path-bool/visual-tests/nesting-01/fracture.svg deleted file mode 100644 index 0e86f1788b..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/fracture.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-01/intersection.svg b/libraries/path-bool/visual-tests/nesting-01/intersection.svg deleted file mode 100644 index 45182cc30e..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/intersection.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-01/original.svg b/libraries/path-bool/visual-tests/nesting-01/original.svg deleted file mode 100644 index 72e961e1f9..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/original.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-01/union.svg b/libraries/path-bool/visual-tests/nesting-01/union.svg deleted file mode 100644 index 7158b23020..0000000000 --- a/libraries/path-bool/visual-tests/nesting-01/union.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/difference.svg b/libraries/path-bool/visual-tests/nesting-02/difference.svg deleted file mode 100644 index 204cfe54a3..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/difference.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/division.svg b/libraries/path-bool/visual-tests/nesting-02/division.svg deleted file mode 100644 index e1f7418267..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/division.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/exclusion.svg b/libraries/path-bool/visual-tests/nesting-02/exclusion.svg deleted file mode 100644 index 6b0fa562a4..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/exclusion.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/fracture.svg b/libraries/path-bool/visual-tests/nesting-02/fracture.svg deleted file mode 100644 index cbaa7fb773..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/fracture.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/intersection.svg b/libraries/path-bool/visual-tests/nesting-02/intersection.svg deleted file mode 100644 index 32fe0d3d67..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/intersection.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/original.svg b/libraries/path-bool/visual-tests/nesting-02/original.svg deleted file mode 100644 index 9538dcfc92..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/original.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-02/union.svg b/libraries/path-bool/visual-tests/nesting-02/union.svg deleted file mode 100644 index 0b77e355f4..0000000000 --- a/libraries/path-bool/visual-tests/nesting-02/union.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/difference.svg b/libraries/path-bool/visual-tests/nesting-03/difference.svg deleted file mode 100644 index 1057abc9ac..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/division.svg b/libraries/path-bool/visual-tests/nesting-03/division.svg deleted file mode 100644 index 7b029cb335..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/exclusion.svg b/libraries/path-bool/visual-tests/nesting-03/exclusion.svg deleted file mode 100644 index 1057abc9ac..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/fracture.svg b/libraries/path-bool/visual-tests/nesting-03/fracture.svg deleted file mode 100644 index 7b029cb335..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/intersection.svg b/libraries/path-bool/visual-tests/nesting-03/intersection.svg deleted file mode 100644 index d129881fea..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/original.svg b/libraries/path-bool/visual-tests/nesting-03/original.svg deleted file mode 100644 index 68fd8000d0..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-03/union.svg b/libraries/path-bool/visual-tests/nesting-03/union.svg deleted file mode 100644 index 58b3dc785c..0000000000 --- a/libraries/path-bool/visual-tests/nesting-03/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/difference.svg b/libraries/path-bool/visual-tests/nesting-04/difference.svg deleted file mode 100644 index dc5f2f8200..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/difference.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/division.svg b/libraries/path-bool/visual-tests/nesting-04/division.svg deleted file mode 100644 index 9836486885..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/division.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/exclusion.svg b/libraries/path-bool/visual-tests/nesting-04/exclusion.svg deleted file mode 100644 index dc5f2f8200..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/exclusion.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/fracture.svg b/libraries/path-bool/visual-tests/nesting-04/fracture.svg deleted file mode 100644 index 9836486885..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/fracture.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/intersection.svg b/libraries/path-bool/visual-tests/nesting-04/intersection.svg deleted file mode 100644 index d796f44cfa..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/intersection.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/original.svg b/libraries/path-bool/visual-tests/nesting-04/original.svg deleted file mode 100644 index 8289f94c5f..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/original.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/nesting-04/union.svg b/libraries/path-bool/visual-tests/nesting-04/union.svg deleted file mode 100644 index cbed7f6c9e..0000000000 --- a/libraries/path-bool/visual-tests/nesting-04/union.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/difference.svg b/libraries/path-bool/visual-tests/real-01/difference.svg deleted file mode 100644 index cfa290bb21..0000000000 --- a/libraries/path-bool/visual-tests/real-01/difference.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/division.svg b/libraries/path-bool/visual-tests/real-01/division.svg deleted file mode 100644 index 1050988650..0000000000 --- a/libraries/path-bool/visual-tests/real-01/division.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/exclusion.svg b/libraries/path-bool/visual-tests/real-01/exclusion.svg deleted file mode 100644 index 007f044982..0000000000 --- a/libraries/path-bool/visual-tests/real-01/exclusion.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/fracture.svg b/libraries/path-bool/visual-tests/real-01/fracture.svg deleted file mode 100644 index d05df9c798..0000000000 --- a/libraries/path-bool/visual-tests/real-01/fracture.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/intersection.svg b/libraries/path-bool/visual-tests/real-01/intersection.svg deleted file mode 100644 index 6811c73e3f..0000000000 --- a/libraries/path-bool/visual-tests/real-01/intersection.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/original.svg b/libraries/path-bool/visual-tests/real-01/original.svg deleted file mode 100644 index d5a474d2cd..0000000000 --- a/libraries/path-bool/visual-tests/real-01/original.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-01/union.svg b/libraries/path-bool/visual-tests/real-01/union.svg deleted file mode 100644 index 61a8c7c542..0000000000 --- a/libraries/path-bool/visual-tests/real-01/union.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/difference.svg b/libraries/path-bool/visual-tests/real-02/difference.svg deleted file mode 100644 index e409ac7681..0000000000 --- a/libraries/path-bool/visual-tests/real-02/difference.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/division.svg b/libraries/path-bool/visual-tests/real-02/division.svg deleted file mode 100644 index 80cd574718..0000000000 --- a/libraries/path-bool/visual-tests/real-02/division.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/exclusion.svg b/libraries/path-bool/visual-tests/real-02/exclusion.svg deleted file mode 100644 index 9fa91f4f91..0000000000 --- a/libraries/path-bool/visual-tests/real-02/exclusion.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/fracture.svg b/libraries/path-bool/visual-tests/real-02/fracture.svg deleted file mode 100644 index 032dc6c4cb..0000000000 --- a/libraries/path-bool/visual-tests/real-02/fracture.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/intersection.svg b/libraries/path-bool/visual-tests/real-02/intersection.svg deleted file mode 100644 index e39315671a..0000000000 --- a/libraries/path-bool/visual-tests/real-02/intersection.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/original.svg b/libraries/path-bool/visual-tests/real-02/original.svg deleted file mode 100644 index 84ce568e6f..0000000000 --- a/libraries/path-bool/visual-tests/real-02/original.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-02/union.svg b/libraries/path-bool/visual-tests/real-02/union.svg deleted file mode 100644 index 48a54a3a6e..0000000000 --- a/libraries/path-bool/visual-tests/real-02/union.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/difference.svg b/libraries/path-bool/visual-tests/real-03/difference.svg deleted file mode 100644 index 17b8a2eb60..0000000000 --- a/libraries/path-bool/visual-tests/real-03/difference.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/division.svg b/libraries/path-bool/visual-tests/real-03/division.svg deleted file mode 100644 index 29c94779b6..0000000000 --- a/libraries/path-bool/visual-tests/real-03/division.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/exclusion.svg b/libraries/path-bool/visual-tests/real-03/exclusion.svg deleted file mode 100644 index 2e7c2febdb..0000000000 --- a/libraries/path-bool/visual-tests/real-03/exclusion.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/fracture.svg b/libraries/path-bool/visual-tests/real-03/fracture.svg deleted file mode 100644 index b0fe6ba413..0000000000 --- a/libraries/path-bool/visual-tests/real-03/fracture.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/intersection.svg b/libraries/path-bool/visual-tests/real-03/intersection.svg deleted file mode 100644 index e1df517ce9..0000000000 --- a/libraries/path-bool/visual-tests/real-03/intersection.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/original.svg b/libraries/path-bool/visual-tests/real-03/original.svg deleted file mode 100644 index eb0bf96a7a..0000000000 --- a/libraries/path-bool/visual-tests/real-03/original.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/real-03/union.svg b/libraries/path-bool/visual-tests/real-03/union.svg deleted file mode 100644 index cd81bc7928..0000000000 --- a/libraries/path-bool/visual-tests/real-03/union.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/difference.svg b/libraries/path-bool/visual-tests/simple-01/difference.svg deleted file mode 100644 index 96354d5814..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/difference.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/division.svg b/libraries/path-bool/visual-tests/simple-01/division.svg deleted file mode 100644 index 4fe8382a32..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/division.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/exclusion.svg b/libraries/path-bool/visual-tests/simple-01/exclusion.svg deleted file mode 100644 index ad6ee042f8..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/exclusion.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/fracture.svg b/libraries/path-bool/visual-tests/simple-01/fracture.svg deleted file mode 100644 index aad2afb342..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/fracture.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/intersection.svg b/libraries/path-bool/visual-tests/simple-01/intersection.svg deleted file mode 100644 index 488ffda77c..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/intersection.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/original.svg b/libraries/path-bool/visual-tests/simple-01/original.svg deleted file mode 100644 index 549185c1c1..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/original.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-01/union.svg b/libraries/path-bool/visual-tests/simple-01/union.svg deleted file mode 100644 index b8aab23f02..0000000000 --- a/libraries/path-bool/visual-tests/simple-01/union.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/difference.svg b/libraries/path-bool/visual-tests/simple-02/difference.svg deleted file mode 100644 index 435cbd5a31..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/difference.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/division.svg b/libraries/path-bool/visual-tests/simple-02/division.svg deleted file mode 100644 index 91396f9ba0..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/division.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/exclusion.svg b/libraries/path-bool/visual-tests/simple-02/exclusion.svg deleted file mode 100644 index a9d4317eb8..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/exclusion.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/fracture.svg b/libraries/path-bool/visual-tests/simple-02/fracture.svg deleted file mode 100644 index b93d72e7ce..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/fracture.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/intersection.svg b/libraries/path-bool/visual-tests/simple-02/intersection.svg deleted file mode 100644 index ce7bb7b894..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/intersection.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/original.svg b/libraries/path-bool/visual-tests/simple-02/original.svg deleted file mode 100644 index 808705ac4e..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/original.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-02/union.svg b/libraries/path-bool/visual-tests/simple-02/union.svg deleted file mode 100644 index 95235cd290..0000000000 --- a/libraries/path-bool/visual-tests/simple-02/union.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/difference.svg b/libraries/path-bool/visual-tests/simple-03/difference.svg deleted file mode 100644 index 64f3ea3247..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/division.svg b/libraries/path-bool/visual-tests/simple-03/division.svg deleted file mode 100644 index e345808e6a..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/exclusion.svg b/libraries/path-bool/visual-tests/simple-03/exclusion.svg deleted file mode 100644 index aa75604de7..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/fracture.svg b/libraries/path-bool/visual-tests/simple-03/fracture.svg deleted file mode 100644 index e6571d2982..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/intersection.svg b/libraries/path-bool/visual-tests/simple-03/intersection.svg deleted file mode 100644 index 41859bf933..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/original.svg b/libraries/path-bool/visual-tests/simple-03/original.svg deleted file mode 100644 index 719cd06f3b..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/original.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-03/union.svg b/libraries/path-bool/visual-tests/simple-03/union.svg deleted file mode 100644 index 9f15c2f113..0000000000 --- a/libraries/path-bool/visual-tests/simple-03/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/difference.svg b/libraries/path-bool/visual-tests/simple-04/difference.svg deleted file mode 100644 index 44633ab8b0..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/difference.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/division.svg b/libraries/path-bool/visual-tests/simple-04/division.svg deleted file mode 100644 index 5c9452e17a..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/division.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/exclusion.svg b/libraries/path-bool/visual-tests/simple-04/exclusion.svg deleted file mode 100644 index 0eb72069eb..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/exclusion.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/fracture.svg b/libraries/path-bool/visual-tests/simple-04/fracture.svg deleted file mode 100644 index b01da51aa3..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/fracture.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/intersection.svg b/libraries/path-bool/visual-tests/simple-04/intersection.svg deleted file mode 100644 index 8a74d3aa44..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/intersection.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/original.svg b/libraries/path-bool/visual-tests/simple-04/original.svg deleted file mode 100644 index 175b08d083..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/original.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-04/union.svg b/libraries/path-bool/visual-tests/simple-04/union.svg deleted file mode 100644 index 901db682f4..0000000000 --- a/libraries/path-bool/visual-tests/simple-04/union.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/difference.svg b/libraries/path-bool/visual-tests/simple-05/difference.svg deleted file mode 100644 index c8ced7fadd..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/division.svg b/libraries/path-bool/visual-tests/simple-05/division.svg deleted file mode 100644 index ea1e621a5a..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/exclusion.svg b/libraries/path-bool/visual-tests/simple-05/exclusion.svg deleted file mode 100644 index 85ce148dd6..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/fracture.svg b/libraries/path-bool/visual-tests/simple-05/fracture.svg deleted file mode 100644 index d4aece55f1..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/intersection.svg b/libraries/path-bool/visual-tests/simple-05/intersection.svg deleted file mode 100644 index 7dd6c44e1e..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/original.svg b/libraries/path-bool/visual-tests/simple-05/original.svg deleted file mode 100644 index 70d133b2d8..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/original.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-05/union.svg b/libraries/path-bool/visual-tests/simple-05/union.svg deleted file mode 100644 index 5b434a4093..0000000000 --- a/libraries/path-bool/visual-tests/simple-05/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/difference.svg b/libraries/path-bool/visual-tests/simple-06/difference.svg deleted file mode 100644 index d7e318710f..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/division.svg b/libraries/path-bool/visual-tests/simple-06/division.svg deleted file mode 100644 index d7e318710f..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/exclusion.svg b/libraries/path-bool/visual-tests/simple-06/exclusion.svg deleted file mode 100644 index c1b696c41b..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/fracture.svg b/libraries/path-bool/visual-tests/simple-06/fracture.svg deleted file mode 100644 index e85812ca63..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/intersection.svg b/libraries/path-bool/visual-tests/simple-06/intersection.svg deleted file mode 100644 index 6db8be9723..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/original.svg b/libraries/path-bool/visual-tests/simple-06/original.svg deleted file mode 100644 index d074477dbc..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/original.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-06/union.svg b/libraries/path-bool/visual-tests/simple-06/union.svg deleted file mode 100644 index c1b696c41b..0000000000 --- a/libraries/path-bool/visual-tests/simple-06/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/difference.svg b/libraries/path-bool/visual-tests/simple-07/difference.svg deleted file mode 100644 index bf204563c3..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/division.svg b/libraries/path-bool/visual-tests/simple-07/division.svg deleted file mode 100644 index db0eb8d0de..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/exclusion.svg b/libraries/path-bool/visual-tests/simple-07/exclusion.svg deleted file mode 100644 index 10010ca56d..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/fracture.svg b/libraries/path-bool/visual-tests/simple-07/fracture.svg deleted file mode 100644 index 12398a8d41..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/intersection.svg b/libraries/path-bool/visual-tests/simple-07/intersection.svg deleted file mode 100644 index 977056e673..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/original.svg b/libraries/path-bool/visual-tests/simple-07/original.svg deleted file mode 100644 index c54e7978ef..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-07/union.svg b/libraries/path-bool/visual-tests/simple-07/union.svg deleted file mode 100644 index 4cbea40eae..0000000000 --- a/libraries/path-bool/visual-tests/simple-07/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/difference.svg b/libraries/path-bool/visual-tests/simple-08/difference.svg deleted file mode 100644 index 879a7f1d1a..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/difference.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/division.svg b/libraries/path-bool/visual-tests/simple-08/division.svg deleted file mode 100644 index 088af61ea4..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/division.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/exclusion.svg b/libraries/path-bool/visual-tests/simple-08/exclusion.svg deleted file mode 100644 index 633bca9afd..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/exclusion.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/fracture.svg b/libraries/path-bool/visual-tests/simple-08/fracture.svg deleted file mode 100644 index 61206af95c..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/fracture.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/intersection.svg b/libraries/path-bool/visual-tests/simple-08/intersection.svg deleted file mode 100644 index f098ab7a92..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/intersection.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/original.svg b/libraries/path-bool/visual-tests/simple-08/original.svg deleted file mode 100644 index b6f46719a6..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/original.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/simple-08/union.svg b/libraries/path-bool/visual-tests/simple-08/union.svg deleted file mode 100644 index 2060282a06..0000000000 --- a/libraries/path-bool/visual-tests/simple-08/union.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/libraries/path-bool/visual-tests/template.svg b/libraries/path-bool/visual-tests/template.svg deleted file mode 100644 index 909afa291e..0000000000 --- a/libraries/path-bool/visual-tests/template.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index 492c4997be..d82f73e387 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -23,7 +23,6 @@ wasm = [ # Local dependencies dyn-any = { workspace = true } core-types = { workspace = true } -path-bool-nodes = { workspace = true } brush-nodes = { workspace = true } graphene-core = { workspace = true } graphene-application-io = { workspace = true } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 86c7b1397e..91b6831809 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -267,7 +267,7 @@ tagged_value! { GradientType(vector::style::GradientType), ReferencePoint(vector::ReferencePoint), CentroidType(vector::misc::CentroidType), - BooleanOperation(path_bool_nodes::BooleanOperation), + BooleanOperation(vector::misc::BooleanOperation), TextAlign(text_nodes::TextAlign), } diff --git a/node-graph/interpreted-executor/Cargo.toml b/node-graph/interpreted-executor/Cargo.toml index 05a9f9c8e9..1013f48862 100644 --- a/node-graph/interpreted-executor/Cargo.toml +++ b/node-graph/interpreted-executor/Cargo.toml @@ -16,7 +16,6 @@ graph-craft = { workspace = true } graphene-core = { workspace = true } wgpu-executor = { workspace = true } core-types = { workspace = true } -path-bool-nodes = { workspace = true } dyn-any = { workspace = true } # Workspace dependencies diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index e4907b640b..74b2733afa 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -86,7 +86,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => BlendMode]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::path_bool::BooleanOperation]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]), @@ -225,7 +225,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => path_bool_nodes::BooleanOperation]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]), ]; diff --git a/node-graph/libraries/vector-types/src/vector/misc.rs b/node-graph/libraries/vector-types/src/vector/misc.rs index 1d74519c12..3270021931 100644 --- a/node-graph/libraries/vector-types/src/vector/misc.rs +++ b/node-graph/libraries/vector-types/src/vector/misc.rs @@ -7,6 +7,23 @@ use glam::DVec2; use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, QuadBez}; use std::ops::Sub; +#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)] +#[widget(Radio)] +pub enum BooleanOperation { + #[default] + #[icon("BooleanUnion")] + Union, + #[icon("BooleanSubtractFront")] + SubtractFront, + #[icon("BooleanSubtractBack")] + SubtractBack, + #[icon("BooleanIntersect")] + Intersect, + #[icon("BooleanDifference")] + Difference, +} + /// Represents different geometric interpretations of calculating the centroid (center of mass). #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)] diff --git a/node-graph/nodes/gstd/src/lib.rs b/node-graph/nodes/gstd/src/lib.rs index 101fdab82f..bb6a083ca0 100644 --- a/node-graph/nodes/gstd/src/lib.rs +++ b/node-graph/nodes/gstd/src/lib.rs @@ -14,7 +14,7 @@ pub use graphene_core::debug; pub use graphic_nodes; pub use graphic_types::{Artboard, Graphic, Vector}; pub use math_nodes; -pub use path_bool_nodes as path_bool; +pub use path_bool_nodes; pub use raster_nodes; pub use repeat_nodes; pub use text_nodes; diff --git a/node-graph/nodes/path-bool/Cargo.toml b/node-graph/nodes/path-bool/Cargo.toml index 54010339e2..34e44b9d0c 100644 --- a/node-graph/nodes/path-bool/Cargo.toml +++ b/node-graph/nodes/path-bool/Cargo.toml @@ -6,22 +6,13 @@ description = "Path boolean operation nodes for Graphene" authors = ["Graphite Authors "] license = "MIT OR Apache-2.0" -[features] -wasm = ["core-types/wasm", "tsify", "wasm-bindgen"] - [dependencies] # Local dependencies -dyn-any = { workspace = true } core-types = { workspace = true } graphic-types = { workspace = true } node-macro = { workspace = true } glam = { workspace = true } linesweeper = { workspace = true } log = { workspace = true } -serde = { workspace = true } smallvec = { workspace = true } vector-types = { workspace = true } - -# Optional workspace dependencies -tsify = { workspace = true, optional = true } -wasm-bindgen = { workspace = true, optional = true } diff --git a/node-graph/nodes/path-bool/src/lib.rs b/node-graph/nodes/path-bool/src/lib.rs index 890a9337a8..3a80a44189 100644 --- a/node-graph/nodes/path-bool/src/lib.rs +++ b/node-graph/nodes/path-bool/src/lib.rs @@ -1,6 +1,5 @@ use core_types::table::{Table, TableRow, TableRowRef}; use core_types::{Color, Ctx}; -use dyn_any::DynAny; use glam::{DAffine2, DVec2}; use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath}; use graphic_types::vector_types::vector::PointId; @@ -11,28 +10,12 @@ use linesweeper::topology::Topology; use linesweeper::{BinaryOp, FillRule, binary_op}; use smallvec::SmallVec; use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez}; +pub use vector_types::vector::misc::BooleanOperation; // TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls, // TODO: since before we used a Vec of single-row tables and now we use a single table // TODO: with multiple rows while still assuming a single row for the boolean operations. -#[cfg_attr(feature = "wasm", derive(tsify::Tsify))] -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)] -#[widget(Radio)] -pub enum BooleanOperation { - #[default] - #[icon("BooleanUnion")] - Union, - #[icon("BooleanSubtractFront")] - SubtractFront, - #[icon("BooleanSubtractBack")] - SubtractBack, - #[icon("BooleanIntersect")] - Intersect, - #[icon("BooleanDifference")] - Difference, -} - /// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method. #[node_macro::node(category(""))] async fn boolean_operation( diff --git a/website/content/blog/2024-10-15-graphite-progress-report-q3-2024.md b/website/content/blog/2024-10-15-graphite-progress-report-q3-2024.md index 8c2b41c0ef..45b7b61364 100644 --- a/website/content/blog/2024-10-15-graphite-progress-report-q3-2024.md +++ b/website/content/blog/2024-10-15-graphite-progress-report-q3-2024.md @@ -235,7 +235,7 @@ EDITOR'S NOTE: The grammatical structure of each bullet point should follow the - Caching of boolean operations enabled by fixing the Select tool's layer click targets robustly despite the boolean node altering the shape data ([commit 6ecb173](https://github.com/GraphiteEditor/Graphite/commit/6ecb173c1c06807f13a859ef90b7d7f43af042be), [#1946](https://github.com/GraphiteEditor/Graphite/pull/1946), [#1986](https://github.com/GraphiteEditor/Graphite/pull/1986)) -- Rewritten boolean operations algorithm (our new `path-bool` library) that runs purely in Rust instead of making high-overhead calls into a JavaScript library ([#1952](https://github.com/GraphiteEditor/Graphite/pull/1952), [#2000](https://github.com/GraphiteEditor/Graphite/pull/2000)) +- Rewritten boolean operations algorithm that runs purely in Rust instead of making high-overhead calls into a JavaScript library ([#1952](https://github.com/GraphiteEditor/Graphite/pull/1952), [#2000](https://github.com/GraphiteEditor/Graphite/pull/2000)) - Internal hash-based data tracking now benefits from caching of hash calculations ([#1876](https://github.com/GraphiteEditor/Graphite/pull/1876))