diff --git a/.gitignore b/.gitignore index ea8c4bf..7517b19 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target + +# Files generated by Zngur in build.rs +/generated +/cpp/build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fde8c6f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cpp/manifold"] + path = cpp/manifold + url = https://github.com/elalish/manifold.git diff --git a/Cargo.lock b/Cargo.lock index fddd308..8d36853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "approx" version = "0.5.1" @@ -11,18 +26,191 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", +] + +[[package]] +name = "ariadne" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" +dependencies = [ + "unicode-width", + "yansi", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-rs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde4c9ce1d8e6e94b4c0ee341c000d79335b7c55b7d7979b36ca803c85c091ba" +dependencies = [ + "unicode-ident", +] + [[package]] name = "bytemuck" version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +[[package]] +name = "cc" +version = "1.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chumsky" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e82d74e6c83060ec269fe9e0d408d6de4a1645d525f9a0bbbb841ba4efd91ac" +dependencies = [ + "hashbrown 0.15.5", + "regex-automata", + "serde", + "stacker", + "unicode-ident", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "glam" version = "0.14.0" @@ -119,6 +307,80 @@ version = "0.30.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + [[package]] name = "matrixmultiply" version = "0.3.10" @@ -129,11 +391,20 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + [[package]] name = "meshbool" version = "0.0.0" dependencies = [ + "build-rs", "nalgebra", + "rayon", + "zngur", ] [[package]] @@ -215,18 +486,107 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rawpointer" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "safe_arch" version = "0.7.4" @@ -236,6 +596,100 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "sailfish" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40efbac4e16ca6b1a5706348ada4d8b67d7b417ac2001aa6c4ae092511bb1763" +dependencies = [ + "itoap", + "ryu", + "sailfish-macros", + "version_check", +] + +[[package]] +name = "sailfish-compiler" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003c945c249a98ec4488826a3ffe72aa66b0dcf8b0218a6bd87c682cf8c3bba6" +dependencies = [ + "filetime", + "home", + "memchr", + "proc-macro2", + "quote", + "serde", + "syn", + "toml", +] + +[[package]] +name = "sailfish-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5c507d85499f2e67cb3c9da7d50741039559afee4ccd137985a188af4d62e6" +dependencies = [ + "proc-macro2", + "sailfish-compiler", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "simba" version = "0.9.1" @@ -249,12 +703,101 @@ dependencies = [ "wide", ] +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wide" version = "0.7.33" @@ -264,3 +807,220 @@ dependencies = [ "bytemuck", "safe_arch", ] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zngur" +version = "0.7.0" +source = "git+https://github.com/HKalbasi/zngur.git?rev=0765d1ba9a6395528406576dfee33142f318b1d5#0765d1ba9a6395528406576dfee33142f318b1d5" +dependencies = [ + "zngur-generator", +] + +[[package]] +name = "zngur-def" +version = "0.7.0" +source = "git+https://github.com/HKalbasi/zngur.git?rev=0765d1ba9a6395528406576dfee33142f318b1d5#0765d1ba9a6395528406576dfee33142f318b1d5" +dependencies = [ + "itertools", +] + +[[package]] +name = "zngur-generator" +version = "0.7.0" +source = "git+https://github.com/HKalbasi/zngur.git?rev=0765d1ba9a6395528406576dfee33142f318b1d5#0765d1ba9a6395528406576dfee33142f318b1d5" +dependencies = [ + "hex", + "itertools", + "sailfish", + "sha2", + "zngur-def", + "zngur-parser", +] + +[[package]] +name = "zngur-parser" +version = "0.7.0" +source = "git+https://github.com/HKalbasi/zngur.git?rev=0765d1ba9a6395528406576dfee33142f318b1d5#0765d1ba9a6395528406576dfee33142f318b1d5" +dependencies = [ + "ariadne", + "chumsky", + "itertools", + "zngur-def", +] diff --git a/Cargo.toml b/Cargo.toml index d7ef28e..0aa81da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,19 @@ name = "meshbool" version = "0.0.0" edition = "2024" +[lib] +name = "meshbool" +crate-type = ["staticlib", "cdylib", "rlib"] + [features] +default = [] +zngur = [ "dep:zngur" ] [dependencies] +rayon = "1.11.0" nalgebra = { version = "0.34", default-features = false, features = ["std"] } + +[build-dependencies] +# zngur = { version = "0.7", optional = true } +zngur = { git = "https://github.com/HKalbasi/zngur.git", rev = "0765d1ba9a6395528406576dfee33142f318b1d5", optional = true } +build-rs = { version = "0.3.2"} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..75eafcf --- /dev/null +++ b/build.rs @@ -0,0 +1,29 @@ +#[cfg(feature = "zngur")] +use zngur::Zngur; + +fn main() { + build_rs::output::rerun_if_changed("Cargo.toml"); + #[allow(unused)] + let crate_dir = build_rs::input::cargo_manifest_dir(); + #[allow(unused)] + let out_dir = build_rs::input::out_dir(); + + #[cfg(feature = "zngur")] + { + build_rs::output::rerun_if_changed("main.zng"); + + let generated_cpp = std::path::PathBuf::from("generated/meshbool/"); + let _ = std::fs::create_dir_all(&generated_cpp); + let rs_file = out_dir.join("generated.rs"); + let h_file = generated_cpp.join("meshbool.h"); + + Zngur::from_zng_file(crate_dir.join("main.zng")) + .with_cpp_file(generated_cpp.join("generated.cpp")) + .with_h_file(h_file) + .with_rs_file(rs_file.clone()) + .generate(); + let s = std::fs::read_to_string(&rs_file).expect("File should exist"); + let new = s.replace("#[no_mangle]", "#[unsafe(no_mangle)]"); + std::fs::write(rs_file, new).expect("Failed to write file"); + } +} diff --git a/cpp/.clang-format b/cpp/.clang-format new file mode 100644 index 0000000..abf2f57 --- /dev/null +++ b/cpp/.clang-format @@ -0,0 +1,4 @@ +BasedOnStyle: Google +PointerAlignment: Left +ReferenceAlignment: Left +DerivePointerAlignment: false \ No newline at end of file diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..e1d819a --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,392 @@ +# Copyright 2020 The Manifold Authors. +# +# 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 +# +# https://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. + +# cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.23) +project(manifold LANGUAGES CXX) + +# Use C++17 +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_VERBOSE_MAKEFILE ON) + +include(CTest) +enable_testing() +include(GNUInstallDirs) +include(CMakeDependentOption) + +# Define Manifold version +set(MANIFOLD_VERSION_MAJOR 3) +set(MANIFOLD_VERSION_MINOR 2) +set(MANIFOLD_VERSION_PATCH 1) +set( + MANIFOLD_VERSION + "${MANIFOLD_VERSION_MAJOR}.${MANIFOLD_VERSION_MINOR}.${MANIFOLD_VERSION_PATCH}" +) + +# Primary user facing options +option(MANIFOLD_CROSS_SECTION "Build CrossSection for 2D support" OFF) +option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF) +option(MANIFOLD_ASSERT "Enable assertions - requires MANIFOLD_DEBUG" OFF) +option(MANIFOLD_STRICT "Treat compile warnings as fatal build errors" ON) +option( + MANIFOLD_DOWNLOADS + "Allow Manifold build to download missing dependencies" + ON +) +option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF) +option(MANIFOLD_PAR "Enable Parallel backend" OFF) +option( + MANIFOLD_OPTIMIZED + "Force optimized build, even with debugging enabled" + OFF +) +option(MANIFOLD_TEST "Enable testing suite" ON) +option(MANIFOLD_PYBIND "Build python bindings" OFF) +# MANIFOLD_CBIND is only available when MANIFOLD_CROSS_SECTION is enabled +cmake_dependent_option( + MANIFOLD_CBIND + "Build C (FFI) bindings" + ON + "MANIFOLD_CROSS_SECTION" + OFF +) +# MANIFOLD_JSBIND is only available when building with Emscripten +cmake_dependent_option( + MANIFOLD_JSBIND + "Build js binding" + ON + "EMSCRIPTEN" + OFF +) +# These three options can force the build to avoid using the system version of +# the dependency +# This will either use the provided source directory via +# FETCHCONTENT_SOURCE_DIR_XXX, or fetch the source from GitHub. +# Note that the dependency will be built as static dependency to avoid dynamic +# library conflict. +# When the system package is unavailable, the option will be automatically set +# to true. +option(MANIFOLD_USE_BUILTIN_TBB "Use builtin tbb" OFF) +option(MANIFOLD_USE_BUILTIN_CLIPPER2 "Use builtin clipper2" OFF) +option(MANIFOLD_USE_BUILTIN_NANOBIND "Use builtin nanobind" OFF) + +# default to Release build +option(CMAKE_BUILD_TYPE "Build type" Release) +# default to building shared library +option(BUILD_SHARED_LIBS "Build shared library" ON) +# Set some option values in the CMake cache +set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags") + +# Development options +option(TRACY_ENABLE "Use tracy profiling" OFF) +option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF) +option(MANIFOLD_FUZZ "Enable fuzzing tests" OFF) +mark_as_advanced(TRACY_ENABLE) +mark_as_advanced(TRACY_MEMORY_USAGE) +mark_as_advanced(MANIFOLD_FUZZ) + +# Always build position independent code for relocatability +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Various Compiler Flags +if(MANIFOLD_FUZZ) + # we should enable debug checks + set(MANIFOLD_DEBUG ON) + # enable fuzztest fuzzing mode + set(FUZZTEST_FUZZING_MODE ON) + # address sanitizer required + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +if(TRACY_ENABLE) + option(CMAKE_BUILD_TYPE "Build type" RelWithDebInfo) +endif() + +if(EMSCRIPTEN) + message("Building for Emscripten") + add_link_options(-sALLOW_MEMORY_GROWTH=1) + add_link_options(-sMAXIMUM_MEMORY=4294967296) + if(MANIFOLD_PAR) + set(CMAKE_THREAD_LIBS_INIT "-pthread") + add_compile_options(-pthread) + # mimalloc is needed for good performance + add_link_options(-sMALLOC=mimalloc) + add_link_options(-sPTHREAD_POOL_SIZE=4) + # The default stack size apparently causes problem when parallelization is + # enabled. + add_link_options(-sSTACK_SIZE=30MB) + add_link_options(-sINITIAL_MEMORY=32MB) + endif() + if(MANIFOLD_DEBUG) + list(APPEND MANIFOLD_FLAGS -fexceptions) + add_link_options(-fexceptions) + add_link_options(-sDISABLE_EXCEPTION_CATCHING=0) + endif() + set(MANIFOLD_PYBIND OFF) + set(BUILD_SHARED_LIBS OFF) +endif() + +if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN) + # for nixos + set( + CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES} + ) +endif() + +if(MSVC) + list(APPEND MANIFOLD_FLAGS /DNOMINMAX /bigobj) +else() + list( + APPEND + WARNING_FLAGS + -Wall + -Wno-unknown-warning-option + -Wno-unused + -Wno-shorten-64-to-32 + ) + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND WARNING_FLAGS -Wno-format) + endif() + elseif(PROJECT_IS_TOP_LEVEL) + # only do -Werror if we are the top level project and + # MANIFOLD_STRICT is on + if(MANIFOLD_STRICT) + list(APPEND WARNING_FLAGS -Werror) + endif() + endif() + list(APPEND MANIFOLD_FLAGS ${WARNING_FLAGS}) + if( + MANIFOLD_OPTIMIZED + OR "${CMAKE_BUILD_TYPE}" STREQUAL "Release" + OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" + ) + list(APPEND MANIFOLD_FLAGS -O3) + endif() + if("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") + list(APPEND MANIFOLD_FLAGS -fno-omit-frame-pointer) + endif() + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # disable fp math optimizations, e.g. FMA for supported architectures, as + # this changes floating-point results + # clang defaults to -ffp-contract=off so we don't have to set that + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-ffp-contract=off HAS_FP_CONTRACT) + check_cxx_compiler_flag(-fexcess-precision=standard HAS_FP_PRECISION) + if(HAS_FP_CONTRACT) + list(APPEND MANIFOLD_FLAGS -ffp-contract=off) + endif() + if(HAS_FP_PRECISION) + list(APPEND MANIFOLD_FLAGS -fexcess-precision=standard) + endif() + endif() + if(CODE_COVERAGE) + list( + APPEND + MANIFOLD_FLAGS + -coverage + -fno-inline-small-functions + -fkeep-inline-functions + -fkeep-static-functions + ) + add_link_options(-coverage) + endif() +endif() + +# RPath settings +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +list( + FIND + CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES + ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} + isSystemDir +) +if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) +endif("${isSystemDir}" STREQUAL "-1") + +include(${PROJECT_SOURCE_DIR}/manifold/cmake/manifoldDeps.cmake) +include(${PROJECT_SOURCE_DIR}/manifold/cmake/configHelper.cmake) + +FetchContent_Declare( + Corrosion + GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git + GIT_TAG v0.5 # Optionally specify a commit hash, version tag or branch here +) +FetchContent_MakeAvailable(Corrosion) + +corrosion_import_crate( + MANIFEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../Cargo.toml" + FEATURES "zngur" +) + +# Add a manually written header file which will be exported +# Requires CMake >=3.23 +target_sources(meshbool INTERFACE + FILE_SET HEADERS + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../generated/" + FILES + "${CMAKE_CURRENT_SOURCE_DIR}/../generated/meshbool/meshbool.h" +) +target_sources(meshbool INTERFACE + FILE_SET HEADERS + BASE_DIRS "include" + FILES + "include/meshbool/common.h" + "include/meshbool/cross_section.h" + "include/meshbool/linalg.h" + "include/meshbool/manifold.h" + "include/meshbool/meshIO.h" + "include/meshbool/optional_assert.h" + "include/meshbool/polygon.h" + "include/meshbool/vec_view.h" + "include/manifold/common.h" + "include/manifold/cross_section.h" + "include/manifold/linalg.h" + "include/manifold/manifold.h" + "include/manifold/meshIO.h" + "include/manifold/optional_assert.h" + "include/manifold/polygon.h" + "include/manifold/vec_view.h" + "manifold_wrapper/src/utils.h" +) + +target_sources(meshbool INTERFACE + FILE_SET HEADERS + BASE_DIRS "manifold_wrapper/src" + FILES + "manifold_wrapper/src/../src/utils.h" +) + + +target_sources(meshbool INTERFACE + FILE_SET HEADERS + BASE_DIRS "new/src" + FILES + "new/src/utils.h" +) +add_library(manifold ALIAS meshbool) + +file(REMOVE_RECURSE ${PROJECT_SOURCE_DIR}/manifold/src) + +# add_subdirectory(src) +add_subdirectory(manifold_wrapper/bindings) +if(MANIFOLD_TEST) + add_subdirectory(manifold_wrapper/samples) + add_subdirectory(manifold_wrapper/test/) + # add_subdirectory(manifold/extras) +endif() +# +# target_include_directories(manifold_test PRIVATE +# ${CMAKE_SOURCE_DIR}/new/src +# ${CMAKE_SOURCE_DIR}/manifold/test/.. +# ) + +include(${PROJECT_SOURCE_DIR}/manifold/cmake/info.cmake) + +# note that the path ${CMAKE_CURRENT_BINARY_DIR}/include is included when we +# build the manifold target (as ${PROJECT_SOURCE_DIR}/include), so users can +# include meshbool/version.h without installing our library. +configure_file( + manifold/cmake/version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/meshbool/version.h + @ONLY +) +set_source_files_properties( + ${CMAKE_CURRENT_BINARY_DIR}/include/meshbool/version.h + PROPERTIES GENERATED TRUE +) + +# If it's an EMSCRIPTEN build, we're done +if(EMSCRIPTEN) + return() +endif() + +# CMake exports +configure_file( + manifold/cmake/manifoldConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake + @ONLY +) +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cmake/manifoldConfigVersion.cmake + VERSION ${MANIFOLD_VERSION} + COMPATIBILITY SameMajorVersion +) + +# Location of inputs for CMake find_package - see: +# https://cmake.org/cmake/help/latest/command/find_package.html +set(EXPORT_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake) + +install( + EXPORT manifoldTargets + NAMESPACE manifold:: + DESTINATION ${EXPORT_INSTALL_DIR}/meshbool +) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/cmake/manifoldConfigVersion.cmake + ${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake + DESTINATION ${EXPORT_INSTALL_DIR}/meshbool +) + +# install public headers +set( + MANIFOLD_PUBLIC_HDRS + common.h + linalg.h + manifold.h + optional_assert.h + polygon.h + vec_view.h + $<$:cross_section.h> + $<$:meshIO.h> +) +list(TRANSFORM MANIFOLD_PUBLIC_HDRS PREPEND include/manifold/) +list( + APPEND + MANIFOLD_PUBLIC_HDRS + ${CMAKE_CURRENT_BINARY_DIR}/include/meshbool/version.h +) + +install( + FILES ${MANIFOLD_PUBLIC_HDRS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/meshbool +) + +# PkgConfig file +if(MANIFOLD_PAR) + set(TEMPLATE_OPTIONAL_TBB "tbb") +endif() +if(MANIFOLD_CROSS_SECTION) + set(TEMPLATE_OPTIONAL_CLIPPER "Clipper2") +endif() +configure_file( + manifold/cmake/manifold.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/manifold.pc + @ONLY +) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/manifold.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig +) diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..d718d35 --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,20 @@ +# Manifold Test Suite + +This directory contains files for testing MeshBool against the Manifold test +suite using C++ bindings that map to the Manifold API. + +These bindings should ***not*** be used directly in new C++ projects and is +recommended to use [Zngur](https://github.com/HKalbasi/zngur) as these bindings +are not optimized and not the main focus of the project. Official bindings for +general use are not currently being worked on. + + +## Build + +``` +mkdir build && cd build +cmake .. +make +``` + +The test bin is at `build/manifold_wrapper/test/manifold_test` diff --git a/cpp/include/manifold/common.h b/cpp/include/manifold/common.h new file mode 100644 index 0000000..0dc04ab --- /dev/null +++ b/cpp/include/manifold/common.h @@ -0,0 +1,4 @@ +#pragma once +// #error "You found me!" + +#include "meshbool/common.h" diff --git a/cpp/include/manifold/linalg.h b/cpp/include/manifold/linalg.h new file mode 100644 index 0000000..12bd09c --- /dev/null +++ b/cpp/include/manifold/linalg.h @@ -0,0 +1,3 @@ +#pragma once +// #error "You found me!" +#include "meshbool/linalg.h" diff --git a/cpp/include/manifold/manifold.h b/cpp/include/manifold/manifold.h new file mode 100644 index 0000000..46b501d --- /dev/null +++ b/cpp/include/manifold/manifold.h @@ -0,0 +1,3 @@ +#pragma once +// #error "You found me!" +#include "meshbool/manifold.h" diff --git a/cpp/include/manifold/meshIO.h b/cpp/include/manifold/meshIO.h new file mode 100644 index 0000000..11a04ef --- /dev/null +++ b/cpp/include/manifold/meshIO.h @@ -0,0 +1,3 @@ +#pragma once +// #error "You found me!" +#include "meshbool/meshIO.h" diff --git a/cpp/include/manifold/optional_assert.h b/cpp/include/manifold/optional_assert.h new file mode 100644 index 0000000..5ff8697 --- /dev/null +++ b/cpp/include/manifold/optional_assert.h @@ -0,0 +1,3 @@ +#pragma once +// #error "You found me!" +#include "meshbool/optional_assert.h" diff --git a/cpp/include/manifold/polygon.h b/cpp/include/manifold/polygon.h new file mode 100644 index 0000000..625cb2d --- /dev/null +++ b/cpp/include/manifold/polygon.h @@ -0,0 +1,3 @@ +#pragma once +// #error "You found me!" +#include "meshbool/polygon.h" diff --git a/cpp/include/manifold/ross_section.h b/cpp/include/manifold/ross_section.h new file mode 100644 index 0000000..1d85f72 --- /dev/null +++ b/cpp/include/manifold/ross_section.h @@ -0,0 +1,3 @@ +#pragma once +// #error "You found me!" +#include "meshbool/cross_section.h" diff --git a/cpp/include/manifold/vec_view.h b/cpp/include/manifold/vec_view.h new file mode 100644 index 0000000..b8015c1 --- /dev/null +++ b/cpp/include/manifold/vec_view.h @@ -0,0 +1,4 @@ +#pragma once +// #error "You found me!" +#include "meshbool/vec_view.h" +#include "meshbool/vec_wrapper.h" diff --git a/cpp/include/meshbool/common.h b/cpp/include/meshbool/common.h new file mode 100644 index 0000000..d81b850 --- /dev/null +++ b/cpp/include/meshbool/common.h @@ -0,0 +1,641 @@ +// Copyright 2021 The Manifold Authors. +// +// 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. + +#pragma once +#include +#include + +#ifdef MANIFOLD_DEBUG +#include +#endif + +#include "linalg.h" +#include "optional_assert.h" +#include "vec_view.h" + +namespace manifold { +/** @addtogroup Math + * @ingroup Core + * @brief Simple math operations. + * */ + +/** @addtogroup LinAlg + * @{ + */ +namespace la = linalg; +using vec2 = la::vec; +using vec3 = la::vec; +using vec4 = la::vec; +using bvec4 = la::vec; +using mat2 = la::mat; +using mat3x2 = la::mat; +using mat4x2 = la::mat; +using mat2x3 = la::mat; +using mat3 = la::mat; +using mat4x3 = la::mat; +using mat3x4 = la::mat; +using mat4 = la::mat; +using ivec2 = la::vec; +using ivec3 = la::vec; +using ivec4 = la::vec; +using quat = la::vec; +/** @} */ + +/** @addtogroup Scalar + * @ingroup Math + * @brief Simple scalar operations. + * @{ + */ + +constexpr double kPi = 3.14159265358979323846264338327950288; +constexpr double kTwoPi = 6.28318530717958647692528676655900576; +constexpr double kHalfPi = 1.57079632679489661923132169163975144; + +/** + * Convert degrees to radians. + * + * @param a Angle in degrees. + */ +constexpr double radians(double a) { return a * kPi / 180; } + +/** + * Convert radians to degrees. + * + * @param a Angle in radians. + */ +constexpr double degrees(double a) { return a * 180 / kPi; } + +/** + * Performs smooth Hermite interpolation between 0 and 1 when edge0 < x < edge1. + * + * @param edge0 Specifies the value of the lower edge of the Hermite function. + * @param edge1 Specifies the value of the upper edge of the Hermite function. + * @param a Specifies the source value for interpolation. + */ +constexpr double smoothstep(double edge0, double edge1, double a) { + const double x = la::clamp((a - edge0) / (edge1 - edge0), 0, 1); + return x * x * (3 - 2 * x); +} + +/** + * Sine function where multiples of 90 degrees come out exact. + * + * @param x Angle in degrees. + */ +inline double sind(double x) { + if (!la::isfinite(x)) return sin(x); + if (x < 0.0) return -sind(-x); + int quo; + x = remquo(fabs(x), 90.0, &quo); + switch (quo % 4) { + case 0: + return sin(radians(x)); + case 1: + return cos(radians(x)); + case 2: + return -sin(radians(x)); + case 3: + return -cos(radians(x)); + } + return 0.0; +} + +/** + * Cosine function where multiples of 90 degrees come out exact. + * + * @param x Angle in degrees. + */ +inline double cosd(double x) { return sind(x + 90.0); } +/** @} */ + +/** @addtogroup Structs + * @ingroup Core + * @brief Miscellaneous data structures for interfacing with this library. + * @{ + */ + +/** + * @brief Single polygon contour, wound CCW. First and last point are implicitly + * connected. Should ensure all input is + * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). + */ +using SimplePolygon = std::vector; + +/** + * @brief Set of polygons with holes. Order of contours is arbitrary. Can + * contain any depth of nested holes and any number of separate polygons. Should + * ensure all input is + * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). + */ +using Polygons = std::vector; + +using RSPolygons = + rust::std::vec::Vec>>; +inline Polygons RSPolygonsToCPPPolygons(const RSPolygons& polys) { + Polygons polys_cpp; + polys_cpp.reserve(polys.len()); + + for (size_t i = 0; i < polys.len(); i++) { + SimplePolygon polys_sub_cpp; + auto vec = polys.get(i).unwrap(); + polys_sub_cpp.reserve(vec.len()); + for (size_t j = 0; j < vec.len(); j++) { + auto p = vec.get(j).unwrap(); + polys_sub_cpp.push_back({p.get_x(), p.get_y()}); + } + polys_cpp.push_back(polys_sub_cpp); + } + return polys_cpp; +} +inline RSPolygons CPPPolygonsToRSPolygons(const Polygons& polys) { + using RustPoint2 = rust::nalgebra::Point2; + auto polys_rs = + ::rust::std::vec::Vec<::rust::std::vec::Vec>::new_(); + for (const auto& vec : polys) { + auto polys_sub_rs = ::rust::std::vec::Vec::new_(); + for (const auto& p : vec) { + polys_sub_rs.push(RustPoint2::new_(p.x, p.y)); + } + polys_rs.push(std::move(polys_sub_rs)); + } + + return polys_rs; +} + +/** + * @brief Defines which edges to sharpen and how much for the Manifold.Smooth() + * constructor. + */ +struct Smoothness { + /// The halfedge index = 3 * tri + i, referring to Mesh.triVerts[tri][i]. + size_t halfedge; + /// A value between 0 and 1, where 0 is sharp and 1 is the default and the + /// curvature is interpolated between these values. The two paired halfedges + /// can have different values while maintaining C-1 continuity (except for 0). + double smoothness; +}; + +/** + * @brief Axis-aligned 3D box, primarily for bounding. + */ +struct Box { + vec3 min = vec3(std::numeric_limits::infinity()); + vec3 max = vec3(-std::numeric_limits::infinity()); + + /** + * Default constructor is an infinite box that contains all space. + */ + constexpr Box() {} + + /** + * Creates a box that contains the two given points. + */ + constexpr Box(const vec3 p1, const vec3 p2) { + min = la::min(p1, p2); + max = la::max(p1, p2); + } + + /** + * Returns the dimensions of the Box. + */ + constexpr vec3 Size() const { return max - min; } + + /** + * Returns the center point of the Box. + */ + constexpr vec3 Center() const { return 0.5 * (max + min); } + + /** + * Returns the absolute-largest coordinate value of any contained + * point. + */ + constexpr double Scale() const { + vec3 absMax = la::max(la::abs(min), la::abs(max)); + return la::max(absMax.x, la::max(absMax.y, absMax.z)); + } + + /** + * Does this box contain (includes equal) the given point? + */ + constexpr bool Contains(const vec3& p) const { + return la::all(la::gequal(p, min)) && la::all(la::gequal(max, p)); + } + + /** + * Does this box contain (includes equal) the given box? + */ + constexpr bool Contains(const Box& box) const { + return la::all(la::gequal(box.min, min)) && + la::all(la::gequal(max, box.max)); + } + + /** + * Expand this box to include the given point. + */ + void Union(const vec3 p) { + min = la::min(min, p); + max = la::max(max, p); + } + + /** + * Expand this box to include the given box. + */ + constexpr Box Union(const Box& box) const { + Box out; + out.min = la::min(min, box.min); + out.max = la::max(max, box.max); + return out; + } + + /** + * Transform the given box by the given axis-aligned affine transform. + * + * Ensure the transform passed in is axis-aligned (rotations are all + * multiples of 90 degrees), or else the resulting bounding box will no longer + * bound properly. + */ + constexpr Box Transform(const mat3x4& transform) const { + Box out; + vec3 minT = transform * vec4(min, 1.0); + vec3 maxT = transform * vec4(max, 1.0); + out.min = la::min(minT, maxT); + out.max = la::max(minT, maxT); + return out; + } + + /** + * Shift this box by the given vector. + */ + constexpr Box operator+(vec3 shift) const { + Box out; + out.min = min + shift; + out.max = max + shift; + return out; + } + + /** + * Shift this box in-place by the given vector. + */ + Box& operator+=(vec3 shift) { + min += shift; + max += shift; + return *this; + } + + /** + * Scale this box by the given vector. + */ + constexpr Box operator*(vec3 scale) const { + Box out; + out.min = min * scale; + out.max = max * scale; + return out; + } + + /** + * Scale this box in-place by the given vector. + */ + Box& operator*=(vec3 scale) { + min *= scale; + max *= scale; + return *this; + } + + /** + * Does this box overlap the one given (including equality)? + */ + constexpr bool DoesOverlap(const Box& box) const { + return min.x <= box.max.x && min.y <= box.max.y && min.z <= box.max.z && + max.x >= box.min.x && max.y >= box.min.y && max.z >= box.min.z; + } + + /** + * Does the given point project within the XY extent of this box + * (including equality)? + */ + constexpr bool DoesOverlap(vec3 p) const { // projected in z + return p.x <= max.x && p.x >= min.x && p.y <= max.y && p.y >= min.y; + } + + /** + * Does this box have finite bounds? + */ + constexpr bool IsFinite() const { + return la::all(la::isfinite(min)) && la::all(la::isfinite(max)); + } +}; + +/** + * @brief Axis-aligned 2D box, primarily for bounding. + */ +struct Rect { + vec2 min = vec2(std::numeric_limits::infinity()); + vec2 max = vec2(-std::numeric_limits::infinity()); + + /** + * Default constructor is an empty rectangle.. + */ + constexpr Rect() {} + + /** + * Create a rectangle that contains the two given points. + */ + constexpr Rect(const vec2 a, const vec2 b) { + min = la::min(a, b); + max = la::max(a, b); + } + + /** @name Information + * Details of the rectangle + */ + ///@{ + + /** + * Return the dimensions of the rectangle. + */ + constexpr vec2 Size() const { return max - min; } + + /** + * Return the area of the rectangle. + */ + constexpr double Area() const { + auto sz = Size(); + return sz.x * sz.y; + } + + /** + * Returns the absolute-largest coordinate value of any contained + * point. + */ + constexpr double Scale() const { + vec2 absMax = la::max(la::abs(min), la::abs(max)); + return la::max(absMax.x, absMax.y); + } + + /** + * Returns the center point of the rectangle. + */ + constexpr vec2 Center() const { return 0.5 * (max + min); } + + /** + * Does this rectangle contain (includes on border) the given point? + */ + constexpr bool Contains(const vec2& p) const { + return la::all(la::gequal(p, min)) && la::all(la::gequal(max, p)); + } + + /** + * Does this rectangle contain (includes equal) the given rectangle? + */ + constexpr bool Contains(const Rect& rect) const { + return la::all(la::gequal(rect.min, min)) && + la::all(la::gequal(max, rect.max)); + } + + /** + * Does this rectangle overlap the one given (including equality)? + */ + constexpr bool DoesOverlap(const Rect& rect) const { + return min.x <= rect.max.x && min.y <= rect.max.y && max.x >= rect.min.x && + max.y >= rect.min.y; + } + + /** + * Is the rectangle empty (containing no space)? + */ + constexpr bool IsEmpty() const { return max.y <= min.y || max.x <= min.x; }; + + /** + * Does this recangle have finite bounds? + */ + constexpr bool IsFinite() const { + return la::all(la::isfinite(min)) && la::all(la::isfinite(max)); + } + + ///@} + + /** @name Modification + */ + ///@{ + + /** + * Expand this rectangle (in place) to include the given point. + */ + void Union(const vec2 p) { + min = la::min(min, p); + max = la::max(max, p); + } + + /** + * Expand this rectangle to include the given Rect. + */ + constexpr Rect Union(const Rect& rect) const { + Rect out; + out.min = la::min(min, rect.min); + out.max = la::max(max, rect.max); + return out; + } + + /** + * Shift this rectangle by the given vector. + */ + constexpr Rect operator+(const vec2 shift) const { + Rect out; + out.min = min + shift; + out.max = max + shift; + return out; + } + + /** + * Shift this rectangle in-place by the given vector. + */ + Rect& operator+=(const vec2 shift) { + min += shift; + max += shift; + return *this; + } + + /** + * Scale this rectangle by the given vector. + */ + constexpr Rect operator*(const vec2 scale) const { + Rect out; + out.min = min * scale; + out.max = max * scale; + return out; + } + + /** + * Scale this rectangle in-place by the given vector. + */ + Rect& operator*=(const vec2 scale) { + min *= scale; + max *= scale; + return *this; + } + + /** + * Transform the rectangle by the given axis-aligned affine transform. + * + * Ensure the transform passed in is axis-aligned (rotations are all + * multiples of 90 degrees), or else the resulting rectangle will no longer + * bound properly. + */ + constexpr Rect Transform(const mat2x3& m) const { + Rect rect; + rect.min = m * vec3(min, 1); + rect.max = m * vec3(max, 1); + return rect; + } + ///@} +}; + +/** + * @brief Boolean operation type: Add (Union), Subtract (Difference), and + * Intersect. + */ +enum class OpType { Add, Subtract, Intersect }; + +constexpr int DEFAULT_SEGMENTS = 0; +constexpr double DEFAULT_ANGLE = 10.0; +constexpr double DEFAULT_LENGTH = 1.0; +/** + * @brief These static properties control how circular shapes are quantized by + * default on construction. + * + * If circularSegments is specified, it takes + * precedence. If it is zero, then instead the minimum is used of the segments + * calculated based on edge length and angle, rounded up to the nearest + * multiple of four. To get numbers not divisible by four, circularSegments + * must be specified. + */ +class Quality { + private: + public: + inline static void SetMinCircularAngle(double angle) { + // TODO + } + inline static void SetMinCircularEdgeLength(double length) { + // TODO + } + inline static void SetCircularSegments(int number) { + // TODO + } + inline static int GetCircularSegments(double radius) { + // TODO + return DEFAULT_SEGMENTS; + } + inline static void ResetToDefaults() { + // TODO + } +}; +/** @} */ + +/** @addtogroup Debug + * @ingroup Optional + * @{ + */ + +/** + * @brief Global parameters that control debugging output. Only has an + * effect when compiled with the MANIFOLD_DEBUG flag. + */ +struct ExecutionParams { + /// Perform extra sanity checks and assertions on the intermediate data + /// structures. + bool intermediateChecks = false; + /// Perform 3D mesh self-intersection test on intermediate boolean results to + /// test for ϵ-validity. For debug purposes only. + bool selfIntersectionChecks = false; + /// If processOverlaps is false, a geometric check will be performed to assert + /// all triangles are CCW. + bool processOverlaps = true; + /// Suppresses printed errors regarding CW triangles. Has no effect if + /// processOverlaps is true. + bool suppressErrors = false; + /// Deprecated! This value no longer has any effect, as cleanup now only + /// occurs on intersected triangles. + bool cleanupTriangles = true; + /// Verbose level: + /// - 0 for no verbose output + /// - 1 for verbose output for the Boolean, including timing info and vector + /// sizes. + /// - 2 for verbose output with triangulator action as well. + int verbose = 0; +}; +/** @} */ + +#ifdef MANIFOLD_DEBUG + +inline std::ostream& operator<<(std::ostream& stream, const Box& box) { + return stream << "min: " << box.min << ", " + << "max: " << box.max; +} + +inline std::ostream& operator<<(std::ostream& stream, const Rect& box) { + return stream << "min: " << box.min << ", " + << "max: " << box.max; +} + +inline std::ostream& operator<<(std::ostream& stream, const Smoothness& s) { + return stream << "halfedge: " << s.halfedge << ", " + << "smoothness: " << s.smoothness; +} + +/** + * Print the contents of this vector to standard output. Only exists if compiled + * with MANIFOLD_DEBUG flag. + */ +template +void Dump(const std::vector& vec) { + std::cout << "Vec = " << std::endl; + for (size_t i = 0; i < vec.size(); ++i) { + std::cout << i << ", " << vec[i] << ", " << std::endl; + } + std::cout << std::endl; +} + +template +void Diff(const std::vector& a, const std::vector& b) { + std::cout << "Diff = " << std::endl; + if (a.size() != b.size()) { + std::cout << "a and b must have the same length, aborting Diff" + << std::endl; + return; + } + for (size_t i = 0; i < a.size(); ++i) { + if (a[i] != b[i]) + std::cout << i << ": " << a[i] << ", " << b[i] << std::endl; + } + std::cout << std::endl; +} + +struct Timer { + std::chrono::high_resolution_clock::time_point start, end; + + void Start() { start = std::chrono::high_resolution_clock::now(); } + + void Stop() { end = std::chrono::high_resolution_clock::now(); } + + float Elapsed() { + return std::chrono::duration_cast(end - start) + .count(); + } + void Print(std::string message) { + std::cout << "----------- " << std::round(Elapsed()) << " ms for " + << message << std::endl; + } +}; +#endif +} // namespace manifold diff --git a/cpp/include/meshbool/cross_section.h b/cpp/include/meshbool/cross_section.h new file mode 100644 index 0000000..23df444 --- /dev/null +++ b/cpp/include/meshbool/cross_section.h @@ -0,0 +1,184 @@ +// Copyright 2023 The Manifold Authors. +// +// 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. + +#pragma once + +#include +#include +#include + +#include "meshbool/common.h" +#include "meshbool/vec_view.h" + +namespace manifold { + +/** @addtogroup Optional + * @brief Optional features that can be enabled through build flags and may + * require extra dependencies. + * @{ + */ + +struct PathImpl; + +/** + * @brief Two-dimensional cross sections guaranteed to be without + * self-intersections, or overlaps between polygons (from construction onwards). + * This class makes use of the + * [Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library for + * polygon clipping (boolean) and offsetting operations. + */ +class CrossSection { +public: + /** @name Basics + * Copy / move / assignment + */ + ///@{ + CrossSection(); + ~CrossSection(); + + CrossSection(const CrossSection &other); + CrossSection &operator=(const CrossSection &other); + CrossSection(CrossSection &&) noexcept; + CrossSection &operator=(CrossSection &&) noexcept; + ///@} + + // Adapted from Clipper2 docs: + // http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/FillRule.htm + // (Copyright © 2010-2023 Angus Johnson) + /** + * Filling rules defining which polygon sub-regions are considered to be + * inside a given polygon, and which sub-regions will not (based on winding + * numbers). See the [Clipper2 + * docs](http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/FillRule.htm) + * for a detailed explaination with illusrations. + */ + enum class FillRule { + EvenOdd, ///< Only odd numbered sub-regions are filled. + NonZero, ///< Only non-zero sub-regions are filled. + Positive, ///< Only sub-regions with winding counts > 0 are filled. + Negative ///< Only sub-regions with winding counts < 0 are filled. + }; + + // Adapted from Clipper2 docs: + // http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/JoinType.htm + // (Copyright © 2010-2023 Angus Johnson) + /** + * Specifies the treatment of path/contour joins (corners) when offseting + * CrossSections. See the [Clipper2 + * doc](http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/JoinType.htm) + * for illustrations. + */ + enum class JoinType { + Square, /*!< Squaring is applied uniformly at all joins where the internal + join angle is less that 90 degrees. The squared edge will be at + exactly the offset distance from the join vertex. */ + Round, /*!< Rounding is applied to all joins that have convex external + angles, and it maintains the exact offset distance from the join + vertex. */ + Miter, /*!< There's a necessary limit to mitered joins (to avoid narrow + angled joins producing excessively long and narrow + [spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)). + So where mitered joins would exceed a given maximum miter distance + (relative to the offset distance), these are 'squared' instead. */ + Bevel /*!< Bevelled joins are similar to 'squared' joins except that + squaring won't occur at a fixed distance. While bevelled joins may + not be as pretty as squared joins, bevelling is much easier (ie + faster) than squaring. And perhaps this is why bevelling rather + than squaring is preferred in numerous graphics display formats + (including + [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin) + and + [PDF](https://helpx.adobe.com/indesign/using/applying-line-stroke-settings.html) + document formats). */ + }; + + /** @name Input & Output + */ + ///@{ + CrossSection(const SimplePolygon &contour, + FillRule fillrule = FillRule::Positive); + CrossSection(const Polygons &contours, + FillRule fillrule = FillRule::Positive); + CrossSection(const Rect &rect); + Polygons ToPolygons() const; + ///@} + + /** @name Constructors + * Topological ops and primitives + */ + ///@{ + std::vector Decompose() const; + static CrossSection Compose(const std::vector &); + static CrossSection Square(const vec2 dims, bool center = false); + static CrossSection Circle(double radius, int circularSegments = 0); + ///@} + + /** @name Information + * Details of the cross-section + */ + ///@{ + bool IsEmpty() const; + size_t NumVert() const; + size_t NumContour() const; + Rect Bounds() const; + double Area() const; + ///@} + + /** @name Transformation + */ + ///@{ + CrossSection Translate(const vec2 v) const; + CrossSection Rotate(double degrees) const; + CrossSection Scale(const vec2 s) const; + CrossSection Mirror(const vec2 ax) const; + CrossSection Transform(const mat2x3 &m) const; + CrossSection Warp(std::function warpFunc) const; + CrossSection WarpBatch(std::function)> warpFunc) const; + CrossSection Simplify(double epsilon = 1e-6) const; + CrossSection Offset(double delta, JoinType jt = JoinType::Round, + double miter_limit = 2.0, int circularSegments = 0) const; + ///@} + + /** @name Boolean + * Combine two manifolds + */ + ///@{ + CrossSection Boolean(const CrossSection &second, OpType op) const; + static CrossSection + BatchBoolean(const std::vector &crossSections, OpType op); + CrossSection operator+(const CrossSection &) const; + CrossSection &operator+=(const CrossSection &); + CrossSection operator-(const CrossSection &) const; + CrossSection &operator-=(const CrossSection &); + CrossSection operator^(const CrossSection &) const; + CrossSection &operator^=(const CrossSection &); + ///@} + + /** @name Convex Hull + */ + ///@{ + CrossSection Hull() const; + static CrossSection Hull(const std::vector &crossSections); + static CrossSection Hull(const SimplePolygon pts); + static CrossSection Hull(const Polygons polys); + ///@} + +private: + mutable std::shared_ptr paths_; + mutable mat2x3 transform_ = la::identity; + CrossSection(std::shared_ptr paths); + std::shared_ptr GetPaths() const; +}; +/** @} */ +} // namespace manifold diff --git a/cpp/include/meshbool/linalg.h b/cpp/include/meshbool/linalg.h new file mode 100644 index 0000000..2b82421 --- /dev/null +++ b/cpp/include/meshbool/linalg.h @@ -0,0 +1,2598 @@ +// Copyright 2024 The Manifold Authors. +// +// 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. + +// Based on linalg.h - 2.2 - Single-header public domain linear algebra library +// +// The intent of this library is to provide the bulk of the functionality +// you need to write programs that frequently use small, fixed-size vectors +// and matrices, in domains such as computational geometry or computer +// graphics. It strives for terse, readable source code. +// +// The original author of this software is Sterling Orsten, and its permanent +// home is . If you find this software +// useful, an acknowledgement in your source text and/or product documentation +// is appreciated, but not required. +// +// The author acknowledges significant insights and contributions by: +// Stan Melax +// Dimitri Diakopoulos +// +// Some features are deprecated. Define LINALG_FORWARD_COMPATIBLE to remove +// them. + +#pragma once +#ifndef LINALG_H +#define LINALG_H + +#include // For std::array +#include // For various unary math functions, such as std::sqrt +#include // To resolve std::abs ambiguity on clang +#include // For std::hash declaration +#include // For std::enable_if, std::is_same, std::declval + +// In Visual Studio 2015, `constexpr` applied to a member function implies +// `const`, which causes ambiguous overload resolution +#if defined(_MSC_VER) && (_MSC_VER <= 1900) +#define LINALG_CONSTEXPR14 +#else +#define LINALG_CONSTEXPR14 constexpr +#endif + +namespace linalg { +// Small, fixed-length vector type, consisting of exactly M elements of type T, +// and presumed to be a column-vector unless otherwise noted. +template +struct vec; + +// Small, fixed-size matrix type, consisting of exactly M rows and N columns of +// type T, stored in column-major order. +template +struct mat; + +// Specialize converter with a function application operator that converts +// type U to type T to enable implicit conversions +template +struct converter {}; +namespace detail { +template +using conv_t = typename std::enable_if::value, + decltype(converter{}( + std::declval()))>::type; + +// Trait for retrieving scalar type of any linear algebra object +template +struct scalar_type {}; +template +struct scalar_type> { + using type = T; +}; +template +struct scalar_type> { + using type = T; +}; + +// Type returned by the compare(...) function which supports all six comparison +// operators against 0 +template +struct ord { + T a, b; +}; +template +constexpr bool operator==(const ord& o, std::nullptr_t) { + return o.a == o.b; +} +template +constexpr bool operator!=(const ord& o, std::nullptr_t) { + return !(o.a == o.b); +} +template +constexpr bool operator<(const ord& o, std::nullptr_t) { + return o.a < o.b; +} +template +constexpr bool operator>(const ord& o, std::nullptr_t) { + return o.b < o.a; +} +template +constexpr bool operator<=(const ord& o, std::nullptr_t) { + return !(o.b < o.a); +} +template +constexpr bool operator>=(const ord& o, std::nullptr_t) { + return !(o.a < o.b); +} + +// Patterns which can be used with the compare(...) function +template +struct any_compare {}; +template +struct any_compare, vec> { + using type = ord; + constexpr ord operator()(const vec& a, const vec& b) const { + return ord{a.x, b.x}; + } +}; +template +struct any_compare, vec> { + using type = ord; + constexpr ord operator()(const vec& a, const vec& b) const { + return !(a.x == b.x) ? ord{a.x, b.x} : ord{a.y, b.y}; + } +}; +template +struct any_compare, vec> { + using type = ord; + constexpr ord operator()(const vec& a, const vec& b) const { + return !(a.x == b.x) ? ord{a.x, b.x} + : !(a.y == b.y) ? ord{a.y, b.y} + : ord{a.z, b.z}; + } +}; +template +struct any_compare, vec> { + using type = ord; + constexpr ord operator()(const vec& a, const vec& b) const { + return !(a.x == b.x) ? ord{a.x, b.x} + : !(a.y == b.y) ? ord{a.y, b.y} + : !(a.z == b.z) ? ord{a.z, b.z} + : ord{a.w, b.w}; + } +}; +template +struct any_compare, mat> { + using type = ord; + constexpr ord operator()(const mat& a, + const mat& b) const { + return compare(a.x, b.x); + } +}; +template +struct any_compare, mat> { + using type = ord; + constexpr ord operator()(const mat& a, + const mat& b) const { + return a.x != b.x ? compare(a.x, b.x) : compare(a.y, b.y); + } +}; +template +struct any_compare, mat> { + using type = ord; + constexpr ord operator()(const mat& a, + const mat& b) const { + return a.x != b.x ? compare(a.x, b.x) + : a.y != b.y ? compare(a.y, b.y) + : compare(a.z, b.z); + } +}; +template +struct any_compare, mat> { + using type = ord; + constexpr ord operator()(const mat& a, + const mat& b) const { + return a.x != b.x ? compare(a.x, b.x) + : a.y != b.y ? compare(a.y, b.y) + : a.z != b.z ? compare(a.z, b.z) + : compare(a.w, b.w); + } +}; + +// Helper for compile-time index-based access to members of vector and matrix +// types +template +struct getter; +template <> +struct getter<0> { + template + constexpr auto operator()(A& a) const -> decltype(a.x) { + return a.x; + } +}; +template <> +struct getter<1> { + template + constexpr auto operator()(A& a) const -> decltype(a.y) { + return a.y; + } +}; +template <> +struct getter<2> { + template + constexpr auto operator()(A& a) const -> decltype(a.z) { + return a.z; + } +}; +template <> +struct getter<3> { + template + constexpr auto operator()(A& a) const -> decltype(a.w) { + return a.w; + } +}; + +// Stand-in for std::integer_sequence/std::make_integer_sequence +template +struct seq {}; +template +struct make_seq_impl; +template +struct make_seq_impl { + using type = seq<>; +}; +template +struct make_seq_impl { + using type = seq; +}; +template +struct make_seq_impl { + using type = seq; +}; +template +struct make_seq_impl { + using type = seq; +}; +template +struct make_seq_impl { + using type = seq; +}; +template +using make_seq = typename make_seq_impl::type; +template +vec constexpr swizzle(const vec& v, seq) { + return {getter{}(v)...}; +} +template +mat constexpr swizzle(const mat& m, + seq i, seq) { + return {swizzle(getter{}(m), i)...}; +} + +// SFINAE helpers to determine result of function application +template +using ret_t = decltype(std::declval()(std::declval()...)); + +// SFINAE helper which is defined if all provided types are scalars +struct empty {}; +template +struct scalars; +template <> +struct scalars<> { + using type = void; +}; +template +struct scalars : std::conditional::value, + scalars, empty>::type {}; +template +using scalars_t = typename scalars::type; + +// Helpers which indicate how apply(F, ...) should be called for various +// arguments +template +struct apply {}; // Patterns which contain only vectors or scalars +template +struct apply, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a) { + return {f(getter{}(a))...}; + } +}; +template +struct apply, vec, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a, + const vec& b) { + return {f(getter{}(a), getter{}(b))...}; + } +}; +template +struct apply, vec, B> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a, B b) { + return {f(getter{}(a), b)...}; + } +}; +template +struct apply, A, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, A a, const vec& b) { + return {f(a, getter{}(b))...}; + } +}; +template +struct apply, vec, vec, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a, + const vec& b, const vec& c) { + return {f(getter{}(a), getter{}(b), getter{}(c))...}; + } +}; +template +struct apply, vec, vec, C> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a, + const vec& b, C c) { + return {f(getter{}(a), getter{}(b), c)...}; + } +}; +template +struct apply, vec, B, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a, B b, + const vec& c) { + return {f(getter{}(a), b, getter{}(c))...}; + } +}; +template +struct apply, vec, B, C> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, const vec& a, B b, C c) { + return {f(getter{}(a), b, c)...}; + } +}; +template +struct apply, A, vec, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, A a, const vec& b, + const vec& c) { + return {f(a, getter{}(b), getter{}(c))...}; + } +}; +template +struct apply, A, vec, C> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, A a, const vec& b, C c) { + return {f(a, getter{}(b), c)...}; + } +}; +template +struct apply, A, B, vec> { + using type = vec, M>; + enum { size = M, mm = 0 }; + template + static constexpr type impl(seq, F f, A a, B b, const vec& c) { + return {f(a, b, getter{}(c))...}; + } +}; +template +struct apply, mat> { + using type = mat, M, N>; + enum { size = N, mm = 0 }; + template + static constexpr type impl(seq, F f, const mat& a) { + return {apply>::impl(make_seq<0, M>{}, f, + getter{}(a))...}; + } +}; +template +struct apply, mat, mat> { + using type = mat, M, N>; + enum { size = N, mm = 1 }; + template + static constexpr type impl(seq, F f, const mat& a, + const mat& b) { + return {apply, vec>::impl( + make_seq<0, M>{}, f, getter{}(a), getter{}(b))...}; + } +}; +template +struct apply, mat, B> { + using type = mat, M, N>; + enum { size = N, mm = 0 }; + template + static constexpr type impl(seq, F f, const mat& a, B b) { + return {apply, B>::impl(make_seq<0, M>{}, f, + getter{}(a), b)...}; + } +}; +template +struct apply, A, mat> { + using type = mat, M, N>; + enum { size = N, mm = 0 }; + template + static constexpr type impl(seq, F f, A a, const mat& b) { + return {apply>::impl(make_seq<0, M>{}, f, a, + getter{}(b))...}; + } +}; +template +struct apply, A...> { + using type = ret_t; + enum { size = 0, mm = 0 }; + static constexpr type impl(seq<>, F f, A... a) { return f(a...); } +}; + +// Function objects for selecting between alternatives +struct min { + template + constexpr auto operator()(A a, B b) const -> + typename std::remove_reference::type { + return a < b ? a : b; + } +}; +struct max { + template + constexpr auto operator()(A a, B b) const -> + typename std::remove_reference::type { + return a < b ? b : a; + } +}; +struct clamp { + template + constexpr auto operator()(A a, B b, C c) const -> + typename std::remove_reference::type { + return a < b ? b : a < c ? a : c; + } +}; +struct select { + template + constexpr auto operator()(A a, B b, C c) const -> + typename std::remove_reference::type { + return a ? b : c; + } +}; +struct lerp { + template + constexpr auto operator()(A a, B b, C c) const + -> decltype(a * (1 - c) + b * c) { + return a * (1 - c) + b * c; + } +}; + +// Function objects for applying operators +struct op_pos { + template + constexpr auto operator()(A a) const -> decltype(+a) { + return +a; + } +}; +struct op_neg { + template + constexpr auto operator()(A a) const -> decltype(-a) { + return -a; + } +}; +struct op_not { + template + constexpr auto operator()(A a) const -> decltype(!a) { + return !a; + } +}; +struct op_cmp { + template + constexpr auto operator()(A a) const -> decltype(~(a)) { + return ~a; + } +}; +struct op_mul { + template + constexpr auto operator()(A a, B b) const -> decltype(a * b) { + return a * b; + } +}; +struct op_div { + template + constexpr auto operator()(A a, B b) const -> decltype(a / b) { + return a / b; + } +}; +struct op_mod { + template + constexpr auto operator()(A a, B b) const -> decltype(a % b) { + return a % b; + } +}; +struct op_add { + template + constexpr auto operator()(A a, B b) const -> decltype(a + b) { + return a + b; + } +}; +struct op_sub { + template + constexpr auto operator()(A a, B b) const -> decltype(a - b) { + return a - b; + } +}; +struct op_lsh { + template + constexpr auto operator()(A a, B b) const -> decltype(a << b) { + return a << b; + } +}; +struct op_rsh { + template + constexpr auto operator()(A a, B b) const -> decltype(a >> b) { + return a >> b; + } +}; +struct op_lt { + template + constexpr auto operator()(A a, B b) const -> decltype(a < b) { + return a < b; + } +}; +struct op_gt { + template + constexpr auto operator()(A a, B b) const -> decltype(a > b) { + return a > b; + } +}; +struct op_le { + template + constexpr auto operator()(A a, B b) const -> decltype(a <= b) { + return a <= b; + } +}; +struct op_ge { + template + constexpr auto operator()(A a, B b) const -> decltype(a >= b) { + return a >= b; + } +}; +struct op_eq { + template + constexpr auto operator()(A a, B b) const -> decltype(a == b) { + return a == b; + } +}; +struct op_ne { + template + constexpr auto operator()(A a, B b) const -> decltype(a != b) { + return a != b; + } +}; +struct op_int { + template + constexpr auto operator()(A a, B b) const -> decltype(a & b) { + return a & b; + } +}; +struct op_xor { + template + constexpr auto operator()(A a, B b) const -> decltype(a ^ b) { + return a ^ b; + } +}; +struct op_un { + template + constexpr auto operator()(A a, B b) const -> decltype(a | b) { + return a | b; + } +}; +struct op_and { + template + constexpr auto operator()(A a, B b) const -> decltype(a && b) { + return a && b; + } +}; +struct op_or { + template + constexpr auto operator()(A a, B b) const -> decltype(a || b) { + return a || b; + } +}; + +// Function objects for applying standard library math functions +struct std_isfinite { + template + constexpr auto operator()(A a) const -> decltype(std::isfinite(a)) { + return std::isfinite(a); + } +}; +struct std_abs { + template + constexpr auto operator()(A a) const -> decltype(std::abs(a)) { + return std::abs(a); + } +}; +struct std_floor { + template + constexpr auto operator()(A a) const -> decltype(std::floor(a)) { + return std::floor(a); + } +}; +struct std_ceil { + template + constexpr auto operator()(A a) const -> decltype(std::ceil(a)) { + return std::ceil(a); + } +}; +struct std_exp { + template + constexpr auto operator()(A a) const -> decltype(std::exp(a)) { + return std::exp(a); + } +}; +struct std_log { + template + constexpr auto operator()(A a) const -> decltype(std::log(a)) { + return std::log(a); + } +}; +struct std_log2 { + template + constexpr auto operator()(A a) const -> decltype(std::log2(a)) { + return std::log2(a); + } +}; +struct std_log10 { + template + constexpr auto operator()(A a) const -> decltype(std::log10(a)) { + return std::log10(a); + } +}; +struct std_sqrt { + template + constexpr auto operator()(A a) const -> decltype(std::sqrt(a)) { + return std::sqrt(a); + } +}; +struct std_sin { + template + constexpr auto operator()(A a) const -> decltype(std::sin(a)) { + return std::sin(a); + } +}; +struct std_cos { + template + constexpr auto operator()(A a) const -> decltype(std::cos(a)) { + return std::cos(a); + } +}; +struct std_tan { + template + constexpr auto operator()(A a) const -> decltype(std::tan(a)) { + return std::tan(a); + } +}; +struct std_asin { + template + constexpr auto operator()(A a) const -> decltype(std::asin(a)) { + return std::asin(a); + } +}; +struct std_acos { + template + constexpr auto operator()(A a) const -> decltype(std::acos(a)) { + return std::acos(a); + } +}; +struct std_atan { + template + constexpr auto operator()(A a) const -> decltype(std::atan(a)) { + return std::atan(a); + } +}; +struct std_sinh { + template + constexpr auto operator()(A a) const -> decltype(std::sinh(a)) { + return std::sinh(a); + } +}; +struct std_cosh { + template + constexpr auto operator()(A a) const -> decltype(std::cosh(a)) { + return std::cosh(a); + } +}; +struct std_tanh { + template + constexpr auto operator()(A a) const -> decltype(std::tanh(a)) { + return std::tanh(a); + } +}; +struct std_round { + template + constexpr auto operator()(A a) const -> decltype(std::round(a)) { + return std::round(a); + } +}; +struct std_fmod { + template + constexpr auto operator()(A a, B b) const -> decltype(std::fmod(a, b)) { + return std::fmod(a, b); + } +}; +struct std_pow { + template + constexpr auto operator()(A a, B b) const -> decltype(std::pow(a, b)) { + return std::pow(a, b); + } +}; +struct std_atan2 { + template + constexpr auto operator()(A a, B b) const -> decltype(std::atan2(a, b)) { + return std::atan2(a, b); + } +}; +struct std_copysign { + template + constexpr auto operator()(A a, B b) const -> decltype(std::copysign(a, b)) { + return std::copysign(a, b); + } +}; +} // namespace detail + +/** @addtogroup LinAlg + * @ingroup Math + */ + +/** @addtogroup vec + * @ingroup LinAlg + * @brief `linalg::vec` defines a fixed-length vector containing exactly + `M` elements of type `T`. + +This data structure can be used to store a wide variety of types of data, +including geometric vectors, points, homogeneous coordinates, plane equations, +colors, texture coordinates, or any other situation where you need to manipulate +a small sequence of numbers. As such, `vec` is supported by a set of +algebraic and component-wise functions, as well as a set of standard reductions. + +`vec`: +- is + [`DefaultConstructible`](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + ```cpp + float3 v; // v contains 0,0,0 + ``` +- is constructible from `M` elements of type `T`: + ```cpp + float3 v {1,2,3}; // v contains 1,2,3 + ``` +- is + [`CopyConstructible`](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and + [`CopyAssignable`](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + ```cpp + float3 v {1,2,3}; // v contains 1,2,3 + float3 u {v}; // u contains 1,2,3 + float3 w; // w contains 0,0,0 + w = u; // w contains 1,2,3 + ``` +- is + [`EqualityComparable`](https://en.cppreference.com/w/cpp/named_req/EqualityComparable) + and + [`LessThanComparable`](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + ```cpp + if(v == y) cout << "v and u contain equal elements in the same positions" << + endl; if(v < u) cout << "v precedes u lexicographically" << endl; + ``` +- is **explicitly** constructible from a single element of type `T`: + ```cpp + float3 v = float3{4}; // v contains 4,4,4 + ``` +- is **explicitly** constructible from a `vec` of some other type `U`: + ```cpp + float3 v {1.1f,2.3f,3.5f}; // v contains 1.1,2.3,3.5 + int3 u = int3{v}; // u contains 1,2,3 + ``` +- has fields `x,y,z,w`: + ```cpp + float y = point.y; // y contains second element of point + pixel.w = 0.5; // fourth element of pixel set to 0.5 + float s = tc.x; // s contains first element of tc + ``` +- supports indexing: + ```cpp + float x = v[0]; // x contains first element of v + v[2] = 5; // third element of v set to 5 + ``` +- supports unary operators `+`, `-`, `!` and `~` in component-wise fashion: + ```cpp + auto v = -float{2,3}; // v is float2{-2,-3} + ``` +- supports binary operators `+`, `-`, `*`, `/`, `%`, `|`, `&`, `^`, `<<` and + `>>` in component-wise fashion: + ```cpp + auto v = float2{1,1} + float2{2,3}; // v is float2{3,4} + ``` +- supports binary operators with a scalar on the left or the right: + ```cpp + auto v = 2 * float3{1,2,3}; // v is float3{2,4,6} + auto u = float3{1,2,3} + 1; // u is float3{2,3,4} + ``` +- supports operators `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=`, `^=`, `<<=` and + `>>=` with vectors or scalars on the right: + ```cpp + float2 v {1,2}; v *= 3; // v is float2{3,6} + ``` +- supports operations on mixed element types: + ```cpp + auto v = float3{1,2,3} + int3{4,5,6}; // v is float3{5,7,9} + ``` +- supports [range-based + for](https://en.cppreference.com/w/cpp/language/range-for): + ```cpp + for(auto elem : float3{1,2,3}) cout << elem << ' '; // prints "1 2 3 " + ``` +- has a flat memory layout: + ```cpp + float3 v {1,2,3}; + float * p = v.data(); // &v[i] == p+i + p[1] = 4; // v contains 1,4,3 + ``` + * @{ + */ +template +struct vec { + T x; + constexpr vec() : x() {} + constexpr vec(const T& x_) : x(x_) {} + // NOTE: vec does NOT have a constructor from pointer, this can conflict + // with initializing its single element from zero + template + constexpr explicit vec(const vec& v) : vec(static_cast(v.x)) {} + constexpr const T& operator[](int) const { return x; } + LINALG_CONSTEXPR14 T& operator[](int) { return x; } + + template > + constexpr vec(const U& u) : vec(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +template +struct vec { + T x, y; + constexpr vec() : x(), y() {} + constexpr vec(const T& x_, const T& y_) : x(x_), y(y_) {} + constexpr explicit vec(const T& s) : vec(s, s) {} + constexpr explicit vec(const T* p) : vec(p[0], p[1]) {} + template + constexpr explicit vec(const vec& v) + : vec(static_cast(v.x), static_cast(v.y)) { + static_assert( + N >= 2, + "You must give extra arguments if your input vector is shorter."); + } + constexpr const T& operator[](int i) const { return i == 0 ? x : y; } + LINALG_CONSTEXPR14 T& operator[](int i) { return i == 0 ? x : y; } + + template > + constexpr vec(const U& u) : vec(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +template +struct vec { + T x, y, z; + constexpr vec() : x(), y(), z() {} + constexpr vec(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {} + constexpr vec(const vec& xy, const T& z_) : vec(xy.x, xy.y, z_) {} + constexpr explicit vec(const T& s) : vec(s, s, s) {} + constexpr explicit vec(const T* p) : vec(p[0], p[1], p[2]) {} + template + constexpr explicit vec(const vec& v) + : vec(static_cast(v.x), static_cast(v.y), static_cast(v.z)) { + static_assert( + N >= 3, + "You must give extra arguments if your input vector is shorter."); + } + constexpr const T& operator[](int i) const { + return i == 0 ? x : i == 1 ? y : z; + } + LINALG_CONSTEXPR14 T& operator[](int i) { + return i == 0 ? x : i == 1 ? y : z; + } + constexpr const vec& xy() const { + return *reinterpret_cast*>(this); + } + vec& xy() { return *reinterpret_cast*>(this); } + + template > + constexpr vec(const U& u) : vec(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +template +struct vec { + T x, y, z, w; + constexpr vec() : x(), y(), z(), w() {} + constexpr vec(const T& x_, const T& y_, const T& z_, const T& w_) + : x(x_), y(y_), z(z_), w(w_) {} + constexpr vec(const vec& xy, const T& z_, const T& w_) + : vec(xy.x, xy.y, z_, w_) {} + constexpr vec(const vec& xyz, const T& w_) + : vec(xyz.x, xyz.y, xyz.z, w_) {} + constexpr explicit vec(const T& s) : vec(s, s, s, s) {} + constexpr explicit vec(const T* p) : vec(p[0], p[1], p[2], p[3]) {} + template + constexpr explicit vec(const vec& v) + : vec(static_cast(v.x), static_cast(v.y), static_cast(v.z), + static_cast(v.w)) { + static_assert( + N >= 4, + "You must give extra arguments if your input vector is shorter."); + } + constexpr const T& operator[](int i) const { + return i == 0 ? x : i == 1 ? y : i == 2 ? z : w; + } + LINALG_CONSTEXPR14 T& operator[](int i) { + return i == 0 ? x : i == 1 ? y : i == 2 ? z : w; + } + constexpr const vec& xy() const { + return *reinterpret_cast*>(this); + } + constexpr const vec& xyz() const { + return *reinterpret_cast*>(this); + } + vec& xy() { return *reinterpret_cast*>(this); } + vec& xyz() { return *reinterpret_cast*>(this); } + + template > + constexpr vec(const U& u) : vec(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +/** @} */ + +/** @addtogroup mat + * @ingroup LinAlg + * @brief `linalg::mat` defines a fixed-size matrix containing exactly + `M` rows and `N` columns of type `T`, in column-major order. + +This data structure is supported by a set of algebraic and component-wise +functions, as well as a set of standard reductions. + +`mat`: +- is + [`DefaultConstructible`](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + ```cpp + float2x2 m; // m contains columns 0,0; 0,0 + ``` +- is constructible from `N` columns of type `vec`: + ```cpp + float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 + ``` +- is constructible from `linalg::identity`: + ```cpp + float3x3 m = linalg::identity; // m contains columns 1,0,0; 0,1,0; 0,0,1 + ``` +- is + [`CopyConstructible`](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and + [`CopyAssignable`](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + ```cpp + float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 + float2x2 n {m}; // n contains columns 1,2; 3,4 + float2x2 p; // p contains columns 0,0; 0,0 + p = n; // p contains columns 1,2; 3,4 + ``` +- is + [`EqualityComparable`](https://en.cppreference.com/w/cpp/named_req/EqualityComparable) + and + [`LessThanComparable`](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + ```cpp + if(m == n) cout << "m and n contain equal elements in the same positions" << + endl; if(m < n) cout << "m precedes n lexicographically when compared in + column-major order" << endl; + ``` +- is **explicitly** constructible from a single element of type `T`: + ```cpp + float2x2 m {5}; // m contains columns 5,5; 5,5 + ``` +- is **explicitly** constructible from a `mat` of some other type `U`: + ```cpp + float2x2 m {int2x2{{5,6},{7,8}}}; // m contains columns 5,6; 7,8 + ``` +- supports indexing into *columns*: + ```cpp + float2x3 m {{1,2},{3,4},{5,6}}; // m contains columns 1,2; 3,4; 5,6 + float2 c = m[0]; // c contains 1,2 + m[1] = {7,8}; // m contains columns 1,2; 7,8; 5,6 + ``` +- supports retrieval (but not assignment) of rows: + ```cpp + float2x3 m {{1,2},{3,4},{5,6}}; // m contains columns 1,2; 3,4; 5,6 + float3 r = m.row(1); // r contains 2,4,6 + ``` + +- supports unary operators `+`, `-`, `!` and `~` in component-wise fashion: + ```cpp + float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 + float2x2 n = -m; // n contains columns -1,-2; -3,-4 + ``` +- supports binary operators `+`, `-`, `*`, `/`, `%`, `|`, `&`, `^`, `<<` and + `>>` in component-wise fashion: + ```cpp + float2x2 a {{0,0},{2,2}}; // a contains columns 0,0; 2,2 + float2x2 b {{1,2},{1,2}}; // b contains columns 1,2; 1,2 + float2x2 c = a + b; // c contains columns 1,2; 3,4 + ``` + +- supports binary operators with a scalar on the left or the right: + ```cpp + auto m = 2 * float2x2{{1,2},{3,4}}; // m is float2x2{{2,4},{6,8}} + ``` + +- supports operators `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=`, `^=`, `<<=` and + `>>=` with matrices or scalars on the right: + ```cpp + float2x2 v {{5,4},{3,2}}; + v *= 3; // v is float2x2{{15,12},{9,6}} + ``` + +- supports operations on mixed element types: + +- supports [range-based + for](https://en.cppreference.com/w/cpp/language/range-for) over columns + +- has a flat memory layout + * @{ + */ +template +struct mat { + using V = vec; + V x; + constexpr mat() : x() {} + constexpr mat(const V& x_) : x(x_) {} + constexpr explicit mat(const T& s) : x(s) {} + constexpr explicit mat(const T* p) : x(p + M * 0) {} + template + constexpr explicit mat(const mat& m) : mat(V(m.x)) {} + constexpr vec row(int i) const { return {x[i]}; } + constexpr const V& operator[](int) const { return x; } + LINALG_CONSTEXPR14 V& operator[](int) { return x; } + + template > + constexpr mat(const U& u) : mat(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +template +struct mat { + using V = vec; + V x, y; + constexpr mat() : x(), y() {} + constexpr mat(const V& x_, const V& y_) : x(x_), y(y_) {} + constexpr explicit mat(const T& s) : x(s), y(s) {} + constexpr explicit mat(const T* p) : x(p + M * 0), y(p + M * 1) {} + template + constexpr explicit mat(const mat& m) : mat(V(m.x), V(m.y)) { + static_assert(P >= 2, "Input matrix dimensions must be at least as big."); + } + constexpr vec row(int i) const { return {x[i], y[i]}; } + constexpr const V& operator[](int j) const { return j == 0 ? x : y; } + LINALG_CONSTEXPR14 V& operator[](int j) { return j == 0 ? x : y; } + + template > + constexpr mat(const U& u) : mat(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +template +struct mat { + using V = vec; + V x, y, z; + constexpr mat() : x(), y(), z() {} + constexpr mat(const V& x_, const V& y_, const V& z_) : x(x_), y(y_), z(z_) {} + constexpr mat(const mat& m_, const V& z_) + : x(m_.x), y(m_.y), z(z_) {} + constexpr explicit mat(const T& s) : x(s), y(s), z(s) {} + constexpr explicit mat(const T* p) + : x(p + M * 0), y(p + M * 1), z(p + M * 2) {} + template + constexpr explicit mat(const mat& m) : mat(V(m.x), V(m.y), V(m.z)) { + static_assert(P >= 3, "Input matrix dimensions must be at least as big."); + } + constexpr vec row(int i) const { return {x[i], y[i], z[i]}; } + constexpr const V& operator[](int j) const { + return j == 0 ? x : j == 1 ? y : z; + } + LINALG_CONSTEXPR14 V& operator[](int j) { + return j == 0 ? x : j == 1 ? y : z; + } + + template > + constexpr mat(const U& u) : mat(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +template +struct mat { + using V = vec; + V x, y, z, w; + constexpr mat() : x(), y(), z(), w() {} + constexpr mat(const V& x_, const V& y_, const V& z_, const V& w_) + : x(x_), y(y_), z(z_), w(w_) {} + constexpr mat(const mat& m_, const V& w_) + : x(m_.x), y(m_.y), z(m_.z), w(w_) {} + constexpr explicit mat(const T& s) : x(s), y(s), z(s), w(s) {} + constexpr explicit mat(const T* p) + : x(p + M * 0), y(p + M * 1), z(p + M * 2), w(p + M * 3) {} + template + constexpr explicit mat(const mat& m) + : mat(V(m.x), V(m.y), V(m.z), V(m.w)) { + static_assert(P >= 4, "Input matrix dimensions must be at least as big."); + } + + constexpr vec row(int i) const { return {x[i], y[i], z[i], w[i]}; } + constexpr const V& operator[](int j) const { + return j == 0 ? x : j == 1 ? y : j == 2 ? z : w; + } + LINALG_CONSTEXPR14 V& operator[](int j) { + return j == 0 ? x : j == 1 ? y : j == 2 ? z : w; + } + + template > + constexpr mat(const U& u) : mat(converter{}(u)) {} + template > + constexpr operator U() const { + return converter{}(*this); + } +}; +/** @} */ + +/** @addtogroup identity + * @ingroup LinAlg + * @brief Define a type which will convert to the multiplicative identity of any + * square matrix. + * @{ + */ +struct identity_t { + constexpr explicit identity_t(int) {} +}; +template +struct converter, identity_t> { + constexpr mat operator()(identity_t) const { return {vec{1}}; } +}; +template +struct converter, identity_t> { + constexpr mat operator()(identity_t) const { + return {{1, 0}, {0, 1}}; + } +}; +template +struct converter, identity_t> { + constexpr mat operator()(identity_t) const { + return {{1, 0}, {0, 1}, {0, 0}}; + } +}; +template +struct converter, identity_t> { + constexpr mat operator()(identity_t) const { + return {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + } +}; +template +struct converter, identity_t> { + constexpr mat operator()(identity_t) const { + return {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, 0}}; + } +}; +template +struct converter, identity_t> { + constexpr mat operator()(identity_t) const { + return {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}; + } +}; +constexpr identity_t identity{1}; +/** @} */ + +/** @addtogroup fold + * @ingroup LinAlg + * @brief Produce a scalar by applying f(A,B) -> A to adjacent pairs of elements + * from a vec/mat in left-to-right/column-major order (matching the + * associativity of arithmetic and logical operators). + * @{ + */ +template +constexpr A fold(F f, A a, const vec& b) { + return f(a, b.x); +} +template +constexpr A fold(F f, A a, const vec& b) { + return f(f(a, b.x), b.y); +} +template +constexpr A fold(F f, A a, const vec& b) { + return f(f(f(a, b.x), b.y), b.z); +} +template +constexpr A fold(F f, A a, const vec& b) { + return f(f(f(f(a, b.x), b.y), b.z), b.w); +} +template +constexpr A fold(F f, A a, const mat& b) { + return fold(f, a, b.x); +} +template +constexpr A fold(F f, A a, const mat& b) { + return fold(f, fold(f, a, b.x), b.y); +} +template +constexpr A fold(F f, A a, const mat& b) { + return fold(f, fold(f, fold(f, a, b.x), b.y), b.z); +} +template +constexpr A fold(F f, A a, const mat& b) { + return fold(f, fold(f, fold(f, fold(f, a, b.x), b.y), b.z), b.w); +} +/** @} */ + +/** @addtogroup apply + * @ingroup LinAlg + * @brief apply(f,...) applies the provided function in an elementwise fashion + * to its arguments, producing an object of the same dimensions. + * @{ + */ + +// Type aliases for the result of calling apply(...) with various arguments, can +// be used with return type SFINAE to constrain overload sets +template +using apply_t = typename detail::apply::type; +template +using mm_apply_t = typename std::enable_if::mm, + apply_t>::type; +template +using no_mm_apply_t = typename std::enable_if::mm, + apply_t>::type; +template +using scalar_t = + typename detail::scalar_type::type; // Underlying scalar type when + // performing elementwise operations + +// apply(f,...) applies the provided function in an elementwise fashion to its +// arguments, producing an object of the same dimensions +template +constexpr apply_t apply(F func, const A&... args) { + return detail::apply::impl( + detail::make_seq<0, detail::apply::size>{}, func, args...); +} + +// map(a,f) is equivalent to apply(f,a) +template +constexpr apply_t map(const A& a, F func) { + return apply(func, a); +} + +// zip(a,b,f) is equivalent to apply(f,a,b) +template +constexpr apply_t zip(const A& a, const B& b, F func) { + return apply(func, a, b); +} +/** @} */ + +/** @addtogroup comparison_ops + * @ingroup LinAlg + * @brief Relational operators are defined to compare the elements of two + * vectors or matrices lexicographically, in column-major order. + * @{ + */ +template +constexpr typename detail::any_compare::type compare(const A& a, + const B& b) { + return detail::any_compare()(a, b); +} +template +constexpr auto operator==(const A& a, const B& b) + -> decltype(compare(a, b) == 0) { + return compare(a, b) == 0; +} +template +constexpr auto operator!=(const A& a, const B& b) + -> decltype(compare(a, b) != 0) { + return compare(a, b) != 0; +} +template +constexpr auto operator<(const A& a, const B& b) + -> decltype(compare(a, b) < 0) { + return compare(a, b) < 0; +} +template +constexpr auto operator>(const A& a, const B& b) + -> decltype(compare(a, b) > 0) { + return compare(a, b) > 0; +} +template +constexpr auto operator<=(const A& a, const B& b) + -> decltype(compare(a, b) <= 0) { + return compare(a, b) <= 0; +} +template +constexpr auto operator>=(const A& a, const B& b) + -> decltype(compare(a, b) >= 0) { + return compare(a, b) >= 0; +} +/** @} */ + +/** @addtogroup reductions + * @ingroup LinAlg + * @brief Functions for coalescing scalar values. + * @{ + */ +template +constexpr bool any(const A& a) { + return fold(detail::op_or{}, false, a); +} +template +constexpr bool all(const A& a) { + return fold(detail::op_and{}, true, a); +} +template +constexpr scalar_t sum(const A& a) { + return fold(detail::op_add{}, scalar_t(0), a); +} +template +constexpr scalar_t product(const A& a) { + return fold(detail::op_mul{}, scalar_t(1), a); +} +template +constexpr scalar_t minelem(const A& a) { + return fold(detail::min{}, a.x, a); +} +template +constexpr scalar_t maxelem(const A& a) { + return fold(detail::max{}, a.x, a); +} +template +int argmin(const vec& a) { + int j = 0; + for (int i = 1; i < M; ++i) + if (a[i] < a[j]) j = i; + return j; +} +template +int argmax(const vec& a) { + int j = 0; + for (int i = 1; i < M; ++i) + if (a[i] > a[j]) j = i; + return j; +} +/** @} */ + +/** @addtogroup unary_ops + * @ingroup LinAlg + * @brief Unary operators are defined component-wise for linalg types. + * @{ + */ +template +constexpr apply_t operator+(const A& a) { + return apply(detail::op_pos{}, a); +} +template +constexpr apply_t operator-(const A& a) { + return apply(detail::op_neg{}, a); +} +template +constexpr apply_t operator~(const A& a) { + return apply(detail::op_cmp{}, a); +} +template +constexpr apply_t operator!(const A& a) { + return apply(detail::op_not{}, a); +} +/** @} */ + +/** @addtogroup binary_ops + * @ingroup LinAlg + * @brief Binary operators are defined component-wise for linalg types, EXCEPT + * for `operator *`, which does standard matrix multiplication, scalar + * multiplication, and component-wise multiplication for same-size vectors. Use + * `cmul` for the matrix Hadamard product. + * @{ + */ +template +constexpr apply_t operator+(const A& a, const B& b) { + return apply(detail::op_add{}, a, b); +} +template +constexpr apply_t operator-(const A& a, const B& b) { + return apply(detail::op_sub{}, a, b); +} +template +constexpr apply_t cmul(const A& a, const B& b) { + return apply(detail::op_mul{}, a, b); +} +template +constexpr apply_t operator/(const A& a, const B& b) { + return apply(detail::op_div{}, a, b); +} +template +constexpr apply_t operator%(const A& a, const B& b) { + return apply(detail::op_mod{}, a, b); +} +template +constexpr apply_t operator|(const A& a, const B& b) { + return apply(detail::op_un{}, a, b); +} +template +constexpr apply_t operator^(const A& a, const B& b) { + return apply(detail::op_xor{}, a, b); +} +template +constexpr apply_t operator&(const A& a, const B& b) { + return apply(detail::op_int{}, a, b); +} +template +constexpr apply_t operator<<(const A& a, const B& b) { + return apply(detail::op_lsh{}, a, b); +} +template +constexpr apply_t operator>>(const A& a, const B& b) { + return apply(detail::op_rsh{}, a, b); +} + +// Binary `operator *` represents the algebraic matrix product - use cmul(a, b) +// for the Hadamard (component-wise) product. +template +constexpr auto operator*(const A& a, const B& b) { + return mul(a, b); +} + +// Binary assignment operators a $= b is always defined as though it were +// explicitly written a = a $ b +template +constexpr auto operator+=(A& a, const B& b) -> decltype(a = a + b) { + return a = a + b; +} +template +constexpr auto operator-=(A& a, const B& b) -> decltype(a = a - b) { + return a = a - b; +} +template +constexpr auto operator*=(A& a, const B& b) -> decltype(a = a * b) { + return a = a * b; +} +template +constexpr auto operator/=(A& a, const B& b) -> decltype(a = a / b) { + return a = a / b; +} +template +constexpr auto operator%=(A& a, const B& b) -> decltype(a = a % b) { + return a = a % b; +} +template +constexpr auto operator|=(A& a, const B& b) -> decltype(a = a | b) { + return a = a | b; +} +template +constexpr auto operator^=(A& a, const B& b) -> decltype(a = a ^ b) { + return a = a ^ b; +} +template +constexpr auto operator&=(A& a, const B& b) -> decltype(a = a & b) { + return a = a & b; +} +template +constexpr auto operator<<=(A& a, const B& b) -> decltype(a = a << b) { + return a = a << b; +} +template +constexpr auto operator>>=(A& a, const B& b) -> decltype(a = a >> b) { + return a = a >> b; +} +/** @} */ + +/** @defgroup swizzles Swizzles + * Swizzles and subobjects. + * @ingroup LinAlg + * @{ + */ +/** + * @brief Returns a vector containing the specified ordered indices, e.g. + * linalg::swizzle<1, 2, 0>(vec4(4, 5, 6, 7)) == vec3(5, 6, 4) + */ +template +constexpr vec swizzle(const vec& a) { + return {detail::getter{}(a)...}; +} +/** + * @brief Returns a vector containing the specified index range, e.g. + * linalg::subvec<1, 4>(vec4(4, 5, 6, 7)) == vec3(5, 6, 7) + */ +template +constexpr vec subvec(const vec& a) { + return detail::swizzle(a, detail::make_seq{}); +} +/** + * @brief Returns a matrix containing the specified row and column range: + * linalg::submat + */ +template +constexpr mat submat(const mat& a) { + return detail::swizzle(a, detail::make_seq{}, + detail::make_seq{}); +} +/** @} */ + +/** @addtogroup unary_STL + * @ingroup LinAlg + * @brief Component-wise standard library math functions. + * @{ + */ +template +constexpr apply_t isfinite(const A& a) { + return apply(detail::std_isfinite{}, a); +} +template +constexpr apply_t abs(const A& a) { + return apply(detail::std_abs{}, a); +} +template +constexpr apply_t floor(const A& a) { + return apply(detail::std_floor{}, a); +} +template +constexpr apply_t ceil(const A& a) { + return apply(detail::std_ceil{}, a); +} +template +constexpr apply_t exp(const A& a) { + return apply(detail::std_exp{}, a); +} +template +constexpr apply_t log(const A& a) { + return apply(detail::std_log{}, a); +} +template +constexpr apply_t log2(const A& a) { + return apply(detail::std_log2{}, a); +} +template +constexpr apply_t log10(const A& a) { + return apply(detail::std_log10{}, a); +} +template +constexpr apply_t sqrt(const A& a) { + return apply(detail::std_sqrt{}, a); +} +template +constexpr apply_t sin(const A& a) { + return apply(detail::std_sin{}, a); +} +template +constexpr apply_t cos(const A& a) { + return apply(detail::std_cos{}, a); +} +template +constexpr apply_t tan(const A& a) { + return apply(detail::std_tan{}, a); +} +template +constexpr apply_t asin(const A& a) { + return apply(detail::std_asin{}, a); +} +template +constexpr apply_t acos(const A& a) { + return apply(detail::std_acos{}, a); +} +template +constexpr apply_t atan(const A& a) { + return apply(detail::std_atan{}, a); +} +template +constexpr apply_t sinh(const A& a) { + return apply(detail::std_sinh{}, a); +} +template +constexpr apply_t cosh(const A& a) { + return apply(detail::std_cosh{}, a); +} +template +constexpr apply_t tanh(const A& a) { + return apply(detail::std_tanh{}, a); +} +template +constexpr apply_t round(const A& a) { + return apply(detail::std_round{}, a); +} +/** @} */ + +/** @addtogroup binary_STL + * @ingroup LinAlg + * @brief Component-wise standard library math functions. Either argument can be + * a vector or a scalar. + * @{ + */ +template +constexpr apply_t fmod(const A& a, const B& b) { + return apply(detail::std_fmod{}, a, b); +} +template +constexpr apply_t pow(const A& a, const B& b) { + return apply(detail::std_pow{}, a, b); +} +template +constexpr apply_t atan2(const A& a, const B& b) { + return apply(detail::std_atan2{}, a, b); +} +template +constexpr apply_t copysign(const A& a, const B& b) { + return apply(detail::std_copysign{}, a, b); +} +/** @} */ + +/** @addtogroup relational + * @ingroup LinAlg + * @brief Component-wise relational functions on vectors. Either argument can be + * a vector or a scalar. + * @{ + */ +template +constexpr apply_t equal(const A& a, const B& b) { + return apply(detail::op_eq{}, a, b); +} +template +constexpr apply_t nequal(const A& a, const B& b) { + return apply(detail::op_ne{}, a, b); +} +template +constexpr apply_t less(const A& a, const B& b) { + return apply(detail::op_lt{}, a, b); +} +template +constexpr apply_t greater(const A& a, const B& b) { + return apply(detail::op_gt{}, a, b); +} +template +constexpr apply_t lequal(const A& a, const B& b) { + return apply(detail::op_le{}, a, b); +} +template +constexpr apply_t gequal(const A& a, const B& b) { + return apply(detail::op_ge{}, a, b); +} +/** @} */ + +/** @addtogroup selection + * @ingroup LinAlg + * @brief Component-wise selection functions on vectors. Either argument can be + * a vector or a scalar. + * @{ + */ +template +constexpr apply_t min(const A& a, const B& b) { + return apply(detail::min{}, a, b); +} +template +constexpr apply_t max(const A& a, const B& b) { + return apply(detail::max{}, a, b); +} +/** + * @brief Clamps the components of x between l and h, provided l[i] < h[i]. + */ +template +constexpr apply_t clamp(const X& x, const L& l, + const H& h) { + return apply(detail::clamp{}, x, l, h); +} +/** + * @brief Returns the component from a if the corresponding component of p is + * true and from b otherwise. + */ +template +constexpr apply_t select(const P& p, const A& a, + const B& b) { + return apply(detail::select{}, p, a, b); +} +/** + * @brief Linear interpolation from a to b as t goes from 0 -> 1. Values beyond + * [a, b] will result if t is outside [0, 1]. + */ +template +constexpr apply_t lerp(const A& a, const B& b, + const T& t) { + return apply(detail::lerp{}, a, b, t); +} +/** @} */ + +/** @defgroup vec_algebra Vector Algebra + * Support for vector algebra. + * @ingroup LinAlg + * @{ + */ +/** + * @brief shorthand for `cross({a.x,a.y,0}, {b.x,b.y,0}).z` + */ +template +constexpr T cross(const vec& a, const vec& b) { + return a.x * b.y - a.y * b.x; +} +/** + * @brief shorthand for `cross({0,0,a.z}, {b.x,b.y,0}).xy()` + */ +template +constexpr vec cross(T a, const vec& b) { + return {-a * b.y, a * b.x}; +} +/** + * @brief shorthand for `cross({a.x,a.y,0}, {0,0,b.z}).xy()` + */ +template +constexpr vec cross(const vec& a, T b) { + return {a.y * b, -a.x * b}; +} +/** + * @brief the [cross or vector + * product](https://en.wikipedia.org/wiki/Cross_product) of vectors `a` and `b` + */ +template +constexpr vec cross(const vec& a, const vec& b) { + return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; +} +/** + * @brief [dot or inner product](https://en.wikipedia.org/wiki/Dot_product) of + * vectors `a` and `b` + */ +template +constexpr T dot(const vec& a, const vec& b) { + return sum(a * b); +} +/** + * @brief *square* of the length or magnitude of vector `a` + */ +template +constexpr T length2(const vec& a) { + return dot(a, a); +} +/** + * @brief length or magnitude of a vector `a` + */ +template +T length(const vec& a) { + return std::sqrt(length2(a)); +} +/** + * @brief unit length vector in the same direction as `a` (undefined for + zero-length vectors) + + */ +template +vec normalize(const vec& a) { + return a / length(a); +} +/** + * @brief *square* of the [Euclidean + * distance](https://en.wikipedia.org/wiki/Euclidean_distance) between points + * `a` and `b` + */ +template +constexpr T distance2(const vec& a, const vec& b) { + return length2(b - a); +} +/** + * @brief [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance) + * between points `a` and `b` + */ +template +T distance(const vec& a, const vec& b) { + return length(b - a); +} +/** + * @brief Return the angle in radians between two unit vectors. + */ +template +T uangle(const vec& a, const vec& b) { + T d = dot(a, b); + return d > 1 ? 0 : std::acos(d < -1 ? -1 : d); +} +/** + * @brief Return the angle in radians between two non-unit vectors. + */ +template +T angle(const vec& a, const vec& b) { + return uangle(normalize(a), normalize(b)); +} +/** + * @brief vector `v` rotated counter-clockwise by the angle `a` in + * [radians](https://en.wikipedia.org/wiki/Radian) + */ +template +vec rot(T a, const vec& v) { + const T s = std::sin(a), c = std::cos(a); + return {v.x * c - v.y * s, v.x * s + v.y * c}; +} +/** + * @brief vector `v` rotated counter-clockwise by the angle `a` in + * [radians](https://en.wikipedia.org/wiki/Radian) around the X axis + */ +template +vec rotx(T a, const vec& v) { + const T s = std::sin(a), c = std::cos(a); + return {v.x, v.y * c - v.z * s, v.y * s + v.z * c}; +} +/** + * @brief vector `v` rotated counter-clockwise by the angle `a` in + * [radians](https://en.wikipedia.org/wiki/Radian) around the Y axis + */ +template +vec roty(T a, const vec& v) { + const T s = std::sin(a), c = std::cos(a); + return {v.x * c + v.z * s, v.y, -v.x * s + v.z * c}; +} +/** + * @brief vector `v` rotated counter-clockwise by the angle `a` in + * [radians](https://en.wikipedia.org/wiki/Radian) around the Z axis + */ +template +vec rotz(T a, const vec& v) { + const T s = std::sin(a), c = std::cos(a); + return {v.x * c - v.y * s, v.x * s + v.y * c, v.z}; +} +/** + * @brief shorthand for `normalize(lerp(a,b,t))` + */ +template +vec nlerp(const vec& a, const vec& b, T t) { + return normalize(lerp(a, b, t)); +} +/** + * @brief [spherical linear interpolation](https://en.wikipedia.org/wiki/Slerp) + * between unit vectors `a` and `b` (undefined for non-unit vectors) by + * parameter `t` + */ +template +vec slerp(const vec& a, const vec& b, T t) { + T th = uangle(a, b); + return th == 0 ? a + : a * (std::sin(th * (1 - t)) / std::sin(th)) + + b * (std::sin(th * t) / std::sin(th)); +} +/** @} */ + +/** @defgroup quaternions Quaternions + * Support for quaternion algebra using 4D vectors of + * arbitrary length, representing xi + yj + zk + w. + * @ingroup LinAlg + * @{ + */ +/** + * @brief + * [conjugate](https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal) + * of quaternion `q` + */ +template +constexpr vec qconj(const vec& q) { + return {-q.x, -q.y, -q.z, q.w}; +} +/** + * @brief [inverse or + * reciprocal](https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal) + * of quaternion `q` (undefined for zero-length quaternions) + */ +template +vec qinv(const vec& q) { + return qconj(q) / length2(q); +} +/** + * @brief + * [exponential](https://en.wikipedia.org/wiki/Quaternion#Exponential,_logarithm,_and_power_functions) + * of quaternion `q` + */ +template +vec qexp(const vec& q) { + const auto v = q.xyz(); + const auto vv = length(v); + return std::exp(q.w) * + vec{v * (vv > 0 ? std::sin(vv) / vv : 0), std::cos(vv)}; +} +/** + * @brief + * [logarithm](https://en.wikipedia.org/wiki/Quaternion#Exponential,_logarithm,_and_power_functions) + * of quaternion `q` + */ +template +vec qlog(const vec& q) { + const auto v = q.xyz(); + const auto vv = length(v), qq = length(q); + return {v * (vv > 0 ? std::acos(q.w / qq) / vv : 0), std::log(qq)}; +} +/** + * @brief quaternion `q` raised to the exponent `p` + */ +template +vec qpow(const vec& q, const T& p) { + const auto v = q.xyz(); + const auto vv = length(v), qq = length(q), th = std::acos(q.w / qq); + return std::pow(qq, p) * + vec{v * (vv > 0 ? std::sin(p * th) / vv : 0), std::cos(p * th)}; +} +/** + * @brief [Hamilton + * product](https://en.wikipedia.org/wiki/Quaternion#Hamilton_product) of + * quaternions `a` and `b` + */ +template +constexpr vec qmul(const vec& a, const vec& b) { + return {a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y, + a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z, + a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z}; +} +/** + * @brief Multiply as many input quaternions together as desired. + */ +template +constexpr vec qmul(const vec& a, R... r) { + return qmul(a, qmul(r...)); +} +/** @} */ + +/** @defgroup quaternion_rotation Quaternion Rotations + * Support for 3D spatial rotations using normalized quaternions. + * @ingroup LinAlg + * @{ + */ +/** + * @brief efficient shorthand for `qrot(q, {1,0,0})` + */ +template +constexpr vec qxdir(const vec& q) { + return {q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, + (q.x * q.y + q.z * q.w) * 2, (q.z * q.x - q.y * q.w) * 2}; +} +/** + * @brief efficient shorthand for `qrot(q, {0,1,0})` + */ +template +constexpr vec qydir(const vec& q) { + return {(q.x * q.y - q.z * q.w) * 2, + q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z, + (q.y * q.z + q.x * q.w) * 2}; +} +/** + * @brief efficient shorthand for `qrot(q, {0,0,1})` + */ +template +constexpr vec qzdir(const vec& q) { + return {(q.z * q.x + q.y * q.w) * 2, (q.y * q.z - q.x * q.w) * 2, + q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z}; +} +/** + * @brief Create an equivalent mat3 rotation matrix from the input quaternion. + */ +template +constexpr mat qmat(const vec& q) { + return {qxdir(q), qydir(q), qzdir(q)}; +} +/** + * @brief Rotate a vector by a quaternion. + */ +template +constexpr vec qrot(const vec& q, const vec& v) { + return qxdir(q) * v.x + qydir(q) * v.y + qzdir(q) * v.z; +} +/** + * @brief Return the angle in radians of the axis-angle representation of the + * input normalized quaternion. + */ +template +T qangle(const vec& q) { + return std::atan2(length(q.xyz()), q.w) * 2; +} +/** + * @brief Return the normalized axis of the axis-angle representation of the + * input normalized quaternion. + */ +template +vec qaxis(const vec& q) { + return normalize(q.xyz()); +} +/** + * @brief Linear interpolation that takes the shortest path - this is not + * geometrically sensible, consider qslerp instead. + */ +template +vec qnlerp(const vec& a, const vec& b, T t) { + return nlerp(a, dot(a, b) < 0 ? -b : b, t); +} +/** + * @brief Spherical linear interpolation that takes the shortest path. + */ +template +vec qslerp(const vec& a, const vec& b, T t) { + return slerp(a, dot(a, b) < 0 ? -b : b, t); +} +/** + * @brief Returns a normalized quaternion representing a rotation by angle in + * radians about the provided axis. + */ +template +vec constexpr rotation_quat(const vec& axis, T angle) { + return {axis * std::sin(angle / 2), std::cos(angle / 2)}; +} +/** + * @brief Returns a normalized quaternion representing the shortest rotation + * from orig vector to dest vector. + */ +template +vec rotation_quat(const vec& orig, const vec& dest); +/** + * @brief Returns a normalized quaternion representing the input rotation + * matrix, which should be orthonormal. + */ +template +vec rotation_quat(const mat& m); +/** @} */ + +/** @addtogroup mat_algebra + * @ingroup LinAlg + * @brief Support for matrix algebra. + * @{ + */ +template +constexpr vec mul(const vec& a, const T& b) { + return cmul(a, b); +} +template +constexpr vec mul(const T& b, const vec& a) { + return cmul(b, a); +} +template +constexpr mat mul(const mat& a, const T& b) { + return cmul(a, b); +} +template +constexpr mat mul(const T& b, const mat& a) { + return cmul(b, a); +} +template +constexpr vec mul(const vec& a, const vec& b) { + return cmul(a, b); +} +template +constexpr vec mul(const mat& a, const vec& b) { + return a.x * b.x; +} +template +constexpr vec mul(const mat& a, const vec& b) { + return a.x * b.x + a.y * b.y; +} +template +constexpr vec mul(const mat& a, const vec& b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} +template +constexpr vec mul(const mat& a, const vec& b) { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} +template +constexpr mat mul(const mat& a, const mat& b) { + return {mul(a, b.x)}; +} +template +constexpr mat mul(const mat& a, const mat& b) { + return {mul(a, b.x), mul(a, b.y)}; +} +template +constexpr mat mul(const mat& a, const mat& b) { + return {mul(a, b.x), mul(a, b.y), mul(a, b.z)}; +} +template +constexpr mat mul(const mat& a, const mat& b) { + return {mul(a, b.x), mul(a, b.y), mul(a, b.z), mul(a, b.w)}; +} +template +constexpr vec mul(const mat& a, const mat& b, + const vec& c) { + return mul(mul(a, b), c); +} +template +constexpr mat mul(const mat& a, const mat& b, + const mat& c) { + return mul(mul(a, b), c); +} +template +constexpr vec mul(const mat& a, const mat& b, + const mat& c, const vec& d) { + return mul(mul(a, b, c), d); +} +template +constexpr mat mul(const mat& a, const mat& b, + const mat& c, const mat& d) { + return mul(mul(a, b, c), d); +} +template +constexpr mat outerprod(const vec& a, const vec& b) { + return {a * b.x}; +} +template +constexpr mat outerprod(const vec& a, const vec& b) { + return {a * b.x, a * b.y}; +} +template +constexpr mat outerprod(const vec& a, const vec& b) { + return {a * b.x, a * b.y, a * b.z}; +} +template +constexpr mat outerprod(const vec& a, const vec& b) { + return {a * b.x, a * b.y, a * b.z, a * b.w}; +} +template +constexpr vec diagonal(const mat& a) { + return {a.x.x}; +} +template +constexpr vec diagonal(const mat& a) { + return {a.x.x, a.y.y}; +} +template +constexpr vec diagonal(const mat& a) { + return {a.x.x, a.y.y, a.z.z}; +} +template +constexpr vec diagonal(const mat& a) { + return {a.x.x, a.y.y, a.z.z, a.w.w}; +} +template +constexpr T trace(const mat& a) { + return sum(diagonal(a)); +} +template +constexpr mat transpose(const mat& m) { + return {m.row(0)}; +} +template +constexpr mat transpose(const mat& m) { + return {m.row(0), m.row(1)}; +} +template +constexpr mat transpose(const mat& m) { + return {m.row(0), m.row(1), m.row(2)}; +} +template +constexpr mat transpose(const mat& m) { + return {m.row(0), m.row(1), m.row(2), m.row(3)}; +} +template +constexpr mat transpose(const vec& m) { + return transpose(mat(m)); +} +template +constexpr mat adjugate(const mat&) { + return {vec{1}}; +} +template +constexpr mat adjugate(const mat& a) { + return {{a.y.y, -a.x.y}, {-a.y.x, a.x.x}}; +} +template +constexpr mat adjugate(const mat& a); +template +constexpr mat adjugate(const mat& a); +template +constexpr mat comatrix(const mat& a) { + return transpose(adjugate(a)); +} +template +constexpr T determinant(const mat& a) { + return a.x.x; +} +template +constexpr T determinant(const mat& a) { + return a.x.x * a.y.y - a.x.y * a.y.x; +} +template +constexpr T determinant(const mat& a) { + return a.x.x * (a.y.y * a.z.z - a.z.y * a.y.z) + + a.x.y * (a.y.z * a.z.x - a.z.z * a.y.x) + + a.x.z * (a.y.x * a.z.y - a.z.x * a.y.y); +} +template +constexpr T determinant(const mat& a); +template +constexpr mat inverse(const mat& a) { + return adjugate(a) / determinant(a); +} +/** @} */ + +/** @addtogroup iterators + * @ingroup LinAlg + * @brief Vectors and matrices can be used as ranges. + * @{ + */ +template +T* begin(vec& a) { + return &a.x; +} +template +const T* begin(const vec& a) { + return &a.x; +} +template +T* end(vec& a) { + return begin(a) + M; +} +template +const T* end(const vec& a) { + return begin(a) + M; +} +template +vec* begin(mat& a) { + return &a.x; +} +template +const vec* begin(const mat& a) { + return &a.x; +} +template +vec* end(mat& a) { + return begin(a) + N; +} +template +const vec* end(const mat& a) { + return begin(a) + N; +} +/** @} */ + +/** @addtogroup transforms + * @ingroup LinAlg + * @brief Factory functions for 3D spatial transformations. + * @{ + */ +enum fwd_axis { + neg_z, + pos_z +}; // Should projection matrices be generated assuming forward is {0,0,-1} or + // {0,0,1} +enum z_range { + neg_one_to_one, + zero_to_one +}; // Should projection matrices map z into the range of [-1,1] or [0,1]? +template +mat translation_matrix(const vec& translation) { + return {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {translation, 1}}; +} +template +mat rotation_matrix(const vec& rotation) { + return {{qxdir(rotation), 0}, + {qydir(rotation), 0}, + {qzdir(rotation), 0}, + {0, 0, 0, 1}}; +} +template +mat scaling_matrix(const vec& scaling) { + return {{scaling.x, 0, 0, 0}, + {0, scaling.y, 0, 0}, + {0, 0, scaling.z, 0}, + {0, 0, 0, 1}}; +} +template +mat pose_matrix(const vec& q, const vec& p) { + return {{qxdir(q), 0}, {qydir(q), 0}, {qzdir(q), 0}, {p, 1}}; +} +template +mat lookat_matrix(const vec& eye, const vec& center, + const vec& view_y_dir, fwd_axis fwd = neg_z); +template +mat frustum_matrix(T x0, T x1, T y0, T y1, T n, T f, + fwd_axis a = neg_z, z_range z = neg_one_to_one); +template +mat perspective_matrix(T fovy, T aspect, T n, T f, fwd_axis a = neg_z, + z_range z = neg_one_to_one) { + T y = n * std::tan(fovy / 2), x = y * aspect; + return frustum_matrix(-x, x, -y, y, n, f, a, z); +} +/** @} */ + +/** @addtogroup array + * @ingroup LinAlg + * @brief Provide implicit conversion between linalg::vec and + * std::array. + * @{ + */ +template +struct converter, std::array> { + vec operator()(const std::array& a) const { return {a[0]}; } +}; +template +struct converter, std::array> { + vec operator()(const std::array& a) const { return {a[0], a[1]}; } +}; +template +struct converter, std::array> { + vec operator()(const std::array& a) const { + return {a[0], a[1], a[2]}; + } +}; +template +struct converter, std::array> { + vec operator()(const std::array& a) const { + return {a[0], a[1], a[2], a[3]}; + } +}; + +template +struct converter, vec> { + std::array operator()(const vec& a) const { return {a[0]}; } +}; +template +struct converter, vec> { + std::array operator()(const vec& a) const { return {a[0], a[1]}; } +}; +template +struct converter, vec> { + std::array operator()(const vec& a) const { + return {a[0], a[1], a[2]}; + } +}; +template +struct converter, vec> { + std::array operator()(const vec& a) const { + return {a[0], a[1], a[2], a[3]}; + } +}; +/** @} */ +} // namespace linalg + +#ifdef MANIFOLD_DEBUG +#include + +namespace linalg { +template +std::ostream& operator<<(std::ostream& out, const vec& v) { + return out << '{' << v[0] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const vec& v) { + return out << '{' << v[0] << ',' << v[1] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const vec& v) { + return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const vec& v) { + return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << '}'; +} + +template +std::ostream& operator<<(std::ostream& out, const mat& m) { + return out << '{' << m[0] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const mat& m) { + return out << '{' << m[0] << ',' << m[1] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const mat& m) { + return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << '}'; +} +template +std::ostream& operator<<(std::ostream& out, const mat& m) { + return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << '}'; +} +} // namespace linalg +#endif + +namespace std { +/** @addtogroup hash + * @ingroup LinAlg + * @brief Provide specializations for std::hash<...> with linalg types. + * @{ + */ +template +struct hash> { + std::size_t operator()(const linalg::vec& v) const { + std::hash h; + return h(v.x); + } +}; +template +struct hash> { + std::size_t operator()(const linalg::vec& v) const { + std::hash h; + return h(v.x) ^ (h(v.y) << 1); + } +}; +template +struct hash> { + std::size_t operator()(const linalg::vec& v) const { + std::hash h; + return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2); + } +}; +template +struct hash> { + std::size_t operator()(const linalg::vec& v) const { + std::hash h; + return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2) ^ (h(v.w) << 3); + } +}; + +template +struct hash> { + std::size_t operator()(const linalg::mat& v) const { + std::hash> h; + return h(v.x); + } +}; +template +struct hash> { + std::size_t operator()(const linalg::mat& v) const { + std::hash> h; + return h(v.x) ^ (h(v.y) << M); + } +}; +template +struct hash> { + std::size_t operator()(const linalg::mat& v) const { + std::hash> h; + return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M * 2)); + } +}; +template +struct hash> { + std::size_t operator()(const linalg::mat& v) const { + std::hash> h; + return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M * 2)) ^ (h(v.w) << (M * 3)); + } +}; +/** @} */ +} // namespace std + +// Definitions of functions too long to be defined inline +template +constexpr linalg::mat linalg::adjugate(const mat& a) { + return {{a.y.y * a.z.z - a.z.y * a.y.z, a.z.y * a.x.z - a.x.y * a.z.z, + a.x.y * a.y.z - a.y.y * a.x.z}, + {a.y.z * a.z.x - a.z.z * a.y.x, a.z.z * a.x.x - a.x.z * a.z.x, + a.x.z * a.y.x - a.y.z * a.x.x}, + {a.y.x * a.z.y - a.z.x * a.y.y, a.z.x * a.x.y - a.x.x * a.z.y, + a.x.x * a.y.y - a.y.x * a.x.y}}; +} + +template +constexpr linalg::mat linalg::adjugate(const mat& a) { + return {{a.y.y * a.z.z * a.w.w + a.w.y * a.y.z * a.z.w + + a.z.y * a.w.z * a.y.w - a.y.y * a.w.z * a.z.w - + a.z.y * a.y.z * a.w.w - a.w.y * a.z.z * a.y.w, + a.x.y * a.w.z * a.z.w + a.z.y * a.x.z * a.w.w + + a.w.y * a.z.z * a.x.w - a.w.y * a.x.z * a.z.w - + a.z.y * a.w.z * a.x.w - a.x.y * a.z.z * a.w.w, + a.x.y * a.y.z * a.w.w + a.w.y * a.x.z * a.y.w + + a.y.y * a.w.z * a.x.w - a.x.y * a.w.z * a.y.w - + a.y.y * a.x.z * a.w.w - a.w.y * a.y.z * a.x.w, + a.x.y * a.z.z * a.y.w + a.y.y * a.x.z * a.z.w + + a.z.y * a.y.z * a.x.w - a.x.y * a.y.z * a.z.w - + a.z.y * a.x.z * a.y.w - a.y.y * a.z.z * a.x.w}, + {a.y.z * a.w.w * a.z.x + a.z.z * a.y.w * a.w.x + + a.w.z * a.z.w * a.y.x - a.y.z * a.z.w * a.w.x - + a.w.z * a.y.w * a.z.x - a.z.z * a.w.w * a.y.x, + a.x.z * a.z.w * a.w.x + a.w.z * a.x.w * a.z.x + + a.z.z * a.w.w * a.x.x - a.x.z * a.w.w * a.z.x - + a.z.z * a.x.w * a.w.x - a.w.z * a.z.w * a.x.x, + a.x.z * a.w.w * a.y.x + a.y.z * a.x.w * a.w.x + + a.w.z * a.y.w * a.x.x - a.x.z * a.y.w * a.w.x - + a.w.z * a.x.w * a.y.x - a.y.z * a.w.w * a.x.x, + a.x.z * a.y.w * a.z.x + a.z.z * a.x.w * a.y.x + + a.y.z * a.z.w * a.x.x - a.x.z * a.z.w * a.y.x - + a.y.z * a.x.w * a.z.x - a.z.z * a.y.w * a.x.x}, + {a.y.w * a.z.x * a.w.y + a.w.w * a.y.x * a.z.y + + a.z.w * a.w.x * a.y.y - a.y.w * a.w.x * a.z.y - + a.z.w * a.y.x * a.w.y - a.w.w * a.z.x * a.y.y, + a.x.w * a.w.x * a.z.y + a.z.w * a.x.x * a.w.y + + a.w.w * a.z.x * a.x.y - a.x.w * a.z.x * a.w.y - + a.w.w * a.x.x * a.z.y - a.z.w * a.w.x * a.x.y, + a.x.w * a.y.x * a.w.y + a.w.w * a.x.x * a.y.y + + a.y.w * a.w.x * a.x.y - a.x.w * a.w.x * a.y.y - + a.y.w * a.x.x * a.w.y - a.w.w * a.y.x * a.x.y, + a.x.w * a.z.x * a.y.y + a.y.w * a.x.x * a.z.y + + a.z.w * a.y.x * a.x.y - a.x.w * a.y.x * a.z.y - + a.z.w * a.x.x * a.y.y - a.y.w * a.z.x * a.x.y}, + {a.y.x * a.w.y * a.z.z + a.z.x * a.y.y * a.w.z + + a.w.x * a.z.y * a.y.z - a.y.x * a.z.y * a.w.z - + a.w.x * a.y.y * a.z.z - a.z.x * a.w.y * a.y.z, + a.x.x * a.z.y * a.w.z + a.w.x * a.x.y * a.z.z + + a.z.x * a.w.y * a.x.z - a.x.x * a.w.y * a.z.z - + a.z.x * a.x.y * a.w.z - a.w.x * a.z.y * a.x.z, + a.x.x * a.w.y * a.y.z + a.y.x * a.x.y * a.w.z + + a.w.x * a.y.y * a.x.z - a.x.x * a.y.y * a.w.z - + a.w.x * a.x.y * a.y.z - a.y.x * a.w.y * a.x.z, + a.x.x * a.y.y * a.z.z + a.z.x * a.x.y * a.y.z + + a.y.x * a.z.y * a.x.z - a.x.x * a.z.y * a.y.z - + a.y.x * a.x.y * a.z.z - a.z.x * a.y.y * a.x.z}}; +} + +template +constexpr T linalg::determinant(const mat& a) { + return a.x.x * (a.y.y * a.z.z * a.w.w + a.w.y * a.y.z * a.z.w + + a.z.y * a.w.z * a.y.w - a.y.y * a.w.z * a.z.w - + a.z.y * a.y.z * a.w.w - a.w.y * a.z.z * a.y.w) + + a.x.y * (a.y.z * a.w.w * a.z.x + a.z.z * a.y.w * a.w.x + + a.w.z * a.z.w * a.y.x - a.y.z * a.z.w * a.w.x - + a.w.z * a.y.w * a.z.x - a.z.z * a.w.w * a.y.x) + + a.x.z * (a.y.w * a.z.x * a.w.y + a.w.w * a.y.x * a.z.y + + a.z.w * a.w.x * a.y.y - a.y.w * a.w.x * a.z.y - + a.z.w * a.y.x * a.w.y - a.w.w * a.z.x * a.y.y) + + a.x.w * (a.y.x * a.w.y * a.z.z + a.z.x * a.y.y * a.w.z + + a.w.x * a.z.y * a.y.z - a.y.x * a.z.y * a.w.z - + a.w.x * a.y.y * a.z.z - a.z.x * a.w.y * a.y.z); +} + +template +linalg::vec linalg::rotation_quat(const vec& orig, + const vec& dest) { + T cosTheta = dot(orig, dest); + if (cosTheta >= 1 - std::numeric_limits::epsilon()) { + return {0, 0, 0, 1}; + } + if (cosTheta < -1 + std::numeric_limits::epsilon()) { + vec axis = cross(vec(0, 0, 1), orig); + if (length2(axis) < std::numeric_limits::epsilon()) + axis = cross(vec(1, 0, 0), orig); + return rotation_quat(normalize(axis), + 3.14159265358979323846264338327950288); + } + vec axis = cross(orig, dest); + T s = std::sqrt((1 + cosTheta) * 2); + return {axis * (1 / s), s * 0.5}; +} + +template +linalg::vec linalg::rotation_quat(const mat& m) { + const vec q{m.x.x - m.y.y - m.z.z, m.y.y - m.x.x - m.z.z, + m.z.z - m.x.x - m.y.y, m.x.x + m.y.y + m.z.z}, + s[]{{1, m.x.y + m.y.x, m.z.x + m.x.z, m.y.z - m.z.y}, + {m.x.y + m.y.x, 1, m.y.z + m.z.y, m.z.x - m.x.z}, + {m.x.z + m.z.x, m.y.z + m.z.y, 1, m.x.y - m.y.x}, + {m.y.z - m.z.y, m.z.x - m.x.z, m.x.y - m.y.x, 1}}; + return copysign(normalize(sqrt(max(T(0), T(1) + q))), s[argmax(q)]); +} + +template +linalg::mat linalg::lookat_matrix(const vec& eye, + const vec& center, + const vec& view_y_dir, + fwd_axis a) { + const vec f = normalize(center - eye), z = a == pos_z ? f : -f, + x = normalize(cross(view_y_dir, z)), y = cross(z, x); + return inverse(mat{{x, 0}, {y, 0}, {z, 0}, {eye, 1}}); +} + +template +linalg::mat linalg::frustum_matrix(T x0, T x1, T y0, T y1, T n, T f, + fwd_axis a, z_range z) { + const T s = a == pos_z ? T(1) : T(-1), o = z == neg_one_to_one ? n : 0; + return {{2 * n / (x1 - x0), 0, 0, 0}, + {0, 2 * n / (y1 - y0), 0, 0}, + {-s * (x0 + x1) / (x1 - x0), -s * (y0 + y1) / (y1 - y0), + s * (f + o) / (f - n), s}, + {0, 0, -(n + o) * f / (f - n), 0}}; +} +#endif diff --git a/cpp/include/meshbool/manifold.h b/cpp/include/meshbool/manifold.h new file mode 100644 index 0000000..e9be9d6 --- /dev/null +++ b/cpp/include/meshbool/manifold.h @@ -0,0 +1,944 @@ +// Copyright 2021 The Manifold Authors. +// +// 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. + +#pragma once +#include // uint32_t, uint64_t +#include +#include +#include +#include // needed for shared_ptr +#include +#include + +#include "common.h" +#include "meshbool/common.h" +#include "meshbool/meshbool.h" +// #include "meshbool/common.h" +#include "meshbool/vec_view.h" +#include "meshbool/vec_wrapper.h" + +namespace manifold { +template +using RustBox = rust::Box; +template +using RustRef = rust::Ref; +template +using RustRefMut = rust::RefMut; +template +using RustDyn = rust::Dyn; +template +using RustFn = rust::Fn; +template +using RustSlice = rust::Slice; + +// workaround for a test +template +int NumUnique(const VecWrapper& in) { + std::set unique; + for (const T& v : in) { + unique.emplace(v); + } + return unique.size(); +} + +/** + * @ingroup Debug + * + * Allows modification of the assertions checked in MANIFOLD_DEBUG mode. + * + * @return ExecutionParams& + */ +inline ExecutionParams& ManifoldParams() { + // TODO + static ExecutionParams ex = ExecutionParams(); + return ex; +} + +// class CsgNode; +// class CsgLeafNode; + +/** @addtogroup Core + * @brief The central classes of the library + * @{ + */ + +/** + * @brief Mesh input/output suitable for pushing directly into graphics + * libraries. + * + * The core (non-optional) parts of MeshGL are the triVerts indices buffer and + * the vertProperties interleaved vertex buffer, which follow the conventions of + * OpenGL (and other graphic libraries') buffers and are therefore generally + * easy to map directly to other applications' data structures. + * + * The triVerts vector has a stride of 3 and specifies triangles as + * vertex indices. For triVerts = [2, 4, 5, 3, 1, 6, ...], the triangles are [2, + * 4, 5], [3, 1, 6], etc. and likewise the halfedges are [2, 4], [4, 5], [5, 2], + * [3, 1], [1, 6], [6, 3], etc. + * + * The triVerts indices should form a manifold mesh: each of the 3 halfedges of + * each triangle should have exactly one paired halfedge in the list, defined as + * having the first index of one equal to the second index of the other and + * vice-versa. However, this is not always possible - consider e.g. a cube with + * normal-vector properties. Shared vertices would turn the cube into a ball by + * interpolating normals - the common solution is to duplicate each corner + * vertex into 3, each with the same position, but different normals + * corresponding to each face. This is exactly what should be done in MeshGL, + * however we request two additional vectors in this case: mergeFromVert and + * mergeToVert. Each vertex mergeFromVert[i] is merged into vertex + * mergeToVert[i], avoiding unreliable floating-point comparisons to recover the + * manifold topology. These merges are simply a union, so which is from and to + * doesn't matter. + * + * If you don't have merge vectors, you can create them with the Merge() method, + * however this will fail if the mesh is not already manifold within the set + * tolerance. For maximum reliability, always store the merge vectors with the + * mesh, e.g. using the EXT_mesh_manifold extension in glTF. + * + * You can have any number of arbitrary floating-point properties per vertex, + * and they will all be interpolated as necessary during operations. It is up to + * you to keep track of which channel represents what type of data. A few of + * Manifold's methods allow you to specify the channel where normals data + * starts, in order to update it automatically for transforms and such. This + * will be easier if your meshes all use the same channels for properties, but + * this is not a requirement. Operations between meshes with different numbers + * of peroperties will simply use the larger numProp and pad the smaller one + * with zeroes. + * + * On output, the triangles are sorted into runs (runIndex, runOriginalID, + * runTransform) that correspond to different mesh inputs. Other 3D libraries + * may refer to these runs as primitives of a mesh (as in glTF) or draw calls, + * as they often represent different materials on different parts of the mesh. + * It is generally a good idea to maintain a map of OriginalIDs to materials to + * make it easy to reapply them after a set of Boolean operations. These runs + * can also be used as input, and thus also ensure a lossless roundtrip of data + * through MeshGL. + * + * As an example, with runIndex = [0, 6, 18, 21] and runOriginalID = [1, 3, 3], + * there are 7 triangles, where the first two are from the input mesh with ID 1, + * the next 4 are from an input mesh with ID 3, and the last triangle is from a + * different copy (instance) of the input mesh with ID 3. These two instances + * can be distinguished by their different runTransform matrices. + * + * You can reconstruct polygonal faces by assembling all the triangles that are + * from the same run and share the same faceID. These faces will be planar + * within the output tolerance. + * + * The halfedgeTangent vector is used to specify the weighted tangent vectors of + * each halfedge for the purpose of using the Refine methods to create a + * smoothly-interpolated surface. They can also be output when calculated + * automatically by the Smooth functions. + * + * MeshGL is an alias for the standard single-precision version. Use MeshGL64 to + * output the full double precision that Manifold uses internally. + */ +template +class MeshGLP { + private: + MeshGLType internal; + + public: + inline MeshGLP(MeshGLType&& i) + : internal(i.clone()), + numProp(*::rust::RefMut(internal.num_prop)), + vertProperties(::rust::RefMut<::rust::std::vec::Vec>( + internal.vert_properties)), + triVerts(::rust::RefMut<::rust::std::vec::Vec>(internal.tri_verts)), + mergeFromVert( + ::rust::RefMut<::rust::std::vec::Vec>(internal.merge_from_vert)), + mergeToVert( + ::rust::RefMut<::rust::std::vec::Vec>(internal.merge_to_vert)), + runIndex(::rust::RefMut<::rust::std::vec::Vec>(internal.run_index)), + runOriginalID(::rust::RefMut<::rust::std::vec::Vec>( + internal.run_original_id)), + runTransform(::rust::RefMut<::rust::std::vec::Vec>( + internal.run_transform)), + faceID(::rust::RefMut<::rust::std::vec::Vec>(internal.face_id)), + halfedgeTangent(internal.tri_verts.len() * 4, 0.0), + tolerance(*::rust::RefMut(internal.tolerance)) {} + // halfedgeTangent(*::rust::RefMut(i.halfedge_tangent)), + inline MeshGLP(const MeshGLP& i) + : internal(i.internal.clone()), + numProp(*::rust::RefMut(internal.num_prop)), + vertProperties(::rust::RefMut<::rust::std::vec::Vec>( + internal.vert_properties)), + triVerts(::rust::RefMut<::rust::std::vec::Vec>(internal.tri_verts)), + mergeFromVert( + ::rust::RefMut<::rust::std::vec::Vec>(internal.merge_from_vert)), + mergeToVert( + ::rust::RefMut<::rust::std::vec::Vec>(internal.merge_to_vert)), + runIndex(::rust::RefMut<::rust::std::vec::Vec>(internal.run_index)), + runOriginalID(::rust::RefMut<::rust::std::vec::Vec>( + internal.run_original_id)), + runTransform(::rust::RefMut<::rust::std::vec::Vec>( + internal.run_transform)), + faceID(::rust::RefMut<::rust::std::vec::Vec>(internal.face_id)), + halfedgeTangent(internal.tri_verts.len() * 4, 0.0), + tolerance(*::rust::RefMut(internal.tolerance)) {} + + /// Number of property vertices + I NumVert() const { return this->internal.num_vert(); }; + /// Number of triangles + I NumTri() const { return this->internal.num_tri(); }; + /// Number of properties per vertex, always >= 3. + I& numProp; + /// Flat, GL-style interleaved list of all vertex properties: propVal = + /// vertProperties[vert * numProp + propIdx]. The first three properties are + /// always the position x, y, z. The stride of the array is numProp. + VecWrapper vertProperties; + /// The vertex indices of the three triangle corners in CCW (from the outside) + /// order, for each triangle. + VecWrapper triVerts; + /// Optional: A list of only the vertex indicies that need to be merged to + /// reconstruct the manifold. + VecWrapper mergeFromVert; + /// Optional: The same length as mergeFromVert, and the corresponding value + /// contains the vertex to merge with. It will have an identical position, but + /// the other properties may differ. + VecWrapper mergeToVert; + /// Optional: Indicates runs of triangles that correspond to a particular + /// input mesh instance. The runs encompass all of triVerts and are sorted + /// by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at + /// triVerts[runIndex[i+1]]. All runIndex values are divisible by 3. Returned + /// runIndex will always be 1 longer than runOriginalID, but same length is + /// also allowed as input: triVerts.size() will be automatically appended in + /// this case. + VecWrapper runIndex; + /// Optional: The OriginalID of the mesh this triangle run came from. This ID + /// is ideal for reapplying materials to the output mesh. Multiple runs may + /// have the same ID, e.g. representing different copies of the same input + /// mesh. If you create an input MeshGL that you want to be able to reference + /// as one or more originals, be sure to set unique values from ReserveIDs(). + VecWrapper runOriginalID; + /// Optional: For each run, a 3x4 transform is stored representing how the + /// corresponding original mesh was transformed to create this triangle run. + /// This matrix is stored in column-major order and the length of the overall + /// vector is 12 * runOriginalID.size(). + VecWrapper runTransform; + /// Optional: Length NumTri, contains the source face ID this triangle comes + /// from. Simplification will maintain all edges between triangles with + /// different faceIDs. Input faceIDs will be maintained to the outputs, but if + /// none are given, they will be filled in with Manifold's coplanar face + /// calculation based on mesh tolerance. + VecWrapper faceID; + /// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If + /// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed + /// as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value + /// Mesh.triVerts[tri][i] along the CCW edge. If empty, mesh is faceted. + + // TODO + // VecWrapper halfedgeTangent; + std::vector halfedgeTangent; + + /// Tolerance for mesh simplification. When creating a Manifold, the tolerance + /// used will be the maximum of this and a baseline tolerance from the size of + /// the bounding box. Any edge shorter than tolerance may be collapsed. + /// Tolerance may be enlarged when floating point error accumulates. + Precision& tolerance; + + // : internal(::std::move(MeshGLType::default_())), + inline MeshGLP() + : internal(MeshGLType::default_()), + numProp(*::rust::RefMut(this->internal.num_prop)), + vertProperties(::rust::RefMut<::rust::std::vec::Vec>( + this->internal.vert_properties)), + triVerts( + ::rust::RefMut<::rust::std::vec::Vec>(this->internal.tri_verts)), + mergeFromVert(::rust::RefMut<::rust::std::vec::Vec>( + this->internal.merge_from_vert)), + mergeToVert(::rust::RefMut<::rust::std::vec::Vec>( + this->internal.merge_to_vert)), + runIndex( + ::rust::RefMut<::rust::std::vec::Vec>(this->internal.run_index)), + runOriginalID(::rust::RefMut<::rust::std::vec::Vec>( + this->internal.run_original_id)), + runTransform(::rust::RefMut<::rust::std::vec::Vec>( + this->internal.run_transform)), + faceID( + ::rust::RefMut<::rust::std::vec::Vec>(this->internal.face_id)), + halfedgeTangent(std::vector()), + tolerance(*::rust::RefMut(this->internal.tolerance)) {} + + inline MeshGLP& operator=(const MeshGLP& other) { + this->internal = other.internal.clone(); + return *this; + } + + inline MeshGLP& operator=(MeshGLP&& other) { + this->internal = MeshGLType(other.internal.clone()); + return *this; + } + + /** + * Updates the mergeFromVert and mergeToVert vectors in order to create a + * manifold solid. If the MeshGL is already manifold, no change will occur and + * the function will return false. Otherwise, this will merge verts along open + * edges within tolerance (the maximum of the MeshGL tolerance and the + * baseline bounding-box tolerance), keeping any from the existing merge + * vectors, and return true. + * + * There is no guarantee the result will be manifold - this is a best-effort + * helper function designed primarily to aid in the case where a manifold + * multi-material MeshGL was produced, but its merge vectors were lost due to + * a round-trip through a file format. Constructing a Manifold from the result + * will report an error status if it is not manifold. + */ + inline bool Merge() { return this->internal.merge(); } + + /** + * Returns the x, y, z position of the ith vertex. + * + * @param v vertex index. + */ + la::vec GetVertPos(size_t v) const { + // TODO: port to Rust + size_t offset = v * numProp; + return la::vec(vertProperties[offset], + vertProperties[offset + 1], + vertProperties[offset + 2]); + } + + /** + * Returns the three vertex indices of the ith triangle. + * + * @param t triangle index. + */ + la::vec GetTriVerts(size_t t) const { + // TODO: port to Rust + size_t offset = 3 * t; + return la::vec(triVerts[offset], triVerts[offset + 1], + triVerts[offset + 2]); + } + + /** + * Returns the x, y, z, w tangent of the ith halfedge. + * + * @param h halfedge index (3 * triangle_index + [0|1|2]). + */ + // la::vec GetTangent(size_t h) const { + // // TODO: port to Rust + // size_t offset = 4 * h; + // return la::vec( + // halfedgeTangent[offset], halfedgeTangent[offset + 1], + // halfedgeTangent[offset + 2], halfedgeTangent[offset + 3]); + // } + friend class Manifold; +}; +/** + * @brief Single-precision - ideal for most uses, especially graphics. + */ +using MeshGL = MeshGLP; +/** + * @brief Double-precision, 64-bit indices - best for huge meshes. + */ +using MeshGL64 = MeshGLP; + +/** + * @brief This library's internal representation of an oriented, 2-manifold, + * triangle mesh - a simple boundary-representation of a solid object. Use this + * class to store and operate on solids, and use MeshGL for input and output. + * + * In addition to storing geometric data, a Manifold can also store an arbitrary + * number of vertex properties. These could be anything, e.g. normals, UV + * coordinates, colors, etc, but this library is completely agnostic. All + * properties are merely float values indexed by channel number. It is up to the + * user to associate channel numbers with meaning. + * + * Manifold allows vertex properties to be shared for efficient storage, or to + * have multiple property verts associated with a single geometric vertex, + * allowing sudden property changes, e.g. at Boolean intersections, without + * sacrificing manifoldness. + * + * Manifolds also keep track of their relationships to their inputs, via + * OriginalIDs and the faceIDs and transforms accessible through MeshGL. This + * allows object-level properties to be re-associated with the output after many + * operations, particularly useful for materials. Since separate object's + * properties are not mixed, there is no requirement that channels have + * consistent meaning between different inputs. + */ +class Manifold { + private: + rust::crate::MeshBool internal; + + public: + // inline Manifold(rust::crate::MeshBool&& i) : internal(::std::move(i)) {} + inline Manifold(rust::crate::MeshBool&& i) : internal(i.clone()) {} + + /** @name Basics + * Copy / move / assignment + */ + ///@{ + inline Manifold() : internal(rust::crate::MeshBool::default_()) {} + ~Manifold() = default; + inline Manifold(const Manifold& other) { + this->internal = other.internal.clone(); + } + inline Manifold& operator=(const Manifold& other) { + this->internal = other.internal.clone(); + return *this; + } + Manifold(Manifold&&) noexcept = default; + Manifold& operator=(Manifold&&) noexcept = default; + ///@} + + /** @name Input & Output + * Create and retrieve arbitrary manifolds + */ + ///@{ + inline Manifold(const MeshGL& mesh) + : internal(rust::crate::MeshBool::from_meshgl_32(mesh.internal).clone()) { + } + inline Manifold(const MeshGL64& mesh) + : internal(rust::crate::MeshBool::from_meshgl_64(mesh.internal).clone()) { + } + // TODO: originally negative 1? + inline MeshGL GetMeshGL(int normalIdx = -1) const { + return this->internal.get_mesh_gl_32(normalIdx); + } + inline MeshGL64 GetMeshGL64(int normalIdx = -1) const { + return this->internal.get_mesh_gl_64(normalIdx); + } + ///@} + + /** @name Constructors + * Topological ops, primitives, and SDF + */ + ///@{ + inline std::vector Decompose() const { + auto l = this->internal.decompose(); + + std::vector return_v; + return_v.reserve(l.len()); + + for (size_t i = 0; i < l.len(); i++) { + return_v.emplace_back(l.get(i).unwrap().clone()); + } + return return_v; + } + inline static Manifold Compose(const std::vector&) { + // TODO + throw "fail"; + return Manifold(); + } + inline static Manifold Tetrahedron() { + return rust::crate::MeshBool::tetrahedron(); + } + inline static Manifold Cube(vec3 size = vec3(1.0), bool center = false) { + return rust::crate::MeshBool::cube( + ::rust::nalgebra::Vector3<::double_t>::new_(size.x, size.y, size.z), + center); + } + inline static Manifold Cylinder(double height, double radiusLow, + double radiusHigh = -1.0, + int circularSegments = 0, + bool center = false) { + return rust::crate::MeshBool::cylinder(height, radiusLow, radiusHigh, + circularSegments, center); + } + inline static Manifold Sphere(double radius, int circularSegments = 0) { + return rust::crate::MeshBool::sphere(radius, circularSegments); + } + inline static Manifold LevelSet(std::function sdf, Box bounds, + double edgeLength, double level = 0, + double tolerance = -1, + bool canParallel = true) { + // TODO + throw "fail"; + return Manifold(); + } + ///@} + + /** @name Polygons + * 3D to 2D and 2D to 3D + */ + ///@{ + inline Polygons Slice(double height = 0) const { + auto polys_rs = this->internal.slice(height); + return RSPolygonsToCPPPolygons(polys_rs); + } + inline Polygons Project() const { + auto polys_rs = this->internal.project(); + return RSPolygonsToCPPPolygons(polys_rs); + } + inline static Manifold Extrude(const Polygons& crossSection, double height, + int nDivisions = 0, double twistDegrees = 0.0, + vec2 scaleTop = vec2(1.0)) { + auto crossSection_rs = CPPPolygonsToRSPolygons(crossSection); + return rust::crate::MeshBool::extrude( + crossSection_rs, height, nDivisions, twistDegrees, + rust::nalgebra::Vector2::new_(scaleTop.x, scaleTop.x)); + } + inline static Manifold Revolve(const Polygons& crossSection, + int circularSegments = 0, + double revolveDegrees = 360.0f) { + auto crossSection_rs = CPPPolygonsToRSPolygons(crossSection); + return rust::crate::MeshBool::revolve(crossSection_rs, circularSegments, + revolveDegrees); + } + ///@} + + enum class Error { + NoError, + NonFiniteVertex, + NotManifold, + VertexOutOfBounds, + PropertiesWrongLength, + MissingPositionProperties, + MergeVectorsDifferentLengths, + MergeIndexOutOfBounds, + TransformWrongLength, + RunIndexWrongLength, + FaceIDWrongLength, + InvalidConstruction, + ResultTooLarge, + }; + + /** @name Information + * Details of the manifold + */ + ///@{ + inline Error Status() const { + using ManifoldError = ::rust::crate::ManifoldError; + auto status = this->internal.status(); + if (status.is_no_error()) { + return Error::NoError; + } else if (status.is_non_finite_vertex()) { + return Error::NonFiniteVertex; + } else if (status.is_invalid_construction()) { + return Error::InvalidConstruction; + } else if (status.is_result_too_large()) { + return Error::ResultTooLarge; + } else if (status.is_not_manifold()) { + return Error::NotManifold; + } else if (status.is_missing_position_properties()) { + return Error::MissingPositionProperties; + } else if (status.is_merge_vectors_different_lengths()) { + return Error::MergeVectorsDifferentLengths; + } else if (status.is_transform_wrong_length()) { + return Error::TransformWrongLength; + } else if (status.is_run_index_wrong_length()) { + return Error::RunIndexWrongLength; + } else if (status.is_face_id_wrong_length()) { + return Error::FaceIDWrongLength; + } else if (status.is_merge_index_out_of_bounds()) { + return Error::MergeIndexOutOfBounds; + } else if (status.is_vertex_out_of_bounds()) { + return Error::VertexOutOfBounds; + } else { + // Shouldn't be possible? + // return Error::NoError; + throw "unexpected"; + } + } + inline bool IsEmpty() const { return this->internal.is_empty(); } + inline size_t NumVert() const { return this->internal.num_vert(); } + inline size_t NumEdge() const { return this->internal.num_edge(); } + inline size_t NumTri() const { return this->internal.num_tri(); } + inline size_t NumProp() const { return this->internal.num_prop(); } + inline size_t NumPropVert() const { return this->internal.num_prop_vert(); } + inline Box BoundingBox() const { + auto rust_aabb = this->internal.bounding_box(); + return Box(vec3(rust_aabb.min.get_x(), rust_aabb.min.get_y(), + rust_aabb.min.get_z()), + vec3(rust_aabb.max.get_x(), rust_aabb.max.get_y(), + rust_aabb.max.get_z())); + } + inline int Genus() const { + return this->internal.genus(); + return 0; + } + inline double GetTolerance() const { return this->internal.get_tolerance(); } + ///@} + + /** @name Measurement + */ + ///@{ + inline double SurfaceArea() const { return this->internal.surface_area(); } + inline double Volume() const { return this->internal.volume(); } + inline double MinGap(const Manifold& other, double searchLength) const { + return this->internal.min_gap(other.internal, searchLength); + } + ///@} + + /** @name Mesh ID + * Details of the manifold's relation to its input meshes, for the purposes + * of reapplying mesh properties. + */ + ///@{ + inline int OriginalID() const { + return this->internal.original_id(); + return 0; + } + inline Manifold AsOriginal() const { return this->internal.as_original(); } + inline static uint32_t ReserveIDs(uint32_t n) { + return ::rust::crate::MeshBool::reserve_ids(n); + return n; + } + ///@} + + /** @name Transformations + */ + ///@{ + inline Manifold Translate(vec3 v) const { + return this->internal.translate( + ::rust::nalgebra::Vector3<::double_t>::new_(v.x, v.y, v.z)); + } + inline Manifold Scale(vec3 size) const { + return this->internal.scale( + ::rust::nalgebra::Vector3<::double_t>::new_(size.x, size.y, size.z)); + } + inline Manifold Rotate(double xDegrees, double yDegrees = 0.0, + double zDegrees = 0.0) const { + return this->internal.rotate(xDegrees, yDegrees, zDegrees); + } + inline Manifold Mirror(vec3 v) const { + return this->internal.mirror( + ::rust::nalgebra::Vector3<::double_t>::new_(v.x, v.y, v.z)); + } + inline Manifold Transform(const mat3x4& m) const { + auto thing = ::rust::nalgebra::Matrix3x4::from_column_slice( + rust::std::slice::from_raw_parts(reinterpret_cast(&m), + 3 * 4)); + return this->internal.transform(thing); + } + inline Manifold Warp(std::function f) const { + using RustPoint3 = ::rust::nalgebra::Point3; + using FT = RustBox, rust::Unit>>>; + + auto f_new = FT::make_box([f](RustRefMut v) -> rust::Unit { + vec3 new_v(v.get_x(), v.get_y(), v.get_z()); + f(new_v); + v = RustPoint3::new_(new_v.x, new_v.y, new_v.z); + return {}; + }); + + return this->internal.warp(std::move(f_new)); + } + inline Manifold WarpBatch(std::function)> f) const { + using RustPoint3 = ::rust::nalgebra::Point3; + using FT = + RustBox>, rust::Unit>>>; + + auto f_new = + FT::make_box([f](RustRefMut> vec) -> rust::Unit { + std::vector new_vec; + new_vec.reserve(vec.len()); + for (size_t i = 0; i < vec.len(); i++) { + auto v = vec.get(i).unwrap(); + new_vec.push_back({v.get_x(), v.get_y(), v.get_z()}); + } + auto p = new_vec.data(); + auto size = new_vec.size(); + f({p, size}); + for (size_t i = 0; i < vec.len(); i++) { + auto& v = new_vec[i]; + vec.get(i).unwrap() = RustPoint3::new_(v.x, v.y, v.z); + } + return {}; + }); + + return this->internal.warp_batch(std::move(f_new)); + } + inline Manifold SetTolerance(double t) const { + return this->internal.set_tolerance(t); + } + inline Manifold Simplify(double tolerance = 0) const { + using T = ::rust::core::option::Option; + if (tolerance == 0) { + return this->internal.simplify(T::None()); + } else { + return this->internal.simplify(T::Some(tolerance)); + } + } + ///@} + + /** @name Boolean + * Combine two manifolds + */ + ///@{ + inline Manifold Boolean(const Manifold& second, OpType op) const { + auto v = rust::crate::OpType(); + switch (op) { + case manifold::OpType::Add: + v = rust::crate::OpType::Add(); + break; + case manifold::OpType::Subtract: + v = rust::crate::OpType::Subtract(); + break; + case manifold::OpType::Intersect: + v = rust::crate::OpType::Intersect(); + break; + } + return this->internal.boolean(second.internal, v); + } + inline static Manifold BatchBoolean(const std::vector& manifolds, + OpType op) { + // TODO + throw "fail"; + return Manifold(); + } + // // Boolean operation shorthand + // Add (Union) + inline Manifold operator+(const Manifold& second) const { + return this->Boolean(second, OpType::Add); + } + inline Manifold& operator+=(const Manifold& second) { + *this = *this + second; + return *this; + } + // Subtract (Difference) + inline Manifold operator-(const Manifold& second) const { + return this->Boolean(second, OpType::Subtract); + } + inline Manifold& operator-=(const Manifold& second) { + *this = *this - second; + return *this; + } + // Intersect + inline Manifold operator^(const Manifold& second) const { + return this->Boolean(second, OpType::Intersect); + } + inline Manifold& operator^=(const Manifold& second) { + *this = *this ^ second; + return *this; + } + inline std::pair Split(const Manifold& other) const { + auto v = this->internal.split(other.internal); + return std::pair(Manifold(v.f0.clone()), Manifold(v.f1.clone())); + } + inline std::pair SplitByPlane(vec3 normal, + double originOffset) const { + auto new_normal = + ::rust::nalgebra::Vector3::new_(normal.x, normal.y, normal.z); + auto v = this->internal.split_by_plane(new_normal, originOffset); + return std::pair(Manifold(v.f0.clone()), Manifold(v.f1.clone())); + } + inline Manifold TrimByPlane(vec3 normal, double originOffset) const { + auto new_normal = + ::rust::nalgebra::Vector3::new_(normal.x, normal.y, normal.z); + return this->internal.trim_by_plane(new_normal, originOffset); + } + ///@} + + /** @name Properties + * Create and modify vertex properties. + */ + ///@{ + inline Manifold SetProperties( + int numProp, + std::function propFunc) const { + using FT = RustBox>, ::rust::nalgebra::Point3, + RustRef>, rust::Unit>>>; + + using ThisOption = ::rust::core::option::Option; + + auto maybe_function = + (propFunc == nullptr) + ? ThisOption::None() + : ThisOption::Some(FT::make_box( + [propFunc]( + ::rust::RefMut<::rust::Slice> a, + ::rust::nalgebra::Point3 b, + ::rust::Ref<::rust::Slice> c) -> rust::Unit { + vec3 new_b(b.get_x(), b.get_y(), b.get_z()); + propFunc(a.as_mut_ptr(), new_b, c.as_ptr()); + return {}; + })); + + return this->internal.set_properties(numProp, std::move(maybe_function)); + } + inline Manifold CalculateCurvature(int gaussianIdx, int meanIdx) const { + return this->internal.calculate_curvature(gaussianIdx, meanIdx); + } + inline Manifold CalculateNormals(int normalIdx, + double minSharpAngle = 60) const { + return this->internal.calculate_normals(normalIdx, minSharpAngle); + } + ///@} + + /** @name Smoothing + * Smooth meshes by calculating tangent vectors and refining to a higher + * triangle count. + */ + ///@{ + inline Manifold Refine(int v) const { return this->internal.refine(v); } + inline Manifold RefineToLength(double v) const { + return this->internal.refine_to_length(v); + } + inline Manifold RefineToTolerance(double v) const { + return this->internal.refine_to_tolerance(v); + } + inline Manifold SmoothByNormals(int normalIdx) const { + // TODO + throw "fail"; + return Manifold(); + } + inline Manifold SmoothOut(double minSharpAngle = 60, + double minSmoothness = 0) const { + // TODO + throw "fail"; + return Manifold(); + } + inline static Manifold Smooth( + const MeshGL& mesh, const std::vector& sharpenedEdges = {}) { + // TODO + throw "fail"; + return Manifold(); + } + inline static Manifold Smooth( + const MeshGL64& mesh, + const std::vector& sharpenedEdges = {}) { + // TODO + throw "fail"; + return Manifold(); + } + ///@} + + /** @name Convex Hull + */ + ///@{ + inline Manifold Hull() const { + // TODO + throw "fail"; + // return this->internal.hull(); + return Manifold(); + } + inline static Manifold Hull(const std::vector& manifolds) { + // TODO + throw "fail"; + return Manifold(); + } + inline static Manifold Hull(const std::vector& pts) { + // TODO + throw "fail"; + return Manifold(); + } +///@} + +/** @name Debugging I/O + * Self-contained mechanism for reading and writing high precision Manifold + * data. Write function creates special-purpose OBJ files, and Read function + * reads them in. Be warned these are not (and not intended to be) + * full-featured OBJ importers/exporters. Their primary use is to extract + * accurate Manifold data for debugging purposes - writing out any info + * needed to accurately reproduce a problem case's state. Consequently, they + * may store and process additional data in comments that other OBJ parsing + * programs won't understand. + * + * The "format" read and written by these functions is not guaranteed to be + * stable from release to release - it will be modified as needed to ensure + * it captures information needed for debugging. The only API guarantee is + * that the ReadOBJ method in a given build/release will read in the output + * of the WriteOBJ method produced by that release. + * + * To work with a file, the caller should prepare the ifstream/ostream + * themselves, as follows: + * + * Reading: + * @code + * std::ifstream ifile; + * ifile.open(filename); + * if (ifile.is_open()) { + * Manifold obj_m = Manifold::ReadOBJ(ifile); + * ifile.close(); + * if (obj_m.Status() != Manifold::Error::NoError) { + * std::cerr << "Failed reading " << filename << ":\n"; + * std::cerr << Manifold::ToString(ob_m.Status()) << "\n"; + * } + * ifile.close(); + * } + * @endcode + * + * Writing: + * @code + * std::ofstream ofile; + * ofile.open(filename); + * if (ofile.is_open()) { + * if (!m.WriteOBJ(ofile)) { + * std::cerr << "Failed writing to " << filename << "\n"; + * } + * } + * ofile.close(); + * @endcode + */ +#ifdef MANIFOLD_DEBUG + static Manifold ReadOBJ(std::istream& stream); + bool WriteOBJ(std::ostream& stream) const; +#endif + + /** @name Testing Hooks + * These are just for internal testing. + */ + ///@{ + inline bool MatchesTriNormals() const { + return this->internal.matches_tri_normals(); + } + inline size_t NumDegenerateTris() const { + return this->internal.num_degenerate_tris(); + } + inline double GetEpsilon() const { return this->internal.get_epsilon(); } + ///@} +}; +/** @} */ + +/** @addtogroup Debug + * @ingroup Optional + * @brief Debugging features + * + * The features require compiler flags to be enabled. Assertions are enabled + * with the MANIFOLD_DEBUG flag and then controlled with ExecutionParams. + * @{ + */ +#ifdef MANIFOLD_DEBUG +inline std::string ToString(const Manifold::Error& error) { + switch (error) { + case Manifold::Error::NoError: + return "No Error"; + case Manifold::Error::NonFiniteVertex: + return "Non Finite Vertex"; + case Manifold::Error::NotManifold: + return "Not Manifold"; + case Manifold::Error::VertexOutOfBounds: + return "Vertex Out Of Bounds"; + case Manifold::Error::PropertiesWrongLength: + return "Properties Wrong Length"; + case Manifold::Error::MissingPositionProperties: + return "Missing Position Properties"; + case Manifold::Error::MergeVectorsDifferentLengths: + return "Merge Vectors Different Lengths"; + case Manifold::Error::MergeIndexOutOfBounds: + return "Merge Index Out Of Bounds"; + case Manifold::Error::TransformWrongLength: + return "Transform Wrong Length"; + case Manifold::Error::RunIndexWrongLength: + return "Run Index Wrong Length"; + case Manifold::Error::FaceIDWrongLength: + return "Face ID Wrong Length"; + case Manifold::Error::InvalidConstruction: + return "Invalid Construction"; + case Manifold::Error::ResultTooLarge: + return "Result Too Large"; + default: + return "Unknown Error"; + }; +} + +inline std::ostream& operator<<(std::ostream& stream, + const Manifold::Error& error) { + return stream << ToString(error); +} +#endif +/** @} */ +} // namespace manifold diff --git a/cpp/include/meshbool/meshIO.h b/cpp/include/meshbool/meshIO.h new file mode 100644 index 0000000..89a132d --- /dev/null +++ b/cpp/include/meshbool/meshIO.h @@ -0,0 +1,72 @@ +// Copyright 2021 The Manifold Authors. +// +// 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. + +#pragma once +#include + +#include "meshbool/manifold.h" + +namespace manifold { + +/** @addtogroup MeshIO + * @ingroup Optional + * @brief 3D model file I/O based on Assimp + * @{ + */ + +/** + * PBR material properties for GLB/glTF files. + */ +struct Material { + /// Roughness value between 0 (shiny) and 1 (matte). + double roughness = 0.2; + /// Metalness value, generally either 0 (dielectric) or 1 (metal). + double metalness = 1; + /// Color (RGB) multiplier to apply to the whole mesh (each value between 0 + /// and 1). + vec3 color = vec3(1.0); + /// Alpha multiplier to apply to the whole mesh (each value between 0 + /// and 1). + double alpha = 1.0; + /// Gives the property index where the first normal channel + /// can be found. 0 indicates the first three property channels following + /// position. A negative value does not save normals. + int normalIdx = -1; + /// Gives the property index where the first color channel + /// can be found. 0 indicates the first three property channels following + /// position. A negative value does not save vertex colors. + int colorIdx = -1; + /// Gives the property index where the alpha channel + /// can be found. 0 indicates the first property channel following + /// position. A negative value does not save vertex alpha. + int alphaIdx = -1; +}; + +/** + * These options only currently affect .glb and .gltf files. + */ +struct ExportOptions { + /// When false, vertex normals are exported, causing the mesh to appear smooth + /// through normal interpolation. + bool faceted = true; + /// PBR material properties. + Material mat = {}; +}; + +MeshGL ImportMesh(const std::string &filename, bool forceCleanup = false); + +void ExportMesh(const std::string &filename, const MeshGL &mesh, + const ExportOptions &options); +/** @} */ +} // namespace manifold diff --git a/cpp/include/meshbool/optional_assert.h b/cpp/include/meshbool/optional_assert.h new file mode 100644 index 0000000..d08a631 --- /dev/null +++ b/cpp/include/meshbool/optional_assert.h @@ -0,0 +1,70 @@ +// Copyright 2022 The Manifold Authors. +// +// 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. + +#pragma once + +#ifdef MANIFOLD_DEBUG +#include +#include +#include +#include +#include + +/** @addtogroup Debug + * @{ + */ +struct userErr : public virtual std::runtime_error { + using std::runtime_error::runtime_error; +}; +struct topologyErr : public virtual std::runtime_error { + using std::runtime_error::runtime_error; +}; +struct geometryErr : public virtual std::runtime_error { + using std::runtime_error::runtime_error; +}; +using logicErr = std::logic_error; +#endif + +#if defined(MANIFOLD_ASSERT) && defined(MANIFOLD_DEBUG) + +template +void AssertFail(const char* file, int line, const char* cond, const char* msg) { + std::ostringstream output; + output << "Error in file: " << file << " (" << line << "): \'" << cond + << "\' is false: " << msg; + throw Ex(output.str()); +} + +template +void AssertFail(const char* file, int line, const std::string& cond, + const std::string& msg) { + std::ostringstream output; + output << "Error in file: " << file << " (" << line << "): \'" << cond + << "\' is false: " << msg; + throw Ex(output.str()); +} + +// DEBUG_ASSERT is slightly slower due to the function call, but gives more +// detailed info. +#define DEBUG_ASSERT(condition, EX, msg) \ + if (!(condition)) AssertFail(__FILE__, __LINE__, #condition, msg); +// ASSERT has almost no overhead, so better to use for frequent calls like +// vector bounds checking. +#define ASSERT(condition, EX) \ + if (!(condition)) throw(EX); +#else +#define DEBUG_ASSERT(condition, EX, msg) +#define ASSERT(condition, EX) +#endif +/** @} */ diff --git a/cpp/include/meshbool/polygon.h b/cpp/include/meshbool/polygon.h new file mode 100644 index 0000000..6d3e725 --- /dev/null +++ b/cpp/include/meshbool/polygon.h @@ -0,0 +1,103 @@ +// Copyright 2021 The Manifold Authors. +// +// 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. + +#pragma once +#include "common.h" +#include "meshbool/common.h" +#include "meshbool/meshbool.h" +#include "meshbool/vec_view.h" + +namespace manifold { + +/** @addtogroup Structs + * @{ + */ + +/** + * @brief Polygon vertex. + */ +struct PolyVert { + /// X-Y position + vec2 pos; + /// ID or index into another vertex vector + int idx; +}; + +/** + * @brief Single polygon contour, wound CCW, with indices. First and last point + * are implicitly connected. Should ensure all input is + * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). + */ +using SimplePolygonIdx = std::vector; + +/** + * @brief Set of indexed polygons with holes. Order of contours is arbitrary. + * Can contain any depth of nested holes and any number of separate polygons. + * Should ensure all input is + * [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid). + */ +using PolygonsIdx = std::vector; +/** @} */ + +/** @addtogroup Triangulation + * @ingroup Core + * @brief Polygon triangulation + * @{ + */ +inline std::vector TriangulateIdx(const PolygonsIdx& polys, + double epsilon = -1, + bool allowConvex = true) { + using RustPolyVert = rust::crate::polygon::PolyVert; + using RustPoint2 = rust::nalgebra::Point2; + auto new_p = + ::rust::std::vec::Vec<::rust::std::vec::Vec>::new_(); + for (const auto& vec : polys) { + auto new_p_sub = ::rust::std::vec::Vec::new_(); + for (const auto& p : vec) { + new_p_sub.push( + RustPolyVert::new_(RustPoint2::new_(p.pos.x, p.pos.y), p.idx)); + } + new_p.push(std::move(new_p_sub)); + } + + auto return_val = + rust::crate::polygon::triangulate_idx(new_p, epsilon, allowConvex); + + std::vector new_cpp; + new_cpp.reserve(return_val.len()); + for (size_t i = 0; i < return_val.len(); i++) { + auto vec = return_val.get(i).unwrap(); + new_cpp.push_back({vec.get_x(), vec.get_y(), vec.get_z()}); + } + return new_cpp; +} + +inline std::vector Triangulate(const Polygons& polygons, + double epsilon = -1, + bool allowConvex = true) { + auto new_p = CPPPolygonsToRSPolygons(polygons); + + auto return_val = + rust::crate::polygon::triangulate(new_p, epsilon, allowConvex); + + std::vector new_cpp; + new_cpp.reserve(return_val.len()); + for (size_t i = 0; i < return_val.len(); i++) { + auto vec = return_val.get(i).unwrap(); + new_cpp.push_back({vec.get_x(), vec.get_y(), vec.get_z()}); + } + return new_cpp; +} +/** @} */ +} // namespace manifold diff --git a/cpp/include/meshbool/vec_view.h b/cpp/include/meshbool/vec_view.h new file mode 100644 index 0000000..b8060e3 --- /dev/null +++ b/cpp/include/meshbool/vec_view.h @@ -0,0 +1,150 @@ +// Copyright 2023 The Manifold Authors. +// +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "meshbool/meshbool.h" +#include "meshbool/optional_assert.h" + +namespace manifold { + +/** + * View for Vec, can perform offset operation. + * This will be invalidated when the original vector is dropped or changes + * length. Roughly equivalent to std::span from c++20 + */ +template +class VecView { + public: + using Iter = T*; + using IterC = const T*; + + VecView() : ptr_(nullptr), size_(0) {} + + VecView(T* ptr, size_t size) : ptr_(ptr), size_(size) {} + + VecView(const std::vector>& v) + : ptr_(v.data()), size_(v.size()) {} + + VecView(const VecView& other) { + ptr_ = other.ptr_; + size_ = other.size_; + } + + VecView& operator=(const VecView& other) { + ptr_ = other.ptr_; + size_ = other.size_; + return *this; + } + + // allows conversion to a const VecView + operator VecView() const { return {ptr_, size_}; } + + inline const T& operator[](size_t i) const { + ASSERT(i < size_, std::out_of_range("Vec out of range")); + return ptr_[i]; + } + + inline T& operator[](size_t i) { + ASSERT(i < size_, std::out_of_range("Vec out of range")); + return ptr_[i]; + } + + IterC cbegin() const { return ptr_; } + IterC cend() const { return ptr_ + size_; } + + IterC begin() const { return cbegin(); } + IterC end() const { return cend(); } + + Iter begin() { return ptr_; } + Iter end() { return ptr_ + size_; } + + const T& front() const { + ASSERT(size_ != 0, + std::out_of_range("Attempt to take the front of an empty vector")); + return ptr_[0]; + } + + const T& back() const { + ASSERT(size_ != 0, + std::out_of_range("Attempt to take the back of an empty vector")); + return ptr_[size_ - 1]; + } + + T& front() { + ASSERT(size_ != 0, + std::out_of_range("Attempt to take the front of an empty vector")); + return ptr_[0]; + } + + T& back() { + ASSERT(size_ != 0, + std::out_of_range("Attempt to take the back of an empty vector")); + return ptr_[size_ - 1]; + } + + size_t size() const { return size_; } + + bool empty() const { return size_ == 0; } + + VecView view(size_t offset = 0, + size_t length = std::numeric_limits::max()) { + if (length == std::numeric_limits::max()) + length = this->size_ - offset; + ASSERT(offset + length <= this->size_, + std::out_of_range("Vec::view out of range")); + return VecView(this->ptr_ + offset, length); + } + + VecView cview( + size_t offset = 0, + size_t length = std::numeric_limits::max()) const { + if (length == std::numeric_limits::max()) + length = this->size_ - offset; + ASSERT(offset + length <= this->size_, + std::out_of_range("Vec::cview out of range")); + return VecView(this->ptr_ + offset, length); + } + + VecView view( + size_t offset = 0, + size_t length = std::numeric_limits::max()) const { + return cview(offset, length); + } + + T* data() { return this->ptr_; } + + const T* data() const { return this->ptr_; } + +#ifdef MANIFOLD_DEBUG + void Dump() const { + std::cout << "Vec = " << std::endl; + for (size_t i = 0; i < size(); ++i) { + std::cout << i << ", " << ptr_[i] << ", " << std::endl; + } + std::cout << std::endl; + } +#endif + + protected: + T* ptr_ = nullptr; + size_t size_ = 0; +}; + +} // namespace manifold diff --git a/cpp/include/meshbool/vec_wrapper.h b/cpp/include/meshbool/vec_wrapper.h new file mode 100644 index 0000000..b285ad6 --- /dev/null +++ b/cpp/include/meshbool/vec_wrapper.h @@ -0,0 +1,146 @@ +// Copyright 2023 The Manifold Authors. +// +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "meshbool/meshbool.h" +#include "meshbool/optional_assert.h" + +namespace manifold { + +template +class VecWrapper { + private: + ::rust::RefMut> internal; + + public: + inline VecWrapper(::rust::RefMut> i) : internal(i) {} + // inline VecWrapper() : internal(rust::std::vec::Vec::new_()) {} + VecWrapper() = delete; + inline size_t size() const { return this->internal.len(); } + + template + class Iterator { + private: + Tj* m_ptr; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = Tj; + using difference_type = std::ptrdiff_t; + using pointer = Tj*; + using reference = Tj&; + + Iterator() = delete; + Iterator(pointer p) : m_ptr(p) {} + + // Dereference + reference operator*() const { return *m_ptr; } + pointer operator->() { return m_ptr; } + + Iterator& operator+(size_t i) { + this->m_ptr + i; + return *this; + } + + // Prefix increment + Iterator& operator++() { + ++m_ptr; + return *this; + } + + // Postfix increment + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } + + // Comparison + friend bool operator==(const Iterator& a, const Iterator& b) { + return a.m_ptr == b.m_ptr; + } + friend bool operator!=(const Iterator& a, const Iterator& b) { + return a.m_ptr != b.m_ptr; + } + }; + + // inline void insert(const Iter &iter, const T &value) {} + inline void insert(const Iterator& iter, std::initializer_list list) {} + + Iterator begin() { return this->data(); } + Iterator begin() const { return this->data(); } + Iterator end() { return this->data() + this->size(); } + Iterator end() const { return this->data() + this->size(); } + + inline VecWrapper& operator=(std::initializer_list list) { + this->clear(); + for (const T& v : list) { + this->push_back(v); + } + return *this; + } + + inline VecWrapper& operator=(VecView view) { + this->clear(); + for (const T& v : view) { + this->push_back(v); + } + return *this; + } + + inline T& operator[](size_t idx) { return *this->internal.get(idx).unwrap(); } + inline const T& operator[](size_t idx) const { + return *this->internal.get(idx).unwrap(); + } + // inline T& operator[](size_t idx) { return this->data()[idx]; } + // inline const T& operator[](size_t idx) const { return this->data()[idx]; } + + inline bool empty() const { return this->internal.is_empty(); } + + inline void push_back(const T& value) { this->internal.push(value); } + inline void push_back(T&& value) { this->internal.push(value); } + inline T* data() { return this->internal.as_mut_ptr(); } + inline const T* data() const { return this->internal.as_ptr(); } + inline T& front() { return this->data()[0]; } + inline const T& front() const { return this->data()[0]; } + inline T& back() { return this->data()[this->internal.len() - 1]; } + inline const T& back() const { + return this->data()[this->internal.len() - 1]; + } + + inline void clear() { this->internal.clear(); } + inline void resize(size_t new_len) { this->internal.resize(new_len, T()); } + inline void resize(size_t new_len, const T& value) { + this->internal.resize(new_len, value); + } + + operator std::vector() const { + std::vector result; + result.reserve(this->size()); + for (size_t i = 0; i < this->size(); ++i) result.push_back((*this)[i]); + return result; + } + + operator const VecView() const { + return VecView(this->data(), this->size()); + } +}; + +} // namespace manifold diff --git a/cpp/manifold b/cpp/manifold new file mode 160000 index 0000000..f6005ff --- /dev/null +++ b/cpp/manifold @@ -0,0 +1 @@ +Subproject commit f6005ffa83832b845c4c7e3b32fc4358cd0f7248 diff --git a/cpp/manifold_wrapper/bindings b/cpp/manifold_wrapper/bindings new file mode 120000 index 0000000..04d3622 --- /dev/null +++ b/cpp/manifold_wrapper/bindings @@ -0,0 +1 @@ +../manifold/bindings/ \ No newline at end of file diff --git a/cpp/manifold_wrapper/extras b/cpp/manifold_wrapper/extras new file mode 120000 index 0000000..4f864a2 --- /dev/null +++ b/cpp/manifold_wrapper/extras @@ -0,0 +1 @@ +../manifold/extras \ No newline at end of file diff --git a/cpp/manifold_wrapper/samples b/cpp/manifold_wrapper/samples new file mode 120000 index 0000000..b4e4e3b --- /dev/null +++ b/cpp/manifold_wrapper/samples @@ -0,0 +1 @@ +../manifold/samples/ \ No newline at end of file diff --git a/cpp/manifold_wrapper/src/tri_dist.h b/cpp/manifold_wrapper/src/tri_dist.h new file mode 100644 index 0000000..cb13604 --- /dev/null +++ b/cpp/manifold_wrapper/src/tri_dist.h @@ -0,0 +1,23 @@ +#pragma once + +#include "meshbool/common.h" +#include "meshbool/meshbool.h" + +inline auto DistanceTriangleTriangleSquared( + const std::array& p, + const std::array& q) { + using RustVec3 = rust::nalgebra::Vector3; + + auto rust_p = rust::std::vec::Vec::with_capacity(3); + for (const auto& t : p) { + rust_p.push(RustVec3::new_(t.x, t.y, t.z)); + } + + auto rust_q = rust::std::vec::Vec::with_capacity(3); + for (const auto& t : q) { + rust_q.push(RustVec3::new_(t.x, t.y, t.z)); + } + + return rust::crate::tri_dis::distance_triangle_triangle_squared( + rust_p.as_slice(), rust_q.as_slice()); +} diff --git a/cpp/manifold_wrapper/src/utils.h b/cpp/manifold_wrapper/src/utils.h new file mode 100644 index 0000000..fcabdbe --- /dev/null +++ b/cpp/manifold_wrapper/src/utils.h @@ -0,0 +1,4 @@ +#pragma once + +// TODO: get from Rust +constexpr double kPrecision = 1e-12; diff --git a/cpp/manifold_wrapper/test b/cpp/manifold_wrapper/test new file mode 120000 index 0000000..199c92a --- /dev/null +++ b/cpp/manifold_wrapper/test @@ -0,0 +1 @@ +../manifold/test/ \ No newline at end of file diff --git a/main.zng b/main.zng new file mode 100644 index 0000000..ba8f127 --- /dev/null +++ b/main.zng @@ -0,0 +1,602 @@ +//#convert_panic_to_exception + +type bool { + #layout(size = 1, align = 1); + + wellknown_traits(Copy); +} + +mod ::std { + mod vec { + // For internal use + type Vec<::nalgebra::Vector3> { + #layout(size = 24, align = 8); + + fn len(&self) -> usize; + fn with_capacity(usize) -> Vec<::nalgebra::Vector3>; + fn push(&mut self, ::nalgebra::Vector3); + fn as_slice(&self) -> &[::nalgebra::Vector3]; + } + type Vec<::nalgebra::Vector3> { + #layout(size = 24, align = 8); + + fn len(&self) -> usize; + fn with_capacity(usize) -> Vec<::nalgebra::Vector3>; + fn push(&mut self, ::nalgebra::Vector3); + fn as_ptr(&self) -> *const ::nalgebra::Vector3; + fn get(&self, usize) -> ::core::option::Option<&::nalgebra::Vector3> deref [::nalgebra::Vector3]; + } + type Vec<::nalgebra::Vector4> { + #layout(size = 24, align = 8); + + fn len(&self) -> usize; + fn with_capacity(usize) -> Vec<::nalgebra::Vector4>; + fn push(&mut self, ::nalgebra::Vector4); + fn as_ptr(&self) -> *const ::nalgebra::Vector4; + } + + type Vec { + #layout(size = 24, align = 8); + + fn new() -> Vec; + fn clone(&self) -> Vec; + fn len(&self) -> usize; + fn get(&self, usize) -> ::core::option::Option<&u32> deref [u32]; + //fn get_mut(&mut self, usize) -> ::core::option::Option<&mut u32> deref [u32]; + fn is_empty(&self) -> bool; + fn push(&mut self, u32); + fn as_ptr(&self) -> *const u32; + fn as_mut_ptr(&mut self) -> *mut u32; + fn clear(&mut self); + fn resize(&mut self, usize, u32); + } + + type Vec { + #layout(size = 24, align = 8); + + fn new() -> Vec; + fn clone(&self) -> Vec; + fn len(&self) -> usize; + fn get(&self, usize) -> ::core::option::Option<&f32> deref [f32]; + //fn get_mut(&mut self, usize) -> ::core::option::Option<&mut f32> deref [f32]; + fn is_empty(&self) -> bool; + fn push(&mut self, f32); + fn as_ptr(&self) -> *const f32; + fn as_mut_ptr(&mut self) -> *mut f32; + fn clear(&mut self); + fn resize(&mut self, usize, f32); + } + + type Vec { + #layout(size = 24, align = 8); + + fn new() -> Vec; + fn clone(&self) -> Vec; + fn len(&self) -> usize; + fn get(&self, usize) -> ::core::option::Option<&u64> deref [u64]; + //fn get_mut(&mut self, usize) -> ::core::option::Option<&mut u64> deref [u64]; + fn is_empty(&self) -> bool; + fn push(&mut self, u64); + fn as_ptr(&self) -> *const u64; + fn as_mut_ptr(&mut self) -> *mut u64; + fn clear(&mut self); + fn resize(&mut self, usize, u64); + } + + type Vec { + #layout(size = 24, align = 8); + + fn new() -> Vec; + fn clone(&self) -> Vec; + fn len(&self) -> usize; + fn get(&self, usize) -> ::core::option::Option<&f64> deref [f64]; + //fn get_mut(&mut self, usize) -> ::core::option::Option<&mut f64> deref [f64]; + fn is_empty(&self) -> bool; + fn push(&mut self, f64); + fn as_ptr(&self) -> *const f64; + fn as_mut_ptr(&mut self) -> *mut f64; + fn clear(&mut self); + fn resize(&mut self, usize, f64); + } + + type Vec { + #layout(size = 24, align = 8); + + fn len(&self) -> usize; + fn get(&self, usize) -> ::core::option::Option<&crate::MeshBool> deref [crate::MeshBool]; + } + } +} + +mod ::core { + mod option { + type Option { + #layout(size = 16, align = 8); + wellknown_traits(Copy); + + constructor None; + constructor Some(f64); + + fn is_some(&self) -> bool; + fn unwrap(self) -> f64; + } + type Option<&f32> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &f32; + } + type Option<&u32> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &u32; + } + type Option<&f64> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &f64; + } + type Option<&u64> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &u64; + } + type Option<&::nalgebra::Vector3> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &::nalgebra::Vector3; + } + type Option<&::nalgebra::Point2> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &::nalgebra::Point2; + } + type Option<&::std::vec::Vec<::nalgebra::Point2>> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &::std::vec::Vec<::nalgebra::Point2>; + } + type Option<&crate::MeshBool> { + #layout(size = 8, align = 8); + + fn unwrap(self) -> &crate::MeshBool; + } + + type Option<&::nalgebra::Point3> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn is_some(&self) -> bool; + fn unwrap(self) -> &::nalgebra::Point3; + } + type Option, &[f64])>> { + #layout(size = 16, align = 8); + + constructor None; + constructor Some(Box, &[f64])>); + } + } +} + +type [f64] { + wellknown_traits(?Sized); + + fn as_ptr(&self) -> *const f64; + fn as_mut_ptr(&mut self) -> *mut f64; +} + +type [::nalgebra::Vector3] { + wellknown_traits(?Sized); +} + +type [::nalgebra::Point3] { + wellknown_traits(?Sized); + + fn len(&self) -> usize; + fn get(&self, usize) -> core::option::Option<&::nalgebra::Point3>; + //fn get_mut(&mut self, usize) -> core::option::Option<&mut ::nalgebra::Point3> deref [::nalgebra::Point3]; +} + + + +mod ::std::slice { + fn from_raw_parts(*const f64, usize) -> &[f64]; +} + +mod ::nalgebra { + type Matrix3x4 { + #layout(size = 96, align = 8); + + fn from_column_slice(&[f64]) -> Matrix3x4; + } + + type Matrix3 { + #layout(size = 72, align = 8); + } + + type Vector2 { + #layout(size = 16, align = 8); + + wellknown_traits(Copy); + + fn new(f64, f64) -> Vector2; + } + + type Vector3 { + #layout(size = 12, align = 4); + + wellknown_traits(Copy); + + fn new(i32, i32, i32) -> Vector3; + + // field name (offset = X, type = T); + + fn get_x(&self) -> i32; + fn get_y(&self) -> i32; + fn get_z(&self) -> i32; + } + + type Vector3 { + #layout(size = 24, align = 8); + + wellknown_traits(Copy); + + fn new(f64, f64, f64) -> Vector3; + + // field name (offset = X, type = T); + } + + type Vector4 { + #layout(size = 32, align = 8); + + wellknown_traits(Copy); + + fn new(f64, f64, f64, f64) -> Vector4; + + fn get_x(&self) -> f64; + fn get_y(&self) -> f64; + fn get_z(&self) -> f64; + fn get_w(&self) -> f64; + } + + type Vector4 { + #layout(size = 16, align = 4); + + wellknown_traits(Copy); + + fn new(i32, i32, i32, i32) -> Vector4; + + // field name (offset = X, type = T); + + fn get_x(&self) -> i32; + fn get_y(&self) -> i32; + fn get_z(&self) -> i32; + fn get_w(&self) -> i32; + } + + type Vector4 { + #layout(size = 4, align = 1); + + wellknown_traits(Copy); + + fn new(bool, bool, bool, bool) -> Vector4; + + // field name (offset = X, type = T); + } + + type Point2 { + #layout(size = 16, align = 8); + + wellknown_traits(Copy); + + fn new(f64, f64) -> Point2; + fn get_x(&self) -> f64; + fn get_y(&self) -> f64; + } + + type Point3 { + #layout(size = 24, align = 8); + + wellknown_traits(Copy); + + fn new(f64, f64, f64) -> Point3; + fn get_x(&self) -> f64; + fn get_y(&self) -> f64; + fn get_z(&self) -> f64; + } +} + +mod ::std { + mod vec { + type Vec { + #layout(size = 24, align = 8); + fn len(&self) -> usize; + fn new() -> Vec; + fn push(&mut self, crate::polygon::PolyVert); + } + type Vec> { + #layout(size = 24, align = 8); + fn len(&self) -> usize; + fn new() -> Vec>; + fn push(&mut self, Vec); + } + + type Vec<::nalgebra::Point2> { + #layout(size = 24, align = 8); + + fn get(&self, usize) -> ::core::option::Option<&::nalgebra::Point2> deref [::nalgebra::Point2]; + fn len(&self) -> usize; + fn new() -> Vec<::nalgebra::Point2>; + fn push(&mut self, ::nalgebra::Point2); + } + type Vec>> { + #layout(size = 24, align = 8); + + fn get(&self, usize) -> ::core::option::Option<&Vec<::nalgebra::Point2>> deref [Vec<::nalgebra::Point2>]; + fn len(&self) -> usize; + fn new() -> Vec>>; + fn push(&mut self, Vec<::nalgebra::Point2>); + } + } +} + +type Box, &[f64])> { + #layout(size = 16, align = 8); +} + +type Box)> { + #layout(size = 16, align = 8); +} + +type Box])> { + #layout(size = 16, align = 8); +} + +mod crate { + mod polygon { + type PolyVert { + #layout(size = 24, align = 8); + field pos (offset = 0, type = ::nalgebra::Point2); + field idx (offset = 16, type = i32); + + fn new(::nalgebra::Point2, i32) -> PolyVert; + } + fn triangulate_idx( + &::std::vec::Vec<::std::vec::Vec>, + f64, + bool + ) -> ::std::vec::Vec<::nalgebra::Vector3>; + fn triangulate( + &::std::vec::Vec<::std::vec::Vec<::nalgebra::Point2>>, + f64, + bool + ) -> ::std::vec::Vec<::nalgebra::Vector3>; + } + type MeshBool { + #layout(size = 320, align = 8); + + fn default() -> MeshBool; + fn simplify(&self, ::core::option::Option) -> MeshBool; + fn genus(&self) -> usize; + fn surface_area(&self) -> f64; + fn volume(&self) -> f64; + fn original_id(&self) -> i32; + fn tetrahedron() -> MeshBool; + fn cube(::nalgebra::Vector3, bool) -> MeshBool; + fn cylinder( + f64, + f64, + f64, + u32, + bool, + ) -> MeshBool; + fn sphere(f64, i32) -> MeshBool; + fn extrude( + &::std::vec::Vec<::std::vec::Vec<::nalgebra::Point2>>, + f64, + u32, + f64, + ::nalgebra::Vector2, + ) -> MeshBool; + fn revolve( + &::std::vec::Vec<::std::vec::Vec<::nalgebra::Point2>>, + i32, + f64, + ) -> MeshBool; + fn decompose(&self) -> ::std::vec::Vec; + fn as_original(&self) -> MeshBool; + fn reserve_ids(u32) -> u32; + fn matches_tri_normals(&self) -> bool; + fn num_degenerate_tris(&self) -> usize; + fn translate(&self, ::nalgebra::Vector3) -> MeshBool; + fn scale(&self, ::nalgebra::Vector3) -> MeshBool; + fn rotate(&self, f64, f64, f64) -> MeshBool; + fn transform(&self, &::nalgebra::Matrix3x4) -> MeshBool; + fn mirror(&self, ::nalgebra::Vector3) -> MeshBool; + fn warp(&self, Box)>) -> MeshBool; + fn warp_batch(&self, Box])>) -> MeshBool; + fn set_properties( + &self, + i32, + ::core::option::Option, &[f64])>>, + ) -> MeshBool; + fn calculate_curvature(&self, i32, i32) -> MeshBool; + fn calculate_normals(&self, i32, f64) -> MeshBool; + fn refine(&self, i32) -> MeshBool; + fn refine_to_length(&self, f64) -> MeshBool; + fn refine_to_tolerance(&self, f64) -> MeshBool; + fn boolean(&self, &MeshBool, OpType) -> MeshBool; + fn split(&self, &MeshBool) -> (MeshBool, MeshBool); + fn split_by_plane(&self, ::nalgebra::Vector3, f64) -> (MeshBool, MeshBool); + fn trim_by_plane(&self, ::nalgebra::Vector3, f64) -> MeshBool; + fn slice(&self, f64) -> ::std::vec::Vec<::std::vec::Vec<::nalgebra::Point2>>; + fn get_mesh_gl_32(&self, i32) -> MeshGL32; + fn get_mesh_gl_64(&self, i32) -> MeshGL64; + fn from_meshgl_32(&MeshGL32) -> MeshBool; + fn from_meshgl_64(&MeshGL64) -> MeshBool; + fn is_empty(&self) -> bool; + fn status(&self) -> ManifoldError; + fn num_vert(&self) -> usize; + fn num_edge(&self) -> usize; + fn num_tri(&self) -> usize; + fn num_prop(&self) -> usize; + fn num_prop_vert(&self) -> usize; + fn bounding_box(&self) -> AABB; + fn get_epsilon(&self) -> f64; + fn get_tolerance(&self) -> f64; + fn set_tolerance(&self, f64) -> MeshBool; + fn project(&self) -> ::std::vec::Vec<::std::vec::Vec<::nalgebra::Point2>>; + fn min_gap(&self, &MeshBool, f64) -> f64; + + fn clone(&self) -> MeshBool; + } + type (MeshBool, MeshBool) { + #layout(size = 640, align = 8); + + field 0 (offset = 0, type = MeshBool); + field 1 (offset = 320, type = MeshBool); + } + + type MeshGL32 { + #layout(size = 200, align = 8); + + fn default() -> MeshGL32; + fn clone(&self) -> MeshGL32; + + fn num_vert(&self) -> usize; + fn num_tri(&self) -> usize; + + //field name (offset = X, type = T); + field num_prop (offset = 192, type = u32); + field vert_properties (offset = 0, type = ::std::vec::Vec); + field tri_verts (offset = 24, type = ::std::vec::Vec); + field merge_from_vert (offset = 48, type = ::std::vec::Vec); + field merge_to_vert (offset = 72, type = ::std::vec::Vec); + field run_index (offset = 96, type = ::std::vec::Vec); + field run_original_id (offset = 120, type = ::std::vec::Vec); + field run_transform (offset = 144, type = ::std::vec::Vec); + field face_id (offset = 168, type = ::std::vec::Vec); + field tolerance (offset = 196, type = f32); + + fn merge(&mut self) -> bool; + } + + type MeshGL64 { + #layout(size = 208, align = 8); + + fn default() -> MeshGL64; + fn clone(&self) -> MeshGL64; + + fn num_vert(&self) -> usize; + fn num_tri(&self) -> usize; + + //field name (offset = X, type = T); + field num_prop (offset = 192, type = u64); + field vert_properties (offset = 0, type = ::std::vec::Vec); + field tri_verts (offset = 24, type = ::std::vec::Vec); + field merge_from_vert (offset = 48, type = ::std::vec::Vec); + field merge_to_vert (offset = 72, type = ::std::vec::Vec); + field run_index (offset = 96, type = ::std::vec::Vec); + field run_original_id (offset = 120, type = ::std::vec::Vec); + field run_transform (offset = 144, type = ::std::vec::Vec); + field face_id (offset = 168, type = ::std::vec::Vec); + field tolerance (offset = 200, type = f64); + + fn merge(&mut self) -> bool; + } + + type AABB { + #layout(size = 48, align = 8); + + wellknown_traits(Copy); + + field min (offset = 0, type = ::nalgebra::Point3); + field max (offset = 24, type = ::nalgebra::Point3); + } + + type OpType { + #layout(size = 1, align = 1); + + wellknown_traits(Copy); + + constructor Add; + constructor Subtract; + constructor Intersect; + } + + type ManifoldError { + #layout(size = 1, align = 1); + + wellknown_traits(Copy); + + constructor NoError; + constructor NonFiniteVertex; + constructor InvalidConstruction; + constructor ResultTooLarge; + constructor NotManifold; + constructor MissingPositionProperties; + constructor MergeVectorsDifferentLengths; + constructor TransformWrongLength; + constructor RunIndexWrongLength; + constructor FaceIDWrongLength; + constructor MergeIndexOutOfBounds; + constructor VertexOutOfBounds; + + fn is_no_error(&self) -> bool; + fn is_non_finite_vertex(&self) -> bool; + fn is_invalid_construction(&self) -> bool; + fn is_result_too_large(&self) -> bool; + fn is_not_manifold(&self) -> bool; + fn is_missing_position_properties(&self) -> bool; + fn is_merge_vectors_different_lengths(&self) -> bool; + fn is_transform_wrong_length(&self) -> bool; + fn is_run_index_wrong_length(&self) -> bool; + fn is_face_id_wrong_length(&self) -> bool; + fn is_merge_index_out_of_bounds(&self) -> bool; + fn is_vertex_out_of_bounds(&self) -> bool; + } + // TMP + mod utils { + fn ccw(::nalgebra::Point2, ::nalgebra::Point2, ::nalgebra::Point2, f64) -> i32; + fn mat3(&::nalgebra::Matrix3x4) -> ::nalgebra::Matrix3; + } + // For internal use + mod tri_dis { + fn distance_triangle_triangle_squared(&[::nalgebra::Vector3], &[::nalgebra::Vector3]) -> f64; + } + + mod subdivision { + type Partition { + #layout(size = 80, align = 8); + + field idx (offset = 48, type = ::nalgebra::Vector4); + field sorted_divisions (offset = 64, type = ::nalgebra::Vector4); + field vert_bary (offset = 0, type = ::std::vec::Vec<::nalgebra::Vector4>); + field tri_vert (offset = 24, type = ::std::vec::Vec<::nalgebra::Vector3>); + + fn interior_offset(&self) -> i32; + fn num_interior(&self) -> i32; + fn get_partition(::nalgebra::Vector4) -> Partition; + fn reindex( + &self, + ::nalgebra::Vector4, + ::nalgebra::Vector4, + ::nalgebra::Vector4, + i32, + ) -> ::std::vec::Vec<::nalgebra::Vector3>; + } + } +} diff --git a/src/boolean3.rs b/src/boolean3.rs index 0bab676..2ef6d11 100644 --- a/src/boolean3.rs +++ b/src/boolean3.rs @@ -500,7 +500,7 @@ fn intersect12( }; b.collider - .collisions_from_fn::<_, _, Kernel12Recorder>(f, a.halfedge.len(), &mut recorder); + .collisions_from_fn::(f, a.halfedge.len(), &mut recorder); let result = recorder.local_store; let mut p1q2 = result.p1q2; @@ -568,7 +568,7 @@ fn winding03( let mut recorder = SimpleRecorder::new(&mut recorderf); let f = |i| a.vert_pos[verts[i as usize] as usize]; b.collider - .collisions_from_fn::<_, _, SimpleRecorder<'_>>(f, verts.len(), &mut recorder); + .collisions_from_fn::>(f, verts.len(), &mut recorder); // flood fill for i in 0..w03.len() { let root = u_a.find(i as u32) as usize; diff --git a/src/collider.rs b/src/collider.rs index 5bb5c31..9eb6dc7 100644 --- a/src/collider.rs +++ b/src/collider.rs @@ -2,6 +2,7 @@ use crate::common::{AABB, AABBOverlap}; use crate::utils::atomic_add_i32; use crate::vec::vec_uninit; use nalgebra::{Matrix3x4, Point3, Vector3}; +use rayon::prelude::*; use std::fmt::Debug; use std::mem; @@ -144,7 +145,7 @@ impl<'a> CreateRadixTree<'a> { } } -struct FindCollision<'a, F, AABBOverlapT, RecorderT> +struct FindCollision<'a, const SELF_COLLISION: bool, F, AABBOverlapT, RecorderT> where F: Fn(i32) -> AABBOverlapT, RecorderT: Recorder, @@ -155,7 +156,8 @@ where recorder: &'a mut RecorderT, } -impl<'a, F, AABBOverlapT, RecorderT> FindCollision<'a, F, AABBOverlapT, RecorderT> +impl<'a, const SELF_COLLISION: bool, F, AABBOverlapT, RecorderT> + FindCollision<'a, SELF_COLLISION, F, AABBOverlapT, RecorderT> where F: Fn(i32) -> AABBOverlapT, AABBOverlapT: Debug, @@ -167,8 +169,9 @@ where let overlaps = self.node_bbox[node as usize].does_overlap(&(self.f)(query_idx)); if overlaps && is_leaf(node) { let leaf_idx = node2leaf(node); - //in c++ selfCollision is always false - self.recorder.record(query_idx, leaf_idx); + if !SELF_COLLISION || leaf_idx != query_idx { + self.recorder.record(query_idx, leaf_idx); + } } overlaps && is_internal(node) //should traverse into node @@ -329,9 +332,11 @@ impl Collider { ); // copy in leaf node Boxes - for i in 0..leaf_bb.len() { - self.node_bbox[i * 2] = leaf_bb[i]; - } + self.node_bbox + .par_iter_mut() + .step_by(2) + .enumerate() + .for_each(|(i, b)| *b = leaf_bb[i]); // create global counters let mut counter = vec![0; self.num_internal()]; @@ -356,7 +361,7 @@ impl Collider { ///If parallel is false, the function will run in sequential mode. /// ///If thread local storage is not needed, use SimpleRecorder. - pub fn collisions_from_slice( + pub fn collisions_from_slice( &self, queries_in: &[AABBOverlapT], recorder: &mut impl Recorder, @@ -372,7 +377,7 @@ impl Collider { let f = |i: i32| -> AABBOverlapT { queries_in[i as usize].clone() }; // TODO: if parallel for query_idx in 0..queries_in.len() { - FindCollision { + FindCollision:: { f: &f, node_bbox: &self.node_bbox, internal_children: &self.internal_children, @@ -382,7 +387,7 @@ impl Collider { } } - pub fn collisions_from_fn( + pub fn collisions_from_fn( &self, f: F, n: usize, @@ -397,7 +402,7 @@ impl Collider { return; } for query_idx in 0..n { - FindCollision { + FindCollision:: { f: &f, node_bbox: &self.node_bbox, internal_children: &self.internal_children, diff --git a/src/common.rs b/src/common.rs index 51adc62..ceb4739 100644 --- a/src/common.rs +++ b/src/common.rs @@ -37,6 +37,11 @@ impl AABB { self.max - self.min } + ///Returns the center point of the Box. + pub fn center(&self) -> Vector3 { + 0.5 * (self.max.coords + self.min.coords) + } + ///Returns the absolute-largest coordinate value of any contained ///point. pub fn scale(&self) -> f64 { @@ -211,6 +216,28 @@ where } } +pub trait FloatKind { + fn is_f64() -> bool; + fn is_f32() -> bool; +} + +impl FloatKind for f64 { + fn is_f64() -> bool { + true + } + fn is_f32() -> bool { + false + } +} +impl FloatKind for f32 { + fn is_f64() -> bool { + false + } + fn is_f32() -> bool { + true + } +} + //lossy_from!([from, from, from], to) macro_rules! lossy_from { ([ $( $f:ty ),* ], $t:ty) => { @@ -225,6 +252,8 @@ macro_rules! lossy_from { } lossy_from!([i32, u32, u64, usize], usize); +lossy_from!([u32, u64], i32); +lossy_from!([u32, u64], u32); lossy_from!([usize], u64); lossy_from!([usize], u32); lossy_from!([f64], f64); diff --git a/src/constructors.rs b/src/constructors.rs index 4de79ef..969e6a6 100644 --- a/src/constructors.rs +++ b/src/constructors.rs @@ -1,7 +1,9 @@ -use crate::MeshBool; use crate::common::{Polygons, Quality, SimplePolygon, cosd, sind}; +use crate::disjoint_sets::DisjointSets; use crate::meshboolimpl::{MeshBoolImpl, Shape}; +use crate::parallel::{copy_if, gather}; use crate::polygon::{PolyVert, PolygonsIdx, SimplePolygonIdx, triangulate_idx}; +use crate::{MeshBool, triangulate}; use nalgebra::{Matrix2, Matrix3x4, Point2, Point3, Vector2, Vector3}; impl MeshBool { @@ -246,4 +248,247 @@ impl MeshBool { meshbool_impl.mark_coplanar(); Self::from(meshbool_impl) } + + ///Constructs a manifold from a set of polygons by revolving this cross-section + ///around its Y-axis and then setting this as the Z-axis of the resulting + ///manifold. If the polygons cross the Y-axis, only the part on the positive X + ///side is used. Geometrically valid input will result in geometrically valid + ///output. + /// + ///@param crossSection A set of non-overlapping polygons to revolve. + ///@param circularSegments Number of segments along its diameter. Default is + ///calculated by the static Defaults. + ///@param revolveDegrees Number of degrees to revolve. Default is 360 degrees. + pub fn revolve( + cross_section: &Polygons, + circular_segments: i32, + mut revolve_degrees: f64, + ) -> Self { + let mut polygons: Polygons = vec![]; + let mut radius: f64 = 0.0; + for poly in cross_section.iter() { + let mut i: usize = 0; + while i < poly.len() && poly[i].x < 0.0 { + i += 1; + } + if i == poly.len() { + continue; + } + polygons.push(Vec::default()); + let start: usize = i; + loop { + if poly[i].x >= 0.0 { + polygons.last_mut().unwrap().push(poly[i]); + radius = radius.max(poly[i].x); + } + let next: usize = if i + 1 == poly.len() { 0 } else { i + 1 }; + if (poly[next].x < 0.0) != (poly[i].x < 0.0) { + let y: f64 = poly[next].y + - poly[next].x * (poly[i].y - poly[next].y) / (poly[i].x - poly[next].x); + polygons.last_mut().unwrap().push(Point2::new(0.0, y)); + } + i = next; + if i == start { + break; + } + } + } + + if polygons.is_empty() { + return Self::invalid(); + } + + if revolve_degrees > 360.0 { + revolve_degrees = 360.0; + } + let is_full_revolution = revolve_degrees == 360.0; + + let n_divisions: i32 = if circular_segments > 2 { + circular_segments + } else { + (Quality::get_circular_segments(radius) as f64 * revolve_degrees / 360.0) as i32 + }; + + let mut meshbool_impl = MeshBoolImpl::default(); + let vert_pos = &mut meshbool_impl.vert_pos; + let mut tri_verts_dh: Vec> = vec![]; + let tri_verts = &mut tri_verts_dh; + + let mut start_poses: Vec = vec![]; + let mut end_poses: Vec = vec![]; + + let d_phi: f64 = revolve_degrees / n_divisions as f64; + // first and last slice are distinguished if not a full revolution. + let n_slices: i32 = if is_full_revolution { + n_divisions + } else { + n_divisions + 1 + }; + + for poly in polygons.iter() { + let mut n_pos_verts: usize = 0; + let mut n_revolve_axis_verts: usize = 0; + for pt in poly.iter() { + if pt.x > 0.0 { + n_pos_verts += 1; + } else { + n_revolve_axis_verts += 1; + } + } + + for poly_vert in 0..poly.len() { + let start_pos_index: usize = vert_pos.len(); + + if !is_full_revolution { + start_poses.push(start_pos_index as i32); + } + + let curr_poly_vertex: Vector2 = poly[poly_vert].coords; + let prev_poly_vertex: Vector2 = poly[if poly_vert == 0 { + poly.len() - 1 + } else { + poly_vert - 1 + }] + .coords; + + let prev_start_pos_index: i32 = start_pos_index as i32 + + (if poly_vert == 0 { + n_revolve_axis_verts as i32 + (n_slices * n_pos_verts as i32) + } else { + 0 + }) + (if prev_poly_vertex.x == 0.0 { + -1 + } else { + -n_slices + }); + + for slice in 0..n_slices { + let phi: f64 = slice as f64 * d_phi; + if slice == 0 || curr_poly_vertex.x > 0.0 { + vert_pos.push(Point3::new( + curr_poly_vertex.x * cosd(phi), + curr_poly_vertex.x * sind(phi), + curr_poly_vertex.y, + )); + } + + if is_full_revolution || slice > 0 { + let last_slice: i32 = (if slice == 0 { n_divisions } else { slice }) - 1; + if curr_poly_vertex.x > 0.0 { + tri_verts.push(Vector3::new( + start_pos_index as i32 + slice, + start_pos_index as i32 + last_slice, + // "Reuse" vertex of first slice if it lies on the revolve axis + if prev_poly_vertex.x == 0.0 { + prev_start_pos_index + } else { + prev_start_pos_index + last_slice + }, + )); + } + + if prev_poly_vertex.x > 0.0 { + tri_verts.push(Vector3::new( + prev_start_pos_index + last_slice, + prev_start_pos_index + slice, + if curr_poly_vertex.x == 0.0 { + start_pos_index as i32 + } else { + start_pos_index as i32 + slice + }, + )); + } + } + } + if !is_full_revolution { + end_poses.push(vert_pos.len() as i32 - 1); + } + } + } + + // Add front and back triangles if not a full revolution. + if !is_full_revolution { + let front_triangles: Vec> = + triangulate(&polygons, meshbool_impl.epsilon, true); + for t in front_triangles.iter() { + tri_verts.push(Vector3::new( + start_poses[t.x as usize], + start_poses[t.y as usize], + start_poses[t.z as usize], + )); + } + + for t in front_triangles.iter() { + tri_verts.push(Vector3::new( + end_poses[t.z as usize], + end_poses[t.y as usize], + end_poses[t.x as usize], + )); + } + } + + meshbool_impl.create_halfedges(tri_verts_dh, vec![]); + meshbool_impl.finish(); + meshbool_impl.initialize_original(false); + meshbool_impl.mark_coplanar(); + return Self::from(meshbool_impl); + } + + // + // This operation returns a vector of Manifolds that are topologically + // disconnected. If everything is connected, the vector is length one, + // containing a copy of the original. It is the inverse operation of Compose(). + // + pub fn decompose(&self) -> Vec { + let uf = DisjointSets::new(self.num_vert() as u32); + // Graph graph; + let p_impl = &self.meshbool_impl; + for halfedge in p_impl.halfedge.iter() { + if halfedge.is_forward() { + uf.unite(halfedge.start_vert as u32, halfedge.end_vert as u32); + } + } + let mut component_indices: Vec = vec![]; + let num_components = uf.connected_components(&mut component_indices); + + if num_components == 1 { + return vec![self.clone()]; + } + let vert_label: Vec = component_indices; + + let num_vert = self.num_vert(); + let mut meshes: Vec = vec![]; + for i in 0..num_components { + let mut meshbool_impl = MeshBoolImpl::default(); + // inherit original object's precision + meshbool_impl.epsilon = p_impl.epsilon; + meshbool_impl.tolerance = p_impl.tolerance; + + let mut vert_new2old: Vec = vec![0; num_vert]; + let n_vert = copy_if(0..num_vert as i32, &mut vert_new2old, |v| { + vert_label[v as usize] == i as i32 + }); + meshbool_impl.vert_pos.resize(n_vert, Default::default()); + vert_new2old.resize(n_vert, Default::default()); + gather(&vert_new2old, &p_impl.vert_pos, &mut meshbool_impl.vert_pos); + + let mut face_new2old: Vec = vec![0; self.num_tri()]; + let halfedge = &p_impl.halfedge; + let n_face = copy_if(0..self.num_tri() as i32, &mut face_new2old, |face| { + vert_label[halfedge[3 * face as usize].start_vert as usize] == i as i32 + }); + + if n_face == 0 { + continue; + } + face_new2old.resize(n_face, Default::default()); + + meshbool_impl.gather_faces_with_old(p_impl, &face_new2old); + meshbool_impl.reindex_verts(&vert_new2old, p_impl.num_vert()); + meshbool_impl.finish(); + + meshes.push(Self::from(meshbool_impl)); + } + return meshes; + } } diff --git a/src/face_op.rs b/src/face_op.rs index b607f67..c75549a 100644 --- a/src/face_op.rs +++ b/src/face_op.rs @@ -1,11 +1,14 @@ +use crate::AABB; +use crate::collider::SimpleRecorder; use crate::common::{Polygons, SimplePolygon}; use crate::meshboolimpl::MeshBoolImpl; use crate::parallel::copy_if; use crate::polygon::{PolyVert, PolygonsIdx, SimplePolygonIdx, triangulate_idx}; use crate::shared::{Halfedge, TriRef, get_axis_aligned_projection}; -use crate::utils::ccw; +use crate::utils::{ccw, next3_i32, next3_usize}; use crate::vec::InsertSorted; use nalgebra::{Matrix2x3, Matrix3x2, Point3, Vector3}; +use std::collections::HashSet; use std::mem; use std::ops::DerefMut; @@ -222,6 +225,79 @@ impl MeshBoolImpl { } } + pub fn slice(&self, height: f64) -> Polygons { + let mut plane: AABB = self.bbox; + plane.min.z = height; + plane.max.z = height; + let mut query: Vec = vec![]; + query.push(plane); + + let mut tris = HashSet::::new(); + let mut record_collision = |_, tri: i32| { + let mut min: f64 = core::f64::INFINITY; + let mut max: f64 = core::f64::NEG_INFINITY; + for j in [0, 1, 2] { + let z: f64 = + self.vert_pos[self.halfedge[3 * tri as usize + j].start_vert as usize].z; + min = min.min(z); + max = max.max(z); + } + + if min <= height && max > height { + tris.insert(tri); + } + }; + + let mut recorder = SimpleRecorder::new(&mut record_collision); + self.collider + .collisions_from_slice::>(&query, &mut recorder, false); + + let mut polys = Polygons::default(); + while !tris.is_empty() { + let start_tri: i32 = *tris.iter().next().unwrap(); + let mut poly = SimplePolygon::default(); + + let mut k: i32 = 0; + for j in [0, 1, 2] { + if self.vert_pos[self.halfedge[3 * start_tri as usize + j].start_vert as usize].z + > height && self.vert_pos + [self.halfedge[3 * start_tri as usize + next3_usize(j)].start_vert as usize] + .z <= height + { + k = next3_i32(j as i32); + break; + } + } + + let mut tri: i32 = start_tri; + loop { + tris.remove(&tri); + if self.vert_pos[self.halfedge[3 * tri as usize + k as usize].end_vert as usize].z + <= height + { + k = next3_i32(k); + } + + let up: Halfedge = self.halfedge[(3 * tri + k) as usize]; + let below: Vector3 = self.vert_pos[up.start_vert as usize].coords; + let above: Vector3 = self.vert_pos[up.end_vert as usize].coords; + let a: f64 = (height - below.z) / (above.z - below.z); + poly.push(below.lerp(&above, a).xy().into()); + + let pair: i32 = up.paired_halfedge; + tri = pair / 3; + k = next3_i32(pair % 3); + if tri == start_tri { + break; + } + } + + polys.push(poly); + } + + return polys; + } + pub fn project(&self) -> Polygons { let projection = get_axis_aligned_projection(Vector3::new(0.0, 0.0, 1.0)); let mut cusps: Vec = vec![Default::default(); self.num_edge()]; diff --git a/src/lib.rs b/src/lib.rs index 80a87a3..b1cfa5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ use crate::boolean3::Boolean3; -use crate::common::{LossyFrom, Polygons}; +use crate::common::{FloatKind, LossyFrom, Polygons}; use crate::meshboolimpl::{MeshBoolImpl, Relation}; use crate::shared::normal_transform; use nalgebra::{Matrix3, Matrix3x4, Point3, UnitQuaternion, Vector2, Vector3}; -use std::ops::{Add, AddAssign, BitXor, BitXorAssign, Sub, SubAssign}; +use std::ops::{Add, AddAssign, BitXor, BitXorAssign, Neg, Sub, SubAssign}; pub use crate::common::AABB; pub use crate::common::OpType; @@ -31,6 +31,166 @@ mod tri_dis; mod utils; mod vec; +#[cfg(feature = "zngur")] +mod generated { + include!(concat!(env!("OUT_DIR"), "/generated.rs")); + + trait PointGetterXY { + type T; + fn get_x(&self) -> Self::T; + fn get_y(&self) -> Self::T; + } + + impl PointGetterXY for ::nalgebra::Point2 { + type T = f64; + + fn get_x(&self) -> Self::T { + self.x + } + fn get_y(&self) -> Self::T { + self.y + } + } + + trait PointGetter { + type T; + fn get_x(&self) -> Self::T; + fn get_y(&self) -> Self::T; + fn get_z(&self) -> Self::T; + } + + impl PointGetter for ::nalgebra::Point3 { + type T = f64; + fn get_x(&self) -> Self::T { + self.x + } + fn get_y(&self) -> Self::T { + self.y + } + fn get_z(&self) -> Self::T { + self.z + } + } + + trait PointGetterW: PointGetter { + fn get_w(&self) -> Self::T; + } + + impl PointGetter for ::nalgebra::Vector4 { + type T = f64; + fn get_x(&self) -> Self::T { + self.x + } + fn get_y(&self) -> Self::T { + self.y + } + fn get_z(&self) -> Self::T { + self.z + } + } + + impl PointGetterW for ::nalgebra::Vector4 { + fn get_w(&self) -> Self::T { + self.w + } + } + + impl PointGetter for ::nalgebra::Vector3 { + type T = i32; + fn get_x(&self) -> Self::T { + self.x + } + fn get_y(&self) -> Self::T { + self.y + } + fn get_z(&self) -> Self::T { + self.z + } + } + + impl PointGetter for ::nalgebra::Vector4 { + type T = i32; + fn get_x(&self) -> Self::T { + self.x + } + fn get_y(&self) -> Self::T { + self.y + } + fn get_z(&self) -> Self::T { + self.z + } + } + + impl PointGetterW for ::nalgebra::Vector4 { + fn get_w(&self) -> Self::T { + self.w + } + } + + trait ManifoldErrorExport { + fn is_no_error(&self) -> bool; + fn is_non_finite_vertex(&self) -> bool; + fn is_invalid_construction(&self) -> bool; + fn is_result_too_large(&self) -> bool; + fn is_not_manifold(&self) -> bool; + fn is_missing_position_properties(&self) -> bool; + fn is_merge_vectors_different_lengths(&self) -> bool; + fn is_transform_wrong_length(&self) -> bool; + fn is_run_index_wrong_length(&self) -> bool; + fn is_face_id_wrong_length(&self) -> bool; + fn is_merge_index_out_of_bounds(&self) -> bool; + fn is_vertex_out_of_bounds(&self) -> bool; + } + + impl ManifoldErrorExport for super::ManifoldError { + fn is_no_error(&self) -> bool { + *self == Self::NoError + } + fn is_non_finite_vertex(&self) -> bool { + *self == Self::NonFiniteVertex + } + fn is_invalid_construction(&self) -> bool { + *self == Self::InvalidConstruction + } + fn is_result_too_large(&self) -> bool { + *self == Self::ResultTooLarge + } + fn is_not_manifold(&self) -> bool { + *self == Self::NotManifold + } + fn is_missing_position_properties(&self) -> bool { + *self == Self::MissingPositionProperties + } + fn is_merge_vectors_different_lengths(&self) -> bool { + *self == Self::MergeVectorsDifferentLengths + } + fn is_transform_wrong_length(&self) -> bool { + *self == Self::TransformWrongLength + } + fn is_run_index_wrong_length(&self) -> bool { + *self == Self::RunIndexWrongLength + } + fn is_face_id_wrong_length(&self) -> bool { + *self == Self::FaceIDWrongLength + } + fn is_merge_index_out_of_bounds(&self) -> bool { + *self == Self::MergeIndexOutOfBounds + } + fn is_vertex_out_of_bounds(&self) -> bool { + *self == Self::VertexOutOfBounds + } + } + + impl crate::MeshBool { + pub fn from_meshgl_32(mesh_gl: &crate::MeshGL32) -> Self { + Self::from_meshgl(mesh_gl) + } + pub fn from_meshgl_64(mesh_gl: &crate::MeshGL64) -> Self { + Self::from_meshgl(mesh_gl) + } + } +} + #[test] fn test() { use nalgebra::Vector3; @@ -49,6 +209,19 @@ fn test() { println!("{:?}", intersection.get_mesh_gl_32(0)); } +fn halfspace(b_box: AABB, mut normal: Vector3, origin_offset: f64) -> MeshBool { + normal.normalize_mut(); + let mut cutter = + MeshBool::cube(Vector3::repeat(2.0), true).translate(Vector3::new(1.0, 0.0, 0.0)); + let size: f64 = (b_box.center() - normal * origin_offset).norm() + 0.5 * b_box.size().norm(); + cutter = cutter + .scale(Vector3::repeat(size)) + .translate(Vector3::new(origin_offset, 0.0, 0.0)); + let y_deg: f64 = normal.z.asin().neg().to_degrees(); + let z_deg: f64 = normal.y.atan2(normal.x).to_degrees(); + return cutter.rotate(0.0, y_deg, z_deg); +} + ///@brief Mesh input/output suitable for pushing directly into graphics ///libraries. /// @@ -474,6 +647,26 @@ impl MeshBool { return Self::from(meshbool_impl); } + ///Curvature is the inverse of the radius of curvature, and signed such that + ///positive is convex and negative is concave. There are two orthogonal + ///principal curvatures at any point on a manifold, with one maximum and the + ///other minimum. Gaussian curvature is their product, while mean + ///curvature is their sum. This approximates them for every vertex and assigns + ///them as vertex properties on the given channels. + /// + ///@param gaussian_idx The property channel index in which to store the Gaussian + ///curvature. An index < 0 will be ignored (stores nothing). The property set + ///will be automatically expanded to include the channel index specified. + /// + ///@param mean_idx The property channel index in which to store the mean + ///curvature. An index < 0 will be ignored (stores nothing). The property set + ///will be automatically expanded to include the channel index specified. + pub fn calculate_curvature(&self, gaussian_idx: i32, mean_idx: i32) -> Self { + let mut meshbool_impl = self.meshbool_impl.clone(); + meshbool_impl.calculate_curvature(gaussian_idx, mean_idx); + Self::from(meshbool_impl) + } + ///Fills in vertex properties for normal vectors, calculated from the mesh ///geometry. Flat faces composed of three or more triangles will remain flat. /// @@ -493,6 +686,72 @@ impl MeshBool { return Self::from(meshbool_impl); } + ///Increase the density of the mesh by splitting every edge into n pieces. For + ///instance, with n = 2, each triangle will be split into 4 triangles. Quads + ///will ignore their interior triangle bisector. These will all be coplanar (and + ///will not be immediately collapsed) unless the Mesh/Manifold has + ///halfedgeTangents specified (e.g. from the Smooth() constructor), in which + ///case the new vertices will be moved to the interpolated surface according to + ///their barycentric coordinates. + /// + ///@param n The number of pieces to split every edge into. Must be > 1. + pub fn refine(&self, n: i32) -> Self { + let mut meshbool_impl = self.meshbool_impl.clone(); + if n > 1 { + meshbool_impl.refine(|_, _, _| n - 1, false); + } + Self::from(meshbool_impl) + } + + ///Increase the density of the mesh by splitting each edge into pieces of + ///roughly the input length. Interior verts are added to keep the rest of the + ///triangulation edges also of roughly the same length. If halfedgeTangents are + ///present (e.g. from the Smooth() constructor), the new vertices will be moved + ///to the interpolated surface according to their barycentric coordinates. Quads + ///will ignore their interior triangle bisector. + /// + ///@param length The length that edges will be broken down to. + pub fn refine_to_length(&self, mut length: f64) -> Self { + length = length.abs(); + let mut meshbool_impl = self.meshbool_impl.clone(); + meshbool_impl.refine(|edge, _, _| (edge.norm() / length) as i32, false); + Self::from(meshbool_impl) + } + + ///Increase the density of the mesh by splitting each edge into pieces such that + ///any point on the resulting triangles is roughly within tolerance of the + ///smoothly curved surface defined by the tangent vectors. This means tightly + ///curving regions will be divided more finely than smoother regions. If + ///halfedgeTangents are not present, the result will simply be a copy of the + ///original. Quads will ignore their interior triangle bisector. + /// + ///@param tolerance The desired maximum distance between the faceted mesh + ///produced and the exact smoothly curving surface. All vertices are exactly on + ///the surface, within rounding error. + pub fn refine_to_tolerance(&self, mut tolerance: f64) -> Self { + tolerance = tolerance.abs(); + let mut meshbool_impl = self.meshbool_impl.clone(); + // if !pImpl.halfedge_tangent.empty() { + if false { + meshbool_impl.refine( + |edge, tangent_start, tangent_end| { + let edge_norm: Vector3 = edge.normalize(); + // Weight heuristic + let t_start: Vector3 = tangent_start.xyz(); + let t_end: Vector3 = tangent_end.xyz(); + // Perpendicular to edge + let start: Vector3 = t_start - edge_norm * edge_norm.dot(&t_start); + let end: Vector3 = t_end - edge_norm * edge_norm.dot(&t_end); + // Circular arc result plus heuristic term for non-circular curves + let d: f64 = 0.5 * (start.norm() + end.norm()) + (start - end).norm(); + (3.0 * d / (4.0 * tolerance)).sqrt() as i32 + }, + true, + ); + } + Self::from(meshbool_impl) + } + /// The central operation of this library: the Boolean combines two manifolds /// into another by calculating their intersections and removing the unused /// portions. @@ -509,9 +768,54 @@ impl MeshBool { Self::from(Boolean3::new(&self.meshbool_impl, &other.meshbool_impl, op).result(op)) } + ///Split cuts this manifold in two using the cutter manifold. The first result + ///is the intersection, second is the difference. This is more efficient than + ///doing them separately. + /// + ///@param cutter + pub fn split(&self, cutter: &Self) -> (Self, Self) { + let impl1 = &self.meshbool_impl; + let impl2 = &cutter.meshbool_impl; + + let boolean = Boolean3::new(impl1, impl2, OpType::Subtract); + let result1 = boolean.result(OpType::Intersect); + let result2 = boolean.result(OpType::Subtract); + (Self::from(result1), Self::from(result2)) + } + + ///Convenient version of Split() for a half-space. + /// + ///@param normal This vector is normal to the cutting plane and its length does + ///not matter. The first result is in the direction of this vector, the second + ///result is on the opposite side. + ///@param originOffset The distance of the plane from the origin in the + ///direction of the normal vector. + pub fn split_by_plane(&self, normal: Vector3, origin_offset: f64) -> (Self, Self) { + self.split(&halfspace(self.bounding_box(), normal, origin_offset)) + } + + ///Identical to SplitByPlane(), but calculating and returning only the first + ///result. + /// + ///@param normal This vector is normal to the cutting plane and its length does + ///not matter. The result is in the direction of this vector from the plane. + ///@param originOffset The distance of the plane from the origin in the + ///direction of the normal vector. + pub fn trim_by_plane(&self, normal: Vector3, origin_offset: f64) -> Self { + self ^ &halfspace(self.bounding_box(), normal, origin_offset) + } + + ///Returns the cross section of this object parallel to the X-Y plane at the + ///specified Z height, defaulting to zero. Using a height equal to the bottom of + ///the bounding box will return the bottom faces, while using a height equal to + ///the top of the bounding box will return empty. + pub fn slice(&self, height: f64) -> Polygons { + self.meshbool_impl.slice(height) + } + fn get_mesh_gl_impl(meshbool_impl: &MeshBoolImpl, normal_idx: i32) -> MeshGLP where - F: LossyFrom + Copy, + F: LossyFrom + Copy + FloatKind, f64: From, I: LossyFrom + Copy, usize: LossyFrom, @@ -524,9 +828,12 @@ impl MeshBool { let update_normals = !is_original && normal_idx >= 0; let out_num_prop = 3 + num_prop; - let tolerance = meshbool_impl + let mut tolerance = meshbool_impl .tolerance .max((f32::EPSILON as f64) * meshbool_impl.bbox.scale()); + if F::is_f32() { + tolerance = tolerance.max(core::f64::EPSILON * meshbool_impl.bbox.scale()); + } let mut tri_verts: Vec = vec![I::lossy_from(0); 3 * num_tri]; @@ -736,7 +1043,7 @@ impl MeshBool { pub fn from_meshgl(mesh_gl: &MeshGLP) -> Self where - F: LossyFrom + Copy, + F: LossyFrom + Copy + FloatKind, f64: From, I: LossyFrom + Copy, usize: LossyFrom, diff --git a/src/meshboolimpl.rs b/src/meshboolimpl.rs index d027f95..a66c69a 100644 --- a/src/meshboolimpl.rs +++ b/src/meshboolimpl.rs @@ -1,5 +1,5 @@ use crate::collider::Collider; -use crate::common::{AABB, LossyFrom, sun_acos}; +use crate::common::{AABB, FloatKind, LossyFrom, sun_acos}; use crate::disjoint_sets::DisjointSets; use crate::mesh_fixes::{FlipTris, transform_normal}; use crate::parallel::exclusive_scan_in_place; @@ -8,6 +8,7 @@ use crate::utils::{atomic_add_i32, mat3, mat4, next3_i32, next3_usize}; use crate::vec::{vec_resize, vec_resize_nofill, vec_uninit}; use crate::{ManifoldError, MeshGLP}; use nalgebra::{Matrix3x4, Point3, Vector3, Vector4}; +use rayon::prelude::*; use std::cmp::Ordering as CmpOrdering; use std::collections::{BTreeMap, HashMap}; use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering as AtomicOrdering}; @@ -172,7 +173,7 @@ impl<'a, const USE_PROP: bool, F: FnMut(i32, i32, i32)> PrepHalfedges<'a, USE_PR } else { self.tri_vert[tri as usize][j as usize] }; - debug_assert!(v0 != v1, "topological degeneracy"); + debug_assert_ne!(v0, v1, "topological degeneracy"); self.halfedges[e as usize] = Halfedge { start_vert: v0, end_vert: v1, @@ -188,7 +189,7 @@ impl<'a, const USE_PROP: bool, F: FnMut(i32, i32, i32)> PrepHalfedges<'a, USE_PR impl MeshBoolImpl { pub fn from_meshgl(mesh_gl: &MeshGLP) -> Self where - F: LossyFrom + Copy, + F: LossyFrom + Copy + FloatKind, f64: From, I: LossyFrom + Copy, usize: LossyFrom, @@ -352,7 +353,7 @@ impl MeshBoolImpl { }, ); } else { - let m: [_; 12] = array::from_fn(|i| f64::from(mesh_gl.run_transform[i * 12])); + let m: [_; 12] = array::from_fn(|j| f64::from(mesh_gl.run_transform[i * 12 + j])); manifold.mesh_relation.mesh_id_transform.insert( mesh_id as i32, Relation { @@ -415,7 +416,7 @@ impl MeshBoolImpl { } manifold.calculate_bbox(); - manifold.set_epsilon(-1.0f64, false); // TODO: if Precision == float + manifold.set_epsilon(-1.0f64, F::is_f32()); // we need to split pinched verts before calculating vertex normals, because // the algorithm doesn't work with pinched verts @@ -582,26 +583,32 @@ impl MeshBoolImpl { tri: i32, } let mut tri_priority = unsafe { vec_uninit(num_tri) }; - for tri in 0..num_tri { - self.mesh_relation.tri_ref[tri].coplanar_id = -1; - if self.halfedge[3 * tri].start_vert < 0 { - tri_priority[tri] = TriPriority { - area2: 0.0, - tri: tri as i32, - }; - continue; - } - - let v = self.vert_pos[self.halfedge[3 * tri].start_vert as usize]; - tri_priority[tri] = TriPriority { - area2: (self.vert_pos[self.halfedge[3 * tri].end_vert as usize] - v) - .cross(&(self.vert_pos[self.halfedge[3 * tri + 1].end_vert as usize] - v)) - .magnitude_squared(), - tri: tri as i32, - }; - } + self.mesh_relation.tri_ref[0..num_tri] + .par_iter_mut() + .enumerate() + .map(|(tri, mesh_relation_tri_ref)| { + mesh_relation_tri_ref.coplanar_id = -1; + if self.halfedge[3 * tri].start_vert < 0 { + TriPriority { + area2: 0.0, + tri: tri as i32, + } + } else { + let v = self.vert_pos[self.halfedge[3 * tri].start_vert as usize]; + TriPriority { + area2: (self.vert_pos[self.halfedge[3 * tri].end_vert as usize] - v) + .cross( + &(self.vert_pos[self.halfedge[3 * tri + 1].end_vert as usize] - v), + ) + .magnitude_squared(), + tri: tri as i32, + } + } + }) + .collect_into_vec(&mut tri_priority); - tri_priority.sort_by(|a, b| b.area2.partial_cmp(&a.area2).unwrap_or(CmpOrdering::Equal)); + tri_priority + .par_sort_by(|a, b| b.area2.partial_cmp(&a.area2).unwrap_or(CmpOrdering::Equal)); let mut interior_halfedges: Vec = Vec::default(); for tp in &tri_priority { @@ -701,8 +708,8 @@ impl MeshBoolImpl { } } - let mut ids: Vec = (0..num_halfedge).collect(); - ids.sort_by_key(|&i| edge[i as usize]); + let mut ids: Vec = (0..num_halfedge).into_par_iter().collect(); + ids.par_sort_by_key(|&i| edge[i as usize]); ids } else { // For larger vertex count, we separate the ids into slices for halfedges @@ -999,6 +1006,7 @@ impl MeshBoolImpl { vec_resize(&mut self.vert_normal, num_vert); let vert_halfedge_map: Vec = (0..self.num_vert()) + .into_par_iter() .map(|_| AtomicI32::new(i32::MAX)) .collect(); @@ -1023,39 +1031,44 @@ impl MeshBoolImpl { if self.face_normal.len() != self.num_tri() { let num_tri = self.num_tri(); vec_resize(&mut self.face_normal, num_tri); - for face in 0..num_tri { - let face = face as i32; - let tri_normal = &mut self.face_normal[face as usize]; - if self.halfedge[(3 * face) as usize].start_vert < 0 { - *tri_normal = Vector3::new(0.0, 0.0, 1.0); - continue; - } + self.face_normal[0..num_tri] + .par_iter_mut() + .enumerate() + .for_each(|(face, tri_normal)| { + let face = face as i32; + if self.halfedge[(3 * face) as usize].start_vert < 0 { + *tri_normal = Vector3::new(0.0, 0.0, 1.0); + return; + } - let mut tri_verts = Vector3::::default(); - for i in 0..3 { - let v = self.halfedge[(3 * face + i) as usize].start_vert; - tri_verts[i as usize] = v; - atomic_min(3 * face + i, v); - } + let mut tri_verts = Vector3::::default(); + for i in 0..3 { + let v = self.halfedge[(3 * face + i) as usize].start_vert; + tri_verts[i as usize] = v; + atomic_min(3 * face + i, v); + } - let mut edge = [Vector3::::default(); 3]; - for i in 0..3 { - let j = next3_usize(i); - edge[i] = (self.vert_pos[tri_verts[j] as usize] - - self.vert_pos[tri_verts[i] as usize]) - .normalize(); - } + let mut edge = [Vector3::::default(); 3]; + for i in 0..3 { + let j = next3_usize(i); + edge[i] = (self.vert_pos[tri_verts[j] as usize] + - self.vert_pos[tri_verts[i] as usize]) + .normalize(); + } - *tri_normal = edge[0].cross(&edge[1]).normalize(); - if tri_normal.x.is_nan() { - *tri_normal = Vector3::new(0.0, 0.0, 1.0); - } - } + *tri_normal = edge[0].cross(&edge[1]).normalize(); + if tri_normal.x.is_nan() { + *tri_normal = Vector3::new(0.0, 0.0, 1.0); + } + }); } else { - for i in 0..self.halfedge.len() { - let i = i as i32; - atomic_min(i, self.halfedge[i as usize].start_vert); - } + self.halfedge + .par_iter_mut() + .enumerate() + .for_each(|(i, halfedge)| { + let i = i as i32; + atomic_min(i, halfedge.start_vert); + }); } for vert in 0..self.num_vert() { diff --git a/src/parallel.rs b/src/parallel.rs index d875c7c..8cf1a36 100644 --- a/src/parallel.rs +++ b/src/parallel.rs @@ -1,4 +1,5 @@ use crate::{common::LossyInto, vec::vec_uninit}; +use rayon::prelude::*; use std::ops::{Add, AddAssign}; ///Compute the inclusive prefix sum for the range `[first, last)` @@ -66,7 +67,7 @@ where ///must be equal or non-overlapping. pub fn exclusive_scan_in_place(io: &mut [IO], init: IO) where - IO: Copy + AddAssign, + IO: Copy + AddAssign + Send + Sync, { let mut acc = init; for i in 0..io.len() { @@ -139,12 +140,16 @@ where ///The map range, input range and the output range must not overlap. pub fn gather(map: &[Map], input: &[IO], output: &mut [IO]) where - IO: Copy, - Map: Copy + LossyInto, + IO: Copy + Send + Sync, + Map: Copy + LossyInto + Send + Sync, { - for i in 0..map.len() { - output[i] = input[map[i].lossy_into()]; - } + output + .par_iter_mut() + .zip(map.par_iter()) + .for_each(|(o, m)| { + let i: usize = (*m).lossy_into(); + *o = input[i]; + }); } ///`gather` copies elements from a source array into a destination range diff --git a/src/properties.rs b/src/properties.rs index 2f6af16..a7497d7 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -1,10 +1,11 @@ use nalgebra::{Matrix2x3, Point3, Vector2, Vector3}; +use rayon::prelude::*; use crate::AABB; use crate::collider::Recorder; use crate::meshboolimpl::MeshBoolImpl; use crate::shared::{Halfedge, get_axis_aligned_projection, next_halfedge}; -use crate::utils::ccw; +use crate::utils::{atomic_add_f64, ccw}; struct CheckHalfedges<'a> { halfedges: &'a [Halfedge], @@ -16,6 +17,56 @@ pub enum Property { SurfaceArea, } +struct CurvatureAngles<'a> { + mean_curvature: &'a mut [f64], + gaussian_curvature: &'a mut [f64], + area: &'a mut [f64], + degree: &'a mut [f64], + halfedge: &'a [Halfedge], + vert_pos: &'a [Point3], + tri_normal: &'a [Vector3], +} + +impl<'a> CurvatureAngles<'a> { + pub fn call(&mut self, tri: usize) { + let mut edge: [Vector3; 3] = Default::default(); + let mut edge_length = Vector3::repeat(0.0_f64); + for i in [0, 1, 2] { + let start_vert: i32 = self.halfedge[3 * tri + i].start_vert; + let end_vert: i32 = self.halfedge[3 * tri + i].end_vert; + edge[i] = self.vert_pos[end_vert as usize] - self.vert_pos[start_vert as usize]; + edge_length[i] = edge[i].norm(); + edge[i] /= edge_length[i]; + let neighbor_tri: i32 = self.halfedge[3 * tri + i].paired_halfedge / 3; + let dihedral: f64 = 0.25 + * edge_length[i] + * self.tri_normal[tri] + .cross(&self.tri_normal[neighbor_tri as usize]) + .dot(&edge[i]) + .asin(); + unsafe { + atomic_add_f64(&mut self.mean_curvature[start_vert as usize], dihedral); + atomic_add_f64(&mut self.mean_curvature[end_vert as usize], dihedral); + atomic_add_f64(&mut self.degree[start_vert as usize], 1.0); + } + } + + let mut phi = Vector3::::default(); + phi[0] = (-edge[2].dot(&edge[0])).acos(); + phi[1] = (-edge[0].dot(&edge[1])).acos(); + phi[2] = core::f64::consts::PI - phi[0] - phi[1]; + let area3: f64 = edge_length[0] * edge_length[1] * edge[0].cross(&edge[1]).norm() / 6.0; + + for i in [0, 1, 2] { + let vert: i32 = self.halfedge[3 * tri + i].start_vert; + unsafe { + atomic_add_f64(&mut self.gaussian_curvature[vert as usize], -phi[i]); + atomic_add_f64(&mut self.area[vert as usize], area3); + } + } + } +} + impl<'a> CheckHalfedges<'a> { fn call(&self, edge: usize) -> bool { let halfedge = self.halfedges[edge]; @@ -177,10 +228,82 @@ impl MeshBoolImpl { return value; } + pub fn calculate_curvature(&mut self, gaussian_idx: i32, mean_idx: i32) { + if self.is_empty() { + return; + } + if gaussian_idx < 0 && mean_idx < 0 { + return; + } + let mut vert_mean_curvature: Vec = vec![0.0; self.num_vert()]; + let mut vert_gaussian_curvature: Vec = vec![core::f64::consts::TAU; self.num_vert()]; + let mut vert_area: Vec = vec![0.0; self.num_vert()]; + let mut degree: Vec = vec![0.0; self.num_vert()]; + { + let mut ca = CurvatureAngles { + mean_curvature: &mut vert_mean_curvature, + gaussian_curvature: &mut vert_gaussian_curvature, + area: &mut vert_area, + degree: &mut degree, + halfedge: &self.halfedge, + vert_pos: &self.vert_pos, + tri_normal: &self.face_normal, + }; + (0..self.num_tri()).for_each(|i| ca.call(i)); + } + (0..self.num_vert()).for_each(|vert| { + let factor: f64 = degree[vert] / (6.0 * vert_area[vert]); + vert_mean_curvature[vert] *= factor; + vert_gaussian_curvature[vert] *= factor; + }); + + let old_num_prop: i32 = self.num_prop() as i32; + let num_prop: i32 = old_num_prop.max(gaussian_idx.max(mean_idx) + 1); + let old_properties = self.properties.clone(); + self.properties = vec![0.0; num_prop as usize * self.num_prop_vert()]; + self.num_prop = num_prop; + + let mut counters: Vec = vec![0; self.num_prop_vert()]; + (0..self.num_tri()).for_each(|tri| { + for i in [0, 1, 2] { + let edge = &self.halfedge[3 * tri + i]; + let vert: i32 = edge.start_vert; + let prop_vert: i32 = edge.prop_vert; + + let old = unsafe { + use core::sync::atomic::{AtomicU8, Ordering}; + let ptr = &mut counters[prop_vert as usize] as *const u8 as *const AtomicU8; + + // Convert to a shared reference + let atomic_ref = &*ptr; + + atomic_ref.swap(1u8, Ordering::SeqCst) + }; + if old == 1 { + continue; + } + + for p in 0..old_num_prop { + self.properties[(num_prop * prop_vert + p) as usize] = + old_properties[(old_num_prop * prop_vert + p) as usize]; + } + + if gaussian_idx >= 0 { + self.properties[(num_prop * prop_vert + gaussian_idx) as usize] = + vert_gaussian_curvature[vert as usize]; + } + if mean_idx >= 0 { + self.properties[(num_prop * prop_vert + mean_idx) as usize] = + vert_mean_curvature[vert as usize]; + } + } + }); + } + pub fn calculate_bbox(&mut self) { - self.bbox.min = self.vert_pos.iter().fold( - Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), - |a, &b| { + self.bbox.min = self.vert_pos.par_iter().cloned().reduce( + || Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), + |a, b| { if a.x.is_nan() { return b; } @@ -191,9 +314,9 @@ impl MeshBoolImpl { }, ); - self.bbox.max = self.vert_pos.iter().fold( - Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), - |a, &b| { + self.bbox.max = self.vert_pos.par_iter().cloned().reduce( + || Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY), + |a, b| { if a.x.is_nan() { return b; } @@ -231,7 +354,11 @@ impl MeshBoolImpl { let mut recorder = MinDistanceRecorder::new(&self, other); self.collider - .collisions_from_slice::<_, MinDistanceRecorder>(&face_box_other, &mut recorder, false); + .collisions_from_slice::( + &face_box_other, + &mut recorder, + false, + ); let min_distance_squared = recorder.get().min(search_length * search_length); return min_distance_squared.sqrt(); } diff --git a/src/smoothing.rs b/src/smoothing.rs index a0259c6..590cd70 100644 --- a/src/smoothing.rs +++ b/src/smoothing.rs @@ -1,7 +1,23 @@ -use nalgebra::{Vector3, Vector4}; +use nalgebra::{ + Matrix3, Matrix3x2, Matrix3x4, Matrix4, Matrix4x2, Matrix4x3, UnitQuaternion, Vector3, Vector4, +}; +// use crate::common::lerp; use crate::meshboolimpl::MeshBoolImpl; -use crate::shared::{TriRef, next_halfedge, safe_normalize}; +use crate::shared::{Barycentric, TriRef, next_halfedge, safe_normalize}; +use crate::utils::{K_PRECISION, mat3, next3_i32, prev3_i32}; + +///Returns a normalized vector orthogonal to ref, in the plane of ref and in, +///unless in and ref are colinear, in which case it falls back to the plane of +///ref and altIn. +#[allow(unused)] +fn orthogonal_to(in_v: Vector3, alt_in: Vector3, ref_v: Vector3) -> Vector3 { + let mut out: Vector3 = in_v - in_v.dot(&ref_v) * ref_v; + if out.dot(&out) < K_PRECISION * in_v.dot(&in_v) { + out = alt_in - alt_in.dot(&ref_v) * ref_v; + } + return safe_normalize(out); +} ///Get the angle between two unit-vectors. fn angle_between(a: Vector3, b: Vector3) -> f64 { @@ -34,6 +50,354 @@ fn circular_tangent(tangent: Vector3, edge_vec: Vector3) -> Vector4 { + vert_pos: &'a mut [Vector3], + vert_bary: &'a [Barycentric], + meshbool_impl: &'a MeshBoolImpl, +} + +#[allow(unused)] +impl<'a> InterpTri<'a> { + pub fn homogeneous(mut v: Vector4) -> Vector4 { + v.x *= v.w; + v.y *= v.w; + v.z *= v.w; + return v; + } + + pub fn homogeneous_vec3(v: Vector3) -> Vector4 { + v.push(1.0) + } + + pub fn h_normalize(v: Vector4) -> Vector3 { + if v.w == 0.0 { v.xyz() } else { v.xyz() / v.w } + } + + pub fn scale(v: Vector4, scale: f64) -> Vector4 { + (scale * v.xyz()).push(v.w) + } + + pub fn bezier(point: Vector3, tangent: Vector4) -> Vector4 { + return Self::homogeneous(point.push(0.0) + tangent); + } + + pub fn cubic_bezier2linear( + p0: Vector4, + p1: Vector4, + p2: Vector4, + p3: Vector4, + x: f64, + ) -> Matrix4x2 { + let mut out = Matrix4x2::::default(); + let p12: Vector4 = p1.lerp(&p2, x); + out.column_mut(0).copy_from(&p0.lerp(&p1, x).lerp(&p12, x)); + out.column_mut(1).copy_from(&p12.lerp(&p2.lerp(&p3, x), x)); + return out; + } + + pub fn bezier_point(points: Matrix4x2, x: f64) -> Vector3 { + return Self::h_normalize(points.column(0).lerp(&points.column(1), x)); + } + + pub fn bezier_tangent(points: Matrix4x2) -> Vector3 { + return safe_normalize( + Self::h_normalize(points.column(1).into()) - Self::h_normalize(points.column(0).into()), + ); + } + + pub fn rotate_from_to( + _v: Vector3, + _start: UnitQuaternion, + _end: UnitQuaternion, + ) -> Vector3 { + todo!("Finish port"); + // return la::qrot(end, la::qrot(la::qconj(start), v)); + } + + pub fn slerp( + x: &UnitQuaternion, + y: &UnitQuaternion, + a: f64, + long_way: bool, + ) -> UnitQuaternion { + let mut z = y.clone(); + let mut cos_theta: f64 = x.dot(&y); + + // Take the long way around the sphere only when requested + if (cos_theta < 0.0) != long_way { + z = y.inverse(); + cos_theta = -cos_theta; + } + + if cos_theta > 1.0 - core::f64::EPSILON { + // for numerical stability + UnitQuaternion::from_quaternion(x.lerp(&z, a)) + } else { + // let angle: f64 = cos_theta.acos(); + todo!("Finish port 2"); + // (((1.0 - a) * angle).sin() * x + (a * angle).sin() * z) / angle.sin() + } + } + + pub fn bezier2bezier( + corners: &Matrix3x2, + tangents_x: &Matrix4x2, + tangents_y: &Matrix4x2, + x: f64, + anchor: &Vector3, + ) -> Matrix4x2 { + let bez = Self::cubic_bezier2linear( + Self::homogeneous_vec3(corners.column(0).into()), + Self::bezier(corners.column(0).into(), tangents_x.column(0).into()), + Self::bezier(corners.column(1).into(), tangents_x.column(1).into()), + Self::homogeneous_vec3(corners.column(1).into()), + x, + ); + let _end = Self::bezier_point(bez, x); + let _tangent = Self::bezier_tangent(bez); + + let n_tangents_x: Matrix3x2 = Matrix3x2::from_columns(&[ + safe_normalize(tangents_x.column(0).xyz()), + -safe_normalize(tangents_x.column(1).xyz()), + ]); + let bi_tangents = Matrix3x2::::from_columns(&[ + orthogonal_to( + tangents_y.column(0).xyz(), + anchor - corners.column(0).clone_owned(), + n_tangents_x.column(0).clone_owned(), + ), + orthogonal_to( + tangents_y.column(1).xyz(), + anchor - corners.column(1), + n_tangents_x.column(1).clone_owned(), + ), + ]); + + let q0: UnitQuaternion = UnitQuaternion::from_matrix(&Matrix3::from_columns(&[ + n_tangents_x.column(0).clone_owned(), + bi_tangents.column(0).clone_owned(), + n_tangents_x.column(0).cross(&bi_tangents.column(0)), + ])); + let q1: UnitQuaternion = UnitQuaternion::from_matrix(&Matrix3::from_columns(&[ + n_tangents_x.column(1).clone_owned(), + bi_tangents.column(1).clone_owned(), + n_tangents_x.column(1).cross(&bi_tangents.column(1)), + ])); + let edge: Vector3 = corners.column(1) - corners.column(0); + let long_way: bool = + n_tangents_x.column(0).dot(&edge) + n_tangents_x.column(1).dot(&edge) < 0.0; + let _q_tmp: UnitQuaternion = Self::slerp(&q0, &q1, x, long_way); + todo!("Finish port 3"); + // let q: UnitQuaternion = la::rotation_quat(la::qxdir(q_tmp), tangent) * q_tmp; + + // let delta: Vector3 = Self::rotate_from_to(tangents_y.column(0).xyz(), q0, q) + // .lerp(&Self::rotate_from_to(tangents_y.column(1).xyz(), q1, q), x); + // let delta_w: f64 = lerp(tangents_y.column(0).w, tangents_y.column(1).w, x); + // + // return Matrix4x2::from_columns(&[Self::homogeneous_vec3(end), delta.push(delta_w)]); + } + + pub fn bezier2d( + corners: &Matrix3x4, + tangents_x: &Matrix4, + tangents_y: &Matrix4, + x: f64, + y: f64, + centroid: &Vector3, + ) -> Vector3 { + let bez0: Matrix4x2 = Self::bezier2bezier( + &Matrix3x2::from_columns(&[ + corners.column(0).clone_owned(), + corners.column(1).clone_owned(), + ]), + &Matrix4x2::from_columns(&[ + tangents_x.column(0).clone_owned(), + tangents_x.column(1).clone_owned(), + ]), + &Matrix4x2::from_columns(&[ + tangents_y.column(0).clone_owned(), + tangents_y.column(1).clone_owned(), + ]), + x, + centroid, + ); + let bez1: Matrix4x2 = Self::bezier2bezier( + &Matrix3x2::from_columns(&[ + corners.column(2).clone_owned(), + corners.column(3).clone_owned(), + ]), + &Matrix4x2::from_columns(&[ + tangents_x.column(2).clone_owned(), + tangents_x.column(3).clone_owned(), + ]), + &Matrix4x2::from_columns(&[ + tangents_y.column(2).clone_owned(), + tangents_y.column(3).clone_owned(), + ]), + 1.0 - x, + centroid, + ); + + let bez: Matrix4x2 = Self::cubic_bezier2linear( + bez0.column(0).clone_owned(), + Self::bezier(bez0.column(0).xyz(), bez0.column(1).clone_owned()), + Self::bezier(bez1.column(0).xyz(), bez1.column(1).clone_owned()), + bez1.column(0).clone_owned(), + y, + ); + return Self::bezier_point(bez, y); + } + + pub fn _call(&mut self, vert: i32) { + let pos = &mut self.vert_pos[vert as usize]; + let tri: i32 = self.vert_bary[vert as usize].tri; + let uvw: Vector4 = self.vert_bary[vert as usize].uvw; + + let halfedges: Vector4 = self.meshbool_impl.get_halfedges(tri); + let corners = Matrix3x4::::from_columns(&[ + self.meshbool_impl.vert_pos + [self.meshbool_impl.halfedge[halfedges[0] as usize].start_vert as usize] + .coords, + self.meshbool_impl.vert_pos + [self.meshbool_impl.halfedge[halfedges[1] as usize].start_vert as usize] + .coords, + self.meshbool_impl.vert_pos + [self.meshbool_impl.halfedge[halfedges[2] as usize].start_vert as usize] + .coords, + if halfedges[3] < 0 { + Vector3::repeat(0.0_f64).into() + } else { + self.meshbool_impl.vert_pos + [self.meshbool_impl.halfedge[halfedges[3] as usize].start_vert as usize] + .coords + }, + ]); + + for i in [0, 1, 2, 3] { + if uvw[i] == 1.0 { + *pos = corners.column(i).clone_owned(); + return; + } + } + + let mut pos_h = Vector4::repeat(0.0_f64); + + if halfedges[3] < 0 { + // tri + let tangent_r: Matrix4x3 = Default::default(); + // let tangent_r: Matrix4x3 = ( + // self.meshbool_impl.halfedge_tangent[halfedges[0] as usize], + // self.meshbool_impl.halfedge_tangent[halfedges[1] as usize], + // self.meshbool_impl.halfedge_tangent[halfedges[2] as usize], + // ); + let tangent_l: Matrix4x3 = Default::default(); + // let tangent_l: Matrix4x3 = ( + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[2] as usize].paired_halfedge], + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[0] as usize].paired_halfedge], + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[1] as usize].paired_halfedge], + // ); + let centroid: Vector3 = mat3(&corners) * Vector3::repeat(1.0_f64 / 3.0); + + for i in [0, 1, 2] { + let j: i32 = next3_i32(i); + let k: i32 = prev3_i32(i); + let x: f64 = uvw[k as usize] / (1.0 - uvw[i as usize]); + + let bez: Matrix4x2 = Self::bezier2bezier( + &Matrix3x2::from_columns(&[ + corners.column(j as usize).clone_owned(), + corners.column(k as usize).clone_owned(), + ]), + &Matrix4x2::from_columns(&[ + tangent_r.column(j as usize).clone_owned(), + tangent_l.column(k as usize).clone_owned(), + ]), + &Matrix4x2::from_columns(&[ + tangent_l.column(j as usize).clone_owned(), + tangent_r.column(k as usize).clone_owned(), + ]), + x, + ¢roid, + ); + + let bez1: Matrix4x2 = Self::cubic_bezier2linear( + bez.column(0).clone_owned(), + Self::bezier( + bez.column(0).clone_owned().xyz(), + bez.column(1).clone_owned(), + ), + Self::bezier( + corners.column(i as usize).clone_owned(), + tangent_r + .column(i as usize) + .clone_owned() + .lerp(&tangent_l.column(i as usize), x), + ), + Self::homogeneous_vec3(corners.column(i as usize).clone_owned()), + uvw[i as usize], + ); + let p: Vector3 = Self::bezier_point(bez1, uvw[i as usize]); + pos_h += Self::homogeneous(p.push(uvw[j as usize] * uvw[k as usize])); + } + } else { + // quad + let tangents_x: Matrix4 = Default::default(); + // let tangents_x: Matrix4 = ( + // self.meshbool_impl.halfedge_tangent[halfedges[0] as usize], + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[0] as usize].paired_halfedge as usize], + // self.meshbool_impl.halfedge_tangent[halfedges[2] as usize], + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[2] as usize].paired_halfedge as usize], + // ); + let tangents_y: Matrix4 = Default::default(); + // let tangents_y: Matrix4 = ( + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[3] as usize].paired_halfedge as usize], + // self.meshbool_impl.halfedge_tangent[halfedges[1] as usize], + // self.meshbool_impl.halfedge_tangent + // [self.meshbool_impl.halfedge[halfedges[1] as usize].paired_halfedge as usize], + // self.meshbool_impl.halfedge_tangent[halfedges[3] as usize], + // ); + let centroid: Vector3 = corners * Vector4::repeat(0.25_f64); + let x: f64 = uvw[1] + uvw[2]; + let y: f64 = uvw[2] + uvw[3]; + let p_x: Vector3 = + Self::bezier2d(&corners, &tangents_x, &tangents_y, x, y, ¢roid); + let p_y: Vector3 = Self::bezier2d( + &Matrix3x4::from_columns(&[ + corners.column(1).clone_owned(), + corners.column(2).clone_owned(), + corners.column(3).clone_owned(), + corners.column(0).clone_owned(), + ]), + &Matrix4::from_columns(&[ + tangents_y.column(1).clone_owned(), + tangents_y.column(2).clone_owned(), + tangents_y.column(3).clone_owned(), + tangents_y.column(0).clone_owned(), + ]), + &Matrix4::from_columns(&[ + tangents_x.column(1).clone_owned(), + tangents_x.column(2).clone_owned(), + tangents_x.column(3).clone_owned(), + tangents_x.column(0).clone_owned(), + ]), + y, + 1.0 - x, + ¢roid, + ); + pos_h += Self::homogeneous(p_x.push(x * (1.0 - x))); + pos_h += Self::homogeneous(p_y.push(y * (1.0 - y))); + } + *pos = Self::h_normalize(pos_h); + } +} + impl MeshBoolImpl { ///Returns a circular tangent for the requested halfedge, orthogonal to the ///given normal vector, and avoiding folding. @@ -402,4 +766,35 @@ impl MeshBoolImpl { } } } + + pub fn refine( + &mut self, + edge_divisions: impl Fn(Vector3, Vector4, Vector4) -> i32 + Send + Sync, + keep_interior: bool, + ) { + if self.is_empty() { + return; + } + let old = self.clone(); + let vert_bary: Vec = self.subdivide(edge_divisions, keep_interior); + if vert_bary.len() == 0 { + return; + } + + // if old.halfedge_tangent.len() == old.halfedge.len() { + if 0 == old.halfedge.len() { + // (0..self.num_vert()).for_each(|i| + // InterpTri({self.vert_pos, vertBary, &old})); + panic!("Should not be possible"); + } + + // self.halfedge_tangent.clear(); + self.finish(); + // if old.halfedge_tangent.len() == old.halfedge.len() { + if 0 == old.halfedge.len() { + self.mark_coplanar(); + panic!("Should not be possible"); + } + self.mesh_relation.original_id = -1; + } } diff --git a/src/sort.rs b/src/sort.rs index f11e2e6..85c0121 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,13 +1,21 @@ use crate::ManifoldError; +use crate::MeshGLP; use crate::collider::Collider; -use crate::common::AABB; +use crate::common::FloatKind; +// <<<<<<< HEAD +use crate::common::{AABB, LossyFrom}; +use crate::disjoint_sets::DisjointSets; use crate::meshboolimpl::MeshBoolImpl; -use crate::parallel::{inclusive_scan, scatter}; -use crate::utils::permute; +use crate::parallel::{gather, inclusive_scan, scatter}; +use crate::shared::Halfedge; +use crate::utils::{K_PRECISION, permute}; use crate::vec::{vec_resize, vec_resize_nofill, vec_uninit}; -use nalgebra::Point3; +use nalgebra::{Point3, Vector3}; +use rayon::prelude::*; use std::mem; +use crate::collider::SimpleRecorder; + const K_NO_CODE: u32 = 0xFFFFFFFF; fn morton_code(position: Point3, bbox: AABB) -> u32 { @@ -20,6 +28,164 @@ fn morton_code(position: Point3, bbox: AABB) -> u32 { } } +struct ReindexFace<'a> { + halfedge: &'a mut [Halfedge], + // halfedge_tangent: &'a mut [Vector4], + old_halfedge: &'a [Halfedge], + // old_halfedge_tangent: &'a [Vector4], + face_new2old: &'a [i32], + face_old2new: &'a [i32], +} + +impl ReindexFace<'_> { + fn call(&mut self, new_face: u32) { + let old_face = self.face_new2old[new_face as usize]; + for i in 0..3 { + let old_edge = 3 * old_face + i; + let mut edge = self.old_halfedge[old_edge as usize]; + let paired_face = edge.paired_halfedge / 3; + let offset = edge.paired_halfedge - 3 * paired_face; + edge.paired_halfedge = 3 * self.face_old2new[paired_face as usize] + offset; + let new_edge = 3 * new_face + i as u32; + self.halfedge[new_edge as usize] = edge; + // if !self.old_halfedge_tangent.is_empty() { + // self.halfedge_tangent[new_edge] = self.old_halfedge_tangent[old_edge]; + // } + } + } +} + +fn merge_mesh_glp(mesh: &mut MeshGLP) -> bool +where + Precision: LossyFrom + Copy + FloatKind, + I: LossyFrom + Copy, + usize: LossyFrom, + u32: LossyFrom, + i32: LossyFrom, + f64: From, +{ + let mut open_edges: Vec<(i32, i32)> = vec![]; + + let mut merge: Vec = (0..mesh.num_vert() as i32).collect(); + for i in 0..mesh.merge_from_vert.len() { + merge[usize::lossy_from(mesh.merge_from_vert[i])] = i32::lossy_from(mesh.merge_to_vert[i]); + } + + let num_vert = mesh.num_vert(); + let num_tri = mesh.num_tri(); + let next: [i32; 3] = [1, 2, 0]; + for tri in 0..num_tri { + for i in [0, 1, 2] { + let mut edge = ( + merge[usize::lossy_from(mesh.tri_verts[3 * tri + next[i] as usize])], + merge[usize::lossy_from(mesh.tri_verts[3 * tri + i])], + ); + let it = open_edges.iter().position(|p| *p == edge); + if it.is_none() { + core::mem::swap(&mut edge.0, &mut edge.1); + open_edges.push(edge); + } else { + open_edges.remove(it.unwrap()); + } + } + } + if open_edges.is_empty() { + return false; + } + + let num_open_vert = open_edges.len(); + let mut open_verts: Vec = vec![0; num_open_vert]; + let mut i = 0; + for edge in open_edges.iter() { + let vert: i32 = edge.0; + open_verts[i] = vert; + i += 1; + } + + let vert_prop_d: Vec = mesh.vert_properties.clone(); + let mut b_box: AABB = Default::default(); + for i in [0, 1, 2] { + let min_max = vert_prop_d[i..vert_prop_d.len()] + .iter() + .cloned() + .step_by(usize::lossy_from(mesh.num_prop)) + .map(|f| (f64::from(f), f64::from(f))) + .reduce(|acc, b| (acc.0.min(b.0), acc.1.max(b.1))) + .unwrap_or((core::f64::INFINITY, core::f64::NEG_INFINITY)); + b_box.min[i] = min_max.0; + b_box.max[i] = min_max.1; + } + + let tolerance: f64 = f64::from(mesh.tolerance).max( + (if Precision::is_f32() { + core::f32::EPSILON as f64 + } else { + K_PRECISION + }) * b_box.scale(), + ); + + // let mut policy = autoPolicy(numOpenVert, 1e5); + let mut vert_box: Vec = vec![Default::default(); num_open_vert]; + let mut vert_morton: Vec = vec![0; num_open_vert]; + + (0..num_open_vert).for_each(|i| { + let vert: i32 = open_verts[i]; + + let center: Vector3 = Vector3::new( + f64::from(mesh.vert_properties[usize::lossy_from(mesh.num_prop) * vert as usize]), + f64::from(mesh.vert_properties[usize::lossy_from(mesh.num_prop) * vert as usize + 1]), + f64::from(mesh.vert_properties[usize::lossy_from(mesh.num_prop) * vert as usize + 2]), + ); + + vert_box[i].min = center.into(); + vert_box[i].min.iter_mut().for_each(|v| { + *v -= tolerance / 2.0; + }); + vert_box[i].max = center.into(); + vert_box[i].max.iter_mut().for_each(|v| { + *v += tolerance / 2.0; + }); + + vert_morton[i] = morton_code(center.into(), b_box); + }); + + let mut vert_new2old: Vec<_> = (0..num_open_vert as i32).into_iter().collect(); + vert_new2old.sort_by_key(|&i| vert_morton[i as usize]); + + permute(&mut vert_morton, &vert_new2old); + permute(&mut vert_box, &vert_new2old); + permute(&mut open_verts, &vert_new2old); + + let collider = Collider::new(&vert_box, &vert_morton); + let uf = DisjointSets::new(num_vert as u32); + + let mut f = |a: i32, b: i32| { + uf.unite(open_verts[a as usize] as u32, open_verts[b as usize] as u32); + }; + + let mut recorder = SimpleRecorder::new(&mut f); + collider.collisions_from_slice::>(&vert_box, &mut recorder, false); + + for i in 0..mesh.merge_from_vert.len() { + uf.unite( + u32::lossy_from(mesh.merge_from_vert[i]), + u32::lossy_from(mesh.merge_to_vert[i]), + ); + } + + mesh.merge_to_vert.clear(); + mesh.merge_from_vert.clear(); + for v in 0..num_vert { + let merge_to: usize = uf.find(v as u32) as usize; + if merge_to != v { + mesh.merge_from_vert.push(I::lossy_from(v)); + mesh.merge_to_vert.push(I::lossy_from(merge_to)); + } + } + + return true; +} + impl MeshBoolImpl { ///Once halfedge_ has been filled in, this function can be called to create the ///rest of the internal data structures. This function also removes the verts @@ -71,12 +237,13 @@ impl MeshBoolImpl { fn sort_verts(&mut self) { let num_vert = self.num_vert(); let mut vert_morton: Vec = unsafe { vec_uninit(num_vert) }; - for vert in 0..num_vert { - vert_morton[vert] = morton_code(self.vert_pos[vert], self.bbox); - } + self.vert_pos + .par_iter() + .map(|vert_p| morton_code(*vert_p, self.bbox)) + .collect_into_vec(&mut vert_morton); - let mut vert_new2old: Vec<_> = (0..num_vert as i32).collect(); - vert_new2old.sort_by_key(|&i| vert_morton[i as usize]); + let mut vert_new2old: Vec<_> = (0..num_vert as i32).into_par_iter().collect(); + vert_new2old.par_sort_by_key(|&i| vert_morton[i as usize]); self.reindex_verts(&vert_new2old, num_vert); @@ -96,20 +263,20 @@ impl MeshBoolImpl { ///Updates the halfedges to point to new vert indices based on a mapping, ///vertNew2Old. This may be a subset, so the total number of original verts is ///also given. - fn reindex_verts(&mut self, vert_new2old: &[i32], old_num_vert: usize) { + pub fn reindex_verts(&mut self, vert_new2old: &[i32], old_num_vert: usize) { let mut vert_old2new: Vec = unsafe { vec_uninit(old_num_vert) }; scatter(0..self.num_vert() as i32, vert_new2old, &mut vert_old2new); let has_prop = self.num_prop() > 0; - for edge in &mut self.halfedge { + self.halfedge.par_iter_mut().for_each(|edge| { if edge.start_vert < 0 { - continue; + return; } edge.start_vert = vert_old2new[edge.start_vert as usize]; edge.end_vert = vert_old2new[edge.end_vert as usize]; if !has_prop { edge.prop_vert = edge.start_vert; } - } + }); } fn compact_props(&mut self) { @@ -143,9 +310,9 @@ impl MeshBoolImpl { } } - for edge in &mut self.halfedge { + self.halfedge.par_iter_mut().for_each(|edge| { edge.prop_vert = prop_old2new[edge.prop_vert as usize]; - } + }); } ///Fills the faceBox and faceMorton input with the bounding boxes and Morton @@ -157,34 +324,39 @@ impl MeshBoolImpl { unsafe { vec_resize_nofill(face_morton, self.num_tri()); } - for face in 0..self.num_tri() { - // Removed tris are marked by all halfedges having pairedHalfedge - // = -1, and this will sort them to the end (the Morton code only - // uses the first 30 of 32 bits). - if self.halfedge[(3 * face) as usize].paired_halfedge < 0 { - face_morton[face] = K_NO_CODE; - continue; - } - - let mut center = Point3::::new(0.0, 0.0, 0.0); - - for i in 0..3 { - let pos = self.vert_pos[self.halfedge[(3 * face + i) as usize].start_vert as usize]; - center += pos.coords; - face_box[face].union_point(pos); - } - - center /= 3.; - - face_morton[face] = morton_code(center, self.bbox); - } + face_box + .par_iter_mut() + .zip_eq(face_morton.par_iter_mut()) + .enumerate() + .for_each(|(face, (face_box_v, face_morton_v))| { + // Removed tris are marked by all halfedges having pairedHalfedge + // = -1, and this will sort them to the end (the Morton code only + // uses the first 30 of 32 bits). + if self.halfedge[(3 * face) as usize].paired_halfedge < 0 { + *face_morton_v = K_NO_CODE; + return; + } + + let mut center = Point3::::new(0.0, 0.0, 0.0); + + for i in 0..3 { + let pos = + self.vert_pos[self.halfedge[(3 * face + i) as usize].start_vert as usize]; + center += pos.coords; + face_box_v.union_point(pos); + } + + center /= 3.; + + *face_morton_v = morton_code(center, self.bbox); + }); } ///Sorts the faces of this manifold according to their input Morton code. The ///bounding box and Morton code arrays are also sorted accordingly. fn sort_faces(&mut self, face_box: &mut Vec, face_morton: &mut Vec) { - let mut face_new2old: Vec<_> = (0..self.num_tri() as i32).collect(); - face_new2old.sort_by_key(|&i| face_morton[i as usize]); + let mut face_new2old: Vec<_> = (0..self.num_tri() as i32).into_par_iter().collect(); + face_new2old.par_sort_by_key(|&i| face_morton[i as usize]); // Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode // to sort them to the end, which allows them to be removed. @@ -201,7 +373,7 @@ impl MeshBoolImpl { ///Creates the halfedge_ vector for this manifold by copying a set of faces from ///another manifold, given by oldHalfedge. Input faceNew2Old defines the old ///faces to gather into this. - fn gather_faces(&mut self, face_new2old: &[i32]) { + pub fn gather_faces(&mut self, face_new2old: &[i32]) { let num_tri = face_new2old.len(); if self.mesh_relation.tri_ref.len() == self.num_tri() { permute(&mut self.mesh_relation.tri_ref, face_new2old); @@ -214,21 +386,97 @@ impl MeshBoolImpl { let mut old_halfedge = unsafe { vec_uninit(3 * num_tri) }; mem::swap(&mut old_halfedge, &mut self.halfedge); + // let mut old_halfedge_tangent = unsafe { vec_uninit(3 * num_tri) }; + // mem::swap(&mut old_halfedge_tangent, &mut self.halfedge_tangent); + let mut face_old2new = unsafe { vec_uninit(old_halfedge.len() / 3) }; scatter(0..num_tri as i32, face_new2old, &mut face_old2new); + let mut reindex_face = ReindexFace { + halfedge: &mut self.halfedge, + // halfedge_tangent: &mut self.halfedge_tangent, + old_halfedge: &old_halfedge, + // old_halfedge_tangent: &old_halfedge_tangent, + face_new2old: &face_new2old, + face_old2new: &face_old2new, + }; for new_face in 0..num_tri { - let new_face = new_face as i32; - let old_face = face_new2old[new_face as usize]; - for i in 0..3 { - let old_edge = 3 * old_face + i; - let mut edge = old_halfedge[old_edge as usize]; - let paired_face = edge.paired_halfedge / 3; - let offset = edge.paired_halfedge - 3 * paired_face; - edge.paired_halfedge = 3 * face_old2new[paired_face as usize] + offset; - let new_edge = 3 * new_face + i; - self.halfedge[new_edge as usize] = edge; + reindex_face.call(new_face as u32); + } + } + + pub fn gather_faces_with_old(&mut self, old: &Self, face_new2old: &[i32]) { + let num_tri = face_new2old.len(); + + unsafe { vec_resize_nofill(&mut self.mesh_relation.tri_ref, num_tri) }; + + gather( + face_new2old, + &old.mesh_relation.tri_ref, + &mut self.mesh_relation.tri_ref, + ); + + for pair in old.mesh_relation.mesh_id_transform.iter() { + self.mesh_relation + .mesh_id_transform + .insert(*pair.0, pair.1.clone()); + } + + if old.num_prop() > 0 { + self.num_prop = old.num_prop; + self.properties = old.properties.clone(); + } + + if old.face_normal.len() == old.num_tri() { + unsafe { + vec_resize_nofill(&mut self.face_normal, num_tri); } + gather(face_new2old, &old.face_normal, &mut self.face_normal); } + + let mut face_old2new = unsafe { vec_uninit(old.num_tri()) }; + scatter(0..num_tri as i32, face_new2old, &mut face_old2new); + + unsafe { vec_resize_nofill(&mut self.halfedge, 3 * num_tri) }; + // if old.halfedge_tangent.len() != 0 { + // halfedgeTangent_.resize_nofill(3 * numTri); + // } + let mut reindex_face = ReindexFace { + halfedge: &mut self.halfedge, + // halfedge_tangent: &mut self.halfedge_tangent, + old_halfedge: &old.halfedge, + // old_halfedge_tangent: &old.halfedge_tangent, + face_new2old: &face_new2old, + face_old2new: &face_old2new, + }; + for new_face in 0..num_tri { + reindex_face.call(new_face as u32); + } + } +} + +///Updates the mergeFromVert and mergeToVert vectors in order to create a +///manifold solid. If the MeshGL is already manifold, no change will occur and +///the function will return false. Otherwise, this will merge verts along open +///edges within tolerance (the maximum of the MeshGL tolerance and the +///baseline bounding-box tolerance), keeping any from the existing merge +///vectors, and return true. +/// +///There is no guarantee the result will be manifold - this is a best-effort +///helper function designed primarily to aid in the case where a manifold +///multi-material MeshGL was produced, but its merge vectors were lost due to +///a round-trip through a file format. Constructing a Manifold from the result +///will report an error status if it is not manifold. +impl MeshGLP +where + F: LossyFrom + Copy + FloatKind, + I: LossyFrom + Copy, + usize: LossyFrom, + u32: LossyFrom, + i32: LossyFrom, + f64: From, +{ + pub fn merge(&mut self) -> bool { + merge_mesh_glp(self) } } diff --git a/src/subdivision.rs b/src/subdivision.rs index 2881e10..fcd5193 100644 --- a/src/subdivision.rs +++ b/src/subdivision.rs @@ -507,7 +507,7 @@ impl MeshBoolImpl { ///that triangle and halfedges[3] = -1, or if the triangle is part of a quad, it ///returns those four indices. If the triangle is part of a quad and is not the ///lower of the two triangle indices, it returns all -1s. - fn get_halfedges(&self, tri: i32) -> Vector4 { + pub fn get_halfedges(&self, tri: i32) -> Vector4 { let mut halfedges = Vector4::repeat(-1i32); for i in 0..3 { halfedges[i as usize] = 3 * tri + i as i32; diff --git a/src/tri_dis.rs b/src/tri_dis.rs index 23c1ceb..490fa70 100644 --- a/src/tri_dis.rs +++ b/src/tri_dis.rs @@ -86,7 +86,7 @@ fn edge_edge_dist( ///@param p First triangle. ///@param q Second triangle. #[inline] -pub fn distance_triangle_triangle_squared(p: &[Vector3; 3], q: &[Vector3; 3]) -> f64 { +pub fn distance_triangle_triangle_squared(p: &[Vector3], q: &[Vector3]) -> f64 { let s_v: [Vector3; 3] = [p[1] - p[0], p[2] - p[1], p[0] - p[2]]; let t_v: [Vector3; 3] = [q[1] - q[0], q[2] - q[1], q[0] - q[2]]; diff --git a/src/utils.rs b/src/utils.rs index a4e827c..9841bcc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use crate::parallel::gather; use crate::vec::vec_uninit; use nalgebra::{Matrix3, Matrix3x4, Matrix4, Point2}; use std::mem; -use std::sync::atomic::{AtomicI32, Ordering}; +use std::sync::atomic::{AtomicI32, AtomicU64, Ordering}; pub const K_PRECISION: f64 = 1e-12; @@ -46,8 +46,8 @@ pub const fn prev3_i32(i: i32) -> i32 { pub fn permute(in_out: &mut Vec, new2old: &[Map]) where - IO: Copy, - Map: Copy + LossyInto, + IO: Copy + Send + Sync, + Map: Copy + LossyInto + Send + Sync, { let mut tmp = unsafe { vec_uninit(new2old.len()) }; mem::swap(&mut tmp, in_out); @@ -59,6 +59,17 @@ pub unsafe fn atomic_add_i32(target: &mut i32, add: i32) -> i32 { atomic_ref.fetch_add(add, Ordering::SeqCst) } +pub unsafe fn atomic_add_f64(target: &mut f64, add: f64) -> f64 { + let atomic_ref: &AtomicU64 = unsafe { std::mem::transmute(target) }; + atomic_ref + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| { + let old = f64::from_bits(old); + Some((old + add).to_bits()) + }) + .map(|v| f64::from_bits(v)) + .unwrap() +} + ///Determines if the three points are wound counter-clockwise, clockwise, or ///colinear within the specified tolerance. ///