├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── img └── clip.gif ├── rustfmt.toml ├── src ├── axis_aligned.rs ├── axis_aligned │ └── clip.rs ├── clip.rs ├── diagonal.rs ├── diagonal │ └── clip.rs ├── lib.rs ├── math.rs ├── octant.rs ├── octant │ └── clip.rs ├── symmetry.rs └── utils.rs └── tests ├── axis_aligned.rs ├── diagonal.rs └── octant.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | merge_group: 5 | pull_request: 6 | paths-ignore: 7 | - "img/**" 8 | - "**.md" 9 | - LICENSE-APACHE 10 | - LICENSE-MIT 11 | - .gitignore 12 | push: 13 | branches: [ main ] 14 | paths-ignore: 15 | - "img/**" 16 | - "**.md" 17 | - LICENSE-APACHE 18 | - LICENSE-MIT 19 | - .gitignore 20 | 21 | env: 22 | CARGO_TERM_COLOR: always 23 | 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | build: 30 | name: cargo build 31 | runs-on: ubuntu-latest 32 | strategy: 33 | matrix: 34 | rust: [ stable, beta, nightly ] 35 | steps: 36 | - name: Checkout sources 37 | uses: actions/checkout@v4 38 | - name: Install toolchain 39 | uses: dtolnay/rust-toolchain@master 40 | with: 41 | toolchain: ${{ matrix.rust }} 42 | - name: Default features 43 | run: cargo build --all-targets 44 | - name: Nightly features 45 | if: matrix.rust == 'nightly' 46 | run: cargo build --all-targets -F "nightly try_fold is_empty" 47 | 48 | clippy: 49 | name: cargo clippy 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | rust: [ stable, beta, nightly ] 54 | env: 55 | RUSTFLAGS: -Dwarnings 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: dtolnay/rust-toolchain@master 59 | with: 60 | toolchain: ${{ matrix.rust }} 61 | components: clippy 62 | - run: cargo clippy --all-targets 63 | - if: matrix.rust == 'nightly' 64 | run: cargo clippy --all-targets --all-features 65 | 66 | fmt: 67 | name: cargo fmt 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: dtolnay/rust-toolchain@stable 72 | with: 73 | components: rustfmt 74 | - run: cargo fmt --all -- --check 75 | 76 | doc: 77 | name: cargo rustdoc 78 | runs-on: ubuntu-latest 79 | env: 80 | RUSTDOCFLAGS: -Dwarnings 81 | steps: 82 | - uses: actions/checkout@v4 83 | - uses: dtolnay/rust-toolchain@nightly 84 | - uses: dtolnay/install@cargo-docs-rs 85 | - run: cargo docs-rs 86 | 87 | test: 88 | name: cargo test 89 | runs-on: ubuntu-latest 90 | strategy: 91 | matrix: 92 | rust: [ stable, beta, nightly ] 93 | steps: 94 | - name: Checkout sources 95 | uses: actions/checkout@v4 96 | - name: Install toolchain 97 | uses: dtolnay/rust-toolchain@master 98 | with: 99 | toolchain: ${{ matrix.rust }} 100 | - name: Default features 101 | run: cargo test --no-fail-fast -- --skip proptest 102 | - name: Nightly features 103 | if: matrix.rust == 'nightly' 104 | run: cargo test --no-fail-fast -F "nightly try_fold is_empty" -- --skip proptest 105 | 106 | proptest: 107 | name: proptest 108 | runs-on: ubuntu-latest 109 | steps: 110 | - name: Checkout sources 111 | uses: actions/checkout@v4 112 | - name: Install toolchain 113 | uses: dtolnay/rust-toolchain@stable 114 | - name: Default features 115 | run: cargo test --no-fail-fast proptest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | /target 4 | /Cargo.lock 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.3.0] – 2024-08-11 11 | 12 | ### Added 13 | 14 | - Add `SignedAxis`, `Axis` and `AnyAxis` iterators (and aliases) 15 | - Add `Diagonal` and `AnyDiagonal` iterators (and aliases) 16 | - Add `Octant` and `AnyOctant` iterators (and aliases) 17 | - Add `Clip` and a new clipping algorithm 18 | - Add property tests for the clipping algorithm 19 | - Add compile-time tests for `Iterator`-related impl checks 20 | 21 | ### Removed 22 | 23 | - Remove the `clipline` function 24 | - Remove the `Clipline` iterator and its variants 25 | - Remove the `Constant` trait 26 | 27 | ## [0.2.0] – 2023-12-18 28 | 29 | ### Fixed 30 | 31 | - Fix broken compilation due to private trait bound 32 | 33 | ## [0.1.3] – 2023-12-07 [YANKED] 34 | 35 | ### Added 36 | 37 | - Implement `DoubleEndedIterator` for vertical and horizontal iterators 38 | - Implement `FusedIterator` for all iterators 39 | - Implement `ExactSizeIterator` for iterators over numeric types that fit into `usize` 40 | 41 | ### Changed 42 | 43 | - Generify the library over the signed numeric types 44 | 45 | ## [0.1.2] – 2023-11-02 46 | 47 | ### Added 48 | 49 | - Add benchmarks against `bresenham` and `line_drawing` 50 | 51 | ### Changed 52 | 53 | - Inline internal functions 54 | 55 | ## [0.1.1] – 2023-10-31 56 | 57 | ### Added 58 | 59 | - Add the `Clipline` iterator 60 | 61 | ### Changed 62 | 63 | - Gate the `clipline` function behind `func` 64 | - Gate the `Clipline` iterator behind `iter` 65 | 66 | ## [0.1.0] – 2023-10-25 67 | 68 | ### Added 69 | 70 | - Add the `clipline` function 71 | 72 | [0.3.0]: https://github.com/nxsaken/clipline/releases/tag/0.3.0 73 | [0.2.0]: https://github.com/nxsaken/clipline/releases/tag/0.2.0 74 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clipline" 3 | version = "0.3.0" 4 | authors = ["Nurzhan Sakén "] 5 | description = "Efficient rasterization of line segments with pixel-perfect clipping." 6 | keywords = ["line", "clipping", "bresenham", "rasterization", "grid"] 7 | categories = ["graphics", "rendering", "algorithms", "game-development"] 8 | repository = "https://github.com/nxsaken/clipline/" 9 | license = "MIT OR Apache-2.0" 10 | readme = "README.md" 11 | rust-version = "1.66.0" 12 | edition = "2021" 13 | include = [ 14 | "**/*.rs", 15 | "Cargo.toml", 16 | ] 17 | 18 | [features] 19 | default = [] 20 | # Enable Octant for all targets 21 | # and Octant for 64-bit targets. 22 | octant_64 = [] 23 | 24 | # Enable unstable features. 25 | nightly = [] 26 | # Enable optimized `try_fold` implementations. 27 | # Requires the unstable `try_trait_v2` feature. 28 | try_fold = ["nightly"] 29 | # Enable optimized `is_empty` implementations. 30 | # Requires the unstable `exact_size_is_empty` feature. 31 | is_empty = ["nightly"] 32 | 33 | [dev-dependencies] 34 | static_assertions = "1.1.0" 35 | proptest = "1.5.0" 36 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nurzhan Sakén 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clipline 2 | 3 | [![CI](https://github.com/nxsaken/clipline/actions/workflows/ci.yml/badge.svg)](https://github.com/nxsaken/clipline/actions/workflows/ci.yml) 4 | [![crates.io](https://img.shields.io/crates/v/clipline.svg)](https://crates.io/crates/clipline) 5 | [![docs.rs](https://img.shields.io/docsrs/clipline)](https://docs.rs/clipline/latest/clipline/) 6 | [![downloads](https://img.shields.io/crates/d/clipline.svg)](https://crates.io/crates/clipline) 7 | 8 | Efficient rasterization of line segments with pixel-perfect [clipping][clip]. 9 | 10 | ## Overview 11 | 12 | - Provides iterators for clipped and unclipped rasterized line segments. 13 | - Eliminates bounds checking: clipped line segments are guaranteed to be within the region. 14 | - Guarantees clipped line segments match the unclipped versions of themselves. 15 | - Supports signed and unsigned integer coordinates of most sizes. 16 | - Uses integer arithmetic only. 17 | - Prevents overflow and division by zero, forbids `clippy::arithmetic_side_effects`. 18 | - Defines the iterators on the entire domains of the underlying numeric types. 19 | - Usable in `const` contexts and `#![no_std]` environments. 20 | 21 | ![`clipline` in action](img/clip.gif) 22 | 23 | ## Usage 24 | 25 | Add `clipline` to `Cargo.toml`: 26 | 27 | ```toml 28 | [dependencies] 29 | clipline = "0.3.0" 30 | ``` 31 | 32 | ### Feature flags 33 | 34 | - `octant_64` 35 | * Enables `Octant` and `AnyOctant` over `i64`/`u64` for all targets, and over `isize`/`usize` for 64-bit targets. 36 | * Use this only if you need the full 64-bit range, as `Octant` will use `u128` and `i128` for some calculations. 37 | - `try_fold`, `is_empty` *(nightly-only)* 38 | * Enable optimized `Iterator::try_fold` and `ExactSizeIterator::is_empty` implementations. 39 | 40 | ### Example 41 | 42 | ```rust 43 | use clipline::{AnyOctant, Clip, Diagonal0, Point}; 44 | 45 | /// Width of the pixel buffer. 46 | const WIDTH: usize = 64; 47 | /// Height of the pixel buffer. 48 | const HEIGHT: usize = 48; 49 | 50 | /// Pixel color value. 51 | const RGBA: u32 = 0xFFFFFFFF; 52 | 53 | /// A function that operates on a single pixel in a pixel buffer. 54 | /// 55 | /// ## Safety 56 | /// `(x, y)` must be inside the `buffer`. 57 | unsafe fn draw(buffer: &mut [u32], (x, y): Point, rgba: u32) { 58 | let index = y as usize * WIDTH + x as usize; 59 | debug_assert!(index < buffer.len()); 60 | *buffer.get_unchecked_mut(index) = rgba; 61 | } 62 | 63 | fn main() { 64 | let mut buffer = [0_u32; WIDTH * HEIGHT]; 65 | 66 | // The clipping region is closed/inclusive, thus 1 needs to be subtracted from the size. 67 | let clip = Clip::::new((0, 0), (WIDTH as i8 - 1, HEIGHT as i8 - 1)).unwrap(); 68 | 69 | // `Clip` has convenience methods for the general iterators. 70 | clip.any_octant((-128, -100), (100, 80)) 71 | // None if the line segment is completely invisible. 72 | // You might want to handle that case differently. 73 | .unwrap() 74 | // clipped to [(0, 1), ..., (58, 47)] 75 | .for_each(|xy| { 76 | // SAFETY: (x, y) has been clipped to the buffer. 77 | unsafe { draw(&mut buffer, xy, RGBA) } 78 | }); 79 | 80 | // Alternatively, use the iterator constructors. 81 | AnyOctant::::clip((12, 0), (87, 23), &clip) 82 | .into_iter() 83 | .flatten() 84 | // clipped to [(12, 0), ..., (63, 16)] 85 | .for_each(|xy| { 86 | // SAFETY: (x, y) has been clipped to the buffer. 87 | unsafe { draw(&mut buffer, xy, RGBA) } 88 | }); 89 | 90 | // Horizontal and vertical line segments. 91 | clip.axis_0(32, 76, -23) 92 | .unwrap() 93 | // clipped to [(63, 32), ..., (0, 32)] 94 | .for_each(|xy| { 95 | // SAFETY: (x, y) has been clipped to the buffer. 96 | unsafe { draw(&mut buffer, xy, RGBA) } 97 | }); 98 | 99 | clip.axis_1(32, -23, 76) 100 | .unwrap() 101 | // clipped to [(32, 0), ..., (32, 47)] 102 | .for_each(|xy| { 103 | // SAFETY: (x, y) has been clipped to the buffer. 104 | unsafe { draw(&mut buffer, xy, RGBA) } 105 | }); 106 | 107 | // Unclipped iterators are also available. 108 | // (-2, -2) -> (12, 12) is covered by Diagonal0, we can construct it directly. 109 | Diagonal0::::new((-2, -2), (12, 12)) 110 | .unwrap() 111 | // Need to check every pixel to avoid going out of bounds. 112 | .filter(|&xy| clip.point(xy)) 113 | .for_each(|xy| { 114 | // SAFETY: (x, y) is inside the buffer. 115 | unsafe { draw(&mut buffer, xy, RGBA) } 116 | }); 117 | } 118 | ``` 119 | 120 | ## Limitations 121 | 122 | * To support usage in `const` contexts, types must have an inherent implementation for every supported numeric type instead of relying on a trait. This and Rust's lack of support for function overloading means that the numeric type parameter must always be specified. 123 | * Currently, only half-open line segments can be iterated. This allows `ExactSizeIterator` to be implemented for all types. Inclusive iterators are tracked in [#1](https://github.com/nxsaken/clipline/issues/1). 124 | 125 | ## References 126 | 127 | `clipline` is inspired by the following papers: 128 | 129 | * [A fast two-dimensional line clipping algorithm via line encoding][spy], Mark S. Sobkow, Paul Pospisil, Yee-Hong Yang, 1987. 130 | * [A new approach to parametric line clipping][dorr], Michael Dörr, 1990. 131 | * [Bresenham's Line Generation Algorithm with Built-in Clipping][kuzmin], Yevgeny P. Kuzmin, 1995. 132 | 133 | [clip]: https://en.wikipedia.org/wiki/Line_clipping 134 | [bres]: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 135 | [spy]: https://doi.org/10.1016/0097-8493(87)90061-6 136 | [dorr]: https://doi.org/10.1016/0097-8493(90)90067-8 137 | [kuzmin]: https://doi.org/10.1111/1467-8659.1450275 -------------------------------------------------------------------------------- /img/clip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsaken/clipline/7a35b307c0c38d63ca74760197a3290132414f32/img/clip.gif -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" -------------------------------------------------------------------------------- /src/axis_aligned.rs: -------------------------------------------------------------------------------- 1 | //! ## Axis-aligned iterators 2 | 3 | use crate::clip::Clip; 4 | use crate::math::{Math, Num, Point}; 5 | use crate::symmetry::{f, vh}; 6 | use crate::utils::map; 7 | 8 | mod clip; 9 | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // Signed-axis-aligned iterators 12 | //////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | /// Iterator over a line segment aligned to the given **signed axis**. 15 | /// 16 | /// A signed axis is defined by the direction and axis-alignment of the line segments aligned to it: 17 | /// - [negative](NegativeAxis) if `FLIP`, [positive](PositiveAxis) otherwise. 18 | /// - [vertical](SignedAxis1) if `VERT`, [horizontal](SignedAxis0) otherwise. 19 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 20 | pub struct SignedAxis { 21 | u: T, 22 | v1: T, 23 | v2: T, 24 | } 25 | 26 | /// Iterator over a line segment aligned to the given 27 | /// **positive** [signed axis](SignedAxis). 28 | pub type PositiveAxis = SignedAxis; 29 | 30 | /// Iterator over a line segment aligned to the given 31 | /// **negative** [signed axis](SignedAxis). 32 | pub type NegativeAxis = SignedAxis; 33 | 34 | /// Iterator over a line segment aligned to the given 35 | /// **horizontal** [signed axis](SignedAxis). 36 | pub type SignedAxis0 = SignedAxis; 37 | 38 | /// Iterator over a line segment aligned to the given 39 | /// **vertical** [signed axis](SignedAxis). 40 | pub type SignedAxis1 = SignedAxis; 41 | 42 | /// Iterator over a line segment aligned to the 43 | /// **positive horizontal** [signed axis](SignedAxis). 44 | /// 45 | /// Covers line segments oriented at `0°`. 46 | pub type PositiveAxis0 = SignedAxis0; 47 | 48 | /// Iterator over a line segment aligned to the 49 | /// **negative horizontal** [signed axis](SignedAxis). 50 | /// 51 | /// Covers line segments oriented at `180°`. 52 | pub type NegativeAxis0 = SignedAxis0; 53 | 54 | /// Iterator over a line segment aligned to the 55 | /// **positive vertical** [signed axis](SignedAxis). 56 | /// 57 | /// Covers line segments oriented at `90°`. 58 | pub type PositiveAxis1 = SignedAxis1; 59 | 60 | /// Iterator over a line segment aligned to the 61 | /// **negative vertical** [signed axis](SignedAxis). 62 | /// 63 | /// Covers line segments oriented at `270°`. 64 | pub type NegativeAxis1 = SignedAxis1; 65 | 66 | macro_rules! signed_axis_impl { 67 | ($T:ty) => { 68 | impl SignedAxis { 69 | #[inline(always)] 70 | #[must_use] 71 | const fn new_inner(u: $T, v1: $T, v2: $T) -> Self { 72 | Self { u, v1, v2 } 73 | } 74 | 75 | /// Returns an iterator over a *half-open* line segment if it is aligned to 76 | /// the given [signed axis](SignedAxis), otherwise returns [`None`]. 77 | /// 78 | /// - A [horizontal](SignedAxis0) line segment has endpoints `(v1, u)` and `(v2, u)`. 79 | /// - A [vertical](SignedAxis1) line segment has endpoints `(u, v1)` and `(u, v2)`. 80 | #[inline] 81 | #[must_use] 82 | pub const fn new(u: $T, v1: $T, v2: $T) -> Option { 83 | if f!(v2 <= v1, v1 <= v2) { 84 | return None; 85 | } 86 | Some(Self::new_inner(u, v1, v2)) 87 | } 88 | 89 | /// Clips a *half-open* line segment to a [rectangular region](Clip) 90 | /// if it aligned to the given [signed axis](SignedAxis), 91 | /// and returns an iterator over it. 92 | /// 93 | /// - A [horizontal](SignedAxis0) line segment has endpoints `(v1, u)` and `(v2, u)`. 94 | /// - A [vertical](SignedAxis1) line segment has endpoints `(u, v1)` and `(u, v2)`. 95 | /// 96 | /// Returns [`None`] if the line segment is not aligned to the signed axis, 97 | /// or if it does not intersect the clipping region. 98 | #[inline] 99 | #[must_use] 100 | pub const fn clip(u: $T, v1: $T, v2: $T, clip: &Clip<$T>) -> Option { 101 | if f!(v2 <= v1, v1 <= v2) { 102 | return None; 103 | } 104 | Self::clip_inner(u, v1, v2, clip) 105 | } 106 | 107 | /// Returns `true` if the iterator has terminated. 108 | #[inline] 109 | #[must_use] 110 | pub const fn is_done(&self) -> bool { 111 | f!(self.v2 <= self.v1, self.v1 <= self.v2) 112 | } 113 | 114 | /// Returns the remaining length of this iterator. 115 | #[inline] 116 | #[must_use] 117 | pub const fn length(&self) -> <$T as Num>::U { 118 | Math::<$T>::delta(f!(self.v2, self.v1), f!(self.v1, self.v2)) 119 | } 120 | } 121 | 122 | impl Iterator for SignedAxis { 123 | type Item = Point<$T>; 124 | 125 | #[inline] 126 | fn next(&mut self) -> Option { 127 | if self.is_done() { 128 | return None; 129 | } 130 | let (x, y) = vh!((self.v1, self.u), (self.u, self.v1)); 131 | self.v1 = f!(self.v1.wrapping_add(1), self.v1.wrapping_sub(1)); 132 | Some((x, y)) 133 | } 134 | 135 | #[inline] 136 | fn size_hint(&self) -> (usize, Option) { 137 | #[allow(unreachable_patterns)] 138 | match usize::try_from(self.length()) { 139 | Ok(length) => (length, Some(length)), 140 | Err(_) => (usize::MAX, None), 141 | } 142 | } 143 | } 144 | 145 | impl DoubleEndedIterator 146 | for SignedAxis 147 | { 148 | #[inline] 149 | fn next_back(&mut self) -> Option { 150 | if self.is_done() { 151 | return None; 152 | } 153 | self.v2 = f!(self.v2.wrapping_sub(1), self.v2.wrapping_add(1)); 154 | let (x, y) = vh!((self.v2, self.u), (self.u, self.v2)); 155 | Some((x, y)) 156 | } 157 | } 158 | 159 | impl core::iter::FusedIterator 160 | for SignedAxis 161 | { 162 | } 163 | }; 164 | } 165 | 166 | signed_axis_impl!(i8); 167 | signed_axis_impl!(u8); 168 | signed_axis_impl!(i16); 169 | signed_axis_impl!(u16); 170 | signed_axis_impl!(i32); 171 | signed_axis_impl!(u32); 172 | signed_axis_impl!(i64); 173 | signed_axis_impl!(u64); 174 | signed_axis_impl!(isize); 175 | signed_axis_impl!(usize); 176 | 177 | macro_rules! signed_axis_exact_size_iter_impl { 178 | ($T:ty) => { 179 | impl ExactSizeIterator for SignedAxis { 180 | #[cfg(feature = "is_empty")] 181 | #[inline] 182 | fn is_empty(&self) -> bool { 183 | self.is_done() 184 | } 185 | } 186 | }; 187 | } 188 | 189 | signed_axis_exact_size_iter_impl!(i8); 190 | signed_axis_exact_size_iter_impl!(u8); 191 | signed_axis_exact_size_iter_impl!(i16); 192 | signed_axis_exact_size_iter_impl!(u16); 193 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 194 | signed_axis_exact_size_iter_impl!(i32); 195 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 196 | signed_axis_exact_size_iter_impl!(u32); 197 | #[cfg(target_pointer_width = "64")] 198 | signed_axis_exact_size_iter_impl!(i64); 199 | #[cfg(target_pointer_width = "64")] 200 | signed_axis_exact_size_iter_impl!(u64); 201 | signed_axis_exact_size_iter_impl!(isize); 202 | signed_axis_exact_size_iter_impl!(usize); 203 | 204 | //////////////////////////////////////////////////////////////////////////////////////////////////// 205 | // Axis-aligned iterators 206 | //////////////////////////////////////////////////////////////////////////////////////////////////// 207 | 208 | /// Iterator over a line segment aligned to the given **axis**, 209 | /// with the direction determined at runtime. 210 | /// 211 | /// An axis is defined by the orientation of the line segments it covers: 212 | /// [vertical](Axis1) if `VERT`, [horizontal](Axis0) otherwise. 213 | /// 214 | /// If you know the [direction](SignedAxis) of the line segment, 215 | /// consider [`PositiveAxis`] and [`NegativeAxis`]. 216 | /// 217 | /// **Note**: an optimized implementation of [`Iterator::fold`] is provided. 218 | /// This makes [`Iterator::for_each`] faster than a `for` loop, since it checks 219 | /// the direction only once instead of on every call to [`Iterator::next`]. 220 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 221 | pub enum Axis { 222 | /// See [`PositiveAxis`]. 223 | Positive(PositiveAxis), 224 | /// See [`NegativeAxis`]. 225 | Negative(NegativeAxis), 226 | } 227 | 228 | /// Iterator over a line segment aligned to the **horizontal** [axis](Axis), 229 | /// with the direction determined at runtime. 230 | /// 231 | /// If you know the [direction](SignedAxis) of the line segment, 232 | /// consider [`PositiveAxis0`] and [`NegativeAxis0`]. 233 | /// 234 | /// **Note**: an optimized implementation of [`Iterator::fold`] is provided. 235 | /// This makes [`Iterator::for_each`] faster than a `for` loop, since it checks 236 | /// the direction only once instead of on every call to [`Iterator::next`]. 237 | pub type Axis0 = Axis; 238 | 239 | /// Iterator over a line segment aligned to the **vertical** [axis](Axis), 240 | /// with the direction determined at runtime. 241 | /// 242 | /// If you know the [direction](SignedAxis) of the line segment, 243 | /// consider [`PositiveAxis1`] and [`NegativeAxis1`]. 244 | /// 245 | /// **Note**: an optimized implementation of [`Iterator::fold`] is provided. 246 | /// This makes [`Iterator::for_each`] faster than a `for` loop, since it checks 247 | /// the direction only once instead of on every call to [`Iterator::next`]. 248 | pub type Axis1 = Axis; 249 | 250 | macro_rules! delegate { 251 | ($self:ident, $me:ident => $call:expr) => { 252 | match $self { 253 | Self::Positive($me) => $call, 254 | Self::Negative($me) => $call, 255 | } 256 | }; 257 | } 258 | 259 | macro_rules! axis_impl { 260 | ($T:ty) => { 261 | impl Axis { 262 | /// Returns an iterator over a *half-open* line segment aligned to the given [axis](Axis). 263 | /// 264 | /// - A [horizontal](Axis0) line segment has endpoints `(v1, u)` and `(v2, u)`. 265 | /// - A [vertical](Axis1) line segment has endpoints `(u, v1)` and `(u, v2)`. 266 | #[inline] 267 | #[must_use] 268 | pub const fn new(u: $T, v1: $T, v2: $T) -> Self { 269 | if v1 <= v2 { 270 | Self::Positive(PositiveAxis::::new_inner(u, v1, v2)) 271 | } else { 272 | Self::Negative(NegativeAxis::::new_inner(u, v1, v2)) 273 | } 274 | } 275 | 276 | /// Clips a *half-open* line segment aligned to the given [axis](Axis) 277 | /// to a [rectangular region](Clip), and returns an iterator over it. 278 | /// 279 | /// - A [horizontal](Axis0) line segment has endpoints `(v1, u)` and `(v2, u)`. 280 | /// - A [vertical](Axis1) line segment has endpoints `(u, v1)` and `(u, v2)`. 281 | /// 282 | /// Returns [`None`] if the line segment is empty or does not intersect the clipping region. 283 | #[inline] 284 | #[must_use] 285 | pub const fn clip(u: $T, v1: $T, v2: $T, clip: &Clip<$T>) -> Option { 286 | if v1 < v2 { 287 | map!( 288 | PositiveAxis::::clip_inner(u, v1, v2, clip), 289 | Self::Positive, 290 | ) 291 | } else if v2 < v1 { 292 | map!( 293 | NegativeAxis::::clip_inner(u, v1, v2, clip), 294 | Self::Negative, 295 | ) 296 | } else { 297 | None 298 | } 299 | } 300 | 301 | /// Returns `true` if the iterator has terminated. 302 | #[inline] 303 | #[must_use] 304 | pub const fn is_done(&self) -> bool { 305 | delegate!(self, me => me.is_done()) 306 | } 307 | 308 | /// Returns the remaining length of this iterator. 309 | #[inline] 310 | #[must_use] 311 | pub const fn length(&self) -> <$T as Num>::U { 312 | delegate!(self, me => me.length()) 313 | } 314 | } 315 | 316 | impl Iterator for Axis { 317 | type Item = Point<$T>; 318 | 319 | #[inline] 320 | fn next(&mut self) -> Option { 321 | delegate!(self, me => me.next()) 322 | } 323 | 324 | #[inline] 325 | fn size_hint(&self) -> (usize, Option) { 326 | delegate!(self, me => me.size_hint()) 327 | } 328 | 329 | #[cfg(feature = "try_fold")] 330 | #[inline] 331 | fn try_fold(&mut self, init: B, f: F) -> R 332 | where 333 | Self: Sized, 334 | F: FnMut(B, Self::Item) -> R, 335 | R: core::ops::Try, 336 | { 337 | delegate!(self, me => me.try_fold(init, f)) 338 | } 339 | 340 | #[inline] 341 | fn fold(self, init: B, f: F) -> B 342 | where 343 | Self: Sized, 344 | F: FnMut(B, Self::Item) -> B, 345 | { 346 | delegate!(self, me => me.fold(init, f)) 347 | } 348 | } 349 | 350 | impl DoubleEndedIterator for Axis { 351 | #[inline] 352 | fn next_back(&mut self) -> Option { 353 | delegate!(self, me => me.next_back()) 354 | } 355 | 356 | #[cfg(feature = "try_fold")] 357 | #[inline] 358 | fn try_rfold(&mut self, init: B, f: F) -> R 359 | where 360 | Self: Sized, 361 | F: FnMut(B, Self::Item) -> R, 362 | R: core::ops::Try, 363 | { 364 | delegate!(self, me => me.try_rfold(init, f)) 365 | } 366 | 367 | #[inline] 368 | fn rfold(self, init: B, f: F) -> B 369 | where 370 | Self: Sized, 371 | F: FnMut(B, Self::Item) -> B, 372 | { 373 | delegate!(self, me => me.rfold(init, f)) 374 | } 375 | } 376 | 377 | impl core::iter::FusedIterator for Axis {} 378 | }; 379 | } 380 | 381 | axis_impl!(i8); 382 | axis_impl!(u8); 383 | axis_impl!(i16); 384 | axis_impl!(u16); 385 | axis_impl!(i32); 386 | axis_impl!(u32); 387 | axis_impl!(i64); 388 | axis_impl!(u64); 389 | axis_impl!(isize); 390 | axis_impl!(usize); 391 | 392 | macro_rules! axis_exact_size_iter_impl { 393 | ($T:ty) => { 394 | impl ExactSizeIterator for Axis { 395 | #[cfg(feature = "is_empty")] 396 | #[inline] 397 | fn is_empty(&self) -> bool { 398 | delegate!(self, me => me.is_empty()) 399 | } 400 | } 401 | }; 402 | } 403 | 404 | axis_exact_size_iter_impl!(i8); 405 | axis_exact_size_iter_impl!(u8); 406 | axis_exact_size_iter_impl!(i16); 407 | axis_exact_size_iter_impl!(u16); 408 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 409 | axis_exact_size_iter_impl!(i32); 410 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 411 | axis_exact_size_iter_impl!(u32); 412 | #[cfg(target_pointer_width = "64")] 413 | axis_exact_size_iter_impl!(i64); 414 | #[cfg(target_pointer_width = "64")] 415 | axis_exact_size_iter_impl!(u64); 416 | axis_exact_size_iter_impl!(isize); 417 | axis_exact_size_iter_impl!(usize); 418 | 419 | //////////////////////////////////////////////////////////////////////////////////////////////////// 420 | // Arbitrary axis-aligned iterator 421 | //////////////////////////////////////////////////////////////////////////////////////////////////// 422 | 423 | /// Iterator over a [horizontal](Axis0) or [vertical](Axis1) line segment, 424 | /// with the axis-alignment and direction determined at runtime. 425 | /// 426 | /// If you know the [axis-alignment](Axis) of the line segment, use [`Axis0`] or [`Axis1`]. 427 | /// 428 | /// **Note**: an optimized implementation of [`Iterator::fold`] is provided. 429 | /// This makes [`Iterator::for_each`] faster than a `for` loop, since it checks 430 | /// the signed axis only once instead of on every call to [`Iterator::next`]. 431 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 432 | pub enum AnyAxis { 433 | /// Horizontal line segment at `0°`, see [`PositiveAxis0`]. 434 | PositiveAxis0(PositiveAxis0), 435 | /// Vertical line segment at `90°`, see [`PositiveAxis1`]. 436 | PositiveAxis1(PositiveAxis1), 437 | /// Horizontal line segment at `180°`, see [`NegativeAxis0`]. 438 | NegativeAxis0(NegativeAxis0), 439 | /// Vertical line segment at `270°`, see [`NegativeAxis1`]. 440 | NegativeAxis1(NegativeAxis1), 441 | } 442 | 443 | /// Delegates calls to signed-axis variants. 444 | macro_rules! delegate { 445 | ($self:ident, $me:ident => $call:expr) => { 446 | match $self { 447 | Self::PositiveAxis0($me) => $call, 448 | Self::NegativeAxis0($me) => $call, 449 | Self::PositiveAxis1($me) => $call, 450 | Self::NegativeAxis1($me) => $call, 451 | } 452 | }; 453 | } 454 | 455 | macro_rules! any_axis_impl { 456 | ($T:ty) => { 457 | impl AnyAxis<$T> { 458 | /// Returns an iterator over a *half-open* line segment 459 | /// if it is aligned to any [axis](Axis), otherwise returns [`None`]. 460 | #[inline] 461 | #[must_use] 462 | pub const fn new((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> Option { 463 | if y1 == y2 { 464 | return match Axis0::<$T>::new(y1, x1, x2) { 465 | Axis::Positive(me) => Some(Self::PositiveAxis0(me)), 466 | Axis::Negative(me) => Some(Self::NegativeAxis0(me)), 467 | }; 468 | } 469 | if x1 == x2 { 470 | return match Axis1::<$T>::new(x1, y1, y2) { 471 | Axis::Positive(me) => Some(Self::PositiveAxis1(me)), 472 | Axis::Negative(me) => Some(Self::NegativeAxis1(me)), 473 | }; 474 | } 475 | None 476 | } 477 | 478 | /// Clips a *half-open* line segment to a [rectangular region](Clip) 479 | /// if it is aligned to any [axis](Axis), and returns an iterator over it. 480 | /// 481 | /// Returns [`None`] if the line segment is not axis-aligned, 482 | /// is empty, or does not intersect the clipping region. 483 | #[inline] 484 | #[must_use] 485 | pub const fn clip((x1, y1): Point<$T>, (x2, y2): Point<$T>, clip: &Clip<$T>) -> Option { 486 | if y1 == y2 { 487 | return map!( 488 | Axis0::<$T>::clip(y1, x1, x2, clip), 489 | me => match me { 490 | Axis::Positive(me) => Self::PositiveAxis0(me), 491 | Axis::Negative(me) => Self::NegativeAxis0(me), 492 | } 493 | ); 494 | } 495 | if x1 == x2 { 496 | return map!( 497 | Axis1::<$T>::clip(x1, y1, y2, clip), 498 | me => match me { 499 | Axis::Positive(me) => Self::PositiveAxis1(me), 500 | Axis::Negative(me) => Self::NegativeAxis1(me), 501 | } 502 | ); 503 | } 504 | None 505 | } 506 | 507 | /// Returns `true` if the iterator has terminated. 508 | #[inline] 509 | #[must_use] 510 | pub const fn is_done(&self) -> bool { 511 | delegate!(self, me => me.is_done()) 512 | } 513 | 514 | /// Returns the remaining length of this iterator. 515 | #[inline] 516 | #[must_use] 517 | pub const fn length(&self) -> <$T as Num>::U { 518 | delegate!(self, me => me.length()) 519 | } 520 | } 521 | 522 | impl Iterator for AnyAxis<$T> { 523 | type Item = Point<$T>; 524 | 525 | #[inline] 526 | fn next(&mut self) -> Option { 527 | delegate!(self, me => me.next()) 528 | } 529 | 530 | #[inline] 531 | fn size_hint(&self) -> (usize, Option) { 532 | delegate!(self, me => me.size_hint()) 533 | } 534 | 535 | #[cfg(feature = "try_fold")] 536 | #[inline] 537 | fn try_fold(&mut self, init: B, f: F) -> R 538 | where 539 | Self: Sized, 540 | F: FnMut(B, Self::Item) -> R, 541 | R: core::ops::Try, 542 | { 543 | delegate!(self, me => me.try_fold(init, f)) 544 | } 545 | 546 | #[inline] 547 | fn fold(self, init: B, f: F) -> B 548 | where 549 | Self: Sized, 550 | F: FnMut(B, Self::Item) -> B, 551 | { 552 | delegate!(self, me => me.fold(init, f)) 553 | } 554 | } 555 | 556 | impl DoubleEndedIterator for AnyAxis<$T> { 557 | #[inline] 558 | fn next_back(&mut self) -> Option { 559 | delegate!(self, me => me.next_back()) 560 | } 561 | 562 | #[cfg(feature = "try_fold")] 563 | #[inline] 564 | fn try_rfold(&mut self, init: B, f: F) -> R 565 | where 566 | Self: Sized, 567 | F: FnMut(B, Self::Item) -> R, 568 | R: core::ops::Try, 569 | { 570 | delegate!(self, me => me.try_rfold(init, f)) 571 | } 572 | 573 | #[inline] 574 | fn rfold(self, init: B, f: F) -> B 575 | where 576 | Self: Sized, 577 | F: FnMut(B, Self::Item) -> B, 578 | { 579 | delegate!(self, me => me.rfold(init, f)) 580 | } 581 | } 582 | 583 | impl core::iter::FusedIterator for AnyAxis<$T> {} 584 | }; 585 | } 586 | 587 | any_axis_impl!(i8); 588 | any_axis_impl!(u8); 589 | any_axis_impl!(i16); 590 | any_axis_impl!(u16); 591 | any_axis_impl!(i32); 592 | any_axis_impl!(u32); 593 | any_axis_impl!(i64); 594 | any_axis_impl!(u64); 595 | any_axis_impl!(isize); 596 | any_axis_impl!(usize); 597 | 598 | macro_rules! any_axis_exact_size_iter_impl { 599 | ($T:ty) => { 600 | impl ExactSizeIterator for AnyAxis<$T> { 601 | #[cfg(feature = "is_empty")] 602 | #[inline] 603 | fn is_empty(&self) -> bool { 604 | delegate!(self, me => me.is_empty()) 605 | } 606 | } 607 | }; 608 | } 609 | 610 | any_axis_exact_size_iter_impl!(i8); 611 | any_axis_exact_size_iter_impl!(u8); 612 | any_axis_exact_size_iter_impl!(i16); 613 | any_axis_exact_size_iter_impl!(u16); 614 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 615 | any_axis_exact_size_iter_impl!(i32); 616 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 617 | any_axis_exact_size_iter_impl!(u32); 618 | #[cfg(target_pointer_width = "64")] 619 | any_axis_exact_size_iter_impl!(i64); 620 | #[cfg(target_pointer_width = "64")] 621 | any_axis_exact_size_iter_impl!(u64); 622 | any_axis_exact_size_iter_impl!(isize); 623 | any_axis_exact_size_iter_impl!(usize); 624 | 625 | #[cfg(test)] 626 | mod static_tests { 627 | use super::*; 628 | use static_assertions::assert_impl_all; 629 | 630 | #[test] 631 | const fn iterator_8() { 632 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 633 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 634 | assert_impl_all!(Axis0: ExactSizeIterator); 635 | assert_impl_all!(Axis0: ExactSizeIterator); 636 | assert_impl_all!(AnyAxis: ExactSizeIterator); 637 | assert_impl_all!(AnyAxis: ExactSizeIterator); 638 | } 639 | 640 | #[test] 641 | const fn iterator_16() { 642 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 643 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 644 | assert_impl_all!(Axis0: ExactSizeIterator); 645 | assert_impl_all!(Axis0: ExactSizeIterator); 646 | assert_impl_all!(AnyAxis: ExactSizeIterator); 647 | assert_impl_all!(AnyAxis: ExactSizeIterator); 648 | } 649 | 650 | #[test] 651 | const fn iterator_32() { 652 | #[cfg(target_pointer_width = "16")] 653 | { 654 | use static_assertions::assert_not_impl_any; 655 | 656 | assert_impl_all!(PositiveAxis0: Iterator); 657 | assert_impl_all!(PositiveAxis0: Iterator); 658 | assert_impl_all!(Axis0: Iterator); 659 | assert_impl_all!(Axis0: Iterator); 660 | assert_impl_all!(AnyAxis: Iterator); 661 | assert_impl_all!(AnyAxis: Iterator); 662 | assert_not_impl_any!(PositiveAxis0: ExactSizeIterator); 663 | assert_not_impl_any!(PositiveAxis0: ExactSizeIterator); 664 | assert_not_impl_any!(Axis0: ExactSizeIterator); 665 | assert_not_impl_any!(Axis0: ExactSizeIterator); 666 | assert_not_impl_any!(AnyAxis: ExactSizeIterator); 667 | assert_not_impl_any!(AnyAxis: ExactSizeIterator); 668 | } 669 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 670 | { 671 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 672 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 673 | assert_impl_all!(Axis0: ExactSizeIterator); 674 | assert_impl_all!(Axis0: ExactSizeIterator); 675 | assert_impl_all!(AnyAxis: ExactSizeIterator); 676 | assert_impl_all!(AnyAxis: ExactSizeIterator); 677 | } 678 | } 679 | 680 | #[test] 681 | const fn iterator_64() { 682 | #[cfg(target_pointer_width = "64")] 683 | { 684 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 685 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 686 | assert_impl_all!(Axis0: ExactSizeIterator); 687 | assert_impl_all!(Axis0: ExactSizeIterator); 688 | assert_impl_all!(AnyAxis: ExactSizeIterator); 689 | assert_impl_all!(AnyAxis: ExactSizeIterator); 690 | } 691 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 692 | { 693 | use static_assertions::assert_not_impl_any; 694 | 695 | assert_impl_all!(PositiveAxis0: Iterator); 696 | assert_impl_all!(PositiveAxis0: Iterator); 697 | assert_impl_all!(Axis0: Iterator); 698 | assert_impl_all!(Axis0: Iterator); 699 | assert_impl_all!(AnyAxis: Iterator); 700 | assert_impl_all!(AnyAxis: Iterator); 701 | assert_not_impl_any!(PositiveAxis0: ExactSizeIterator); 702 | assert_not_impl_any!(PositiveAxis0: ExactSizeIterator); 703 | assert_not_impl_any!(Axis0: ExactSizeIterator); 704 | assert_not_impl_any!(Axis0: ExactSizeIterator); 705 | assert_not_impl_any!(AnyAxis: ExactSizeIterator); 706 | assert_not_impl_any!(AnyAxis: ExactSizeIterator); 707 | } 708 | } 709 | 710 | #[test] 711 | const fn iterator_pointer_size() { 712 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 713 | assert_impl_all!(PositiveAxis0: ExactSizeIterator); 714 | assert_impl_all!(Axis0: ExactSizeIterator); 715 | assert_impl_all!(Axis0: ExactSizeIterator); 716 | assert_impl_all!(AnyAxis: ExactSizeIterator); 717 | assert_impl_all!(AnyAxis: ExactSizeIterator); 718 | } 719 | } 720 | -------------------------------------------------------------------------------- /src/axis_aligned/clip.rs: -------------------------------------------------------------------------------- 1 | //! ### Signed axis clipping 2 | 3 | use super::{f, vh, SignedAxis}; 4 | use crate::clip::Clip; 5 | 6 | macro_rules! clip_impl { 7 | ($T:ty) => { 8 | impl SignedAxis { 9 | #[inline(always)] 10 | #[must_use] 11 | const fn reject( 12 | u: $T, 13 | v1: $T, 14 | v2: $T, 15 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 16 | ) -> bool { 17 | // TODO: strict comparison for closed line segments 18 | vh!( 19 | (u < wy1 || wy2 < u) || f!(v2 <= wx1 || wx2 < v1, v1 < wx1 || wx2 <= v2), 20 | (u < wx1 || wx2 < u) || f!(v2 <= wy1 || wy2 < v1, v1 < wy1 || wy2 <= v2) 21 | ) 22 | } 23 | 24 | #[inline(always)] 25 | #[must_use] 26 | const fn cv1(v1: $T, &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> $T { 27 | match (FLIP, VERT) { 28 | (false, false) if v1 < wx1 => wx1, 29 | (false, true) if v1 < wy1 => wy1, 30 | (true, false) if wx2 < v1 => wx2, 31 | (true, true) if wy2 < v1 => wy2, 32 | _ => v1, 33 | } 34 | } 35 | 36 | #[inline(always)] 37 | #[must_use] 38 | const fn cv2(v2: $T, &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> $T { 39 | match (FLIP, VERT) { 40 | (false, false) if wx2 < v2 => wx2.wrapping_add(1), 41 | (false, true) if wy2 < v2 => wy2.wrapping_add(1), 42 | (true, false) if v2 < wx1 => wx1.wrapping_sub(1), 43 | (true, true) if v2 < wy1 => wy1.wrapping_sub(1), 44 | _ => v2, 45 | } 46 | } 47 | 48 | #[inline(always)] 49 | #[must_use] 50 | pub(super) const fn clip_inner(u: $T, v1: $T, v2: $T, clip: &Clip<$T>) -> Option { 51 | if Self::reject(u, v1, v2, clip) { 52 | return None; 53 | } 54 | Some(Self::new_inner(u, Self::cv1(v1, clip), Self::cv2(v2, clip))) 55 | } 56 | } 57 | }; 58 | } 59 | 60 | clip_impl!(i8); 61 | clip_impl!(u8); 62 | clip_impl!(i16); 63 | clip_impl!(u16); 64 | clip_impl!(i32); 65 | clip_impl!(u32); 66 | clip_impl!(i64); 67 | clip_impl!(u64); 68 | clip_impl!(isize); 69 | clip_impl!(usize); 70 | -------------------------------------------------------------------------------- /src/clip.rs: -------------------------------------------------------------------------------- 1 | //! ## Clipping 2 | //! 3 | //! This module provides the [`Clip`] type representing a rectangular clipping region, 4 | //! as well as methods for constructing iterators over clipped line segments of common types. 5 | 6 | use crate::axis_aligned::{AnyAxis, Axis0, Axis1}; 7 | use crate::diagonal::AnyDiagonal; 8 | use crate::math::Point; 9 | use crate::octant::AnyOctant; 10 | 11 | /// A rectangular region defined by its minimum and maximum [corners](Point). 12 | /// 13 | /// *Both corners are included in the region.* 14 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] 15 | pub struct Clip { 16 | pub(crate) wx1: T, 17 | pub(crate) wy1: T, 18 | pub(crate) wx2: T, 19 | pub(crate) wy2: T, 20 | } 21 | 22 | macro_rules! clip_impl { 23 | ($T:ty) => { 24 | impl Clip<$T> { 25 | /// Returns a new [`Clip`] if `x1 <= x2 && y1 <= y2`, otherwise returns [`None`]. 26 | #[inline] 27 | #[must_use] 28 | pub const fn new((wx1, wy1): Point<$T>, (wx2, wy2): Point<$T>) -> Option { 29 | if wx2 < wx1 || wy2 < wy1 { 30 | return None; 31 | } 32 | Some(Self { wx1, wy1, wx2, wy2 }) 33 | } 34 | 35 | /// Returns the minimum corner of this clipping region. 36 | #[inline] 37 | #[must_use] 38 | pub const fn min(&self) -> Point<$T> { 39 | (self.wx1, self.wy1) 40 | } 41 | 42 | /// Returns the maximum corner of this clipping region. 43 | #[inline] 44 | #[must_use] 45 | pub const fn max(&self) -> Point<$T> { 46 | (self.wx2, self.wy2) 47 | } 48 | 49 | /// Checks if this region contains a [point](Point). 50 | #[inline] 51 | #[must_use] 52 | pub const fn point(&self, (x, y): Point<$T>) -> bool { 53 | self.wx1 <= x && x <= self.wx2 && self.wy1 <= y && y <= self.wy2 54 | } 55 | 56 | /// Clips a *half-open* [horizontal](Axis0) line segment 57 | /// to this region, and returns an iterator over it. 58 | /// 59 | /// Returns [`None`] if the line segment does not intersect this clipping region. 60 | #[inline] 61 | #[must_use] 62 | pub const fn axis_0(&self, y: $T, x1: $T, x2: $T) -> Option> { 63 | Axis0::<$T>::clip(y, x1, x2, self) 64 | } 65 | 66 | /// Clips a *half-open* [vertical](Axis1) line segment 67 | /// to this region, and returns an iterator over it. 68 | /// 69 | /// Returns [`None`] if the line segment does not intersect this clipping region. 70 | #[inline] 71 | #[must_use] 72 | pub const fn axis_1(&self, x: $T, y1: $T, y2: $T) -> Option> { 73 | Axis1::<$T>::clip(x, y1, y2, self) 74 | } 75 | 76 | /// Clips a *half-open* line segment to this region 77 | /// if it is aligned to [any axis](AnyAxis), and returns an iterator over it, 78 | /// . 79 | /// 80 | /// Returns [`None`] if the line segment is not axis-aligned, 81 | /// or if it does not intersect this clipping region. 82 | #[inline] 83 | #[must_use] 84 | pub const fn any_axis(&self, p1: Point<$T>, p2: Point<$T>) -> Option> { 85 | AnyAxis::<$T>::clip(p1, p2, self) 86 | } 87 | 88 | /// Clips a *half-open* line segment to this region 89 | /// if it is [diagonal](AnyDiagonal), and returns an iterator over it. 90 | /// 91 | /// Returns [`None`] if the line segment is not diagonal, 92 | /// or if it does not intersect this clipping region. 93 | #[inline] 94 | #[must_use] 95 | pub const fn any_diagonal( 96 | &self, 97 | p1: Point<$T>, 98 | p2: Point<$T>, 99 | ) -> Option> { 100 | AnyDiagonal::<$T>::clip(p1, p2, self) 101 | } 102 | 103 | /// Clips a *half-open* [arbitrary](AnyOctant) line segment 104 | /// to this region, and returns an iterator over it. 105 | /// 106 | /// Returns [`None`] if the line segment does not intersect this clipping region. 107 | #[inline] 108 | #[must_use] 109 | pub const fn any_octant(&self, p1: Point<$T>, p2: Point<$T>) -> Option> { 110 | AnyOctant::<$T>::clip(p1, p2, self) 111 | } 112 | } 113 | }; 114 | } 115 | 116 | clip_impl!(i8); 117 | clip_impl!(u8); 118 | clip_impl!(i16); 119 | clip_impl!(u16); 120 | clip_impl!(i32); 121 | clip_impl!(u32); 122 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 123 | clip_impl!(isize); 124 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 125 | clip_impl!(usize); 126 | -------------------------------------------------------------------------------- /src/diagonal.rs: -------------------------------------------------------------------------------- 1 | //! ## Diagonal iterators 2 | 3 | use crate::clip::Clip; 4 | use crate::math::{Math, Num, Point}; 5 | use crate::symmetry::{fx, fy}; 6 | use crate::utils::{map, reject_if}; 7 | 8 | pub mod clip; 9 | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // Diagonal quadrant iterators 12 | //////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | /// Iterator over a diagonal line segment in the given **quadrant**. 15 | /// 16 | /// A quadrant is defined by its symmetries relative to [`Diagonal0`]: 17 | /// - `FX`: flip the `x` axis if `true`. 18 | /// - `FY`: flip the `y` axis if `true`. 19 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 20 | pub struct Diagonal { 21 | x1: T, 22 | y1: T, 23 | x2: T, 24 | } 25 | 26 | /// Iterator over a [diagonal](Diagonal) line segment in the 27 | /// [quadrant](Diagonal) where `x` and `y` **both increase**. 28 | /// 29 | /// Covers line segments oriented at `45°`. 30 | pub type Diagonal0 = Diagonal; 31 | 32 | /// Iterator over a [diagonal](Diagonal) line segment in the 33 | /// [quadrant](Diagonal) where `x` **increases** and `y` **decreases**. 34 | /// 35 | /// Covers line segments oriented at `315°`. 36 | pub type Diagonal1 = Diagonal; 37 | 38 | /// Iterator over a [diagonal](Diagonal) line segment in the 39 | /// [quadrant](Diagonal) where `x` **decreases** and `y` **increases**. 40 | /// 41 | /// Covers line segments oriented at `135°`. 42 | pub type Diagonal2 = Diagonal; 43 | 44 | /// Iterator over a [diagonal](Diagonal) line segment in the 45 | /// [quadrant](Diagonal) where `x` and `y` **both decrease**. 46 | /// 47 | /// Covers line segments oriented at `225°`. 48 | pub type Diagonal3 = Diagonal; 49 | 50 | macro_rules! diagonal_impl { 51 | ($T:ty) => { 52 | impl Diagonal { 53 | #[inline(always)] 54 | #[must_use] 55 | pub(crate) const fn new_inner((x1, y1): Point<$T>, x2: $T) -> Self { 56 | Self { x1, y1, x2 } 57 | } 58 | 59 | #[inline(always)] 60 | #[must_use] 61 | const fn covers((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> bool { 62 | let (u1, u2) = fx!((x1, x2), (x2, x1)); 63 | let dx = if u1 < u2 { 64 | Math::<$T>::delta(u2, u1) 65 | } else { 66 | return false; 67 | }; 68 | let (v1, v2) = fy!((y1, y2), (y2, y1)); 69 | let dy = if v1 < v2 { 70 | Math::<$T>::delta(v2, v1) 71 | } else { 72 | return false; 73 | }; 74 | dx == dy 75 | } 76 | 77 | /// Returns an iterator over a *half-open* line segment 78 | /// if it is diagonal and covered by the given [quadrant](Diagonal). 79 | /// 80 | /// Returns [`None`] if the line segment is not diagonal or covered by the quadrant. 81 | #[inline] 82 | #[must_use] 83 | pub const fn new((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> Option { 84 | if !Self::covers((x1, y1), (x2, y2)) { 85 | return None; 86 | }; 87 | Some(Self::new_inner((x1, y1), x2)) 88 | } 89 | 90 | /// Clips a *half-open* line segment to a [rectangular region](Clip) 91 | /// if it is diagonal and covered by the given [quadrant](Diagonal), 92 | /// and returns an iterator over it. 93 | /// 94 | /// Returns [`None`] if the line segment is not diagonal or covered by the quadrant, 95 | /// or if it does not intersect the clipping region. 96 | #[inline] 97 | #[must_use] 98 | pub const fn clip( 99 | (x1, y1): Point<$T>, 100 | (x2, y2): Point<$T>, 101 | clip: &Clip<$T>, 102 | ) -> Option { 103 | let &Clip { wx1, wy1, wx2, wy2 } = clip; 104 | let (u1, u2) = fx!((x1, x2), (x2, x1)); 105 | reject_if!(u2 <= wx1 || wx2 < u1); 106 | let (v1, v2) = fx!((y1, y2), (y2, y1)); 107 | reject_if!(v2 <= wy1 || wy2 < v1); 108 | if !Self::covers((x1, y1), (x2, y2)) { 109 | return None; 110 | }; 111 | Self::clip_inner((x1, y1), (x2, y2), clip) 112 | } 113 | 114 | /// Returns `true` if the iterator has terminated. 115 | #[inline] 116 | #[must_use] 117 | pub const fn is_done(&self) -> bool { 118 | fx!(self.x2 <= self.x1, self.x1 <= self.x2) 119 | } 120 | 121 | /// Returns the remaining length of this iterator. 122 | #[inline] 123 | #[must_use] 124 | pub const fn length(&self) -> <$T as Num>::U { 125 | Math::<$T>::delta(fx!(self.x2, self.x1), fx!(self.x1, self.x2)) 126 | } 127 | } 128 | 129 | impl Iterator for Diagonal { 130 | type Item = Point<$T>; 131 | 132 | #[inline] 133 | fn next(&mut self) -> Option { 134 | if self.is_done() { 135 | return None; 136 | } 137 | let (x, y) = (self.x1, self.y1); 138 | self.x1 = fx!(self.x1.wrapping_add(1), self.x1.wrapping_sub(1)); 139 | self.y1 = fy!(self.y1.wrapping_add(1), self.y1.wrapping_sub(1)); 140 | Some((x, y)) 141 | } 142 | 143 | #[inline] 144 | fn size_hint(&self) -> (usize, Option) { 145 | #[allow(unreachable_patterns)] 146 | match usize::try_from(self.length()) { 147 | Ok(length) => (length, Some(length)), 148 | Err(_) => (usize::MAX, None), 149 | } 150 | } 151 | } 152 | 153 | impl core::iter::FusedIterator for Diagonal {} 154 | }; 155 | } 156 | 157 | diagonal_impl!(i8); 158 | diagonal_impl!(u8); 159 | diagonal_impl!(i16); 160 | diagonal_impl!(u16); 161 | diagonal_impl!(i32); 162 | diagonal_impl!(u32); 163 | diagonal_impl!(i64); 164 | diagonal_impl!(u64); 165 | diagonal_impl!(isize); 166 | diagonal_impl!(usize); 167 | 168 | macro_rules! diagonal_exact_size_iter_impl { 169 | ($T:ty) => { 170 | impl ExactSizeIterator for Diagonal { 171 | #[cfg(feature = "is_empty")] 172 | #[inline] 173 | fn is_empty(&self) -> bool { 174 | self.is_done() 175 | } 176 | } 177 | }; 178 | } 179 | 180 | diagonal_exact_size_iter_impl!(i8); 181 | diagonal_exact_size_iter_impl!(u8); 182 | diagonal_exact_size_iter_impl!(i16); 183 | diagonal_exact_size_iter_impl!(u16); 184 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 185 | diagonal_exact_size_iter_impl!(i32); 186 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 187 | diagonal_exact_size_iter_impl!(u32); 188 | #[cfg(target_pointer_width = "64")] 189 | diagonal_exact_size_iter_impl!(i64); 190 | #[cfg(target_pointer_width = "64")] 191 | diagonal_exact_size_iter_impl!(u64); 192 | diagonal_exact_size_iter_impl!(isize); 193 | diagonal_exact_size_iter_impl!(usize); 194 | 195 | //////////////////////////////////////////////////////////////////////////////////////////////////// 196 | // Arbitrary diagonal iterator 197 | //////////////////////////////////////////////////////////////////////////////////////////////////// 198 | 199 | /// Iterator over any [diagonal](Diagonal) line segment, 200 | /// with the orientation determined at runtime. 201 | /// 202 | /// If you know the orientation of the line segment beforehand, 203 | /// use an iterator from the [`Diagonal`] family. 204 | /// 205 | /// **Note**: an optimized implementation of [`Iterator::fold`] is provided. 206 | /// This makes [`Iterator::for_each`] faster than a `for` loop, since it checks 207 | /// the orientation only once instead of on every call to [`Iterator::next`]. 208 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 209 | pub enum AnyDiagonal { 210 | /// Diagonal line segment at `45°`, see [`Diagonal0`]. 211 | Diagonal0(Diagonal0), 212 | /// Diagonal line segment at `135°`, see [`Diagonal1`]. 213 | Diagonal1(Diagonal1), 214 | /// Diagonal line segment at `225°`, see [`Diagonal2`]. 215 | Diagonal2(Diagonal2), 216 | /// Diagonal line segment at `315°`, see [`Diagonal3`]. 217 | Diagonal3(Diagonal3), 218 | } 219 | 220 | macro_rules! delegate { 221 | ($self:ident, $me:ident => $call:expr) => { 222 | match $self { 223 | Self::Diagonal0($me) => $call, 224 | Self::Diagonal1($me) => $call, 225 | Self::Diagonal2($me) => $call, 226 | Self::Diagonal3($me) => $call, 227 | } 228 | }; 229 | } 230 | 231 | macro_rules! quadrant { 232 | ($Quadrant:ident, $T:ty, $p1:expr, $x2:expr) => { 233 | Self::$Quadrant($Quadrant::<$T>::new_inner($p1, $x2)) 234 | }; 235 | ($Quadrant:ident, $T:ty, $p1:expr, $p2:expr, $clip:expr) => { 236 | map!($Quadrant::<$T>::clip_inner($p1, $p2, $clip), Self::$Quadrant) 237 | }; 238 | } 239 | 240 | pub(crate) use quadrant; 241 | 242 | macro_rules! any_diagonal_impl { 243 | ($T:ty) => { 244 | impl AnyDiagonal<$T> { 245 | /// Returns an iterator over a *half-open* line segment 246 | /// if it is diagonal, otherwise returns [`None`]. 247 | #[inline] 248 | #[must_use] 249 | pub const fn new((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> Option { 250 | if x1 < x2 { 251 | let dx = Math::<$T>::delta(x2, x1); 252 | if y1 < y2 { 253 | let dy = Math::<$T>::delta(y2, y1); 254 | reject_if!(dx != dy); 255 | return Some(quadrant!(Diagonal0, $T, (x1, y1), x2)); 256 | } 257 | let dy = Math::<$T>::delta(y1, y2); 258 | reject_if!(dx != dy); 259 | return Some(quadrant!(Diagonal1, $T, (x1, y1), x2)); 260 | } 261 | let dx = Math::<$T>::delta(x1, x2); 262 | if y1 < y2 { 263 | let dy = Math::<$T>::delta(y2, y1); 264 | reject_if!(dx != dy); 265 | return Some(quadrant!(Diagonal2, $T, (x1, y1), x2)); 266 | } 267 | let dy = Math::<$T>::delta(y1, y2); 268 | reject_if!(dx != dy); 269 | return Some(quadrant!(Diagonal3, $T, (x1, y1), x2)); 270 | } 271 | 272 | /// Clips a *half-open* line segment to a [rectangular region](Clip) 273 | /// if it is diagonal, and returns an iterator over it. 274 | /// 275 | /// Returns [`None`] if the given line segment is not diagonal, 276 | /// or if it does not intersect the clipping region. 277 | #[inline] 278 | #[must_use] 279 | pub const fn clip( 280 | (x1, y1): Point<$T>, 281 | (x2, y2): Point<$T>, 282 | clip: &Clip<$T> 283 | ) -> Option { 284 | let &Clip { wx1, wy1, wx2, wy2 } = clip; 285 | if x1 < x2 { 286 | // TODO: strict comparison for closed line segments 287 | reject_if!(x2 <= wx1 || wx2 < x1); 288 | let dx = Math::<$T>::delta(x2, x1); 289 | if y1 < y2 { 290 | reject_if!(y2 <= wy1 || wy2 < y1); 291 | let dy = Math::<$T>::delta(y2, y1); 292 | reject_if!(dx != dy); 293 | return quadrant!(Diagonal0, $T, (x1, y1), (x2, y2), clip); 294 | } 295 | reject_if!(y1 < wy1 || wy2 <= y2); 296 | let dy = Math::<$T>::delta(y1, y2); 297 | reject_if!(dx != dy); 298 | return quadrant!(Diagonal1, $T, (x1, y1), (x2, y2), clip); 299 | } 300 | reject_if!(x1 < wx1 || wx2 <= x2); 301 | let dx = Math::<$T>::delta(x1, x2); 302 | if y1 < y2 { 303 | reject_if!(y2 <= wy1 || wy2 < y1); 304 | let dy = Math::<$T>::delta(y2, y1); 305 | reject_if!(dx != dy); 306 | return quadrant!(Diagonal2, $T, (x1, y1), (x2, y2), clip); 307 | } 308 | reject_if!(y1 < wy1 || wy2 <= y2); 309 | let dy = Math::<$T>::delta(y1, y2); 310 | reject_if!(dx != dy); 311 | return quadrant!(Diagonal3, $T, (x1, y1), (x2, y2), clip); 312 | } 313 | 314 | /// Returns `true` if the iterator has terminated. 315 | #[inline] 316 | #[must_use] 317 | pub const fn is_done(&self) -> bool { 318 | delegate!(self, me => me.is_done()) 319 | } 320 | 321 | /// Returns the remaining length of this iterator. 322 | #[inline] 323 | #[must_use] 324 | pub const fn length(&self) -> <$T as Num>::U { 325 | delegate!(self, me => me.length()) 326 | } 327 | } 328 | 329 | impl Iterator for AnyDiagonal<$T> { 330 | type Item = Point<$T>; 331 | 332 | #[inline] 333 | fn next(&mut self) -> Option { 334 | delegate!(self, me => me.next()) 335 | } 336 | 337 | #[inline] 338 | fn size_hint(&self) -> (usize, Option) { 339 | delegate!(self, me => me.size_hint()) 340 | } 341 | 342 | #[cfg(feature = "try_fold")] 343 | #[inline] 344 | fn try_fold(&mut self, init: B, f: F) -> R 345 | where 346 | Self: Sized, 347 | F: FnMut(B, Self::Item) -> R, 348 | R: core::ops::Try, 349 | { 350 | delegate!(self, me => me.try_fold(init, f)) 351 | } 352 | 353 | #[inline] 354 | fn fold(self, init: B, f: F) -> B 355 | where 356 | Self: Sized, 357 | F: FnMut(B, Self::Item) -> B, 358 | { 359 | delegate!(self, me => me.fold(init, f)) 360 | } 361 | } 362 | 363 | impl core::iter::FusedIterator for AnyDiagonal<$T> {} 364 | }; 365 | } 366 | 367 | any_diagonal_impl!(i8); 368 | any_diagonal_impl!(u8); 369 | any_diagonal_impl!(i16); 370 | any_diagonal_impl!(u16); 371 | any_diagonal_impl!(i32); 372 | any_diagonal_impl!(u32); 373 | any_diagonal_impl!(i64); 374 | any_diagonal_impl!(u64); 375 | any_diagonal_impl!(isize); 376 | any_diagonal_impl!(usize); 377 | 378 | macro_rules! any_diagonal_exact_size_iter_impl { 379 | ($T:ty) => { 380 | impl ExactSizeIterator for AnyDiagonal<$T> { 381 | #[cfg(feature = "is_empty")] 382 | #[inline] 383 | fn is_empty(&self) -> bool { 384 | delegate!(self, me => me.is_empty()) 385 | } 386 | } 387 | }; 388 | } 389 | 390 | any_diagonal_exact_size_iter_impl!(i8); 391 | any_diagonal_exact_size_iter_impl!(u8); 392 | any_diagonal_exact_size_iter_impl!(i16); 393 | any_diagonal_exact_size_iter_impl!(u16); 394 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 395 | any_diagonal_exact_size_iter_impl!(i32); 396 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 397 | any_diagonal_exact_size_iter_impl!(u32); 398 | #[cfg(target_pointer_width = "64")] 399 | any_diagonal_exact_size_iter_impl!(i64); 400 | #[cfg(target_pointer_width = "64")] 401 | any_diagonal_exact_size_iter_impl!(u64); 402 | any_diagonal_exact_size_iter_impl!(isize); 403 | any_diagonal_exact_size_iter_impl!(usize); 404 | 405 | #[cfg(test)] 406 | mod static_tests { 407 | use super::*; 408 | use static_assertions::assert_impl_all; 409 | 410 | #[test] 411 | const fn iterator_8() { 412 | assert_impl_all!(Diagonal0: ExactSizeIterator); 413 | assert_impl_all!(Diagonal0: ExactSizeIterator); 414 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 415 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 416 | } 417 | 418 | #[test] 419 | const fn iterator_16() { 420 | assert_impl_all!(Diagonal0: ExactSizeIterator); 421 | assert_impl_all!(Diagonal0: ExactSizeIterator); 422 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 423 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 424 | } 425 | 426 | #[test] 427 | const fn iterator_32() { 428 | #[cfg(target_pointer_width = "16")] 429 | { 430 | use static_assertions::assert_not_impl_any; 431 | 432 | assert_impl_all!(Diagonal0: Iterator); 433 | assert_impl_all!(Diagonal0: Iterator); 434 | assert_impl_all!(AnyDiagonal: Iterator); 435 | assert_impl_all!(AnyDiagonal: Iterator); 436 | assert_not_impl_any!(Diagonal0: ExactSizeIterator); 437 | assert_not_impl_any!(Diagonal0: ExactSizeIterator); 438 | assert_not_impl_any!(AnyDiagonal: ExactSizeIterator); 439 | assert_not_impl_any!(AnyDiagonal: ExactSizeIterator); 440 | } 441 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 442 | { 443 | assert_impl_all!(Diagonal0: ExactSizeIterator); 444 | assert_impl_all!(Diagonal0: ExactSizeIterator); 445 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 446 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 447 | } 448 | } 449 | 450 | #[test] 451 | const fn iterator_64() { 452 | #[cfg(target_pointer_width = "64")] 453 | { 454 | assert_impl_all!(Diagonal0: ExactSizeIterator); 455 | assert_impl_all!(Diagonal0: ExactSizeIterator); 456 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 457 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 458 | } 459 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 460 | { 461 | use static_assertions::assert_not_impl_any; 462 | 463 | assert_impl_all!(Diagonal0: Iterator); 464 | assert_impl_all!(Diagonal0: Iterator); 465 | assert_impl_all!(AnyDiagonal: Iterator); 466 | assert_impl_all!(AnyDiagonal: Iterator); 467 | assert_not_impl_any!(Diagonal0: ExactSizeIterator); 468 | assert_not_impl_any!(Diagonal0: ExactSizeIterator); 469 | assert_not_impl_any!(AnyDiagonal: ExactSizeIterator); 470 | assert_not_impl_any!(AnyDiagonal: ExactSizeIterator); 471 | } 472 | } 473 | 474 | #[test] 475 | const fn iterator_pointer_size() { 476 | assert_impl_all!(Diagonal0: ExactSizeIterator); 477 | assert_impl_all!(Diagonal0: ExactSizeIterator); 478 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 479 | assert_impl_all!(AnyDiagonal: ExactSizeIterator); 480 | } 481 | } 482 | -------------------------------------------------------------------------------- /src/diagonal/clip.rs: -------------------------------------------------------------------------------- 1 | //! ### Diagonal clipping 2 | 3 | use crate::clip::Clip; 4 | use crate::diagonal::Diagonal; 5 | use crate::math::{Delta, Math, Num, Point}; 6 | use crate::symmetry::{fx, fy}; 7 | 8 | const O: bool = false; 9 | const I: bool = true; 10 | 11 | type LineCode = (bool, bool, bool, bool); 12 | 13 | const INSIDE_INSIDE: LineCode = (O, O, O, O); 14 | const INSIDE_Y_EXIT: LineCode = (O, O, O, I); 15 | const INSIDE_X_EXIT: LineCode = (O, O, I, O); 16 | const INSIDE_XY_EXIT: LineCode = (O, O, I, I); 17 | const Y_ENTRY_INSIDE: LineCode = (O, I, O, O); 18 | const Y_ENTRY_Y_EXIT: LineCode = (O, I, O, I); 19 | const Y_ENTRY_X_EXIT: LineCode = (O, I, I, O); 20 | const Y_ENTRY_XY_EXIT: LineCode = (O, I, I, I); 21 | const X_ENTRY_INSIDE: LineCode = (I, O, O, O); 22 | const X_ENTRY_Y_EXIT: LineCode = (I, O, O, I); 23 | const X_ENTRY_X_EXIT: LineCode = (I, O, I, O); 24 | const X_ENTRY_XY_EXIT: LineCode = (I, O, I, I); 25 | const XY_ENTRY_INSIDE: LineCode = (I, I, O, O); 26 | const XY_ENTRY_Y_EXIT: LineCode = (I, I, O, I); 27 | const XY_ENTRY_X_EXIT: LineCode = (I, I, I, O); 28 | const XY_ENTRY_XY_EXIT: LineCode = (I, I, I, I); 29 | 30 | macro_rules! clip_impl { 31 | ($T:ty, $add:ident, $sub:ident) => { 32 | #[allow(non_snake_case)] 33 | impl Diagonal { 34 | #[inline(always)] 35 | #[must_use] 36 | const fn enters_x(x1: $T, &Clip { wx1, wx2, .. }: &Clip<$T>) -> bool { 37 | fx!(x1 < wx1, wx2 < x1) 38 | } 39 | 40 | #[inline(always)] 41 | #[must_use] 42 | const fn enters_y(y1: $T, &Clip { wy1, wy2, .. }: &Clip<$T>) -> bool { 43 | fy!(y1 < wy1, wy2 < y1) 44 | } 45 | 46 | #[inline(always)] 47 | #[must_use] 48 | const fn exits_x(x2: $T, &Clip { wx1, wx2, .. }: &Clip<$T>) -> bool { 49 | fx!(wx2 < x2, x2 < wx1) 50 | } 51 | 52 | #[inline(always)] 53 | #[must_use] 54 | const fn exits_y(y2: $T, &Clip { wy1, wy2, .. }: &Clip<$T>) -> bool { 55 | fy!(wy2 < y2, y2 < wy1) 56 | } 57 | 58 | #[inline(always)] 59 | #[must_use] 60 | const fn Dx1(x1: $T, &Clip { wx1, wx2, .. }: &Clip<$T>) -> <$T as Num>::U { 61 | fx!(Math::<$T>::delta(wx1, x1), Math::<$T>::delta(x1, wx2)) 62 | } 63 | 64 | #[inline(always)] 65 | #[must_use] 66 | const fn Dx2(x1: $T, &Clip { wx1, wx2, .. }: &Clip<$T>) -> <$T as Num>::U { 67 | fx!(Math::<$T>::delta(wx2, x1), Math::<$T>::delta(x1, wx1)) 68 | } 69 | 70 | #[inline(always)] 71 | #[must_use] 72 | const fn Dy1(y1: $T, &Clip { wy1, wy2, .. }: &Clip<$T>) -> <$T as Num>::U { 73 | fy!(Math::<$T>::delta(wy1, y1), Math::<$T>::delta(y1, wy2)) 74 | } 75 | 76 | #[inline(always)] 77 | #[must_use] 78 | const fn Dy2(y1: $T, &Clip { wy1, wy2, .. }: &Clip<$T>) -> <$T as Num>::U { 79 | fy!(Math::<$T>::delta(wy2, y1), Math::<$T>::delta(y1, wy1)) 80 | } 81 | 82 | #[inline(always)] 83 | #[must_use] 84 | const fn c1_x( 85 | y1: $T, 86 | Dx1: <$T as Num>::U, 87 | &Clip { wx1, wx2, .. }: &Clip<$T>, 88 | ) -> Point<$T> { 89 | let cx1 = fx!(wx1, wx2); 90 | let cy1 = fy!(y1.$add(Dx1), y1.$sub(Dx1)); 91 | (cx1, cy1) 92 | } 93 | 94 | #[inline(always)] 95 | #[must_use] 96 | const fn c1_y( 97 | x1: $T, 98 | Dy1: <$T as Num>::U, 99 | &Clip { wy1, wy2, .. }: &Clip<$T>, 100 | ) -> Point<$T> { 101 | let cy1 = fy!(wy1, wy2); 102 | let cx1 = fx!(x1.$add(Dy1), x1.$sub(Dy1)); 103 | (cx1, cy1) 104 | } 105 | 106 | #[inline(always)] 107 | #[must_use] 108 | const fn c1((x1, y1): Point<$T>, (Dx1, Dy1): Delta<$T>, clip: &Clip<$T>) -> Point<$T> { 109 | if Dy1 < Dx1 { 110 | Self::c1_x(y1, Dx1, clip) 111 | } else { 112 | Self::c1_y(x1, Dy1, clip) 113 | } 114 | } 115 | 116 | #[inline(always)] 117 | #[must_use] 118 | const fn cx2_x(&Clip { wx1, wx2, .. }: &Clip<$T>) -> $T { 119 | fx!(wx2.wrapping_add(1), wx1.wrapping_sub(1)) 120 | } 121 | 122 | #[inline(always)] 123 | #[must_use] 124 | const fn cx2_y(x1: $T, Dy2: <$T as Num>::U) -> $T { 125 | fx!(x1.$add(Dy2).wrapping_add(1), x1.$sub(Dy2).wrapping_sub(1)) 126 | } 127 | 128 | #[inline(always)] 129 | #[must_use] 130 | const fn cx2(x1: $T, (Dx2, Dy2): Delta<$T>, clip: &Clip<$T>) -> $T { 131 | if Dx2 < Dy2 { 132 | Self::cx2_x(clip) 133 | } else { 134 | Self::cx2_y(x1, Dy2) 135 | } 136 | } 137 | 138 | #[inline(always)] 139 | #[must_use] 140 | pub(crate) const fn clip_inner( 141 | (x1, y1): Point<$T>, 142 | (x2, y2): Point<$T>, 143 | clip: &Clip<$T>, 144 | ) -> Option { 145 | let (c1, cx2) = match ( 146 | Self::enters_x(x1, clip), 147 | Self::enters_y(y1, clip), 148 | Self::exits_x(x2, clip), 149 | Self::exits_y(y2, clip), 150 | ) { 151 | INSIDE_INSIDE => ((x1, y1), x2), 152 | INSIDE_Y_EXIT => ((x1, y1), Self::cx2_y(x1, Self::Dy2(y1, clip))), 153 | INSIDE_X_EXIT => ((x1, y1), Self::cx2_x(clip)), 154 | INSIDE_XY_EXIT => { 155 | ((x1, y1), Self::cx2(x1, (Self::Dx2(x1, clip), Self::Dy2(y1, clip)), clip)) 156 | } 157 | Y_ENTRY_INSIDE => (Self::c1_y(x1, Self::Dy1(y1, clip), clip), x2), 158 | Y_ENTRY_Y_EXIT => ( 159 | Self::c1_y(x1, Self::Dy1(y1, clip), clip), 160 | Self::cx2_y(x1, Self::Dy2(y1, clip)), 161 | ), 162 | Y_ENTRY_X_EXIT => { 163 | let Dy1 = Self::Dy1(y1, clip); 164 | let Dx2 = Self::Dx2(x1, clip); 165 | if Dx2 < Dy1 { 166 | return None; 167 | } 168 | (Self::c1_y(x1, Dy1, clip), Self::cx2_x(clip)) 169 | } 170 | Y_ENTRY_XY_EXIT => { 171 | let Dy1 = Self::Dy1(y1, clip); 172 | let Dx2 = Self::Dx2(x1, clip); 173 | if Dx2 < Dy1 { 174 | return None; 175 | } 176 | (Self::c1_y(x1, Dy1, clip), Self::cx2(x1, (Dx2, Self::Dy2(y1, clip)), clip)) 177 | } 178 | X_ENTRY_INSIDE => (Self::c1_x(y1, Self::Dx1(x1, clip), clip), x2), 179 | X_ENTRY_Y_EXIT => ( 180 | Self::c1_x(y1, Self::Dx1(x1, clip), clip), 181 | Self::cx2_y(x1, Self::Dy2(y1, clip)), 182 | ), 183 | X_ENTRY_X_EXIT => { 184 | (Self::c1_x(y1, Self::Dx1(x1, clip), clip), Self::cx2_x(clip)) 185 | } 186 | X_ENTRY_XY_EXIT => { 187 | let Dx1 = Self::Dx1(x1, clip); 188 | let Dy2 = Self::Dy2(y1, clip); 189 | if Dy2 < Dx1 { 190 | return None; 191 | } 192 | (Self::c1_x(y1, Dx1, clip), Self::cx2(x1, (Self::Dx2(x1, clip), Dy2), clip)) 193 | } 194 | XY_ENTRY_INSIDE => { 195 | (Self::c1((x1, y1), (Self::Dx1(x1, clip), Self::Dy1(y1, clip)), clip), x2) 196 | } 197 | XY_ENTRY_Y_EXIT => ( 198 | Self::c1((x1, y1), (Self::Dx1(x1, clip), Self::Dy1(y1, clip)), clip), 199 | Self::cx2_y(x1, Self::Dy2(y1, clip)), 200 | ), 201 | XY_ENTRY_X_EXIT => ( 202 | Self::c1((x1, y1), (Self::Dx1(x1, clip), Self::Dy1(y1, clip)), clip), 203 | Self::cx2_x(clip), 204 | ), 205 | XY_ENTRY_XY_EXIT => { 206 | let Dy1 = Self::Dy1(y1, clip); 207 | let Dx2 = Self::Dx2(x1, clip); 208 | if Dx2 < Dy1 { 209 | return None; 210 | } 211 | let Dx1 = Self::Dx1(x1, clip); 212 | let Dy2 = Self::Dy2(y1, clip); 213 | if Dy2 < Dx1 { 214 | return None; 215 | } 216 | (Self::c1((x1, y1), (Dx1, Dy1), clip), Self::cx2(x1, (Dx2, Dy2), clip)) 217 | } 218 | }; 219 | Some(Self::new_inner(c1, cx2)) 220 | } 221 | } 222 | }; 223 | } 224 | 225 | clip_impl!(i8, wrapping_add_unsigned, wrapping_sub_unsigned); 226 | clip_impl!(u8, wrapping_add, wrapping_sub); 227 | clip_impl!(i16, wrapping_add_unsigned, wrapping_sub_unsigned); 228 | clip_impl!(u16, wrapping_add, wrapping_sub); 229 | clip_impl!(i32, wrapping_add_unsigned, wrapping_sub_unsigned); 230 | clip_impl!(u32, wrapping_add, wrapping_sub); 231 | clip_impl!(i64, wrapping_add_unsigned, wrapping_sub_unsigned); 232 | clip_impl!(u64, wrapping_add, wrapping_sub); 233 | clip_impl!(isize, wrapping_add_unsigned, wrapping_sub_unsigned); 234 | clip_impl!(usize, wrapping_add, wrapping_sub); 235 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # clipline 2 | //! 3 | //! Efficient rasterization of line segments with pixel-perfect [clipping][clip]. 4 | //! 5 | //! ## Overview 6 | //! 7 | //! - Provides iterators for clipped and unclipped rasterized line segments. 8 | //! - Eliminates bounds checking: clipped line segments are guaranteed to be within the region. 9 | //! - Guarantees clipped line segments match the unclipped versions of themselves. 10 | //! - Supports signed and unsigned integer coordinates of most sizes. 11 | //! - Uses integer arithmetic only. 12 | //! - Prevents overflow and division by zero, forbids `clippy::arithmetic_side_effects`. 13 | //! - Defines the iterators on the entire domains of the underlying numeric types. 14 | //! - Usable in `const` contexts and `#![no_std]` environments. 15 | //! 16 | //! ## Usage 17 | //! 18 | //! - **Unclipped** iterators are created using constructors: [`AnyOctant::::new`]. 19 | //! - For **clipped** iterators: 20 | //! - Define a rectangular clipping region using the [`Clip`] type. 21 | //! - Construct the desired iterator, e.g. [`AnyOctant::`]: 22 | //! - **Builder style**: using one of the methods on [`Clip`], e.g. [`Clip::::any_octant`]. 23 | //! Should be preferred, as it avoids specifying the numeric type again. 24 | //! - **Constructor style**: [`AnyOctant::::clip`]. 25 | //! 26 | //! ### Octant iterators 27 | //! 28 | //! For an arbitrary line segment, use the [`AnyOctant`] iterator, 29 | //! which determines the type of the line segment at runtime 30 | //! and handles it with a specialized iterator. 31 | //! 32 | //! If you know more about the line segment, you can use an iterator 33 | //! from the [axis-aligned](Axis) or [diagonal](Diagonal) families (more below), 34 | //! or the generic [`Octant`] backed by one of the eight cases of [Bresenham's algorithm][bres]: 35 | //! 36 | //! - [`Octant0`]: `x` and `y` both increase, `x` changes faster than `y`. 37 | //! - [`Octant1`]: `x` increases and `y` decreases, `y` changes faster than `x`. 38 | //! - [`Octant2`]: `x` decreases and `y` increases, `x` changes faster than `y`. 39 | //! - [`Octant3`]: `x` and `y` both decrease, `y` changes faster than `x`. 40 | //! - [`Octant4`]: `x` and `y` both increase, `x` changes faster than `y`. 41 | //! - [`Octant5`]: `x` increases and `y` decreases, `y` changes faster than `x`. 42 | //! - [`Octant6`]: `x` decreases and `y` increases, `x` changes faster than `y`. 43 | //! - [`Octant7`]: `x` and `y` both decrease, `y` changes faster than `x`. 44 | //! 45 | //! ### Axis-aligned iterators 46 | //! 47 | //! For an arbitrary axis-aligned line segment, use the [`AnyAxis`] iterator, 48 | //! which determines both the axis-alignment and direction at runtime. 49 | //! 50 | //! If you know the axis-alignment of the line segment but not the direction, 51 | //! use the generic [`Axis`] iterator, or one of its type aliases: 52 | //! 53 | //! - [`Axis0`]: horizontal, runtime direction. 54 | //! - [`Axis1`]: vertical, runtime direction. 55 | //! 56 | //! If you also know the direction, use the generic [`SignedAxis`] iterator, 57 | //! or one of its type aliases: 58 | //! 59 | //! - [`PositiveAxis`]/[`NegativeAxis`]: fixed direction, generic orientation. 60 | //! - [`SignedAxis0`]/[`SignedAxis1`]: fixed orientation, generic direction. 61 | //! - [`PositiveAxis0`]/[`NegativeAxis0`]/[`PositiveAxis1`]/[`NegativeAxis1`]: both fixed. 62 | //! 63 | //! ### Diagonal iterators 64 | //! 65 | //! For an arbitrary diagonal line segment, use the [`AnyDiagonal`] iterator, 66 | //! which determines the orientation at runtime. 67 | //! 68 | //! If you know the orientation, use the generic [`Diagonal`] iterator, 69 | //! or one of its type aliases: 70 | //! 71 | //! - [`Diagonal0`]: `x` and `y` both increase. 72 | //! - [`Diagonal1`]: `x` increases and `y` decreases. 73 | //! - [`Diagonal2`]: `x` decreases and `y` increases. 74 | //! - [`Diagonal3`]: `x` and `y` both decrease. 75 | //! 76 | //! ## Example 77 | //! 78 | //! ``` 79 | //! use clipline::{AnyOctant, Clip, Diagonal0, Point}; 80 | //! 81 | //! /// Width of the pixel buffer. 82 | //! const WIDTH: usize = 64; 83 | //! /// Height of the pixel buffer. 84 | //! const HEIGHT: usize = 48; 85 | //! 86 | //! /// Pixel color value. 87 | //! const RGBA: u32 = 0xFFFFFFFF; 88 | //! 89 | //! /// A function that operates on a single pixel in a pixel buffer. 90 | //! /// 91 | //! /// ## Safety 92 | //! /// `(x, y)` must be inside the `buffer`. 93 | //! unsafe fn draw(buffer: &mut [u32], (x, y): Point, rgba: u32) { 94 | //! let index = y as usize * WIDTH + x as usize; 95 | //! debug_assert!(index < buffer.len()); 96 | //! *buffer.get_unchecked_mut(index) = rgba; 97 | //! } 98 | //! 99 | //! fn main() { 100 | //! let mut buffer = [0_u32; WIDTH * HEIGHT]; 101 | //! 102 | //! // The clipping region is closed/inclusive, thus 1 needs to be subtracted from the size. 103 | //! let clip = Clip::::new((0, 0), (WIDTH as i8 - 1, HEIGHT as i8 - 1)).unwrap(); 104 | //! 105 | //! // `Clip` has convenience methods for the general iterators. 106 | //! clip.any_octant((-128, -100), (100, 80)) 107 | //! // None if the line segment is completely invisible. 108 | //! // You might want to handle that case differently. 109 | //! .unwrap() 110 | //! // clipped to [(0, 1), ..., (58, 47)] 111 | //! .for_each(|xy| { 112 | //! // SAFETY: (x, y) has been clipped to the buffer. 113 | //! unsafe { draw(&mut buffer, xy, RGBA) } 114 | //! }); 115 | //! 116 | //! // Alternatively, use the iterator constructors. 117 | //! AnyOctant::::clip((12, 0), (87, 23), &clip) 118 | //! .into_iter() 119 | //! .flatten() 120 | //! // clipped to [(12, 0), ..., (63, 16)] 121 | //! .for_each(|xy| { 122 | //! // SAFETY: (x, y) has been clipped to the buffer. 123 | //! unsafe { draw(&mut buffer, xy, RGBA) } 124 | //! }); 125 | //! 126 | //! // Horizontal and vertical line segments. 127 | //! clip.axis_0(32, 76, -23) 128 | //! .unwrap() 129 | //! // clipped to [(63, 32), ..., (0, 32)] 130 | //! .for_each(|xy| { 131 | //! // SAFETY: (x, y) has been clipped to the buffer. 132 | //! unsafe { draw(&mut buffer, xy, RGBA) } 133 | //! }); 134 | //! 135 | //! clip.axis_1(32, -23, 76) 136 | //! .unwrap() 137 | //! // clipped to [(32, 0), ..., (32, 47)] 138 | //! .for_each(|xy| { 139 | //! // SAFETY: (x, y) has been clipped to the buffer. 140 | //! unsafe { draw(&mut buffer, xy, RGBA) } 141 | //! }); 142 | //! 143 | //! // Unclipped iterators are also available. 144 | //! // (-2, -2) -> (12, 12) is covered by Diagonal0, we can construct it directly. 145 | //! Diagonal0::::new((-2, -2), (12, 12)) 146 | //! .unwrap() 147 | //! // Need to check every pixel to avoid going out of bounds. 148 | //! .filter(|&xy| clip.point(xy)) 149 | //! .for_each(|xy| { 150 | //! // SAFETY: (x, y) is inside the buffer. 151 | //! unsafe { draw(&mut buffer, xy, RGBA) } 152 | //! }); 153 | //! } 154 | //! ``` 155 | //! 156 | //! ## Limitations 157 | //! 158 | //! * To support usage in `const` contexts, types must have an inherent implementation for every 159 | //! supported numeric type instead of relying on a trait. This and Rust's lack of support for 160 | //! function overloading means that the numeric type parameter must always be specified. 161 | //! * Currently, only half-open line segments can be iterated. This allows [`ExactSizeIterator`] 162 | //! to be implemented for all types. Inclusive iterators are tracked in [#1]. 163 | //! 164 | //! ## Feature flags 165 | //! 166 | //! - `octant_64` 167 | //! * Enables [`Octant`] and [`AnyOctant`] over [`i64`]/[`u64`] for all targets, 168 | //! and over [`isize`]/[`usize`] for 64-bit targets. 169 | //! * Use this only if you need the full 64-bit range, as [`Octant`] will use 170 | //! [`u128`] and [`i128`] for some calculations. 171 | //! - `try_fold`, `is_empty` *(nightly-only)* 172 | //! * Enable optimized [`Iterator::try_fold`] and [`ExactSizeIterator::is_empty`] implementations. 173 | //! 174 | //! ## References 175 | //! 176 | //! `clipline` is inspired by the following papers: 177 | //! 178 | //! * [A fast two-dimensional line clipping algorithm via line encoding][spy], 179 | //! Mark S. Sobkow, Paul Pospisil, Yee-Hong Yang, 1987. 180 | //! * [A new approach to parametric line clipping][dorr], 181 | //! Michael Dörr, 1990. 182 | //! * [Bresenham's Line Generation Algorithm with Built-in Clipping][kuzmin], 183 | //! Yevgeny P. Kuzmin, 1995. 184 | //! 185 | //! [clip]: https://en.wikipedia.org/wiki/Line_clipping 186 | //! [bres]: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 187 | //! [spy]: https://doi.org/10.1016/0097-8493(87)90061-6 188 | //! [dorr]: https://doi.org/10.1016/0097-8493(90)90067-8 189 | //! [kuzmin]: https://doi.org/10.1111/1467-8659.1450275 190 | //! [#1]: https://github.com/nxsaken/clipline/issues/1 191 | 192 | #![no_std] 193 | #![cfg_attr(feature = "try_fold", feature(try_trait_v2))] 194 | #![cfg_attr(feature = "is_empty", feature(exact_size_is_empty))] 195 | #![forbid( 196 | clippy::arithmetic_side_effects, 197 | clippy::undocumented_unsafe_blocks, 198 | clippy::unnecessary_safety_comment, 199 | clippy::missing_safety_doc, 200 | clippy::unnecessary_safety_doc 201 | )] 202 | #![deny(missing_docs)] 203 | #![warn(clippy::nursery, clippy::cargo, clippy::pedantic)] 204 | #![allow( 205 | clippy::module_name_repetitions, 206 | clippy::inline_always, 207 | clippy::similar_names, 208 | clippy::if_not_else, 209 | clippy::cast_lossless 210 | )] 211 | 212 | mod axis_aligned; 213 | mod clip; 214 | mod diagonal; 215 | mod math; 216 | mod octant; 217 | mod symmetry; 218 | mod utils; 219 | 220 | pub use clip::Clip; 221 | pub use math::Point; 222 | 223 | pub use octant::AnyOctant; 224 | pub use octant::Octant; 225 | pub use octant::Octant0; 226 | pub use octant::Octant1; 227 | pub use octant::Octant2; 228 | pub use octant::Octant3; 229 | pub use octant::Octant4; 230 | pub use octant::Octant5; 231 | pub use octant::Octant6; 232 | pub use octant::Octant7; 233 | 234 | pub use diagonal::AnyDiagonal; 235 | pub use diagonal::Diagonal; 236 | pub use diagonal::Diagonal0; 237 | pub use diagonal::Diagonal1; 238 | pub use diagonal::Diagonal2; 239 | pub use diagonal::Diagonal3; 240 | 241 | pub use axis_aligned::AnyAxis; 242 | pub use axis_aligned::Axis; 243 | pub use axis_aligned::Axis0; 244 | pub use axis_aligned::Axis1; 245 | pub use axis_aligned::NegativeAxis; 246 | pub use axis_aligned::NegativeAxis0; 247 | pub use axis_aligned::NegativeAxis1; 248 | pub use axis_aligned::PositiveAxis; 249 | pub use axis_aligned::PositiveAxis0; 250 | pub use axis_aligned::PositiveAxis1; 251 | pub use axis_aligned::SignedAxis; 252 | pub use axis_aligned::SignedAxis0; 253 | pub use axis_aligned::SignedAxis1; 254 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | //! ## Math types 2 | 3 | /// Numeric type representing a coordinate. 4 | pub trait Num { 5 | /// Wide signed type for differences of [`Self::U`] values. 6 | type I2: Copy + Eq + Ord + core::fmt::Debug; 7 | /// Unsigned type for absolute offsets. 8 | type U: Copy + Eq + Ord + core::fmt::Debug; 9 | /// Wide unsigned type for multiplying offsets. 10 | type U2: Copy + Eq + Ord + core::fmt::Debug; 11 | } 12 | 13 | /// A generic 2D point. 14 | pub type Point = (T, T); 15 | 16 | /// Absolute offset between two [points](Point). 17 | pub type Delta = (::U, ::U); 18 | 19 | /// Product between two [`Delta`] offsets. 20 | pub type Delta2 = (::U2, ::U2); 21 | 22 | macro_rules! num_impl { 23 | ($([$i:ty, $i2:ty, $u:ty, $u2:ty]$(,)?)+) => { 24 | $( 25 | impl Num for $i { 26 | type I2 = $i2; 27 | type U = $u; 28 | type U2 = $u2; 29 | } 30 | impl Num for $u { 31 | type I2 = $i2; 32 | type U = Self; 33 | type U2 = $u2; 34 | } 35 | )+ 36 | }; 37 | } 38 | 39 | num_impl!([i8, i16, u8, u16], [i16, i32, u16, u32], [i32, i64, u32, u64], [i64, i128, u64, u128],); 40 | #[cfg(target_pointer_width = "16")] 41 | num_impl!([isize, i32, usize, u32]); 42 | #[cfg(target_pointer_width = "32")] 43 | num_impl!([isize, i64, usize, u64]); 44 | #[cfg(target_pointer_width = "64")] 45 | num_impl!([isize, i128, usize, u128]); 46 | 47 | /// Generic math functions. 48 | pub struct Math(T); 49 | 50 | macro_rules! min_math_impl { 51 | ($T:ty) => { 52 | impl Math<$T> { 53 | /// Subtracts two signed integers, returning the unsigned difference. 54 | /// 55 | /// *`min` must be less or equal to `max`.* 56 | #[inline(always)] 57 | pub const fn delta(max: $T, min: $T) -> <$T as Num>::U { 58 | debug_assert!(min <= max); 59 | #[allow(clippy::cast_sign_loss)] 60 | <$T as Num>::U::wrapping_sub(max as _, min as _) 61 | } 62 | } 63 | }; 64 | } 65 | 66 | min_math_impl!(i8); 67 | min_math_impl!(u8); 68 | min_math_impl!(i16); 69 | min_math_impl!(u16); 70 | min_math_impl!(i32); 71 | min_math_impl!(u32); 72 | min_math_impl!(i64); 73 | min_math_impl!(u64); 74 | min_math_impl!(isize); 75 | min_math_impl!(usize); 76 | 77 | macro_rules! math_impl { 78 | ($T:ty) => { 79 | impl Math<$T> { 80 | /// Subtracts two unsigned integers, returning the wide signed difference. 81 | #[inline(always)] 82 | pub const fn error(a: <$T as Num>::U, b: <$T as Num>::U) -> <$T as Num>::I2 { 83 | <$T as Num>::I2::wrapping_sub(a as _, b as _) 84 | } 85 | 86 | /// Multiplies two narrow unsigned integers, widening the result. 87 | #[inline(always)] 88 | pub const fn wide_mul(a: <$T as Num>::U, b: <$T as Num>::U) -> <$T as Num>::U2 { 89 | <$T as Num>::U2::wrapping_mul(a as _, b as _) 90 | } 91 | 92 | /// Doubles a narrow unsigned integer, widening the result. 93 | #[inline(always)] 94 | pub const fn double(a: <$T as Num>::U) -> <$T as Num>::U2 { 95 | <$T as Num>::U2::wrapping_shl(a as _, 1) 96 | } 97 | 98 | /// Divides an unsigned integer by 2 and returns the quotient with the remainder. 99 | #[inline(always)] 100 | pub const fn half(a: <$T as Num>::U) -> (<$T as Num>::U, <$T as Num>::U) { 101 | (a.wrapping_shr(1), a & 1) 102 | } 103 | 104 | /// Divides a wide unsigned integer by a non-zero narrow unsigned integer, 105 | /// returning the narrow quotient and remainder. 106 | /// 107 | /// ### Safety 108 | /// The divisor must be non-zero, and the quotient must fit into the narrow type. 109 | #[inline(always)] 110 | pub const unsafe fn div_rem( 111 | a: <$T as Num>::U2, 112 | b: <$T as Num>::U, 113 | ) -> (<$T as Num>::U, <$T as Num>::U) { 114 | debug_assert!(b != 0); 115 | let (Some(q), Some(r)) = ( 116 | <$T as Num>::U2::checked_div(a, b as _), 117 | <$T as Num>::U2::checked_rem(a, b as _), 118 | ) else { 119 | core::hint::unreachable_unchecked() 120 | }; 121 | debug_assert!(q <= <$T as Num>::U::MAX as _); 122 | #[allow(clippy::cast_possible_truncation)] 123 | (q as _, r as _) 124 | } 125 | } 126 | }; 127 | } 128 | 129 | math_impl!(i8); 130 | math_impl!(u8); 131 | math_impl!(i16); 132 | math_impl!(u16); 133 | math_impl!(i32); 134 | math_impl!(u32); 135 | #[cfg(feature = "octant_64")] 136 | math_impl!(i64); 137 | #[cfg(feature = "octant_64")] 138 | math_impl!(u64); 139 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 140 | math_impl!(isize); 141 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 142 | math_impl!(usize); 143 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 144 | math_impl!(isize); 145 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 146 | math_impl!(usize); 147 | 148 | #[cfg(test)] 149 | mod static_tests { 150 | use super::*; 151 | 152 | #[cfg(target_pointer_width = "16")] 153 | #[test] 154 | const fn isize_16_bit_is_num() { 155 | static_assertions::assert_impl_all!(isize: Num); 156 | static_assertions::assert_type_eq_all!(::I2, i32); 157 | static_assertions::assert_type_eq_all!(::U2, u32); 158 | } 159 | 160 | #[cfg(target_pointer_width = "32")] 161 | #[test] 162 | const fn isize_32_bit_is_num() { 163 | static_assertions::assert_impl_all!(isize: Num); 164 | static_assertions::assert_type_eq_all!(::I2, i64); 165 | static_assertions::assert_type_eq_all!(::U2, u64); 166 | } 167 | 168 | #[cfg(target_pointer_width = "64")] 169 | #[test] 170 | const fn isize_64_bit_is_num() { 171 | static_assertions::assert_impl_all!(isize: Num); 172 | static_assertions::assert_type_eq_all!(::I2, i128); 173 | static_assertions::assert_type_eq_all!(::U2, u128); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/octant.rs: -------------------------------------------------------------------------------- 1 | //! ## Octant iterators 2 | 3 | use crate::clip::Clip; 4 | use crate::math::{Delta, Math, Num, Point}; 5 | use crate::symmetry::{fx, fy, xy}; 6 | use crate::utils::{map, reject_if}; 7 | use crate::{axis_aligned, diagonal}; 8 | 9 | mod clip; 10 | 11 | //////////////////////////////////////////////////////////////////////////////////////////////////// 12 | // Octant iterators 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// 14 | 15 | /// Iterator over a line segment in the given **octant**, 16 | /// backed by one of the eight cases of [Bresenham's algorithm][1]. 17 | /// 18 | /// An octant is defined by its symmetries relative to [`Octant0`]: 19 | /// - `FX`: flip the `x` axis if `true`. 20 | /// - `FY`: flip the `y` axis if `true`. 21 | /// - `SWAP`: swap the `x` and `y` axes if `true`. 22 | /// 23 | /// [1]: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 24 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 25 | pub struct Octant { 26 | x: T, 27 | y: T, 28 | error: T::I2, 29 | dx: T::U, 30 | dy: T::U, 31 | end: T, 32 | } 33 | 34 | /// Iterator over a line segment in the 35 | /// [octant](Octant) where `x` and `y` **both increase**, 36 | /// with `x` changing faster than `y` *(gentle slope)*. 37 | /// 38 | /// Covers line segments spanning the `(0°, 45°]` sector. 39 | pub type Octant0 = Octant; 40 | 41 | /// Iterator over a line segment in the 42 | /// [octant](Octant) where `x` and `y` **both increase**, 43 | /// with `y` changing faster than `x` *(steep slope)*. 44 | /// 45 | /// Covers line segments spanning the `(45°, 90°)` sector. 46 | pub type Octant1 = Octant; 47 | 48 | /// Iterator over a line segment in the 49 | /// [octant](Octant) where `x` **increases** and `y` **decreases**, 50 | /// with `x` changing faster than `y` *(gentle slope)*. 51 | /// 52 | /// Covers line segments spanning the `[315°, 360°)` sector. 53 | pub type Octant2 = Octant; 54 | 55 | /// Iterator over a line segment in the 56 | /// [octant](Octant) where `x` **increases** and `y` **decreases**, 57 | /// with `y` changing faster than `x` *(steep slope)*. 58 | /// 59 | /// Covers line segments spanning the `(270°, 315°)` sector. 60 | pub type Octant3 = Octant; 61 | 62 | /// Iterator over a line segment in the 63 | /// [octant](Octant) where `x` **decreases** and `y` **increases**, 64 | /// with `x` changing faster than `y` *(gentle slope)*. 65 | /// 66 | /// Covers line segments spanning the `[135°, 180°)` sector. 67 | pub type Octant4 = Octant; 68 | 69 | /// Iterator over a line segment in the 70 | /// [octant](Octant) where `x` **decreases** and `y` **increases**, 71 | /// with `y` changing faster than `x` *(steep slope)*. 72 | /// 73 | /// Covers line segments spanning the `(90°, 135°)` sector. 74 | pub type Octant5 = Octant; 75 | 76 | /// Iterator over a line segment in the 77 | /// [octant](Octant) where `x` and `y` **both decrease**, 78 | /// with `x` changing faster than `y` *(gentle slope)*. 79 | /// 80 | /// Covers line segments spanning the `(180°, 225°]` sector. 81 | pub type Octant6 = Octant; 82 | 83 | /// Iterator over a line segment in the 84 | /// [octant](Octant) where `x` and `y` **both decrease**, 85 | /// with `y` changing faster than `x` *(steep slope)*. 86 | /// 87 | /// Covers line segments spanning the `(225°, 270°)` sector. 88 | pub type Octant7 = Octant; 89 | 90 | macro_rules! octant_impl { 91 | ($T:ty) => { 92 | impl Octant { 93 | #[inline(always)] 94 | #[must_use] 95 | const fn new_inner( 96 | (x1, y1): Point<$T>, 97 | (x2, y2): Point<$T>, 98 | (dx, dy): Delta<$T>, 99 | ) -> Self { 100 | let (half_du, r) = Math::<$T>::half(xy!(dx, dy)); 101 | let error = Math::<$T>::error(xy!(dy, dx), half_du.wrapping_add(r)); 102 | let end = xy!(x2, y2); 103 | Self { x: x1, y: y1, error, dx, dy, end } 104 | } 105 | 106 | #[inline(always)] 107 | #[must_use] 108 | const fn covers((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> Option> { 109 | let (u1, u2) = fx!((x1, x2), (x2, x1)); 110 | let dx = if u1 < u2 { 111 | Math::<$T>::delta(u2, u1) 112 | } else { 113 | return None; 114 | }; 115 | let (v1, v2) = fy!((y1, y2), (y2, y1)); 116 | let dy = if v1 < v2 { 117 | Math::<$T>::delta(v2, v1) 118 | } else { 119 | return None; 120 | }; 121 | reject_if!(xy!(dx < dy, dy <= dx)); 122 | Some((dx, dy)) 123 | } 124 | 125 | /// Returns an iterator over a *half-open* line segment 126 | /// if it is covered by the given [octant](Octant), 127 | /// otherwise returns [`None`]. 128 | /// 129 | /// **Note**: `(x2, y2)` is not included. 130 | #[inline] 131 | #[must_use] 132 | pub const fn new((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> Option { 133 | let Some(delta) = Self::covers((x1, y1), (x2, y2)) else { 134 | return None; 135 | }; 136 | Some(Self::new_inner((x1, y1), (x2, y2), delta)) 137 | } 138 | 139 | /// Returns an iterator over a *half-open* line segment, 140 | /// if it is covered by the [octant](Octant), 141 | /// clipped to the [rectangular region](Clip). 142 | /// 143 | /// Returns [`None`] if the line segment is not covered by the octant, 144 | /// or if the line segment does not intersect the clipping region. 145 | /// 146 | /// **Note**: `(x2, y2)` is not included. 147 | #[inline] 148 | #[must_use] 149 | pub const fn clip( 150 | (x1, y1): Point<$T>, 151 | (x2, y2): Point<$T>, 152 | clip: &Clip<$T>, 153 | ) -> Option { 154 | let &Clip { wx1, wy1, wx2, wy2 } = clip; 155 | let (u1, u2) = fx!((x1, x2), (x2, x1)); 156 | // TODO: strict comparison for closed line segments 157 | reject_if!(xy!(u2 <= wx1, u2 < wx1) || wx2 < u1); 158 | let (v1, v2) = fy!((y1, y2), (y2, y1)); 159 | reject_if!(xy!(v2 < wy1, v2 <= wy1) || wy2 < v1); 160 | let Some(delta) = Self::covers((x1, y1), (x2, y2)) else { 161 | return None; 162 | }; 163 | Self::clip_inner((x1, y1), (x2, y2), delta, clip) 164 | } 165 | 166 | /// Returns `true` if the iterator has terminated. 167 | #[inline] 168 | #[must_use] 169 | pub const fn is_done(&self) -> bool { 170 | let (a, b) = xy!( 171 | fx!((self.end, self.x), (self.x, self.end)), 172 | fy!((self.end, self.y), (self.y, self.end)) 173 | ); 174 | a <= b 175 | } 176 | 177 | /// Returns the remaining length of this iterator. 178 | #[inline] 179 | #[must_use] 180 | pub const fn length(&self) -> <$T as Num>::U { 181 | let (a, b) = xy!( 182 | fx!((self.end, self.x), (self.x, self.end)), 183 | fy!((self.end, self.y), (self.y, self.end)) 184 | ); 185 | #[allow(clippy::cast_sign_loss)] 186 | <$T as Num>::U::wrapping_sub(a as _, b as _) 187 | } 188 | } 189 | 190 | impl Iterator 191 | for Octant 192 | { 193 | type Item = Point<$T>; 194 | 195 | #[inline] 196 | fn next(&mut self) -> Option { 197 | if self.is_done() { 198 | return None; 199 | } 200 | let (x, y) = (self.x, self.y); 201 | if 0 <= self.error { 202 | xy!( 203 | self.y = fy!(self.y.wrapping_add(1), self.y.wrapping_sub(1)), 204 | self.x = fx!(self.x.wrapping_add(1), self.x.wrapping_sub(1)), 205 | ); 206 | self.error = self.error.wrapping_sub_unsigned(xy!(self.dx, self.dy) as _); 207 | } 208 | xy!( 209 | self.x = fx!(self.x.wrapping_add(1), self.x.wrapping_sub(1)), 210 | self.y = fy!(self.y.wrapping_add(1), self.y.wrapping_sub(1)), 211 | ); 212 | self.error = self.error.wrapping_add_unsigned(xy!(self.dy, self.dx) as _); 213 | Some((x, y)) 214 | } 215 | 216 | #[inline] 217 | fn size_hint(&self) -> (usize, Option) { 218 | #[allow(unreachable_patterns)] 219 | match usize::try_from(self.length()) { 220 | Ok(length) => (length, Some(length)), 221 | Err(_) => (usize::MAX, None), 222 | } 223 | } 224 | } 225 | 226 | impl core::iter::FusedIterator 227 | for Octant 228 | { 229 | } 230 | }; 231 | } 232 | 233 | octant_impl!(i8); 234 | octant_impl!(u8); 235 | octant_impl!(i16); 236 | octant_impl!(u16); 237 | octant_impl!(i32); 238 | octant_impl!(u32); 239 | #[cfg(feature = "octant_64")] 240 | octant_impl!(i64); 241 | #[cfg(feature = "octant_64")] 242 | octant_impl!(u64); 243 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 244 | octant_impl!(isize); 245 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 246 | octant_impl!(usize); 247 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 248 | octant_impl!(isize); 249 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 250 | octant_impl!(usize); 251 | 252 | macro_rules! octant_exact_size_iter_impl { 253 | ($T:ty) => { 254 | impl ExactSizeIterator 255 | for Octant 256 | { 257 | #[cfg(feature = "is_empty")] 258 | #[inline] 259 | fn is_empty(&self) -> bool { 260 | self.is_done() 261 | } 262 | } 263 | }; 264 | } 265 | 266 | octant_exact_size_iter_impl!(i8); 267 | octant_exact_size_iter_impl!(u8); 268 | octant_exact_size_iter_impl!(i16); 269 | octant_exact_size_iter_impl!(u16); 270 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 271 | octant_exact_size_iter_impl!(i32); 272 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 273 | octant_exact_size_iter_impl!(u32); 274 | #[cfg(feature = "octant_64")] 275 | octant_exact_size_iter_impl!(i64); 276 | #[cfg(feature = "octant_64")] 277 | octant_exact_size_iter_impl!(u64); 278 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 279 | octant_exact_size_iter_impl!(isize); 280 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 281 | octant_exact_size_iter_impl!(usize); 282 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 283 | octant_exact_size_iter_impl!(isize); 284 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 285 | octant_exact_size_iter_impl!(usize); 286 | 287 | //////////////////////////////////////////////////////////////////////////////////////////////////// 288 | // Arbitrary iterator 289 | //////////////////////////////////////////////////////////////////////////////////////////////////// 290 | 291 | /// Iterator over an arbitrary line segment. 292 | /// 293 | /// Chooses a specialized iterator variant **at runtime** based 294 | /// on the orientation and direction of the line segment. 295 | /// 296 | /// If you know the orientation of the line segment, use one of the [octant](Octant), 297 | /// [diagonal](crate::Diagonal), or [axis-aligned](crate::Axis) iterators. 298 | /// 299 | /// **Note**: an optimized implementation of [`Iterator::fold`] is provided. 300 | /// This makes [`Iterator::for_each`] faster than a `for` loop, since it chooses 301 | /// the underlying iterator only once instead of on every call to [`Iterator::next`]. 302 | #[derive(Clone, PartialEq, Eq, Debug)] 303 | pub enum AnyOctant { 304 | /// Horizontal line segment at `0°`, see [`PositiveAxis0`](crate::PositiveAxis0). 305 | PositiveAxis0(axis_aligned::PositiveAxis0), 306 | /// Vertical line segment at `90°`, see [`NegativeAxis0`](crate::NegativeAxis0). 307 | NegativeAxis0(axis_aligned::NegativeAxis0), 308 | /// Horizontal line segment at `180°`, see [`PositiveAxis1`](crate::PositiveAxis1). 309 | PositiveAxis1(axis_aligned::PositiveAxis1), 310 | /// Vertical line segment at `270°`, see [`NegativeAxis1`](crate::NegativeAxis1). 311 | NegativeAxis1(axis_aligned::NegativeAxis1), 312 | /// Diagonal line segment at `45°`, see [`Diagonal0`](crate::Diagonal0). 313 | Diagonal0(diagonal::Diagonal0), 314 | /// Diagonal line segment at `315°`, see [`Diagonal1`](crate::Diagonal1). 315 | Diagonal1(diagonal::Diagonal1), 316 | /// Diagonal line segment at `135°`, see [`Diagonal2`](crate::Diagonal2). 317 | Diagonal2(diagonal::Diagonal2), 318 | /// Diagonal line segment at `225°`, see [`Diagonal3`](crate::Diagonal3). 319 | Diagonal3(diagonal::Diagonal3), 320 | /// Gently-sloped line segment in `(0°, 45°)`, see [`Octant0`]. 321 | Octant0(Octant0), 322 | /// Steeply-sloped line segment in `(45°, 90°)`, see [`Octant1`]. 323 | Octant1(Octant1), 324 | /// Gently-sloped line segment in `(315°, 360°)`, see [`Octant2`]. 325 | Octant2(Octant2), 326 | /// Steeply-sloped line segment in `(270°, 315°)`, see [`Octant3`]. 327 | Octant3(Octant3), 328 | /// Gently-sloped line segment in `(135°, 180°)`, see [`Octant4`]. 329 | Octant4(Octant4), 330 | /// Steeply-sloped line segment in `(90°, 135°)`, see [`Octant5`]. 331 | Octant5(Octant5), 332 | /// Gently-sloped line segment in `(180°, 225°)`, see [`Octant6`]. 333 | Octant6(Octant6), 334 | /// Steeply-sloped line segment in `(225°, 270°)`, see [`Octant7`]. 335 | Octant7(Octant7), 336 | } 337 | 338 | macro_rules! delegate { 339 | ($self:ident, $me:ident => $call:expr) => { 340 | match $self { 341 | Self::PositiveAxis0($me) => $call, 342 | Self::NegativeAxis0($me) => $call, 343 | Self::PositiveAxis1($me) => $call, 344 | Self::NegativeAxis1($me) => $call, 345 | Self::Diagonal0($me) => $call, 346 | Self::Diagonal1($me) => $call, 347 | Self::Diagonal2($me) => $call, 348 | Self::Diagonal3($me) => $call, 349 | Self::Octant0($me) => $call, 350 | Self::Octant1($me) => $call, 351 | Self::Octant2($me) => $call, 352 | Self::Octant3($me) => $call, 353 | Self::Octant4($me) => $call, 354 | Self::Octant5($me) => $call, 355 | Self::Octant6($me) => $call, 356 | Self::Octant7($me) => $call, 357 | } 358 | }; 359 | } 360 | 361 | macro_rules! octant { 362 | ($Octant:ident, $T:ty, $p1:expr, $p2:expr, $delta:expr) => { 363 | Self::$Octant($Octant::<$T>::new_inner($p1, $p2, $delta)) 364 | }; 365 | ($Octant:ident, $T:ty, $p1:expr, $p2:expr, $delta:expr, $clip:expr) => { 366 | map!($Octant::<$T>::clip_inner($p1, $p2, $delta, $clip), Self::$Octant) 367 | }; 368 | } 369 | 370 | macro_rules! any_octant_impl { 371 | ($T:ty) => { 372 | impl AnyOctant<$T> { 373 | /// Returns an iterator over an arbitrary *half-open* line segment. 374 | #[inline] 375 | #[must_use] 376 | pub const fn new((x1, y1): Point<$T>, (x2, y2): Point<$T>) -> Self { 377 | use diagonal::{Diagonal0, Diagonal1, Diagonal2, Diagonal3}; 378 | if y1 == y2 { 379 | use axis_aligned::Axis0; 380 | return match Axis0::<$T>::new(y1, x1, x2) { 381 | Axis0::Positive(me) => Self::PositiveAxis0(me), 382 | Axis0::Negative(me) => Self::NegativeAxis0(me), 383 | }; 384 | } 385 | if x1 == x2 { 386 | use axis_aligned::Axis1; 387 | return match Axis1::<$T>::new(x1, y1, y2) { 388 | Axis1::Positive(me) => Self::PositiveAxis1(me), 389 | Axis1::Negative(me) => Self::NegativeAxis1(me), 390 | }; 391 | } 392 | if x1 < x2 { 393 | let dx = Math::<$T>::delta(x2, x1); 394 | if y1 < y2 { 395 | let dy = Math::<$T>::delta(y2, y1); 396 | if dy < dx { 397 | return octant!(Octant0, $T, (x1, y1), (x2, y2), (dx, dy)); 398 | } 399 | if dx < dy { 400 | return octant!(Octant1, $T, (x1, y1), (x2, y2), (dx, dy)); 401 | } 402 | return diagonal::quadrant!(Diagonal0, $T, (x1, y1), x2); 403 | } 404 | let dy = Math::<$T>::delta(y1, y2); 405 | if dy < dx { 406 | return octant!(Octant2, $T, (x1, y1), (x2, y2), (dx, dy)); 407 | } 408 | if dx < dy { 409 | return octant!(Octant3, $T, (x1, y1), (x2, y2), (dx, dy)); 410 | } 411 | return diagonal::quadrant!(Diagonal1, $T, (x1, y1), x2); 412 | } 413 | let dx = Math::<$T>::delta(x1, x2); 414 | if y1 < y2 { 415 | let dy = Math::<$T>::delta(y2, y1); 416 | if dy < dx { 417 | return octant!(Octant4, $T, (x1, y1), (x2, y2), (dx, dy)); 418 | } 419 | if dx < dy { 420 | return octant!(Octant5, $T, (x1, y1), (x2, y2), (dx, dy)); 421 | } 422 | return diagonal::quadrant!(Diagonal2, $T, (x1, y1), x2); 423 | } 424 | let dy = Math::<$T>::delta(y1, y2); 425 | if dy < dx { 426 | return octant!(Octant6, $T, (x1, y1), (x2, y2), (dx, dy)); 427 | } 428 | if dx < dy { 429 | return octant!(Octant7, $T, (x1, y1), (x2, y2), (dx, dy)); 430 | } 431 | return diagonal::quadrant!(Diagonal3, $T, (x1, y1), x2); 432 | } 433 | 434 | /// Clips an arbitrary *half-open* line segment to a [rectangular region](Clip), 435 | /// and returns an iterator over it. 436 | /// 437 | /// Returns [`None`] if the line segment does not intersect the clipping region. 438 | #[inline] 439 | #[must_use] 440 | pub const fn clip( 441 | (x1, y1): Point<$T>, 442 | (x2, y2): Point<$T>, 443 | clip: &Clip<$T>, 444 | ) -> Option { 445 | use diagonal::{Diagonal0, Diagonal1, Diagonal2, Diagonal3}; 446 | if y1 == y2 { 447 | use axis_aligned::Axis0; 448 | return map!( 449 | Axis0::<$T>::clip(y1, x1, x2, clip), 450 | me => match me { 451 | Axis0::Positive(me) => Self::PositiveAxis0(me), 452 | Axis0::Negative(me) => Self::NegativeAxis0(me), 453 | } 454 | ); 455 | } 456 | if x1 == x2 { 457 | use axis_aligned::Axis1; 458 | return map!( 459 | Axis1::<$T>::clip(x1, y1, y2, clip), 460 | me => match me { 461 | Axis1::Positive(me) => Self::PositiveAxis1(me), 462 | Axis1::Negative(me) => Self::NegativeAxis1(me), 463 | } 464 | ); 465 | } 466 | let &Clip { wx1, wy1, wx2, wy2 } = clip; 467 | if x1 < x2 { 468 | reject_if!(x2 < wx1 || wx2 < x1); 469 | let dx = Math::<$T>::delta(x2, x1); 470 | if y1 < y2 { 471 | reject_if!(y2 < wy1 || wy2 < y1); 472 | let dy = Math::<$T>::delta(y2, y1); 473 | if dy < dx { 474 | // TODO: strict comparison for closed line segments 475 | reject_if!(x2 == wx1); 476 | return octant!(Octant0, $T, (x1, y1), (x2, y2), (dx, dy), clip); 477 | } 478 | if dx < dy { 479 | reject_if!(y2 == wy1); 480 | return octant!(Octant1, $T, (x1, y1), (x2, y2), (dx, dy), clip); 481 | } 482 | return diagonal::quadrant!(Diagonal0, $T, (x1, y1), (x2, y2), clip); 483 | } 484 | reject_if!(y1 < wy1 || wy2 < y2); 485 | let dy = Math::<$T>::delta(y1, y2); 486 | if dy < dx { 487 | reject_if!(x2 == wx1); 488 | return octant!(Octant2, $T, (x1, y1), (x2, y2), (dx, dy), clip); 489 | } 490 | if dx < dy { 491 | reject_if!(y2 == wy2); 492 | return octant!(Octant3, $T, (x1, y1), (x2, y2), (dx, dy), clip); 493 | } 494 | return diagonal::quadrant!(Diagonal1, $T, (x1, y1), (x2, y2), clip); 495 | } 496 | reject_if!(x1 < wx1 || wx2 < x2); 497 | let dx = Math::<$T>::delta(x1, x2); 498 | if y1 < y2 { 499 | reject_if!(y2 < wy1 || wy2 < y1); 500 | let dy = Math::<$T>::delta(y2, y1); 501 | if dy < dx { 502 | reject_if!(x2 == wx2); 503 | return octant!(Octant4, $T, (x1, y1), (x2, y2), (dx, dy), clip); 504 | } 505 | if dx < dy { 506 | reject_if!(y2 == wy1); 507 | return octant!(Octant5, $T, (x1, y1), (x2, y2), (dx, dy), clip); 508 | } 509 | return diagonal::quadrant!(Diagonal2, $T, (x1, y1), (x2, y2), clip); 510 | } 511 | reject_if!(y1 < wy1 || wy2 < y2); 512 | let dy = Math::<$T>::delta(y1, y2); 513 | if dy < dx { 514 | reject_if!(x2 == wx2); 515 | return octant!(Octant6, $T, (x1, y1), (x2, y2), (dx, dy), clip); 516 | } 517 | if dx < dy { 518 | reject_if!(y2 == wy2); 519 | return octant!(Octant7, $T, (x1, y1), (x2, y2), (dx, dy), clip); 520 | } 521 | return diagonal::quadrant!(Diagonal3, $T, (x1, y1), (x2, y2), clip); 522 | } 523 | 524 | /// Returns `true` if the iterator has terminated. 525 | #[inline] 526 | #[must_use] 527 | pub const fn is_done(&self) -> bool { 528 | delegate!(self, me => me.is_done()) 529 | } 530 | 531 | /// Returns the remaining length of this iterator. 532 | #[inline] 533 | #[must_use] 534 | pub const fn length(&self) -> <$T as Num>::U { 535 | delegate!(self, me => me.length()) 536 | } 537 | } 538 | 539 | impl Iterator for AnyOctant<$T> { 540 | type Item = Point<$T>; 541 | 542 | #[inline] 543 | fn next(&mut self) -> Option { 544 | delegate!(self, me => me.next()) 545 | } 546 | 547 | #[inline] 548 | fn size_hint(&self) -> (usize, Option) { 549 | delegate!(self, me => me.size_hint()) 550 | } 551 | 552 | #[cfg(feature = "try_fold")] 553 | #[inline] 554 | fn try_fold(&mut self, init: B, f: F) -> R 555 | where 556 | Self: Sized, 557 | F: FnMut(B, Self::Item) -> R, 558 | R: core::ops::Try, 559 | { 560 | delegate!(self, me => me.try_fold(init, f)) 561 | } 562 | 563 | #[inline] 564 | fn fold(self, init: B, f: F) -> B 565 | where 566 | Self: Sized, 567 | F: FnMut(B, Self::Item) -> B, 568 | { 569 | delegate!(self, me => me.fold(init, f)) 570 | } 571 | } 572 | 573 | impl core::iter::FusedIterator for AnyOctant<$T> {} 574 | }; 575 | } 576 | 577 | any_octant_impl!(i8); 578 | any_octant_impl!(u8); 579 | any_octant_impl!(i16); 580 | any_octant_impl!(u16); 581 | any_octant_impl!(i32); 582 | any_octant_impl!(u32); 583 | #[cfg(feature = "octant_64")] 584 | any_octant_impl!(i64); 585 | #[cfg(feature = "octant_64")] 586 | any_octant_impl!(u64); 587 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 588 | any_octant_impl!(isize); 589 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 590 | any_octant_impl!(usize); 591 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 592 | any_octant_impl!(isize); 593 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 594 | any_octant_impl!(usize); 595 | 596 | macro_rules! any_octant_exact_size_iter_impl { 597 | ($T:ty) => { 598 | impl ExactSizeIterator for AnyOctant<$T> { 599 | #[cfg(feature = "is_empty")] 600 | #[inline] 601 | fn is_empty(&self) -> bool { 602 | delegate!(self, me => me.is_empty()) 603 | } 604 | } 605 | }; 606 | } 607 | 608 | any_octant_exact_size_iter_impl!(i8); 609 | any_octant_exact_size_iter_impl!(u8); 610 | any_octant_exact_size_iter_impl!(i16); 611 | any_octant_exact_size_iter_impl!(u16); 612 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 613 | any_octant_exact_size_iter_impl!(i32); 614 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 615 | any_octant_exact_size_iter_impl!(u32); 616 | #[cfg(feature = "octant_64")] 617 | any_octant_exact_size_iter_impl!(i64); 618 | #[cfg(feature = "octant_64")] 619 | any_octant_exact_size_iter_impl!(u64); 620 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 621 | any_octant_exact_size_iter_impl!(isize); 622 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 623 | any_octant_exact_size_iter_impl!(usize); 624 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 625 | any_octant_exact_size_iter_impl!(isize); 626 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 627 | any_octant_exact_size_iter_impl!(usize); 628 | 629 | #[cfg(test)] 630 | mod static_tests { 631 | use super::*; 632 | use static_assertions::assert_impl_all; 633 | 634 | #[test] 635 | const fn iterator_8() { 636 | assert_impl_all!(Octant0: ExactSizeIterator); 637 | assert_impl_all!(Octant0: ExactSizeIterator); 638 | assert_impl_all!(AnyOctant: ExactSizeIterator); 639 | assert_impl_all!(AnyOctant: ExactSizeIterator); 640 | } 641 | 642 | #[test] 643 | const fn iterator_16() { 644 | assert_impl_all!(Octant0: ExactSizeIterator); 645 | assert_impl_all!(Octant0: ExactSizeIterator); 646 | assert_impl_all!(AnyOctant: ExactSizeIterator); 647 | assert_impl_all!(AnyOctant: ExactSizeIterator); 648 | } 649 | 650 | #[test] 651 | const fn iterator_32() { 652 | #[cfg(target_pointer_width = "16")] 653 | { 654 | use static_assertions::assert_not_impl_any; 655 | 656 | assert_impl_all!(Octant0: Iterator); 657 | assert_impl_all!(Octant0: Iterator); 658 | assert_impl_all!(AnyOctant: Iterator); 659 | assert_impl_all!(AnyOctant: Iterator); 660 | assert_not_impl_any!(Octant0: ExactSizeIterator); 661 | assert_not_impl_any!(Octant0: ExactSizeIterator); 662 | assert_not_impl_any!(AnyOctant: ExactSizeIterator); 663 | assert_not_impl_any!(AnyOctant: ExactSizeIterator); 664 | } 665 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] 666 | { 667 | assert_impl_all!(Octant0: ExactSizeIterator); 668 | assert_impl_all!(Octant0: ExactSizeIterator); 669 | assert_impl_all!(AnyOctant: ExactSizeIterator); 670 | assert_impl_all!(AnyOctant: ExactSizeIterator); 671 | } 672 | } 673 | 674 | #[test] 675 | const fn iterator_64() { 676 | #[cfg(feature = "octant_64")] 677 | { 678 | #[cfg(target_pointer_width = "64")] 679 | { 680 | assert_impl_all!(Octant0: ExactSizeIterator); 681 | assert_impl_all!(Octant0: ExactSizeIterator); 682 | assert_impl_all!(AnyOctant: ExactSizeIterator); 683 | assert_impl_all!(AnyOctant: ExactSizeIterator); 684 | } 685 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 686 | { 687 | use static_assertions::assert_not_impl_any; 688 | 689 | assert_impl_all!(Octant0: Iterator); 690 | assert_impl_all!(Octant0: Iterator); 691 | assert_impl_all!(AnyOctant: Iterator); 692 | assert_impl_all!(AnyOctant: Iterator); 693 | assert_not_impl_any!(Octant0: ExactSizeIterator); 694 | assert_not_impl_any!(Octant0: ExactSizeIterator); 695 | assert_not_impl_any!(AnyOctant: ExactSizeIterator); 696 | assert_not_impl_any!(AnyOctant: ExactSizeIterator); 697 | } 698 | } 699 | #[cfg(not(feature = "octant_64"))] 700 | { 701 | use static_assertions::assert_not_impl_any; 702 | 703 | assert_not_impl_any!(Octant0: Iterator); 704 | assert_not_impl_any!(Octant0: Iterator); 705 | assert_not_impl_any!(AnyOctant: Iterator); 706 | assert_not_impl_any!(AnyOctant: Iterator); 707 | } 708 | } 709 | 710 | #[test] 711 | const fn iterator_pointer_size() { 712 | #[cfg(target_pointer_width = "64")] 713 | { 714 | #[cfg(feature = "octant_64")] 715 | { 716 | assert_impl_all!(Octant0: ExactSizeIterator); 717 | assert_impl_all!(Octant0: ExactSizeIterator); 718 | assert_impl_all!(AnyOctant: ExactSizeIterator); 719 | assert_impl_all!(AnyOctant: ExactSizeIterator); 720 | } 721 | #[cfg(not(feature = "octant_64"))] 722 | { 723 | use static_assertions::assert_not_impl_any; 724 | 725 | assert_not_impl_any!(Octant0: Iterator); 726 | assert_not_impl_any!(Octant0: Iterator); 727 | assert_not_impl_any!(AnyOctant: Iterator); 728 | assert_not_impl_any!(AnyOctant: Iterator); 729 | } 730 | } 731 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 732 | { 733 | assert_impl_all!(Octant0: ExactSizeIterator); 734 | assert_impl_all!(Octant0: ExactSizeIterator); 735 | assert_impl_all!(AnyOctant: ExactSizeIterator); 736 | assert_impl_all!(AnyOctant: ExactSizeIterator); 737 | } 738 | } 739 | } 740 | 741 | #[cfg(test)] 742 | mod tests { 743 | use super::*; 744 | use crate::axis_aligned::{NegativeAxis0, NegativeAxis1, PositiveAxis0, PositiveAxis1}; 745 | use crate::diagonal::{Diagonal0, Diagonal1, Diagonal2, Diagonal3}; 746 | 747 | #[test] 748 | fn axis_aligned_lines_are_special_cased() { 749 | assert_eq!( 750 | AnyOctant::PositiveAxis0(PositiveAxis0::::new(0, 0, 255).unwrap()), 751 | AnyOctant::::new((0, 0), (255, 0)), 752 | ); 753 | assert_eq!( 754 | AnyOctant::PositiveAxis1(PositiveAxis1::::new(0, 0, 255).unwrap()), 755 | AnyOctant::::new((0, 0), (0, 255)), 756 | ); 757 | assert_eq!( 758 | AnyOctant::NegativeAxis0(NegativeAxis0::::new(0, 255, 0).unwrap()), 759 | AnyOctant::::new((255, 0), (0, 0)), 760 | ); 761 | assert_eq!( 762 | AnyOctant::NegativeAxis1(NegativeAxis1::::new(0, 255, 0).unwrap()), 763 | AnyOctant::::new((0, 255), (0, 0)), 764 | ); 765 | } 766 | 767 | #[test] 768 | fn diagonal_lines_are_special_cased() { 769 | assert_eq!( 770 | AnyOctant::::new((0, 0), (255, 255)), 771 | AnyOctant::Diagonal0(Diagonal0::::new((0, 0), (255, 255)).unwrap()), 772 | ); 773 | assert_eq!( 774 | AnyOctant::::new((0, 255), (255, 0)), 775 | AnyOctant::Diagonal1(Diagonal1::::new((0, 255), (255, 0)).unwrap()), 776 | ); 777 | assert_eq!( 778 | AnyOctant::::new((255, 0), (0, 255)), 779 | AnyOctant::Diagonal2(Diagonal2::::new((255, 0), (0, 255)).unwrap()), 780 | ); 781 | assert_eq!( 782 | AnyOctant::::new((255, 255), (0, 0)), 783 | AnyOctant::Diagonal3(Diagonal3::::new((255, 255), (0, 0)).unwrap()), 784 | ); 785 | } 786 | 787 | #[test] 788 | fn exclusive_covers_whole_domain() { 789 | const MAX: u8 = u8::MAX; 790 | for i in 0..=MAX { 791 | assert_eq!(AnyOctant::::new((0, i), (MAX, MAX)).count(), MAX as usize); 792 | assert_eq!(AnyOctant::::new((MAX, MAX), (0, i)).count(), MAX as usize); 793 | assert_eq!(AnyOctant::::new((i, 0), (MAX, MAX)).count(), MAX as usize); 794 | assert_eq!(AnyOctant::::new((MAX, MAX), (i, 0)).count(), MAX as usize); 795 | assert_eq!(AnyOctant::::new((0, MAX), (MAX, i)).count(), MAX as usize); 796 | assert_eq!(AnyOctant::::new((MAX, i), (0, MAX)).count(), MAX as usize); 797 | assert_eq!(AnyOctant::::new((MAX, 0), (i, MAX)).count(), MAX as usize); 798 | assert_eq!(AnyOctant::::new((i, MAX), (MAX, 0)).count(), MAX as usize); 799 | } 800 | } 801 | } 802 | -------------------------------------------------------------------------------- /src/octant/clip.rs: -------------------------------------------------------------------------------- 1 | //! ### Octant clipping 2 | 3 | use super::Octant; 4 | use crate::clip::Clip; 5 | use crate::math::{Delta, Delta2, Math, Num, Point}; 6 | use crate::symmetry::{fx, fy, xy}; 7 | 8 | const O: bool = false; 9 | const I: bool = true; 10 | 11 | type LineCode = (bool, bool, bool, bool); 12 | 13 | const INSIDE_INSIDE: LineCode = (O, O, O, O); 14 | const INSIDE_V_EXIT: LineCode = (O, O, O, I); 15 | const INSIDE_U_EXIT: LineCode = (O, O, I, O); 16 | const INSIDE_UV_EXIT: LineCode = (O, O, I, I); 17 | const V_ENTRY_INSIDE: LineCode = (O, I, O, O); 18 | const V_ENTRY_V_EXIT: LineCode = (O, I, O, I); 19 | const V_ENTRY_U_EXIT: LineCode = (O, I, I, O); 20 | const V_ENTRY_UV_EXIT: LineCode = (O, I, I, I); 21 | const U_ENTRY_INSIDE: LineCode = (I, O, O, O); 22 | const U_ENTRY_V_EXIT: LineCode = (I, O, O, I); 23 | const U_ENTRY_U_EXIT: LineCode = (I, O, I, O); 24 | const U_ENTRY_UV_EXIT: LineCode = (I, O, I, I); 25 | const UV_ENTRY_INSIDE: LineCode = (I, I, O, O); 26 | const UV_ENTRY_V_EXIT: LineCode = (I, I, O, I); 27 | const UV_ENTRY_U_EXIT: LineCode = (I, I, I, O); 28 | const UV_ENTRY_UV_EXIT: LineCode = (I, I, I, I); 29 | 30 | macro_rules! clip_impl { 31 | ($T:ty, $add:ident, $sub:ident) => { 32 | impl Octant { 33 | #[inline(always)] 34 | #[must_use] 35 | const fn enters_u(u1: $T, &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> bool { 36 | xy!(fx!(u1 < wx1, wx2 < u1), fy!(u1 < wy1, wy2 < u1)) 37 | } 38 | 39 | #[inline(always)] 40 | #[must_use] 41 | const fn enters_v(v1: $T, &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> bool { 42 | xy!(fy!(v1 < wy1, wy2 < v1), fx!(v1 < wx1, wx2 < v1)) 43 | } 44 | 45 | #[inline(always)] 46 | #[must_use] 47 | const fn exits_u(u2: $T, &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> bool { 48 | xy!(fx!(wx2 < u2, u2 < wx1), fy!(wy2 < u2, u2 < wy1)) 49 | } 50 | 51 | #[inline(always)] 52 | #[must_use] 53 | const fn exits_v(v2: $T, &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> bool { 54 | xy!(fy!(wy2 < v2, v2 < wy1), fx!(wx2 < v2, v2 < wx1)) 55 | } 56 | 57 | #[allow(non_snake_case)] 58 | #[inline(always)] 59 | #[must_use] 60 | const fn tu1( 61 | u1: $T, 62 | dv: <$T as Num>::U, 63 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 64 | ) -> <$T as Num>::U2 { 65 | let Du1 = xy!( 66 | fx!(Math::<$T>::delta(wx1, u1), Math::<$T>::delta(u1, wx2)), 67 | fy!(Math::<$T>::delta(wy1, u1), Math::<$T>::delta(u1, wy2)), 68 | ); 69 | Math::<$T>::wide_mul(Du1, dv) 70 | } 71 | 72 | #[allow(non_snake_case)] 73 | #[inline(always)] 74 | #[must_use] 75 | const fn tu2( 76 | u1: $T, 77 | dv: <$T as Num>::U, 78 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 79 | ) -> <$T as Num>::U2 { 80 | let Du2 = xy!( 81 | fx!(Math::<$T>::delta(wx2, u1), Math::<$T>::delta(u1, wx1)), 82 | fy!(Math::<$T>::delta(wy2, u1), Math::<$T>::delta(u1, wy1)), 83 | ); 84 | Math::<$T>::wide_mul(Du2, dv) 85 | } 86 | 87 | #[allow(non_snake_case)] 88 | #[inline(always)] 89 | #[must_use] 90 | const fn tv1( 91 | v1: $T, 92 | du: <$T as Num>::U, 93 | half_du: <$T as Num>::U, 94 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 95 | ) -> <$T as Num>::U2 { 96 | let Dv1 = xy!( 97 | fy!(Math::<$T>::delta(wy1, v1), Math::<$T>::delta(v1, wy2)), 98 | fx!(Math::<$T>::delta(wx1, v1), Math::<$T>::delta(v1, wx2)), 99 | ); 100 | Math::<$T>::wide_mul(Dv1, du).wrapping_sub(half_du as _) 101 | } 102 | 103 | #[allow(non_snake_case)] 104 | #[inline(always)] 105 | #[must_use] 106 | const fn tv2( 107 | v1: $T, 108 | du: <$T as Num>::U, 109 | half_du: <$T as Num>::U, 110 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 111 | ) -> <$T as Num>::U2 { 112 | let Dv2 = xy!( 113 | fy!(Math::<$T>::delta(wy2, v1), Math::<$T>::delta(v1, wy1)), 114 | fx!(Math::<$T>::delta(wx2, v1), Math::<$T>::delta(v1, wx1)), 115 | ); 116 | Math::<$T>::wide_mul(Dv2, du).wrapping_add(half_du as _) 117 | } 118 | 119 | #[inline(always)] 120 | #[must_use] 121 | const fn cu1_v( 122 | u1: $T, 123 | tv1: <$T as Num>::U2, 124 | (half_du, dv): Delta<$T>, 125 | mut error: <$T as Num>::I2, 126 | ) -> ($T, <$T as Num>::I2) { 127 | // SAFETY: the line segment is slanted and non-empty, thus dv != 0. 128 | let (mut q, r) = unsafe { Math::<$T>::div_rem(tv1, dv) }; 129 | error = error.wrapping_sub(half_du as _).$sub(r as _); 130 | #[allow(unused_comparisons)] 131 | if 0 < r { 132 | q = xy!( 133 | fx!(q.wrapping_add(1), q.wrapping_add(1)), 134 | fy!(q.wrapping_add(1), q.wrapping_add(1)) 135 | ); 136 | error = error.$add(dv as _); 137 | }; 138 | let cu1 = xy!(fx!(u1.$add(q), u1.$sub(q)), fy!(u1.$add(q), u1.$sub(q)),); 139 | (cu1, error) 140 | } 141 | 142 | #[inline(always)] 143 | #[must_use] 144 | const fn cv1_u( 145 | v1: $T, 146 | tu1: <$T as Num>::U2, 147 | du: <$T as Num>::U, 148 | mut error: <$T as Num>::I2, 149 | ) -> ($T, <$T as Num>::I2) { 150 | // SAFETY: the line segment is slanted and non-empty, thus dv != 0. 151 | let (mut q, r) = unsafe { Math::<$T>::div_rem(tu1, du) }; 152 | error = error.$add(r as _); 153 | if { 154 | let du = du as <$T as Num>::U2; 155 | let r2 = Math::<$T>::double(r); 156 | du <= r2 157 | } { 158 | q = q.wrapping_add(1); 159 | error = error.$sub(du as _); 160 | }; 161 | let cv1 = xy!(fy!(v1.$add(q), v1.$sub(q)), fx!(v1.$add(q), v1.$sub(q)),); 162 | (cv1, error) 163 | } 164 | 165 | /// Clips at vertical entry. 166 | #[inline(always)] 167 | #[must_use] 168 | const fn c1_u( 169 | v1: $T, 170 | tu1: <$T as Num>::U2, 171 | du: <$T as Num>::U, 172 | error: <$T as Num>::I2, 173 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 174 | ) -> (Point<$T>, <$T as Num>::I2) { 175 | let cu1 = xy!(fx!(wx1, wx2), fy!(wy1, wy2)); 176 | let (cv1, error) = Self::cv1_u(v1, tu1, du, error); 177 | ((cu1, cv1), error) 178 | } 179 | 180 | /// Clips at horizontal entry. 181 | #[inline(always)] 182 | #[must_use] 183 | const fn c1_v( 184 | u1: $T, 185 | tv1: <$T as Num>::U2, 186 | (half_du, dv): Delta<$T>, 187 | error: <$T as Num>::I2, 188 | &Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>, 189 | ) -> (Point<$T>, <$T as Num>::I2) { 190 | let (cu1, error) = Self::cu1_v(u1, tv1, (half_du, dv), error); 191 | let cv1 = xy!(fy!(wy1, wy2), fx!(wx1, wx2)); 192 | ((cu1, cv1), error) 193 | } 194 | 195 | #[inline(always)] 196 | #[must_use] 197 | const fn c1_uv( 198 | (u1, v1): Point<$T>, 199 | (tu1, tv1): Delta2<$T>, 200 | (du, dv): Delta<$T>, 201 | half_du: <$T as Num>::U, 202 | error: <$T as Num>::I2, 203 | clip: &Clip<$T>, 204 | ) -> (Point<$T>, <$T as Num>::I2) { 205 | if tv1 < tu1 { 206 | Self::c1_u(v1, tu1, du, error, clip) 207 | } else { 208 | Self::c1_v(u1, tv1, (half_du, dv), error, clip) 209 | } 210 | } 211 | 212 | #[inline(always)] 213 | #[must_use] 214 | const fn cu2_u(&Clip { wx1, wy1, wx2, wy2 }: &Clip<$T>) -> $T { 215 | // it is overflow-safe to add/sub 1 because of the exit condition 216 | xy!( 217 | fx!(wx2.wrapping_add(1), wx1.wrapping_sub(1)), 218 | fy!(wy2.wrapping_add(1), wy1.wrapping_sub(1)) 219 | ) 220 | } 221 | 222 | #[inline(always)] 223 | #[must_use] 224 | const fn cu2_v( 225 | u1: $T, 226 | tv2: <$T as Num>::U2, 227 | dv: <$T as Num>::U, 228 | r0: <$T as Num>::U, 229 | ) -> $T { 230 | // SAFETY: the line segment is slanted and non-empty, thus dv != 0. 231 | let (mut q, r) = unsafe { Math::<$T>::div_rem(tv2, dv) }; 232 | if r == 0 && r0 == 0 { 233 | q = q.wrapping_sub(1); 234 | } 235 | // it is overflow-safe to add/sub 1 because of the exit condition 236 | xy!( 237 | fx!(u1.$add(q).wrapping_add(1), u1.$sub(q).wrapping_sub(1)), 238 | fy!(u1.$add(q).wrapping_add(1), u1.$sub(q).wrapping_sub(1)), 239 | ) 240 | } 241 | 242 | #[inline(always)] 243 | #[must_use] 244 | const fn cu2_uv( 245 | u1: $T, 246 | (tu2, tv2): Delta2<$T>, 247 | dv: <$T as Num>::U, 248 | r0: <$T as Num>::U, 249 | clip: &Clip<$T>, 250 | ) -> $T { 251 | if tu2 < tv2 { 252 | Self::cu2_u(clip) 253 | } else { 254 | Self::cu2_v(u1, tv2, dv, r0) 255 | } 256 | } 257 | 258 | #[allow(clippy::too_many_lines)] 259 | #[inline(always)] 260 | #[must_use] 261 | pub(super) const fn clip_inner( 262 | (x1, y1): Point<$T>, 263 | (x2, y2): Point<$T>, 264 | (dx, dy): Delta<$T>, 265 | clip: &Clip<$T>, 266 | ) -> Option { 267 | let (u1, v1) = xy!((x1, y1), (y1, x1)); 268 | let (u2, v2) = xy!((x2, y2), (y2, x2)); 269 | let (du, dv) = xy!((dx, dy), (dy, dx)); 270 | let (half_du, r0) = Math::<$T>::half(du); 271 | let error = Math::<$T>::error(dv, half_du.wrapping_add(r0)); // FIXME: check this 272 | let (((cu1, cv1), error), end) = match ( 273 | Self::enters_u(u1, clip), 274 | Self::enters_v(v1, clip), 275 | Self::exits_u(u2, clip), 276 | Self::exits_v(v2, clip), 277 | ) { 278 | INSIDE_INSIDE => (((u1, v1), error), u2), 279 | INSIDE_V_EXIT => { 280 | let tv2 = Self::tv2(v1, du, half_du, clip); 281 | (((u1, v1), error), Self::cu2_v(u1, tv2, dv, r0)) 282 | } 283 | INSIDE_U_EXIT => (((u1, v1), error), Self::cu2_u(clip)), 284 | INSIDE_UV_EXIT => { 285 | let tu2 = Self::tu2(u1, dv, clip); 286 | let tv2 = Self::tv2(v1, du, half_du, clip); 287 | (((u1, v1), error), Self::cu2_uv(u1, (tu2, tv2), dv, r0, clip)) 288 | } 289 | V_ENTRY_INSIDE => { 290 | let tv1 = Self::tv1(v1, du, half_du, clip); 291 | (Self::c1_v(u1, tv1, (half_du, dv), error, clip), u2) 292 | } 293 | V_ENTRY_V_EXIT => { 294 | let tv1 = Self::tv1(v1, du, half_du, clip); 295 | let tv2 = Self::tv2(v1, du, half_du, clip); 296 | ( 297 | Self::c1_v(u1, tv1, (half_du, dv), error, clip), 298 | Self::cu2_v(u1, tv2, dv, r0), 299 | ) 300 | } 301 | V_ENTRY_U_EXIT => { 302 | let tv1 = Self::tv1(v1, du, half_du, clip); 303 | let tu2 = Self::tu2(u1, dv, clip); 304 | if tu2 < tv1 { 305 | return None; 306 | } 307 | (Self::c1_v(u1, tv1, (half_du, dv), error, clip), Self::cu2_u(clip)) 308 | } 309 | V_ENTRY_UV_EXIT => { 310 | let tv1 = Self::tv1(v1, du, half_du, clip); 311 | let tu2 = Self::tu2(u1, dv, clip); 312 | if tu2 < tv1 { 313 | return None; 314 | } 315 | let tv2 = Self::tv2(v1, du, half_du, clip); 316 | ( 317 | Self::c1_v(u1, tv1, (half_du, dv), error, clip), 318 | Self::cu2_uv(u1, (tu2, tv2), dv, r0, clip), 319 | ) 320 | } 321 | U_ENTRY_INSIDE => { 322 | (Self::c1_u(v1, Self::tu1(u1, dv, clip), du, error, clip), u2) 323 | } 324 | U_ENTRY_V_EXIT => { 325 | let tv2 = Self::tv2(v1, du, half_du, clip); 326 | ( 327 | Self::c1_u(v1, Self::tu1(u1, dv, clip), du, error, clip), 328 | Self::cu2_v(u1, tv2, dv, r0), 329 | ) 330 | } 331 | U_ENTRY_U_EXIT => ( 332 | Self::c1_u(v1, Self::tu1(u1, dv, clip), du, error, clip), 333 | Self::cu2_u(clip), 334 | ), 335 | U_ENTRY_UV_EXIT => { 336 | let tu1 = Self::tu1(u1, dv, clip); 337 | let tv2 = Self::tv2(v1, du, half_du, clip); 338 | if tv2 < tu1 { 339 | return None; 340 | } 341 | let tu2 = Self::tu2(u1, dv, clip); 342 | ( 343 | Self::c1_u(v1, tu1, du, error, clip), 344 | Self::cu2_uv(u1, (tu2, tv2), dv, r0, clip), 345 | ) 346 | } 347 | UV_ENTRY_INSIDE => { 348 | let tu1 = Self::tu1(u1, dv, clip); 349 | let tv1 = Self::tv1(v1, du, half_du, clip); 350 | (Self::c1_uv((u1, v1), (tu1, tv1), (du, dv), half_du, error, clip), u2) 351 | } 352 | UV_ENTRY_V_EXIT => { 353 | let tu1 = Self::tu1(u1, dv, clip); 354 | let tv1 = Self::tv1(v1, du, half_du, clip); 355 | let tv2 = Self::tv2(v1, du, half_du, clip); 356 | ( 357 | Self::c1_uv((u1, v1), (tu1, tv1), (du, dv), half_du, error, clip), 358 | Self::cu2_v(u1, tv2, dv, r0), 359 | ) 360 | } 361 | UV_ENTRY_U_EXIT => { 362 | let tu1 = Self::tu1(u1, dv, clip); 363 | let tv1 = Self::tv1(v1, du, half_du, clip); 364 | ( 365 | Self::c1_uv((u1, v1), (tu1, tv1), (du, dv), half_du, error, clip), 366 | Self::cu2_u(clip), 367 | ) 368 | } 369 | UV_ENTRY_UV_EXIT => { 370 | let tv1 = Self::tv1(v1, du, half_du, clip); 371 | let tu2 = Self::tu2(u1, dv, clip); 372 | if tu2 < tv1 { 373 | return None; 374 | } 375 | let tu1 = Self::tu1(u1, dv, clip); 376 | let tv2 = Self::tv2(v1, du, half_du, clip); 377 | if tv2 < tu1 { 378 | return None; 379 | } 380 | ( 381 | Self::c1_uv((u1, v1), (tu1, tv1), (du, dv), half_du, error, clip), 382 | Self::cu2_uv(u1, (tu2, tv2), dv, r0, clip), 383 | ) 384 | } 385 | }; 386 | let (x, y) = xy!((cu1, cv1), (cv1, cu1)); 387 | Some(Self { x, y, error, dx, dy, end }) 388 | } 389 | } 390 | }; 391 | } 392 | 393 | clip_impl!(i8, wrapping_add_unsigned, wrapping_sub_unsigned); 394 | clip_impl!(u8, wrapping_add, wrapping_sub); 395 | clip_impl!(i16, wrapping_add_unsigned, wrapping_sub_unsigned); 396 | clip_impl!(u16, wrapping_add, wrapping_sub); 397 | clip_impl!(i32, wrapping_add_unsigned, wrapping_sub_unsigned); 398 | clip_impl!(u32, wrapping_add, wrapping_sub); 399 | #[cfg(feature = "octant_64")] 400 | clip_impl!(i64, wrapping_add_unsigned, wrapping_sub_unsigned); 401 | #[cfg(feature = "octant_64")] 402 | clip_impl!(u64, wrapping_add, wrapping_sub); 403 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 404 | clip_impl!(isize, wrapping_add_unsigned, wrapping_sub_unsigned); 405 | #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] 406 | clip_impl!(usize, wrapping_add, wrapping_sub); 407 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 408 | clip_impl!(isize, wrapping_add_unsigned, wrapping_sub_unsigned); 409 | #[cfg(all(target_pointer_width = "64", feature = "octant_64"))] 410 | clip_impl!(usize, wrapping_add, wrapping_sub); 411 | -------------------------------------------------------------------------------- /src/symmetry.rs: -------------------------------------------------------------------------------- 1 | //! ## Symmetry utilities 2 | 3 | /// Selects an expression based on `VERT`. 4 | macro_rules! vh { 5 | ($a:expr, $b:expr$(,)?) => { 6 | if !VERT { 7 | $a 8 | } else { 9 | $b 10 | } 11 | }; 12 | } 13 | 14 | /// Selects an expression based on `SWAP`. 15 | macro_rules! xy { 16 | ($a:expr, $b:expr$(,)?) => { 17 | if !SWAP { 18 | $a 19 | } else { 20 | $b 21 | } 22 | }; 23 | ($a_b:expr) => {{ 24 | let (a, b) = $a_b; 25 | if !SWAP { 26 | a 27 | } else { 28 | b 29 | } 30 | }}; 31 | } 32 | 33 | /// Selects an expression based on `FLIP`. 34 | macro_rules! f { 35 | ($pos:expr, $neg:expr$(,)?) => { 36 | if !FLIP { 37 | $pos 38 | } else { 39 | $neg 40 | } 41 | }; 42 | } 43 | 44 | /// Selects an expression based on `FX`. 45 | macro_rules! fx { 46 | ($pos:expr, $neg:expr$(,)?) => { 47 | if !FX { 48 | $pos 49 | } else { 50 | $neg 51 | } 52 | }; 53 | } 54 | 55 | /// Selects an expression based on `FY`. 56 | macro_rules! fy { 57 | ($pos:expr, $neg:expr$(,)?) => { 58 | if !FY { 59 | $pos 60 | } else { 61 | $neg 62 | } 63 | }; 64 | } 65 | 66 | pub(crate) use {f, fx, fy, vh, xy}; 67 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! ## Utilities 2 | 3 | /// Maps over an [`Option`]. 4 | macro_rules! map { 5 | ($option:expr, $some:pat => $mapped:expr$(,)?) => { 6 | match $option { 7 | None => None, 8 | Some($some) => Some($mapped), 9 | } 10 | }; 11 | ($option:expr, $func:expr$(,)?) => { 12 | match $option { 13 | None => None, 14 | Some(me) => Some($func(me)), 15 | } 16 | }; 17 | } 18 | 19 | /// Short-circuits with [`None`] if the `condition` is `true`. 20 | macro_rules! reject_if { 21 | ($condition:expr) => { 22 | if $condition { 23 | return None; 24 | } 25 | }; 26 | } 27 | 28 | pub(crate) use {map, reject_if}; 29 | -------------------------------------------------------------------------------- /tests/axis_aligned.rs: -------------------------------------------------------------------------------- 1 | //! ## Axis-aligned iterator tests 2 | 3 | mod iterator { 4 | #[test] 5 | fn length_is_correct() { 6 | for v1 in -2..4 { 7 | for v2 in -2..4 { 8 | let length = i8::abs_diff(v1, v2); 9 | assert_eq!(clipline::Axis0::::new(0, v1, v2).length(), length); 10 | assert_eq!(clipline::Axis1::::new(0, v1, v2).length(), length); 11 | } 12 | } 13 | } 14 | 15 | #[test] 16 | fn coordinate_order_is_correct() { 17 | let points = [-2, -1, 0, 1]; 18 | clipline::Axis0::::new(0, -2, 2).enumerate().for_each(|(i, (x, y))| { 19 | assert_eq!(x, points[i]); 20 | assert_eq!(y, 0); 21 | }); 22 | clipline::Axis1::::new(0, -2, 2).enumerate().for_each(|(i, (x, y))| { 23 | assert_eq!(x, 0); 24 | assert_eq!(y, points[i]); 25 | }); 26 | } 27 | 28 | #[test] 29 | fn direction_is_correct() { 30 | clipline::Axis0::::new(0, -2, 2) 31 | .enumerate() 32 | .for_each(|(i, (x, _))| assert_eq!(x, [-2, -1, 0, 1][i])); 33 | clipline::Axis0::::new(0, 2, -2) 34 | .enumerate() 35 | .for_each(|(i, (x, _))| assert_eq!(x, [2, 1, 0, -1][i])); 36 | clipline::Axis1::::new(0, -2, 2) 37 | .enumerate() 38 | .for_each(|(i, (_, y))| assert_eq!(y, [-2, -1, 0, 1][i])); 39 | clipline::Axis1::::new(0, 2, -2) 40 | .enumerate() 41 | .for_each(|(i, (_, y))| assert_eq!(y, [2, 1, 0, -1][i])); 42 | } 43 | 44 | #[test] 45 | fn positive_reverse_is_correct() { 46 | let mut rev = clipline::Axis0::::new(0, 0, 5).rev().collect::>(); 47 | rev.reverse(); 48 | assert_eq!(clipline::Axis0::::new(0, 0, 5).collect::>(), rev); 49 | } 50 | 51 | #[test] 52 | fn positive_double_ended_iteration_is_correct() { 53 | let mut line = clipline::Axis0::::new(0, 0, 2); 54 | assert_eq!(line.next_back(), Some((1, 0))); 55 | assert_eq!(line.next(), Some((0, 0))); 56 | assert!(line.is_done()); 57 | } 58 | 59 | #[test] 60 | fn negative_reverse_is_correct() { 61 | let mut rev = clipline::Axis0::::new(0, 5, 0).rev().collect::>(); 62 | rev.reverse(); 63 | assert_eq!(clipline::Axis0::::new(0, 5, 0).collect::>(), rev); 64 | } 65 | 66 | #[test] 67 | fn negative_double_ended_iteration_is_correct() { 68 | let mut line = clipline::Axis0::::new(0, 0, -2); 69 | assert_eq!(line.next_back(), Some((-1, 0))); 70 | assert_eq!(line.next(), Some((0, 0))); 71 | assert!(line.is_done()); 72 | } 73 | } 74 | 75 | mod clip { 76 | use clipline::Clip; 77 | 78 | const CLIP: Clip = match Clip::::new((0, 0), (63, 47)) { 79 | Some(clip) => clip, 80 | None => unreachable!(), 81 | }; 82 | 83 | #[test] 84 | fn axis_0_correct() { 85 | let mut neg = CLIP.axis_0(32, 76, -23).unwrap(); 86 | assert_eq!(neg.next(), Some((63, 32))); 87 | assert_eq!(neg.next_back(), Some((0, 32))); 88 | 89 | let mut neg = CLIP.axis_0(32, 63, 0).unwrap(); 90 | assert_eq!(neg.next(), Some((63, 32))); 91 | assert_eq!(neg.next_back(), Some((1, 32))); 92 | 93 | let mut pos = CLIP.axis_0(32, -23, 76).unwrap(); 94 | assert_eq!(pos.next(), Some((0, 32))); 95 | assert_eq!(pos.next_back(), Some((63, 32))); 96 | 97 | let mut pos = CLIP.axis_0(32, 0, 63).unwrap(); 98 | assert_eq!(pos.next(), Some((0, 32))); 99 | assert_eq!(pos.next_back(), Some((62, 32))); 100 | } 101 | 102 | #[test] 103 | fn axis_1_correct() { 104 | let mut neg = CLIP.axis_1(32, 76, -23).unwrap(); 105 | assert_eq!(neg.next(), Some((32, 47))); 106 | assert_eq!(neg.next_back(), Some((32, 0))); 107 | 108 | let mut neg = CLIP.axis_1(32, 47, 0).unwrap(); 109 | assert_eq!(neg.next(), Some((32, 47))); 110 | assert_eq!(neg.next_back(), Some((32, 1))); 111 | 112 | let mut pos = CLIP.axis_1(32, -23, 76).unwrap(); 113 | assert_eq!(pos.next(), Some((32, 0))); 114 | assert_eq!(pos.next_back(), Some((32, 47))); 115 | 116 | let mut pos = CLIP.axis_1(32, 0, 47).unwrap(); 117 | assert_eq!(pos.next(), Some((32, 0))); 118 | assert_eq!(pos.next_back(), Some((32, 46))); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/diagonal.rs: -------------------------------------------------------------------------------- 1 | //! ## Diagonal iterator tests 2 | 3 | use clipline::{AnyDiagonal, Diagonal0, Diagonal1, Diagonal2, Diagonal3}; 4 | 5 | mod quadrant { 6 | use super::*; 7 | 8 | #[test] 9 | fn all_quadrants_exclude_empty_lines() { 10 | assert!(Diagonal0::::new((0, 0), (0, 0)).is_none()); 11 | assert!(Diagonal1::::new((0, 0), (0, 0)).is_none()); 12 | assert!(Diagonal2::::new((0, 0), (0, 0)).is_none()); 13 | assert!(Diagonal3::::new((0, 0), (0, 0)).is_none()); 14 | } 15 | 16 | #[test] 17 | fn all_quadrants_include_correct_directions() { 18 | assert_eq!(Diagonal0::::new((0, 0), (2, 2)).unwrap().nth(1), Some((1, 1))); 19 | assert_eq!(Diagonal1::::new((0, 0), (2, -2)).unwrap().nth(1), Some((1, -1))); 20 | assert_eq!(Diagonal2::::new((0, 0), (-2, 2)).unwrap().nth(1), Some((-1, 1))); 21 | assert_eq!(Diagonal3::::new((0, 0), (-2, -2)).unwrap().nth(1), Some((-1, -1))); 22 | } 23 | } 24 | 25 | mod iterator { 26 | use super::*; 27 | 28 | #[test] 29 | fn length_is_correct() { 30 | for v1 in -2..4 { 31 | for v2 in -2..4 { 32 | let length = i8::abs_diff(v1, v2); 33 | assert_eq!(AnyDiagonal::::new((v1, v1), (v2, v2)).unwrap().length(), length); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/octant.rs: -------------------------------------------------------------------------------- 1 | //! ## Octant iterator tests 2 | 3 | mod octant_bounds { 4 | use clipline::{Octant0, Octant1, Octant2, Octant3, Octant4, Octant5, Octant6, Octant7}; 5 | 6 | #[test] 7 | fn all_octants_exclude_empty_line() { 8 | assert!(Octant0::::new((0, 0), (0, 0)).is_none()); 9 | assert!(Octant1::::new((0, 0), (0, 0)).is_none()); 10 | assert!(Octant2::::new((0, 0), (0, 0)).is_none()); 11 | assert!(Octant3::::new((0, 0), (0, 0)).is_none()); 12 | assert!(Octant4::::new((0, 0), (0, 0)).is_none()); 13 | assert!(Octant5::::new((0, 0), (0, 0)).is_none()); 14 | assert!(Octant6::::new((0, 0), (0, 0)).is_none()); 15 | assert!(Octant7::::new((0, 0), (0, 0)).is_none()); 16 | } 17 | 18 | #[test] 19 | fn octant_0_excludes_0_and_includes_45_degrees() { 20 | assert!(Octant0::::new((0, 0), (1, 0)).is_none()); 21 | assert!(Octant0::::new((0, 0), (1, 1)).is_some()); 22 | } 23 | 24 | #[test] 25 | fn octant_1_excludes_45_degrees_and_excludes_90_degrees() { 26 | assert!(Octant1::::new((0, 0), (1, 1)).is_none()); 27 | assert!(Octant1::::new((0, 0), (0, 1)).is_none()); 28 | } 29 | 30 | #[test] 31 | fn octant_2_includes_315_degrees_and_excludes_0_degrees() { 32 | assert!(Octant2::::new((0, 0), (1, -1)).is_some()); 33 | assert!(Octant2::::new((0, 0), (1, 0)).is_none()); 34 | } 35 | 36 | #[test] 37 | fn octant_3_excludes_270_degrees_and_excludes_315_degrees() { 38 | assert!(Octant3::::new((0, 0), (0, -1)).is_none()); 39 | assert!(Octant3::::new((0, 0), (1, -1)).is_none()); 40 | } 41 | 42 | #[test] 43 | fn octant_4_includes_135_and_excludes_180_degrees() { 44 | assert!(Octant4::::new((0, 0), (-1, 1)).is_some()); 45 | assert!(Octant4::::new((0, 0), (-1, 0)).is_none()); 46 | } 47 | 48 | #[test] 49 | fn octant_5_excludes_90_and_excludes_135_degrees() { 50 | assert!(Octant5::::new((0, 0), (0, 1)).is_none()); 51 | assert!(Octant5::::new((0, 0), (-1, 1)).is_none()); 52 | } 53 | 54 | #[test] 55 | fn octant_6_excludes_180_degrees_and_includes_225_degrees() { 56 | assert!(Octant6::::new((0, 0), (-1, 0)).is_none()); 57 | assert!(Octant6::::new((0, 0), (-1, -1)).is_some()); 58 | } 59 | 60 | #[test] 61 | fn octant_7_excludes_225_degrees_and_excludes_270_degrees() { 62 | assert!(Octant7::::new((0, 0), (-1, -1)).is_none()); 63 | assert!(Octant7::::new((0, 0), (0, -1)).is_none()); 64 | } 65 | } 66 | 67 | mod iterator { 68 | use clipline::{Octant0, Octant1, Octant2, Octant3, Octant4, Octant5, Octant6, Octant7}; 69 | 70 | #[test] 71 | fn octant_0_produces_correct_points() { 72 | let points = vec![(0, 0), (1, 0), (2, 1), (3, 1), (4, 2)]; 73 | assert_eq!(Octant0::::new((0, 0), (5, 2)).unwrap().collect::>(), points); 74 | } 75 | 76 | #[test] 77 | fn octant_1_produces_correct_points() { 78 | let points = vec![(0, 0), (0, 1), (1, 2), (1, 3), (2, 4)]; 79 | assert_eq!(Octant1::::new((0, 0), (2, 5)).unwrap().collect::>(), points); 80 | } 81 | 82 | #[test] 83 | fn octant_2_produces_correct_points() { 84 | let points = vec![(0, 0), (1, 0), (2, -1), (3, -1), (4, -2)]; 85 | assert_eq!(Octant2::::new((0, 0), (5, -2)).unwrap().collect::>(), points); 86 | } 87 | 88 | #[test] 89 | fn octant_3_produces_correct_points() { 90 | let points = vec![(0, 0), (0, -1), (1, -2), (1, -3), (2, -4)]; 91 | assert_eq!(Octant3::::new((0, 0), (2, -5)).unwrap().collect::>(), points); 92 | } 93 | 94 | #[test] 95 | fn octant_4_produces_correct_points() { 96 | let points = vec![(0, 0), (-1, 0), (-2, 1), (-3, 1), (-4, 2)]; 97 | assert_eq!(Octant4::::new((0, 0), (-5, 2)).unwrap().collect::>(), points); 98 | } 99 | 100 | #[test] 101 | fn octant_5_produces_correct_points() { 102 | let points = vec![(0, 0), (0, 1), (-1, 2), (-1, 3), (-2, 4)]; 103 | assert_eq!(Octant5::::new((0, 0), (-2, 5)).unwrap().collect::>(), points); 104 | } 105 | 106 | #[test] 107 | fn octant_6_produces_correct_points() { 108 | let points = vec![(0, 0), (-1, 0), (-2, -1), (-3, -1), (-4, -2)]; 109 | assert_eq!(Octant6::::new((0, 0), (-5, -2)).unwrap().collect::>(), points); 110 | } 111 | 112 | #[test] 113 | fn octant_7_produces_correct_points() { 114 | let points = vec![(0, 0), (0, -1), (-1, -2), (-1, -3), (-2, -4)]; 115 | assert_eq!(Octant7::::new((0, 0), (-2, -5)).unwrap().collect::>(), points); 116 | } 117 | } 118 | 119 | mod proptest { 120 | use clipline::{AnyOctant, Clip, Point}; 121 | use proptest::prelude::{prop_assert, proptest, ProptestConfig}; 122 | 123 | fn config() -> ProptestConfig { 124 | ProptestConfig { cases: 4000000, failure_persistence: None, ..ProptestConfig::default() } 125 | } 126 | 127 | mod u8 { 128 | use super::*; 129 | 130 | const MIN: u8 = 0; 131 | const MAX: u8 = 255; 132 | const HALF_0: u8 = 127; 133 | const HALF_1: u8 = 128; 134 | const QUARTER_0: u8 = 63; 135 | const QUARTER_1: u8 = 64; 136 | const QUARTER_2: u8 = 191; 137 | const QUARTER_3: u8 = 192; 138 | 139 | const CLIPS: [(&str, Point, Point); 37] = [ 140 | ("FULL", (MIN, MIN), (MAX, MAX)), 141 | ("POINT", (HALF_0, HALF_0), (HALF_0, HALF_0)), 142 | ("SMALL", (HALF_0, HALF_0), (HALF_1, HALF_1)), 143 | ("CENTER HALF-SIZE 0", (QUARTER_0, QUARTER_1), (QUARTER_2, QUARTER_3)), 144 | ("CENTER HALF-SIZE 1", (QUARTER_1, QUARTER_0), (QUARTER_3, QUARTER_2)), 145 | ("TOP-LEFT-CENTER QUARTER-SIZE 0", (QUARTER_0, QUARTER_0), (HALF_0, HALF_0)), 146 | ("TOP-LEFT-CENTER QUARTER-SIZE 1", (QUARTER_0, QUARTER_1), (HALF_1, HALF_0)), 147 | ("TOP-LEFT-CENTER QUARTER-SIZE 2", (QUARTER_1, QUARTER_0), (HALF_0, HALF_1)), 148 | ("TOP-LEFT-CENTER QUARTER-SIZE 3", (QUARTER_1, QUARTER_1), (HALF_1, HALF_1)), 149 | ("TOP-RIGHT-CENTER QUARTER-SIZE 0", (HALF_0, QUARTER_0), (QUARTER_2, HALF_0)), 150 | ("TOP-RIGHT-CENTER QUARTER-SIZE 1", (HALF_0, QUARTER_1), (QUARTER_3, HALF_1)), 151 | ("TOP-RIGHT-CENTER QUARTER-SIZE 2", (HALF_1, QUARTER_0), (QUARTER_2, HALF_1)), 152 | ("TOP-RIGHT-CENTER QUARTER-SIZE 3", (HALF_1, QUARTER_1), (QUARTER_3, HALF_0)), 153 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 0", (QUARTER_0, HALF_0), (HALF_0, QUARTER_2)), 154 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 1", (QUARTER_0, HALF_1), (HALF_1, QUARTER_3)), 155 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 2", (QUARTER_1, HALF_0), (HALF_0, QUARTER_2)), 156 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 3", (QUARTER_1, HALF_1), (HALF_1, QUARTER_3)), 157 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 0", (HALF_0, HALF_0), (QUARTER_3, QUARTER_2)), 158 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 1", (HALF_0, HALF_1), (QUARTER_3, QUARTER_3)), 159 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 2", (HALF_1, HALF_0), (QUARTER_2, QUARTER_2)), 160 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 3", (HALF_1, HALF_1), (QUARTER_2, QUARTER_3)), 161 | ("TOP-LEFT QUARTER 0", (MIN, MIN), (QUARTER_0, QUARTER_0)), 162 | ("TOP-LEFT QUARTER 1", (MIN, MIN), (QUARTER_1, QUARTER_1)), 163 | ("TOP-RIGHT QUARTER 0", (QUARTER_2, MIN), (MAX, QUARTER_2)), 164 | ("TOP-RIGHT QUARTER 1", (QUARTER_3, MIN), (MAX, QUARTER_3)), 165 | ("BOTTOM-LEFT QUARTER 0", (MIN, QUARTER_2), (QUARTER_2, MAX)), 166 | ("BOTTOM-LEFT QUARTER 1", (MIN, QUARTER_3), (QUARTER_3, MAX)), 167 | ("BOTTOM-RIGHT QUARTER 0", (QUARTER_2, QUARTER_2), (MAX, MAX)), 168 | ("BOTTOM-RIGHT QUARTER 1", (QUARTER_3, QUARTER_3), (MAX, MAX)), 169 | ("TOP HALF 0", (MIN, MIN), (MAX, HALF_0)), 170 | ("TOP HALF 1", (MIN, MIN), (MAX, HALF_1)), 171 | ("BOTTOM HALF 0", (MIN, HALF_0), (MAX, MAX)), 172 | ("BOTTOM HALF 1", (MIN, HALF_1), (MAX, MAX)), 173 | ("LEFT HALF 0", (MIN, MIN), (HALF_0, MAX)), 174 | ("LEFT HALF 1", (MIN, MIN), (HALF_1, MAX)), 175 | ("RIGHT HALF 0", (HALF_0, MIN), (MAX, MAX)), 176 | ("RIGHT HALF 1", (HALF_1, MIN), (MAX, MAX)), 177 | ]; 178 | 179 | proptest! { 180 | #![proptest_config(config())] 181 | #[test] 182 | fn clipped_matches_unclipped( 183 | clip in proptest::sample::select(&CLIPS), 184 | ((p1, p2)): (Point, Point) 185 | ) { 186 | let (_, w1, w2) = clip; 187 | let clip = Clip::::new(w1, w2).unwrap(); 188 | let clipped = clip.any_octant(p1, p2).into_iter().flatten(); 189 | let naive_clipped = AnyOctant::::new(p1, p2).filter(|&xy| clip.point(xy)); 190 | prop_assert!(clipped.eq(naive_clipped)); 191 | } 192 | } 193 | } 194 | 195 | mod i8 { 196 | use super::*; 197 | 198 | const MIN: i8 = -128; 199 | const MAX: i8 = 127; 200 | const HALF_0: i8 = 0; 201 | const HALF_1: i8 = 1; 202 | const QUARTER_0: i8 = -64; 203 | const QUARTER_1: i8 = -63; 204 | const QUARTER_2: i8 = 64; 205 | const QUARTER_3: i8 = 65; 206 | 207 | const CLIPS: [(&str, Point, Point); 37] = [ 208 | ("FULL", (MIN, MIN), (MAX, MAX)), 209 | ("POINT", (HALF_0, HALF_0), (HALF_0, HALF_0)), 210 | ("SMALL", (HALF_0, HALF_0), (HALF_1, HALF_1)), 211 | ("CENTER HALF-SIZE 0", (QUARTER_0, QUARTER_1), (QUARTER_2, QUARTER_3)), 212 | ("CENTER HALF-SIZE 1", (QUARTER_1, QUARTER_0), (QUARTER_3, QUARTER_2)), 213 | ("TOP-LEFT-CENTER QUARTER-SIZE 0", (QUARTER_0, QUARTER_0), (HALF_0, HALF_0)), 214 | ("TOP-LEFT-CENTER QUARTER-SIZE 1", (QUARTER_0, QUARTER_1), (HALF_1, HALF_0)), 215 | ("TOP-LEFT-CENTER QUARTER-SIZE 2", (QUARTER_1, QUARTER_0), (HALF_0, HALF_1)), 216 | ("TOP-LEFT-CENTER QUARTER-SIZE 3", (QUARTER_1, QUARTER_1), (HALF_1, HALF_1)), 217 | ("TOP-RIGHT-CENTER QUARTER-SIZE 0", (HALF_0, QUARTER_0), (QUARTER_2, HALF_0)), 218 | ("TOP-RIGHT-CENTER QUARTER-SIZE 1", (HALF_0, QUARTER_1), (QUARTER_3, HALF_1)), 219 | ("TOP-RIGHT-CENTER QUARTER-SIZE 2", (HALF_1, QUARTER_0), (QUARTER_2, HALF_1)), 220 | ("TOP-RIGHT-CENTER QUARTER-SIZE 3", (HALF_1, QUARTER_1), (QUARTER_3, HALF_0)), 221 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 0", (QUARTER_0, HALF_0), (HALF_0, QUARTER_2)), 222 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 1", (QUARTER_0, HALF_1), (HALF_1, QUARTER_3)), 223 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 2", (QUARTER_1, HALF_0), (HALF_0, QUARTER_2)), 224 | ("BOTTOM-LEFT-CENTER QUARTER-SIZE 3", (QUARTER_1, HALF_1), (HALF_1, QUARTER_3)), 225 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 0", (HALF_0, HALF_0), (QUARTER_3, QUARTER_2)), 226 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 1", (HALF_0, HALF_1), (QUARTER_3, QUARTER_3)), 227 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 2", (HALF_1, HALF_0), (QUARTER_2, QUARTER_2)), 228 | ("BOTTOM-RIGHT-CENTER QUARTER-SIZE 3", (HALF_1, HALF_1), (QUARTER_2, QUARTER_3)), 229 | ("TOP-LEFT QUARTER 0", (MIN, MIN), (QUARTER_0, QUARTER_0)), 230 | ("TOP-LEFT QUARTER 1", (MIN, MIN), (QUARTER_1, QUARTER_1)), 231 | ("TOP-RIGHT QUARTER 0", (QUARTER_2, MIN), (MAX, QUARTER_2)), 232 | ("TOP-RIGHT QUARTER 1", (QUARTER_3, MIN), (MAX, QUARTER_3)), 233 | ("BOTTOM-LEFT QUARTER 0", (MIN, QUARTER_2), (QUARTER_2, MAX)), 234 | ("BOTTOM-LEFT QUARTER 1", (MIN, QUARTER_3), (QUARTER_3, MAX)), 235 | ("BOTTOM-RIGHT QUARTER 0", (QUARTER_2, QUARTER_2), (MAX, MAX)), 236 | ("BOTTOM-RIGHT QUARTER 1", (QUARTER_3, QUARTER_3), (MAX, MAX)), 237 | ("TOP HALF 0", (MIN, MIN), (MAX, HALF_0)), 238 | ("TOP HALF 1", (MIN, MIN), (MAX, HALF_1)), 239 | ("BOTTOM HALF 0", (MIN, HALF_0), (MAX, MAX)), 240 | ("BOTTOM HALF 1", (MIN, HALF_1), (MAX, MAX)), 241 | ("LEFT HALF 0", (MIN, MIN), (HALF_0, MAX)), 242 | ("LEFT HALF 1", (MIN, MIN), (HALF_1, MAX)), 243 | ("RIGHT HALF 0", (HALF_0, MIN), (MAX, MAX)), 244 | ("RIGHT HALF 1", (HALF_1, MIN), (MAX, MAX)), 245 | ]; 246 | 247 | proptest! { 248 | #![proptest_config(config())] 249 | #[test] 250 | fn clipped_matches_unclipped( 251 | clip in proptest::sample::select(&CLIPS), 252 | ((p1, p2)): (Point, Point) 253 | ) { 254 | let (_, w1, w2) = clip; 255 | let clip = Clip::::new(w1, w2).unwrap(); 256 | let clipped = clip.any_octant(p1, p2).into_iter().flatten(); 257 | let naive_clipped = AnyOctant::::new(p1, p2).filter(|&xy| clip.point(xy)); 258 | prop_assert!(clipped.eq(naive_clipped)); 259 | } 260 | } 261 | } 262 | } 263 | --------------------------------------------------------------------------------