├── .clang-format ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── clang_format.sh │ ├── file_format.sh │ ├── linux_builds.yml │ ├── sources.list │ └── static_checks.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── images ├── conv_hm.png ├── conv_opt.png ├── test_input.png ├── tri_ec.png ├── tri_mono.png └── tri_opt.png ├── src ├── polypartition.cpp └── polypartition.h └── test ├── SConstruct ├── image.cpp ├── image.h ├── imageio.cpp ├── imageio.h ├── test.cpp ├── test_convexpartition_HM.txt ├── test_convexpartition_OPT.txt ├── test_input.txt ├── test_input_format.txt ├── test_triangulate_EC.txt ├── test_triangulate_MONO.txt └── test_triangulate_OPT.txt /.clang-format: -------------------------------------------------------------------------------- 1 | # Commented out parameters are those with the same value as base LLVM style 2 | # We can uncomment them if we want to change their value, or enforce the 3 | # chosen value in case the base style changes (last sync: Clang 6.0.1). 4 | --- 5 | ### General config, applies to all languages ### 6 | BasedOnStyle: LLVM 7 | AccessModifierOffset: -4 8 | AlignAfterOpenBracket: DontAlign 9 | # AlignConsecutiveAssignments: false 10 | # AlignConsecutiveDeclarations: false 11 | # AlignEscapedNewlines: Right 12 | # AlignOperands: true 13 | AlignTrailingComments: false 14 | AllowAllParametersOfDeclarationOnNextLine: false 15 | # AllowShortBlocksOnASingleLine: false 16 | # AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: Inline 18 | # AllowShortIfStatementsOnASingleLine: false 19 | # AllowShortLoopsOnASingleLine: false 20 | # AlwaysBreakAfterDefinitionReturnType: None 21 | # AlwaysBreakAfterReturnType: None 22 | # AlwaysBreakBeforeMultilineStrings: false 23 | # AlwaysBreakTemplateDeclarations: false 24 | # BinPackArguments: true 25 | # BinPackParameters: true 26 | # BraceWrapping: 27 | # AfterClass: false 28 | # AfterControlStatement: false 29 | # AfterEnum: false 30 | # AfterFunction: false 31 | # AfterNamespace: false 32 | # AfterObjCDeclaration: false 33 | # AfterStruct: false 34 | # AfterUnion: false 35 | # AfterExternBlock: false 36 | # BeforeCatch: false 37 | # BeforeElse: false 38 | # IndentBraces: false 39 | # SplitEmptyFunction: true 40 | # SplitEmptyRecord: true 41 | # SplitEmptyNamespace: true 42 | # BreakBeforeBinaryOperators: None 43 | # BreakBeforeBraces: Attach 44 | # BreakBeforeInheritanceComma: false 45 | BreakBeforeTernaryOperators: false 46 | # BreakConstructorInitializersBeforeComma: false 47 | BreakConstructorInitializers: AfterColon 48 | # BreakStringLiterals: true 49 | ColumnLimit: 0 50 | # CommentPragmas: '^ IWYU pragma:' 51 | # CompactNamespaces: false 52 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 53 | ConstructorInitializerIndentWidth: 8 54 | ContinuationIndentWidth: 8 55 | Cpp11BracedListStyle: false 56 | # DerivePointerAlignment: false 57 | # DisableFormat: false 58 | # ExperimentalAutoDetectBinPacking: false 59 | # FixNamespaceComments: true 60 | # ForEachMacros: 61 | # - foreach 62 | # - Q_FOREACH 63 | # - BOOST_FOREACH 64 | # IncludeBlocks: Preserve 65 | IncludeCategories: 66 | - Regex: '".*"' 67 | Priority: 1 68 | - Regex: '^<.*\.h>' 69 | Priority: 2 70 | - Regex: '^<.*' 71 | Priority: 3 72 | # IncludeIsMainRegex: '(Test)?$' 73 | IndentCaseLabels: true 74 | # IndentPPDirectives: None 75 | IndentWidth: 2 76 | # IndentWrappedFunctionNames: false 77 | # JavaScriptQuotes: Leave 78 | # JavaScriptWrapImports: true 79 | KeepEmptyLinesAtTheStartOfBlocks: false 80 | # MacroBlockBegin: '' 81 | # MacroBlockEnd: '' 82 | # MaxEmptyLinesToKeep: 1 83 | # NamespaceIndentation: None 84 | # PenaltyBreakAssignment: 2 85 | # PenaltyBreakBeforeFirstCallParameter: 19 86 | # PenaltyBreakComment: 300 87 | # PenaltyBreakFirstLessLess: 120 88 | # PenaltyBreakString: 1000 89 | # PenaltyExcessCharacter: 1000000 90 | # PenaltyReturnTypeOnItsOwnLine: 60 91 | # PointerAlignment: Right 92 | # RawStringFormats: 93 | # - Delimiter: pb 94 | # Language: TextProto 95 | # BasedOnStyle: google 96 | # ReflowComments: true 97 | # SortIncludes: true 98 | # SortUsingDeclarations: true 99 | # SpaceAfterCStyleCast: false 100 | # SpaceAfterTemplateKeyword: true 101 | # SpaceBeforeAssignmentOperators: true 102 | # SpaceBeforeParens: ControlStatements 103 | # SpaceInEmptyParentheses: false 104 | # SpacesBeforeTrailingComments: 1 105 | # SpacesInAngles: false 106 | # SpacesInContainerLiterals: true 107 | # SpacesInCStyleCastParentheses: false 108 | # SpacesInParentheses: false 109 | # SpacesInSquareBrackets: false 110 | TabWidth: 4 111 | UseTab: Never 112 | --- 113 | ### C++ specific config ### 114 | Language: Cpp 115 | Standard: Cpp03 116 | ... 117 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/clang_format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script runs clang-format on all relevant files in the repo. 4 | # This is the primary script responsible for fixing style violations. 5 | 6 | set -uo pipefail 7 | IFS=$'\n\t' 8 | 9 | CLANG_FORMAT_FILE_EXTS=(".c" ".h" ".cpp" ".hpp") 10 | 11 | # Loops through all text files tracked by Git. 12 | git grep -zIl '' | 13 | while IFS= read -rd '' f; do 14 | for extension in ${CLANG_FORMAT_FILE_EXTS[@]}; do 15 | if [[ "$f" == *"$extension" ]]; then 16 | # Run clang-format. 17 | clang-format -i "$f" 18 | fi 19 | done 20 | done 21 | 22 | git diff > patch.patch 23 | 24 | # If no patch has been generated all is OK, clean up, and exit. 25 | if [ ! -s patch.patch ] ; then 26 | printf "Files in this commit comply with the clang-format style rules.\n" 27 | rm -f patch.patch 28 | exit 0 29 | fi 30 | 31 | # A patch has been created, notify the user, clean up, and exit. 32 | printf "\n*** The following differences were found between the code " 33 | printf "and the formatting rules:\n\n" 34 | cat patch.patch 35 | printf "\n*** Aborting, please fix your commit(s) with 'git commit --amend' or 'git rebase -i '\n" 36 | rm -f patch.patch 37 | exit 1 38 | -------------------------------------------------------------------------------- /.github/workflows/file_format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script ensures proper POSIX text file formatting and a few other things. 4 | 5 | # We need dos2unix and recode. 6 | if [ ! -x "$(command -v dos2unix)" -o ! -x "$(command -v recode)" ]; then 7 | printf "Install 'dos2unix' and 'recode' to use this script.\n" 8 | fi 9 | 10 | set -uo pipefail 11 | IFS=$'\n\t' 12 | 13 | # Loops through all text files tracked by Git. 14 | git grep -zIl '' | 15 | while IFS= read -rd '' f; do 16 | # Ensure that files are UTF-8 formatted. 17 | recode UTF-8 "$f" 2> /dev/null 18 | # Ensure that files have LF line endings and do not contain a BOM. 19 | dos2unix "$f" 2> /dev/null 20 | # Remove trailing space characters and ensures that files end 21 | # with newline characters. -l option handles newlines conveniently. 22 | perl -i -ple 's/\s*$//g' "$f" 23 | # Remove the character sequence "== true" if it has a leading space. 24 | perl -i -pe 's/\x20== true//g' "$f" 25 | # Disallow empty lines after the opening brace. 26 | sed -z -i 's/\x7B\x0A\x0A/\x7B\x0A/g' "$f" 27 | # Disallow some empty lines before the closing brace. 28 | sed -z -i 's/\x0A\x0A\x7D/\x0A\x7D/g' "$f" 29 | done 30 | 31 | git diff > patch.patch 32 | 33 | # If no patch has been generated all is OK, clean up, and exit. 34 | if [ ! -s patch.patch ] ; then 35 | printf "Files in this commit comply with the formatting rules.\n" 36 | rm -f patch.patch 37 | exit 0 38 | fi 39 | 40 | # A patch has been created, notify the user, clean up, and exit. 41 | printf "\n*** The following differences were found between the code " 42 | printf "and the formatting rules:\n\n" 43 | cat patch.patch 44 | printf "\n*** Aborting, please fix your commit(s) with 'git commit --amend' or 'git rebase -i '\n" 45 | rm -f patch.patch 46 | exit 1 47 | -------------------------------------------------------------------------------- /.github/workflows/linux_builds.yml: -------------------------------------------------------------------------------- 1 | name: 🐧 Linux Builds 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | linux-gcc: 6 | runs-on: "ubuntu-20.04" 7 | name: Compile and Test (GCC) 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | # Azure repositories are not reliable, we need to prevent Azure giving us packages. 13 | - name: Make apt sources.list use the default Ubuntu repositories 14 | run: | 15 | sudo rm -f /etc/apt/sources.list.d/* 16 | sudo cp -f ./.github/workflows/sources.list /etc/apt/sources.list 17 | sudo apt-get update 18 | 19 | # Install all packages (except SCons). 20 | - name: Configure dependencies 21 | run: | 22 | sudo apt-get install build-essential 23 | 24 | # Use Python 3.x release (works cross platform; 25 | # best to keep self contained in it's own step). 26 | - name: Set up Python 3.x 27 | uses: actions/setup-python@v2 28 | with: 29 | # Semantic version range syntax or exact version of a Python version. 30 | python-version: "3.x" 31 | # Optional - x64 or x86 architecture, defaults to x64. 32 | architecture: "x64" 33 | 34 | # Setup scons, print Python version and SCons version info, 35 | # so if anything is broken it won't run the build. 36 | - name: Configuring Python packages 37 | run: | 38 | python -c "import sys; print(sys.version)" 39 | python -m pip install scons 40 | python --version 41 | scons --version 42 | 43 | - name: Compilation 44 | run: | 45 | cd test/ 46 | scons platform=linux verbose=yes warnings=extra werror=yes --jobs=2 47 | 48 | - name: Execute Tests 49 | run: | 50 | cd test/ 51 | ./polypartition_test 52 | -------------------------------------------------------------------------------- /.github/workflows/sources.list: -------------------------------------------------------------------------------- 1 | deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse 2 | deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse 3 | deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse 4 | deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse 5 | -------------------------------------------------------------------------------- /.github/workflows/static_checks.yml: -------------------------------------------------------------------------------- 1 | name: 📊 Static Checks 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | format: 6 | name: File formatting (file_format.sh) 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Install dependencies 13 | run: | 14 | sudo apt-get update -qq 15 | sudo apt-get install -qq dos2unix recode 16 | 17 | - name: File formatting checks (file_format.sh) 18 | run: | 19 | bash ./.github/workflows/file_format.sh 20 | 21 | - name: Style checks via clang-format (clang_format.sh) 22 | run: | 23 | bash ./.github/workflows/clang_format.sh 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sconsign.dblite 2 | polypartition_test 3 | *.o 4 | *.bmp 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-2021 Ivan Fratric and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### PolyPartition 2 | 3 | PolyPartition is a lightweight C++ library for polygon partition 4 | and triangulation. PolyPartition implements multiple algorithms 5 | for both convex partitioning and triangulation. Different 6 | algorithms produce different quality of results (and their 7 | complexity varies accordingly). The implemented methods/algorithms 8 | with their advantages and disadvantages are outlined below. 9 | 10 | For input parameters and return values see method declarations 11 | in `polypartition.h`. All methods require that the input polygons 12 | are not self-intersecting, are defined in the correct vertex order 13 | (counter-clockwise for non-holes, clockwise for holes), and any holes 14 | must be explicitly marked as holes (you can use `SetHole(true)`). 15 | Polygon vertices can easily be ordered correctly by 16 | calling `TPPLPoly::SetOrientation` method. 17 | 18 | Input polygon: 19 | 20 | ![images/test_input.png](images/test_input.png) 21 | 22 | #### Triangulation by ear clipping 23 | 24 | Method: `TPPLPartition::Triangulate_EC` 25 | 26 | Time/Space complexity: `O(n^2)/O(n)` 27 | 28 | Supports holes: Yes, by calling `TPPLPartition::RemoveHoles`. 29 | 30 | Quality of solution: Satisfactory in most cases. 31 | 32 | Example: 33 | 34 | ![images/tri_ec.png](images/tri_ec.png) 35 | 36 | 37 | #### Optimal triangulation in terms of edge length using dynamic programming algorithm 38 | 39 | Method: `TPPLPartition::Triangulate_OPT` 40 | 41 | Time/Space complexity: `O(n^3)/O(n^2)` 42 | 43 | Supports holes: No. You could call `TPPLPartition::RemoveHoles` prior 44 | to calling `TPPLPartition::Triangulate_OPT`, but the solution would no 45 | longer be optimal, thus defeating the purpose. 46 | 47 | Quality of solution: Optimal in terms of minimal edge length. 48 | 49 | Example: 50 | 51 | ![images/tri_opt.png](images/tri_opt.png) 52 | 53 | 54 | #### Triangulation by partition into monotone polygons 55 | 56 | Method: `TPPLPartition::Triangulate_MONO` 57 | 58 | Time/Space complexity: `O(n*log(n))/O(n)` 59 | 60 | Supports holes: Yes, by design 61 | 62 | Quality of solution: Poor. Many thin triangles are created in most cases. 63 | 64 | Example: 65 | 66 | ![images/tri_mono.png](images/tri_mono.png) 67 | 68 | 69 | #### Convex partition using Hertel-Mehlhorn algorithm 70 | 71 | Method: `TPPLPartition::ConvexPartition_HM` 72 | 73 | Time/Space complexity: `O(n^2)/O(n)` 74 | 75 | Supports holes: Yes, by calling `TPPLPartition::RemoveHoles`. 76 | 77 | Quality of solution: At most four times the minimum number of convex 78 | polygons is created. However, in practice it works much better 79 | than that and often gives optimal partition. 80 | 81 | Example: 82 | 83 | ![images/conv_hm.png](images/conv_hm.png) 84 | 85 | 86 | #### Optimal convex partition using dynamic programming algorithm by Keil and Snoeyink 87 | 88 | Method: `TPPLPartition::ConvexPartition_OPT` 89 | 90 | Time/Space complexity: `O(n^3)/O(n^3)` 91 | 92 | Supports holes: No. You could call `TPPLPartition::RemoveHoles` 93 | prior to calling `TPPLPartition::Triangulate_OPT`, but the solution 94 | would no longer be optimal, thus defeating the purpose. 95 | 96 | Quality of solution: Optimal. A minimum number of convex polygons is produced. 97 | 98 | Example: 99 | 100 | ![images/conv_opt.png](images/conv_opt.png) 101 | -------------------------------------------------------------------------------- /images/conv_hm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanfratric/polypartition/b000a4a2a72b46e1305fb6e95b080448d7c12049/images/conv_hm.png -------------------------------------------------------------------------------- /images/conv_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanfratric/polypartition/b000a4a2a72b46e1305fb6e95b080448d7c12049/images/conv_opt.png -------------------------------------------------------------------------------- /images/test_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanfratric/polypartition/b000a4a2a72b46e1305fb6e95b080448d7c12049/images/test_input.png -------------------------------------------------------------------------------- /images/tri_ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanfratric/polypartition/b000a4a2a72b46e1305fb6e95b080448d7c12049/images/tri_ec.png -------------------------------------------------------------------------------- /images/tri_mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanfratric/polypartition/b000a4a2a72b46e1305fb6e95b080448d7c12049/images/tri_mono.png -------------------------------------------------------------------------------- /images/tri_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivanfratric/polypartition/b000a4a2a72b46e1305fb6e95b080448d7c12049/images/tri_opt.png -------------------------------------------------------------------------------- /src/polypartition.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #include "polypartition.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | TPPLPoly::TPPLPoly() { 32 | hole = false; 33 | numpoints = 0; 34 | points = NULL; 35 | } 36 | 37 | TPPLPoly::~TPPLPoly() { 38 | delete[] points; 39 | } 40 | 41 | void TPPLPoly::Clear() { 42 | delete[] points; 43 | hole = false; 44 | numpoints = 0; 45 | points = NULL; 46 | } 47 | 48 | void TPPLPoly::Init(long numpoints) { 49 | Clear(); 50 | this->numpoints = numpoints; 51 | points = new TPPLPoint[numpoints]; 52 | } 53 | 54 | void TPPLPoly::Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3) { 55 | Init(3); 56 | points[0] = p1; 57 | points[1] = p2; 58 | points[2] = p3; 59 | } 60 | 61 | TPPLPoly::TPPLPoly(const TPPLPoly &src) : 62 | TPPLPoly() { 63 | hole = src.hole; 64 | numpoints = src.numpoints; 65 | 66 | if (numpoints > 0) { 67 | points = new TPPLPoint[numpoints]; 68 | memcpy(points, src.points, numpoints * sizeof(TPPLPoint)); 69 | } 70 | } 71 | 72 | TPPLPoly &TPPLPoly::operator=(const TPPLPoly &src) { 73 | Clear(); 74 | hole = src.hole; 75 | numpoints = src.numpoints; 76 | 77 | if (numpoints > 0) { 78 | points = new TPPLPoint[numpoints]; 79 | memcpy(points, src.points, numpoints * sizeof(TPPLPoint)); 80 | } 81 | 82 | return *this; 83 | } 84 | 85 | TPPLOrientation TPPLPoly::GetOrientation() const { 86 | long i1, i2; 87 | tppl_float area = 0; 88 | for (i1 = 0; i1 < numpoints; i1++) { 89 | i2 = i1 + 1; 90 | if (i2 == numpoints) { 91 | i2 = 0; 92 | } 93 | area += points[i1].x * points[i2].y - points[i1].y * points[i2].x; 94 | } 95 | if (area > 0) { 96 | return TPPL_ORIENTATION_CCW; 97 | } 98 | if (area < 0) { 99 | return TPPL_ORIENTATION_CW; 100 | } 101 | return TPPL_ORIENTATION_NONE; 102 | } 103 | 104 | void TPPLPoly::SetOrientation(TPPLOrientation orientation) { 105 | TPPLOrientation polyorientation = GetOrientation(); 106 | if (polyorientation != TPPL_ORIENTATION_NONE && polyorientation != orientation) { 107 | Invert(); 108 | } 109 | } 110 | 111 | void TPPLPoly::Invert() { 112 | std::reverse(points, points + numpoints); 113 | } 114 | 115 | TPPLPartition::PartitionVertex::PartitionVertex() : 116 | previous(NULL), next(NULL) { 117 | } 118 | 119 | TPPLPoint TPPLPartition::Normalize(const TPPLPoint &p) { 120 | TPPLPoint r; 121 | tppl_float n = tppl_sqrt(p.x * p.x + p.y * p.y); 122 | if (n != 0) { 123 | r = p / n; 124 | } else { 125 | r.x = 0; 126 | r.y = 0; 127 | } 128 | return r; 129 | } 130 | 131 | tppl_float TPPLPartition::Distance(const TPPLPoint &p1, const TPPLPoint &p2) { 132 | tppl_float dx, dy; 133 | dx = p2.x - p1.x; 134 | dy = p2.y - p1.y; 135 | return (tppl_sqrt(dx * dx + dy * dy)); 136 | } 137 | 138 | // Checks if two lines intersect. 139 | int TPPLPartition::Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TPPLPoint &p22) { 140 | if ((p11.x == p21.x) && (p11.y == p21.y)) { 141 | return 0; 142 | } 143 | if ((p11.x == p22.x) && (p11.y == p22.y)) { 144 | return 0; 145 | } 146 | if ((p12.x == p21.x) && (p12.y == p21.y)) { 147 | return 0; 148 | } 149 | if ((p12.x == p22.x) && (p12.y == p22.y)) { 150 | return 0; 151 | } 152 | 153 | TPPLPoint v1ort, v2ort, v; 154 | tppl_float dot11, dot12, dot21, dot22; 155 | 156 | v1ort.x = p12.y - p11.y; 157 | v1ort.y = p11.x - p12.x; 158 | 159 | v2ort.x = p22.y - p21.y; 160 | v2ort.y = p21.x - p22.x; 161 | 162 | v = p21 - p11; 163 | dot21 = v.x * v1ort.x + v.y * v1ort.y; 164 | v = p22 - p11; 165 | dot22 = v.x * v1ort.x + v.y * v1ort.y; 166 | 167 | v = p11 - p21; 168 | dot11 = v.x * v2ort.x + v.y * v2ort.y; 169 | v = p12 - p21; 170 | dot12 = v.x * v2ort.x + v.y * v2ort.y; 171 | 172 | if (dot11 * dot12 > 0) { 173 | return 0; 174 | } 175 | if (dot21 * dot22 > 0) { 176 | return 0; 177 | } 178 | 179 | return 1; 180 | } 181 | 182 | // Removes holes from inpolys by merging them with non-holes. 183 | int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) { 184 | TPPLPolyList polys; 185 | TPPLPolyList::iterator holeiter, polyiter, iter, iter2; 186 | long i, i2, holepointindex, polypointindex; 187 | TPPLPoint holepoint, polypoint, bestpolypoint; 188 | TPPLPoint linep1, linep2; 189 | tppl_float v1dist, v2dist; 190 | TPPLPoly newpoly; 191 | bool hasholes; 192 | bool pointvisible; 193 | bool pointfound; 194 | 195 | // Check for the trivial case of no holes. 196 | hasholes = false; 197 | for (iter = inpolys->begin(); iter != inpolys->end(); iter++) { 198 | if (iter->IsHole()) { 199 | hasholes = true; 200 | break; 201 | } 202 | } 203 | if (!hasholes) { 204 | for (iter = inpolys->begin(); iter != inpolys->end(); iter++) { 205 | outpolys->push_back(*iter); 206 | } 207 | return 1; 208 | } 209 | 210 | polys = *inpolys; 211 | 212 | while (1) { 213 | // Find the hole point with the largest x. 214 | hasholes = false; 215 | for (iter = polys.begin(); iter != polys.end(); iter++) { 216 | if (!iter->IsHole()) { 217 | continue; 218 | } 219 | 220 | if (!hasholes) { 221 | hasholes = true; 222 | holeiter = iter; 223 | holepointindex = 0; 224 | } 225 | 226 | for (i = 0; i < iter->GetNumPoints(); i++) { 227 | if (iter->GetPoint(i).x > holeiter->GetPoint(holepointindex).x) { 228 | holeiter = iter; 229 | holepointindex = i; 230 | } 231 | } 232 | } 233 | if (!hasholes) { 234 | break; 235 | } 236 | holepoint = holeiter->GetPoint(holepointindex); 237 | 238 | pointfound = false; 239 | for (iter = polys.begin(); iter != polys.end(); iter++) { 240 | if (iter->IsHole()) { 241 | continue; 242 | } 243 | for (i = 0; i < iter->GetNumPoints(); i++) { 244 | if (iter->GetPoint(i).x <= holepoint.x) { 245 | continue; 246 | } 247 | if (!InCone(iter->GetPoint((i + iter->GetNumPoints() - 1) % (iter->GetNumPoints())), 248 | iter->GetPoint(i), 249 | iter->GetPoint((i + 1) % (iter->GetNumPoints())), 250 | holepoint)) { 251 | continue; 252 | } 253 | polypoint = iter->GetPoint(i); 254 | if (pointfound) { 255 | v1dist = Distance(holepoint, polypoint); 256 | v2dist = Distance(holepoint, bestpolypoint); 257 | if (v2dist < v1dist) { 258 | continue; 259 | } 260 | } 261 | pointvisible = true; 262 | for (iter2 = polys.begin(); iter2 != polys.end(); iter2++) { 263 | if (iter2->IsHole()) { 264 | continue; 265 | } 266 | for (i2 = 0; i2 < iter2->GetNumPoints(); i2++) { 267 | linep1 = iter2->GetPoint(i2); 268 | linep2 = iter2->GetPoint((i2 + 1) % (iter2->GetNumPoints())); 269 | if (Intersects(holepoint, polypoint, linep1, linep2)) { 270 | pointvisible = false; 271 | break; 272 | } 273 | } 274 | if (!pointvisible) { 275 | break; 276 | } 277 | } 278 | if (pointvisible) { 279 | pointfound = true; 280 | bestpolypoint = polypoint; 281 | polyiter = iter; 282 | polypointindex = i; 283 | } 284 | } 285 | } 286 | 287 | if (!pointfound) { 288 | return 0; 289 | } 290 | 291 | newpoly.Init(holeiter->GetNumPoints() + polyiter->GetNumPoints() + 2); 292 | i2 = 0; 293 | for (i = 0; i <= polypointindex; i++) { 294 | newpoly[i2] = polyiter->GetPoint(i); 295 | i2++; 296 | } 297 | for (i = 0; i <= holeiter->GetNumPoints(); i++) { 298 | newpoly[i2] = holeiter->GetPoint((i + holepointindex) % holeiter->GetNumPoints()); 299 | i2++; 300 | } 301 | for (i = polypointindex; i < polyiter->GetNumPoints(); i++) { 302 | newpoly[i2] = polyiter->GetPoint(i); 303 | i2++; 304 | } 305 | 306 | polys.erase(holeiter); 307 | polys.erase(polyiter); 308 | polys.push_back(newpoly); 309 | } 310 | 311 | for (iter = polys.begin(); iter != polys.end(); iter++) { 312 | outpolys->push_back(*iter); 313 | } 314 | 315 | return 1; 316 | } 317 | 318 | bool TPPLPartition::IsConvex(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3) { 319 | tppl_float tmp; 320 | tmp = (p3.y - p1.y) * (p2.x - p1.x) - (p3.x - p1.x) * (p2.y - p1.y); 321 | if (tmp > 0) { 322 | return 1; 323 | } else { 324 | return 0; 325 | } 326 | } 327 | 328 | bool TPPLPartition::IsReflex(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3) { 329 | tppl_float tmp; 330 | tmp = (p3.y - p1.y) * (p2.x - p1.x) - (p3.x - p1.x) * (p2.y - p1.y); 331 | if (tmp < 0) { 332 | return 1; 333 | } else { 334 | return 0; 335 | } 336 | } 337 | 338 | bool TPPLPartition::IsInside(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p) { 339 | if (IsConvex(p1, p, p2)) { 340 | return false; 341 | } 342 | if (IsConvex(p2, p, p3)) { 343 | return false; 344 | } 345 | if (IsConvex(p3, p, p1)) { 346 | return false; 347 | } 348 | return true; 349 | } 350 | 351 | bool TPPLPartition::InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p) { 352 | bool convex; 353 | 354 | convex = IsConvex(p1, p2, p3); 355 | 356 | if (convex) { 357 | if (!IsConvex(p1, p2, p)) { 358 | return false; 359 | } 360 | if (!IsConvex(p2, p3, p)) { 361 | return false; 362 | } 363 | return true; 364 | } else { 365 | if (IsConvex(p1, p2, p)) { 366 | return true; 367 | } 368 | if (IsConvex(p2, p3, p)) { 369 | return true; 370 | } 371 | return false; 372 | } 373 | } 374 | 375 | bool TPPLPartition::InCone(PartitionVertex *v, TPPLPoint &p) { 376 | TPPLPoint p1, p2, p3; 377 | 378 | p1 = v->previous->p; 379 | p2 = v->p; 380 | p3 = v->next->p; 381 | 382 | return InCone(p1, p2, p3, p); 383 | } 384 | 385 | void TPPLPartition::UpdateVertexReflexity(PartitionVertex *v) { 386 | PartitionVertex *v1 = NULL, *v3 = NULL; 387 | v1 = v->previous; 388 | v3 = v->next; 389 | v->isConvex = !IsReflex(v1->p, v->p, v3->p); 390 | } 391 | 392 | void TPPLPartition::UpdateVertex(PartitionVertex *v, PartitionVertex *vertices, long numvertices) { 393 | long i; 394 | PartitionVertex *v1 = NULL, *v3 = NULL; 395 | TPPLPoint vec1, vec3; 396 | 397 | v1 = v->previous; 398 | v3 = v->next; 399 | 400 | v->isConvex = IsConvex(v1->p, v->p, v3->p); 401 | 402 | vec1 = Normalize(v1->p - v->p); 403 | vec3 = Normalize(v3->p - v->p); 404 | v->angle = vec1.x * vec3.x + vec1.y * vec3.y; 405 | 406 | if (v->isConvex) { 407 | v->isEar = true; 408 | for (i = 0; i < numvertices; i++) { 409 | if ((vertices[i].p.x == v->p.x) && (vertices[i].p.y == v->p.y)) { 410 | continue; 411 | } 412 | if ((vertices[i].p.x == v1->p.x) && (vertices[i].p.y == v1->p.y)) { 413 | continue; 414 | } 415 | if ((vertices[i].p.x == v3->p.x) && (vertices[i].p.y == v3->p.y)) { 416 | continue; 417 | } 418 | if (IsInside(v1->p, v->p, v3->p, vertices[i].p)) { 419 | v->isEar = false; 420 | break; 421 | } 422 | } 423 | } else { 424 | v->isEar = false; 425 | } 426 | } 427 | 428 | // Triangulation by ear removal. 429 | int TPPLPartition::Triangulate_EC(TPPLPoly *poly, TPPLPolyList *triangles) { 430 | if (!poly->Valid()) { 431 | return 0; 432 | } 433 | 434 | long numvertices; 435 | PartitionVertex *vertices = NULL; 436 | PartitionVertex *ear = NULL; 437 | TPPLPoly triangle; 438 | long i, j; 439 | bool earfound; 440 | 441 | if (poly->GetNumPoints() < 3) { 442 | return 0; 443 | } 444 | if (poly->GetNumPoints() == 3) { 445 | triangles->push_back(*poly); 446 | return 1; 447 | } 448 | 449 | numvertices = poly->GetNumPoints(); 450 | 451 | vertices = new PartitionVertex[numvertices]; 452 | for (i = 0; i < numvertices; i++) { 453 | vertices[i].isActive = true; 454 | vertices[i].p = poly->GetPoint(i); 455 | if (i == (numvertices - 1)) { 456 | vertices[i].next = &(vertices[0]); 457 | } else { 458 | vertices[i].next = &(vertices[i + 1]); 459 | } 460 | if (i == 0) { 461 | vertices[i].previous = &(vertices[numvertices - 1]); 462 | } else { 463 | vertices[i].previous = &(vertices[i - 1]); 464 | } 465 | } 466 | for (i = 0; i < numvertices; i++) { 467 | UpdateVertex(&vertices[i], vertices, numvertices); 468 | } 469 | 470 | for (i = 0; i < numvertices - 3; i++) { 471 | earfound = false; 472 | // Find the most extruded ear. 473 | for (j = 0; j < numvertices; j++) { 474 | if (!vertices[j].isActive) { 475 | continue; 476 | } 477 | if (!vertices[j].isEar) { 478 | continue; 479 | } 480 | if (!earfound) { 481 | earfound = true; 482 | ear = &(vertices[j]); 483 | } else { 484 | if (vertices[j].angle > ear->angle) { 485 | ear = &(vertices[j]); 486 | } 487 | } 488 | } 489 | if (!earfound) { 490 | delete[] vertices; 491 | return 0; 492 | } 493 | 494 | triangle.Triangle(ear->previous->p, ear->p, ear->next->p); 495 | triangles->push_back(triangle); 496 | 497 | ear->isActive = false; 498 | ear->previous->next = ear->next; 499 | ear->next->previous = ear->previous; 500 | 501 | if (i == numvertices - 4) { 502 | break; 503 | } 504 | 505 | UpdateVertex(ear->previous, vertices, numvertices); 506 | UpdateVertex(ear->next, vertices, numvertices); 507 | } 508 | for (i = 0; i < numvertices; i++) { 509 | if (vertices[i].isActive) { 510 | triangle.Triangle(vertices[i].previous->p, vertices[i].p, vertices[i].next->p); 511 | triangles->push_back(triangle); 512 | break; 513 | } 514 | } 515 | 516 | delete[] vertices; 517 | 518 | return 1; 519 | } 520 | 521 | int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) { 522 | TPPLPolyList outpolys; 523 | TPPLPolyList::iterator iter; 524 | 525 | if (!RemoveHoles(inpolys, &outpolys)) { 526 | return 0; 527 | } 528 | for (iter = outpolys.begin(); iter != outpolys.end(); iter++) { 529 | if (!Triangulate_EC(&(*iter), triangles)) { 530 | return 0; 531 | } 532 | } 533 | return 1; 534 | } 535 | 536 | int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) { 537 | if (!poly->Valid()) { 538 | return 0; 539 | } 540 | 541 | TPPLPolyList triangles; 542 | TPPLPolyList::iterator iter1, iter2; 543 | TPPLPoly *poly1 = NULL, *poly2 = NULL; 544 | TPPLPoly newpoly; 545 | TPPLPoint d1, d2, p1, p2, p3; 546 | long i11, i12, i21, i22, i13, i23, j, k; 547 | bool isdiagonal; 548 | long numreflex; 549 | 550 | // Check if the poly is already convex. 551 | numreflex = 0; 552 | for (i11 = 0; i11 < poly->GetNumPoints(); i11++) { 553 | if (i11 == 0) { 554 | i12 = poly->GetNumPoints() - 1; 555 | } else { 556 | i12 = i11 - 1; 557 | } 558 | if (i11 == (poly->GetNumPoints() - 1)) { 559 | i13 = 0; 560 | } else { 561 | i13 = i11 + 1; 562 | } 563 | if (IsReflex(poly->GetPoint(i12), poly->GetPoint(i11), poly->GetPoint(i13))) { 564 | numreflex = 1; 565 | break; 566 | } 567 | } 568 | if (numreflex == 0) { 569 | parts->push_back(*poly); 570 | return 1; 571 | } 572 | 573 | if (!Triangulate_EC(poly, &triangles)) { 574 | return 0; 575 | } 576 | 577 | for (iter1 = triangles.begin(); iter1 != triangles.end(); iter1++) { 578 | poly1 = &(*iter1); 579 | for (i11 = 0; i11 < poly1->GetNumPoints(); i11++) { 580 | d1 = poly1->GetPoint(i11); 581 | i12 = (i11 + 1) % (poly1->GetNumPoints()); 582 | d2 = poly1->GetPoint(i12); 583 | 584 | isdiagonal = false; 585 | for (iter2 = iter1; iter2 != triangles.end(); iter2++) { 586 | if (iter1 == iter2) { 587 | continue; 588 | } 589 | poly2 = &(*iter2); 590 | 591 | for (i21 = 0; i21 < poly2->GetNumPoints(); i21++) { 592 | if ((d2.x != poly2->GetPoint(i21).x) || (d2.y != poly2->GetPoint(i21).y)) { 593 | continue; 594 | } 595 | i22 = (i21 + 1) % (poly2->GetNumPoints()); 596 | if ((d1.x != poly2->GetPoint(i22).x) || (d1.y != poly2->GetPoint(i22).y)) { 597 | continue; 598 | } 599 | isdiagonal = true; 600 | break; 601 | } 602 | if (isdiagonal) { 603 | break; 604 | } 605 | } 606 | 607 | if (!isdiagonal) { 608 | continue; 609 | } 610 | 611 | p2 = poly1->GetPoint(i11); 612 | if (i11 == 0) { 613 | i13 = poly1->GetNumPoints() - 1; 614 | } else { 615 | i13 = i11 - 1; 616 | } 617 | p1 = poly1->GetPoint(i13); 618 | if (i22 == (poly2->GetNumPoints() - 1)) { 619 | i23 = 0; 620 | } else { 621 | i23 = i22 + 1; 622 | } 623 | p3 = poly2->GetPoint(i23); 624 | 625 | if (!IsConvex(p1, p2, p3)) { 626 | continue; 627 | } 628 | 629 | p2 = poly1->GetPoint(i12); 630 | if (i12 == (poly1->GetNumPoints() - 1)) { 631 | i13 = 0; 632 | } else { 633 | i13 = i12 + 1; 634 | } 635 | p3 = poly1->GetPoint(i13); 636 | if (i21 == 0) { 637 | i23 = poly2->GetNumPoints() - 1; 638 | } else { 639 | i23 = i21 - 1; 640 | } 641 | p1 = poly2->GetPoint(i23); 642 | 643 | if (!IsConvex(p1, p2, p3)) { 644 | continue; 645 | } 646 | 647 | newpoly.Init(poly1->GetNumPoints() + poly2->GetNumPoints() - 2); 648 | k = 0; 649 | for (j = i12; j != i11; j = (j + 1) % (poly1->GetNumPoints())) { 650 | newpoly[k] = poly1->GetPoint(j); 651 | k++; 652 | } 653 | for (j = i22; j != i21; j = (j + 1) % (poly2->GetNumPoints())) { 654 | newpoly[k] = poly2->GetPoint(j); 655 | k++; 656 | } 657 | 658 | triangles.erase(iter2); 659 | *iter1 = newpoly; 660 | poly1 = &(*iter1); 661 | i11 = -1; 662 | 663 | continue; 664 | } 665 | } 666 | 667 | for (iter1 = triangles.begin(); iter1 != triangles.end(); iter1++) { 668 | parts->push_back(*iter1); 669 | } 670 | 671 | return 1; 672 | } 673 | 674 | int TPPLPartition::ConvexPartition_HM(TPPLPolyList *inpolys, TPPLPolyList *parts) { 675 | TPPLPolyList outpolys; 676 | TPPLPolyList::iterator iter; 677 | 678 | if (!RemoveHoles(inpolys, &outpolys)) { 679 | return 0; 680 | } 681 | for (iter = outpolys.begin(); iter != outpolys.end(); iter++) { 682 | if (!ConvexPartition_HM(&(*iter), parts)) { 683 | return 0; 684 | } 685 | } 686 | return 1; 687 | } 688 | 689 | // Minimum-weight polygon triangulation by dynamic programming. 690 | // Time complexity: O(n^3) 691 | // Space complexity: O(n^2) 692 | int TPPLPartition::Triangulate_OPT(TPPLPoly *poly, TPPLPolyList *triangles) { 693 | if (!poly->Valid()) { 694 | return 0; 695 | } 696 | 697 | long i, j, k, gap, n; 698 | DPState **dpstates = NULL; 699 | TPPLPoint p1, p2, p3, p4; 700 | long bestvertex; 701 | tppl_float weight, minweight, d1, d2; 702 | Diagonal diagonal, newdiagonal; 703 | DiagonalList diagonals; 704 | TPPLPoly triangle; 705 | int ret = 1; 706 | 707 | n = poly->GetNumPoints(); 708 | dpstates = new DPState *[n]; 709 | for (i = 1; i < n; i++) { 710 | dpstates[i] = new DPState[i]; 711 | } 712 | 713 | // Initialize states and visibility. 714 | for (i = 0; i < (n - 1); i++) { 715 | p1 = poly->GetPoint(i); 716 | for (j = i + 1; j < n; j++) { 717 | dpstates[j][i].visible = true; 718 | dpstates[j][i].weight = 0; 719 | dpstates[j][i].bestvertex = -1; 720 | if (j != (i + 1)) { 721 | p2 = poly->GetPoint(j); 722 | 723 | // Visibility check. 724 | if (i == 0) { 725 | p3 = poly->GetPoint(n - 1); 726 | } else { 727 | p3 = poly->GetPoint(i - 1); 728 | } 729 | if (i == (n - 1)) { 730 | p4 = poly->GetPoint(0); 731 | } else { 732 | p4 = poly->GetPoint(i + 1); 733 | } 734 | if (!InCone(p3, p1, p4, p2)) { 735 | dpstates[j][i].visible = false; 736 | continue; 737 | } 738 | 739 | if (j == 0) { 740 | p3 = poly->GetPoint(n - 1); 741 | } else { 742 | p3 = poly->GetPoint(j - 1); 743 | } 744 | if (j == (n - 1)) { 745 | p4 = poly->GetPoint(0); 746 | } else { 747 | p4 = poly->GetPoint(j + 1); 748 | } 749 | if (!InCone(p3, p2, p4, p1)) { 750 | dpstates[j][i].visible = false; 751 | continue; 752 | } 753 | 754 | for (k = 0; k < n; k++) { 755 | p3 = poly->GetPoint(k); 756 | if (k == (n - 1)) { 757 | p4 = poly->GetPoint(0); 758 | } else { 759 | p4 = poly->GetPoint(k + 1); 760 | } 761 | if (Intersects(p1, p2, p3, p4)) { 762 | dpstates[j][i].visible = false; 763 | break; 764 | } 765 | } 766 | } 767 | } 768 | } 769 | dpstates[n - 1][0].visible = true; 770 | dpstates[n - 1][0].weight = 0; 771 | dpstates[n - 1][0].bestvertex = -1; 772 | 773 | for (gap = 2; gap < n; gap++) { 774 | for (i = 0; i < (n - gap); i++) { 775 | j = i + gap; 776 | if (!dpstates[j][i].visible) { 777 | continue; 778 | } 779 | bestvertex = -1; 780 | for (k = (i + 1); k < j; k++) { 781 | if (!dpstates[k][i].visible) { 782 | continue; 783 | } 784 | if (!dpstates[j][k].visible) { 785 | continue; 786 | } 787 | 788 | if (k <= (i + 1)) { 789 | d1 = 0; 790 | } else { 791 | d1 = Distance(poly->GetPoint(i), poly->GetPoint(k)); 792 | } 793 | if (j <= (k + 1)) { 794 | d2 = 0; 795 | } else { 796 | d2 = Distance(poly->GetPoint(k), poly->GetPoint(j)); 797 | } 798 | 799 | weight = dpstates[k][i].weight + dpstates[j][k].weight + d1 + d2; 800 | 801 | if ((bestvertex == -1) || (weight < minweight)) { 802 | bestvertex = k; 803 | minweight = weight; 804 | } 805 | } 806 | if (bestvertex == -1) { 807 | for (i = 1; i < n; i++) { 808 | delete[] dpstates[i]; 809 | } 810 | delete[] dpstates; 811 | 812 | return 0; 813 | } 814 | 815 | dpstates[j][i].bestvertex = bestvertex; 816 | dpstates[j][i].weight = minweight; 817 | } 818 | } 819 | 820 | newdiagonal.index1 = 0; 821 | newdiagonal.index2 = n - 1; 822 | diagonals.push_back(newdiagonal); 823 | while (!diagonals.empty()) { 824 | diagonal = *(diagonals.begin()); 825 | diagonals.pop_front(); 826 | bestvertex = dpstates[diagonal.index2][diagonal.index1].bestvertex; 827 | if (bestvertex == -1) { 828 | ret = 0; 829 | break; 830 | } 831 | triangle.Triangle(poly->GetPoint(diagonal.index1), poly->GetPoint(bestvertex), poly->GetPoint(diagonal.index2)); 832 | triangles->push_back(triangle); 833 | if (bestvertex > (diagonal.index1 + 1)) { 834 | newdiagonal.index1 = diagonal.index1; 835 | newdiagonal.index2 = bestvertex; 836 | diagonals.push_back(newdiagonal); 837 | } 838 | if (diagonal.index2 > (bestvertex + 1)) { 839 | newdiagonal.index1 = bestvertex; 840 | newdiagonal.index2 = diagonal.index2; 841 | diagonals.push_back(newdiagonal); 842 | } 843 | } 844 | 845 | for (i = 1; i < n; i++) { 846 | delete[] dpstates[i]; 847 | } 848 | delete[] dpstates; 849 | 850 | return ret; 851 | } 852 | 853 | void TPPLPartition::UpdateState(long a, long b, long w, long i, long j, DPState2 **dpstates) { 854 | Diagonal newdiagonal; 855 | DiagonalList *pairs = NULL; 856 | long w2; 857 | 858 | w2 = dpstates[a][b].weight; 859 | if (w > w2) { 860 | return; 861 | } 862 | 863 | pairs = &(dpstates[a][b].pairs); 864 | newdiagonal.index1 = i; 865 | newdiagonal.index2 = j; 866 | 867 | if (w < w2) { 868 | pairs->clear(); 869 | pairs->push_front(newdiagonal); 870 | dpstates[a][b].weight = w; 871 | } else { 872 | if ((!pairs->empty()) && (i <= pairs->begin()->index1)) { 873 | return; 874 | } 875 | while ((!pairs->empty()) && (pairs->begin()->index2 >= j)) { 876 | pairs->pop_front(); 877 | } 878 | pairs->push_front(newdiagonal); 879 | } 880 | } 881 | 882 | void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { 883 | DiagonalList *pairs = NULL; 884 | DiagonalList::iterator iter, lastiter; 885 | long top; 886 | long w; 887 | 888 | if (!dpstates[i][j].visible) { 889 | return; 890 | } 891 | top = j; 892 | w = dpstates[i][j].weight; 893 | if (k - j > 1) { 894 | if (!dpstates[j][k].visible) { 895 | return; 896 | } 897 | w += dpstates[j][k].weight + 1; 898 | } 899 | if (j - i > 1) { 900 | pairs = &(dpstates[i][j].pairs); 901 | iter = pairs->end(); 902 | lastiter = pairs->end(); 903 | while (iter != pairs->begin()) { 904 | iter--; 905 | if (!IsReflex(vertices[iter->index2].p, vertices[j].p, vertices[k].p)) { 906 | lastiter = iter; 907 | } else { 908 | break; 909 | } 910 | } 911 | if (lastiter == pairs->end()) { 912 | w++; 913 | } else { 914 | if (IsReflex(vertices[k].p, vertices[i].p, vertices[lastiter->index1].p)) { 915 | w++; 916 | } else { 917 | top = lastiter->index1; 918 | } 919 | } 920 | } 921 | UpdateState(i, k, w, top, j, dpstates); 922 | } 923 | 924 | void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { 925 | DiagonalList *pairs = NULL; 926 | DiagonalList::iterator iter, lastiter; 927 | long top; 928 | long w; 929 | 930 | if (!dpstates[j][k].visible) { 931 | return; 932 | } 933 | top = j; 934 | w = dpstates[j][k].weight; 935 | 936 | if (j - i > 1) { 937 | if (!dpstates[i][j].visible) { 938 | return; 939 | } 940 | w += dpstates[i][j].weight + 1; 941 | } 942 | if (k - j > 1) { 943 | pairs = &(dpstates[j][k].pairs); 944 | 945 | iter = pairs->begin(); 946 | if ((!pairs->empty()) && (!IsReflex(vertices[i].p, vertices[j].p, vertices[iter->index1].p))) { 947 | lastiter = iter; 948 | while (iter != pairs->end()) { 949 | if (!IsReflex(vertices[i].p, vertices[j].p, vertices[iter->index1].p)) { 950 | lastiter = iter; 951 | iter++; 952 | } else { 953 | break; 954 | } 955 | } 956 | if (IsReflex(vertices[lastiter->index2].p, vertices[k].p, vertices[i].p)) { 957 | w++; 958 | } else { 959 | top = lastiter->index2; 960 | } 961 | } else { 962 | w++; 963 | } 964 | } 965 | UpdateState(i, k, w, j, top, dpstates); 966 | } 967 | 968 | int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) { 969 | if (!poly->Valid()) { 970 | return 0; 971 | } 972 | 973 | TPPLPoint p1, p2, p3, p4; 974 | PartitionVertex *vertices = NULL; 975 | DPState2 **dpstates = NULL; 976 | long i, j, k, n, gap; 977 | DiagonalList diagonals, diagonals2; 978 | Diagonal diagonal, newdiagonal; 979 | DiagonalList *pairs = NULL, *pairs2 = NULL; 980 | DiagonalList::iterator iter, iter2; 981 | int ret; 982 | TPPLPoly newpoly; 983 | std::vector indices; 984 | std::vector::iterator iiter; 985 | bool ijreal, jkreal; 986 | 987 | n = poly->GetNumPoints(); 988 | vertices = new PartitionVertex[n]; 989 | 990 | dpstates = new DPState2 *[n]; 991 | for (i = 0; i < n; i++) { 992 | dpstates[i] = new DPState2[n]; 993 | } 994 | 995 | // Initialize vertex information. 996 | for (i = 0; i < n; i++) { 997 | vertices[i].p = poly->GetPoint(i); 998 | vertices[i].isActive = true; 999 | if (i == 0) { 1000 | vertices[i].previous = &(vertices[n - 1]); 1001 | } else { 1002 | vertices[i].previous = &(vertices[i - 1]); 1003 | } 1004 | if (i == (poly->GetNumPoints() - 1)) { 1005 | vertices[i].next = &(vertices[0]); 1006 | } else { 1007 | vertices[i].next = &(vertices[i + 1]); 1008 | } 1009 | } 1010 | for (i = 1; i < n; i++) { 1011 | UpdateVertexReflexity(&(vertices[i])); 1012 | } 1013 | 1014 | // Initialize states and visibility. 1015 | for (i = 0; i < (n - 1); i++) { 1016 | p1 = poly->GetPoint(i); 1017 | for (j = i + 1; j < n; j++) { 1018 | dpstates[i][j].visible = true; 1019 | if (j == i + 1) { 1020 | dpstates[i][j].weight = 0; 1021 | } else { 1022 | dpstates[i][j].weight = 2147483647; 1023 | } 1024 | if (j != (i + 1)) { 1025 | p2 = poly->GetPoint(j); 1026 | 1027 | // Visibility check. 1028 | if (!InCone(&vertices[i], p2)) { 1029 | dpstates[i][j].visible = false; 1030 | continue; 1031 | } 1032 | if (!InCone(&vertices[j], p1)) { 1033 | dpstates[i][j].visible = false; 1034 | continue; 1035 | } 1036 | 1037 | for (k = 0; k < n; k++) { 1038 | p3 = poly->GetPoint(k); 1039 | if (k == (n - 1)) { 1040 | p4 = poly->GetPoint(0); 1041 | } else { 1042 | p4 = poly->GetPoint(k + 1); 1043 | } 1044 | if (Intersects(p1, p2, p3, p4)) { 1045 | dpstates[i][j].visible = false; 1046 | break; 1047 | } 1048 | } 1049 | } 1050 | } 1051 | } 1052 | for (i = 0; i < (n - 2); i++) { 1053 | j = i + 2; 1054 | if (dpstates[i][j].visible) { 1055 | dpstates[i][j].weight = 0; 1056 | newdiagonal.index1 = i + 1; 1057 | newdiagonal.index2 = i + 1; 1058 | dpstates[i][j].pairs.push_back(newdiagonal); 1059 | } 1060 | } 1061 | 1062 | dpstates[0][n - 1].visible = true; 1063 | vertices[0].isConvex = false; // By convention. 1064 | 1065 | for (gap = 3; gap < n; gap++) { 1066 | for (i = 0; i < n - gap; i++) { 1067 | if (vertices[i].isConvex) { 1068 | continue; 1069 | } 1070 | k = i + gap; 1071 | if (dpstates[i][k].visible) { 1072 | if (!vertices[k].isConvex) { 1073 | for (j = i + 1; j < k; j++) { 1074 | TypeA(i, j, k, vertices, dpstates); 1075 | } 1076 | } else { 1077 | for (j = i + 1; j < (k - 1); j++) { 1078 | if (vertices[j].isConvex) { 1079 | continue; 1080 | } 1081 | TypeA(i, j, k, vertices, dpstates); 1082 | } 1083 | TypeA(i, k - 1, k, vertices, dpstates); 1084 | } 1085 | } 1086 | } 1087 | for (k = gap; k < n; k++) { 1088 | if (vertices[k].isConvex) { 1089 | continue; 1090 | } 1091 | i = k - gap; 1092 | if ((vertices[i].isConvex) && (dpstates[i][k].visible)) { 1093 | TypeB(i, i + 1, k, vertices, dpstates); 1094 | for (j = i + 2; j < k; j++) { 1095 | if (vertices[j].isConvex) { 1096 | continue; 1097 | } 1098 | TypeB(i, j, k, vertices, dpstates); 1099 | } 1100 | } 1101 | } 1102 | } 1103 | 1104 | // Recover solution. 1105 | ret = 1; 1106 | newdiagonal.index1 = 0; 1107 | newdiagonal.index2 = n - 1; 1108 | diagonals.push_front(newdiagonal); 1109 | while (!diagonals.empty()) { 1110 | diagonal = *(diagonals.begin()); 1111 | diagonals.pop_front(); 1112 | if ((diagonal.index2 - diagonal.index1) <= 1) { 1113 | continue; 1114 | } 1115 | pairs = &(dpstates[diagonal.index1][diagonal.index2].pairs); 1116 | if (pairs->empty()) { 1117 | ret = 0; 1118 | break; 1119 | } 1120 | if (!vertices[diagonal.index1].isConvex) { 1121 | iter = pairs->end(); 1122 | iter--; 1123 | j = iter->index2; 1124 | newdiagonal.index1 = j; 1125 | newdiagonal.index2 = diagonal.index2; 1126 | diagonals.push_front(newdiagonal); 1127 | if ((j - diagonal.index1) > 1) { 1128 | if (iter->index1 != iter->index2) { 1129 | pairs2 = &(dpstates[diagonal.index1][j].pairs); 1130 | while (1) { 1131 | if (pairs2->empty()) { 1132 | ret = 0; 1133 | break; 1134 | } 1135 | iter2 = pairs2->end(); 1136 | iter2--; 1137 | if (iter->index1 != iter2->index1) { 1138 | pairs2->pop_back(); 1139 | } else { 1140 | break; 1141 | } 1142 | } 1143 | if (ret == 0) { 1144 | break; 1145 | } 1146 | } 1147 | newdiagonal.index1 = diagonal.index1; 1148 | newdiagonal.index2 = j; 1149 | diagonals.push_front(newdiagonal); 1150 | } 1151 | } else { 1152 | iter = pairs->begin(); 1153 | j = iter->index1; 1154 | newdiagonal.index1 = diagonal.index1; 1155 | newdiagonal.index2 = j; 1156 | diagonals.push_front(newdiagonal); 1157 | if ((diagonal.index2 - j) > 1) { 1158 | if (iter->index1 != iter->index2) { 1159 | pairs2 = &(dpstates[j][diagonal.index2].pairs); 1160 | while (1) { 1161 | if (pairs2->empty()) { 1162 | ret = 0; 1163 | break; 1164 | } 1165 | iter2 = pairs2->begin(); 1166 | if (iter->index2 != iter2->index2) { 1167 | pairs2->pop_front(); 1168 | } else { 1169 | break; 1170 | } 1171 | } 1172 | if (ret == 0) { 1173 | break; 1174 | } 1175 | } 1176 | newdiagonal.index1 = j; 1177 | newdiagonal.index2 = diagonal.index2; 1178 | diagonals.push_front(newdiagonal); 1179 | } 1180 | } 1181 | } 1182 | 1183 | if (ret == 0) { 1184 | for (i = 0; i < n; i++) { 1185 | delete[] dpstates[i]; 1186 | } 1187 | delete[] dpstates; 1188 | delete[] vertices; 1189 | 1190 | return ret; 1191 | } 1192 | 1193 | newdiagonal.index1 = 0; 1194 | newdiagonal.index2 = n - 1; 1195 | diagonals.push_front(newdiagonal); 1196 | while (!diagonals.empty()) { 1197 | diagonal = *(diagonals.begin()); 1198 | diagonals.pop_front(); 1199 | if ((diagonal.index2 - diagonal.index1) <= 1) { 1200 | continue; 1201 | } 1202 | 1203 | indices.clear(); 1204 | diagonals2.clear(); 1205 | indices.push_back(diagonal.index1); 1206 | indices.push_back(diagonal.index2); 1207 | diagonals2.push_front(diagonal); 1208 | 1209 | while (!diagonals2.empty()) { 1210 | diagonal = *(diagonals2.begin()); 1211 | diagonals2.pop_front(); 1212 | if ((diagonal.index2 - diagonal.index1) <= 1) { 1213 | continue; 1214 | } 1215 | ijreal = true; 1216 | jkreal = true; 1217 | pairs = &(dpstates[diagonal.index1][diagonal.index2].pairs); 1218 | if (!vertices[diagonal.index1].isConvex) { 1219 | iter = pairs->end(); 1220 | iter--; 1221 | j = iter->index2; 1222 | if (iter->index1 != iter->index2) { 1223 | ijreal = false; 1224 | } 1225 | } else { 1226 | iter = pairs->begin(); 1227 | j = iter->index1; 1228 | if (iter->index1 != iter->index2) { 1229 | jkreal = false; 1230 | } 1231 | } 1232 | 1233 | newdiagonal.index1 = diagonal.index1; 1234 | newdiagonal.index2 = j; 1235 | if (ijreal) { 1236 | diagonals.push_back(newdiagonal); 1237 | } else { 1238 | diagonals2.push_back(newdiagonal); 1239 | } 1240 | 1241 | newdiagonal.index1 = j; 1242 | newdiagonal.index2 = diagonal.index2; 1243 | if (jkreal) { 1244 | diagonals.push_back(newdiagonal); 1245 | } else { 1246 | diagonals2.push_back(newdiagonal); 1247 | } 1248 | 1249 | indices.push_back(j); 1250 | } 1251 | 1252 | std::sort(indices.begin(), indices.end()); 1253 | newpoly.Init((long)indices.size()); 1254 | k = 0; 1255 | for (iiter = indices.begin(); iiter != indices.end(); iiter++) { 1256 | newpoly[k] = vertices[*iiter].p; 1257 | k++; 1258 | } 1259 | parts->push_back(newpoly); 1260 | } 1261 | 1262 | for (i = 0; i < n; i++) { 1263 | delete[] dpstates[i]; 1264 | } 1265 | delete[] dpstates; 1266 | delete[] vertices; 1267 | 1268 | return ret; 1269 | } 1270 | 1271 | // Creates a monotone partition of a list of polygons that 1272 | // can contain holes. Triangulates a set of polygons by 1273 | // first partitioning them into monotone polygons. 1274 | // Time complexity: O(n*log(n)), n is the number of vertices. 1275 | // Space complexity: O(n) 1276 | // The algorithm used here is outlined in the book 1277 | // "Computational Geometry: Algorithms and Applications" 1278 | // by Mark de Berg, Otfried Cheong, Marc van Kreveld, and Mark Overmars. 1279 | int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monotonePolys) { 1280 | TPPLPolyList::iterator iter; 1281 | MonotoneVertex *vertices = NULL; 1282 | long i, numvertices, vindex, vindex2, newnumvertices, maxnumvertices; 1283 | long polystartindex, polyendindex; 1284 | TPPLPoly *poly = NULL; 1285 | MonotoneVertex *v = NULL, *v2 = NULL, *vprev = NULL, *vnext = NULL; 1286 | ScanLineEdge newedge; 1287 | bool error = false; 1288 | 1289 | numvertices = 0; 1290 | for (iter = inpolys->begin(); iter != inpolys->end(); iter++) { 1291 | if (!iter->Valid()) { 1292 | return 0; 1293 | } 1294 | numvertices += iter->GetNumPoints(); 1295 | } 1296 | 1297 | maxnumvertices = numvertices * 3; 1298 | vertices = new MonotoneVertex[maxnumvertices]; 1299 | newnumvertices = numvertices; 1300 | 1301 | polystartindex = 0; 1302 | for (iter = inpolys->begin(); iter != inpolys->end(); iter++) { 1303 | poly = &(*iter); 1304 | polyendindex = polystartindex + poly->GetNumPoints() - 1; 1305 | for (i = 0; i < poly->GetNumPoints(); i++) { 1306 | vertices[i + polystartindex].p = poly->GetPoint(i); 1307 | if (i == 0) { 1308 | vertices[i + polystartindex].previous = polyendindex; 1309 | } else { 1310 | vertices[i + polystartindex].previous = i + polystartindex - 1; 1311 | } 1312 | if (i == (poly->GetNumPoints() - 1)) { 1313 | vertices[i + polystartindex].next = polystartindex; 1314 | } else { 1315 | vertices[i + polystartindex].next = i + polystartindex + 1; 1316 | } 1317 | } 1318 | polystartindex = polyendindex + 1; 1319 | } 1320 | 1321 | // Construct the priority queue. 1322 | long *priority = new long[numvertices]; 1323 | for (i = 0; i < numvertices; i++) { 1324 | priority[i] = i; 1325 | } 1326 | std::sort(priority, &(priority[numvertices]), VertexSorter(vertices)); 1327 | 1328 | // Determine vertex types. 1329 | TPPLVertexType *vertextypes = new TPPLVertexType[maxnumvertices]; 1330 | for (i = 0; i < numvertices; i++) { 1331 | v = &(vertices[i]); 1332 | vprev = &(vertices[v->previous]); 1333 | vnext = &(vertices[v->next]); 1334 | 1335 | if (Below(vprev->p, v->p) && Below(vnext->p, v->p)) { 1336 | if (IsConvex(vnext->p, vprev->p, v->p)) { 1337 | vertextypes[i] = TPPL_VERTEXTYPE_START; 1338 | } else { 1339 | vertextypes[i] = TPPL_VERTEXTYPE_SPLIT; 1340 | } 1341 | } else if (Below(v->p, vprev->p) && Below(v->p, vnext->p)) { 1342 | if (IsConvex(vnext->p, vprev->p, v->p)) { 1343 | vertextypes[i] = TPPL_VERTEXTYPE_END; 1344 | } else { 1345 | vertextypes[i] = TPPL_VERTEXTYPE_MERGE; 1346 | } 1347 | } else { 1348 | vertextypes[i] = TPPL_VERTEXTYPE_REGULAR; 1349 | } 1350 | } 1351 | 1352 | // Helpers. 1353 | long *helpers = new long[maxnumvertices]; 1354 | 1355 | // Binary search tree that holds edges intersecting the scanline. 1356 | // Note that while set doesn't actually have to be implemented as 1357 | // a tree, complexity requirements for operations are the same as 1358 | // for the balanced binary search tree. 1359 | std::set edgeTree; 1360 | // Store iterators to the edge tree elements. 1361 | // This makes deleting existing edges much faster. 1362 | std::set::iterator *edgeTreeIterators, edgeIter; 1363 | edgeTreeIterators = new std::set::iterator[maxnumvertices]; 1364 | std::pair::iterator, bool> edgeTreeRet; 1365 | for (i = 0; i < numvertices; i++) { 1366 | edgeTreeIterators[i] = edgeTree.end(); 1367 | } 1368 | 1369 | // For each vertex. 1370 | for (i = 0; i < numvertices; i++) { 1371 | vindex = priority[i]; 1372 | v = &(vertices[vindex]); 1373 | vindex2 = vindex; 1374 | v2 = v; 1375 | 1376 | // Depending on the vertex type, do the appropriate action. 1377 | // Comments in the following sections are copied from 1378 | // "Computational Geometry: Algorithms and Applications". 1379 | // Notation: e_i = e subscript i, v_i = v subscript i, etc. 1380 | switch (vertextypes[vindex]) { 1381 | case TPPL_VERTEXTYPE_START: 1382 | // Insert e_i in T and set helper(e_i) to v_i. 1383 | newedge.p1 = v->p; 1384 | newedge.p2 = vertices[v->next].p; 1385 | newedge.index = vindex; 1386 | edgeTreeRet = edgeTree.insert(newedge); 1387 | edgeTreeIterators[vindex] = edgeTreeRet.first; 1388 | helpers[vindex] = vindex; 1389 | break; 1390 | 1391 | case TPPL_VERTEXTYPE_END: 1392 | if (edgeTreeIterators[v->previous] == edgeTree.end()) { 1393 | error = true; 1394 | break; 1395 | } 1396 | // If helper(e_i - 1) is a merge vertex 1397 | if (vertextypes[helpers[v->previous]] == TPPL_VERTEXTYPE_MERGE) { 1398 | // Insert the diagonal connecting vi to helper(e_i - 1) in D. 1399 | AddDiagonal(vertices, &newnumvertices, vindex, helpers[v->previous], 1400 | vertextypes, edgeTreeIterators, &edgeTree, helpers); 1401 | } 1402 | // Delete e_i - 1 from T 1403 | edgeTree.erase(edgeTreeIterators[v->previous]); 1404 | break; 1405 | 1406 | case TPPL_VERTEXTYPE_SPLIT: 1407 | // Search in T to find the edge e_j directly left of v_i. 1408 | newedge.p1 = v->p; 1409 | newedge.p2 = v->p; 1410 | edgeIter = edgeTree.lower_bound(newedge); 1411 | if (edgeIter == edgeTree.begin()) { 1412 | error = true; 1413 | break; 1414 | } 1415 | edgeIter--; 1416 | // Insert the diagonal connecting vi to helper(e_j) in D. 1417 | AddDiagonal(vertices, &newnumvertices, vindex, helpers[edgeIter->index], 1418 | vertextypes, edgeTreeIterators, &edgeTree, helpers); 1419 | vindex2 = newnumvertices - 2; 1420 | v2 = &(vertices[vindex2]); 1421 | // helper(e_j) in v_i. 1422 | helpers[edgeIter->index] = vindex; 1423 | // Insert e_i in T and set helper(e_i) to v_i. 1424 | newedge.p1 = v2->p; 1425 | newedge.p2 = vertices[v2->next].p; 1426 | newedge.index = vindex2; 1427 | edgeTreeRet = edgeTree.insert(newedge); 1428 | edgeTreeIterators[vindex2] = edgeTreeRet.first; 1429 | helpers[vindex2] = vindex2; 1430 | break; 1431 | 1432 | case TPPL_VERTEXTYPE_MERGE: 1433 | if (edgeTreeIterators[v->previous] == edgeTree.end()) { 1434 | error = true; 1435 | break; 1436 | } 1437 | // if helper(e_i - 1) is a merge vertex 1438 | if (vertextypes[helpers[v->previous]] == TPPL_VERTEXTYPE_MERGE) { 1439 | // Insert the diagonal connecting vi to helper(e_i - 1) in D. 1440 | AddDiagonal(vertices, &newnumvertices, vindex, helpers[v->previous], 1441 | vertextypes, edgeTreeIterators, &edgeTree, helpers); 1442 | vindex2 = newnumvertices - 2; 1443 | } 1444 | // Delete e_i - 1 from T. 1445 | edgeTree.erase(edgeTreeIterators[v->previous]); 1446 | // Search in T to find the edge e_j directly left of v_i. 1447 | newedge.p1 = v->p; 1448 | newedge.p2 = v->p; 1449 | edgeIter = edgeTree.lower_bound(newedge); 1450 | if (edgeIter == edgeTree.begin()) { 1451 | error = true; 1452 | break; 1453 | } 1454 | edgeIter--; 1455 | // If helper(e_j) is a merge vertex. 1456 | if (vertextypes[helpers[edgeIter->index]] == TPPL_VERTEXTYPE_MERGE) { 1457 | // Insert the diagonal connecting v_i to helper(e_j) in D. 1458 | AddDiagonal(vertices, &newnumvertices, vindex2, helpers[edgeIter->index], 1459 | vertextypes, edgeTreeIterators, &edgeTree, helpers); 1460 | } 1461 | // helper(e_j) <- v_i 1462 | helpers[edgeIter->index] = vindex2; 1463 | break; 1464 | 1465 | case TPPL_VERTEXTYPE_REGULAR: 1466 | // If the interior of P lies to the right of v_i. 1467 | if (Below(v->p, vertices[v->previous].p)) { 1468 | if (edgeTreeIterators[v->previous] == edgeTree.end()) { 1469 | error = true; 1470 | break; 1471 | } 1472 | // If helper(e_i - 1) is a merge vertex. 1473 | if (vertextypes[helpers[v->previous]] == TPPL_VERTEXTYPE_MERGE) { 1474 | // Insert the diagonal connecting v_i to helper(e_i - 1) in D. 1475 | AddDiagonal(vertices, &newnumvertices, vindex, helpers[v->previous], 1476 | vertextypes, edgeTreeIterators, &edgeTree, helpers); 1477 | vindex2 = newnumvertices - 2; 1478 | v2 = &(vertices[vindex2]); 1479 | } 1480 | // Delete e_i - 1 from T. 1481 | edgeTree.erase(edgeTreeIterators[v->previous]); 1482 | // Insert e_i in T and set helper(e_i) to v_i. 1483 | newedge.p1 = v2->p; 1484 | newedge.p2 = vertices[v2->next].p; 1485 | newedge.index = vindex2; 1486 | edgeTreeRet = edgeTree.insert(newedge); 1487 | edgeTreeIterators[vindex2] = edgeTreeRet.first; 1488 | helpers[vindex2] = vindex; 1489 | } else { 1490 | // Search in T to find the edge e_j directly left of v_i. 1491 | newedge.p1 = v->p; 1492 | newedge.p2 = v->p; 1493 | edgeIter = edgeTree.lower_bound(newedge); 1494 | if (edgeIter == edgeTree.begin()) { 1495 | error = true; 1496 | break; 1497 | } 1498 | edgeIter--; 1499 | // If helper(e_j) is a merge vertex. 1500 | if (vertextypes[helpers[edgeIter->index]] == TPPL_VERTEXTYPE_MERGE) { 1501 | // Insert the diagonal connecting v_i to helper(e_j) in D. 1502 | AddDiagonal(vertices, &newnumvertices, vindex, helpers[edgeIter->index], 1503 | vertextypes, edgeTreeIterators, &edgeTree, helpers); 1504 | } 1505 | // helper(e_j) <- v_i. 1506 | helpers[edgeIter->index] = vindex; 1507 | } 1508 | break; 1509 | } 1510 | 1511 | if (error) 1512 | break; 1513 | } 1514 | 1515 | char *used = new char[newnumvertices]; 1516 | memset(used, 0, newnumvertices * sizeof(char)); 1517 | 1518 | if (!error) { 1519 | // Return result. 1520 | long size; 1521 | TPPLPoly mpoly; 1522 | for (i = 0; i < newnumvertices; i++) { 1523 | if (used[i]) { 1524 | continue; 1525 | } 1526 | v = &(vertices[i]); 1527 | vnext = &(vertices[v->next]); 1528 | size = 1; 1529 | while (vnext != v) { 1530 | vnext = &(vertices[vnext->next]); 1531 | size++; 1532 | } 1533 | mpoly.Init(size); 1534 | v = &(vertices[i]); 1535 | mpoly[0] = v->p; 1536 | vnext = &(vertices[v->next]); 1537 | size = 1; 1538 | used[i] = 1; 1539 | used[v->next] = 1; 1540 | while (vnext != v) { 1541 | mpoly[size] = vnext->p; 1542 | used[vnext->next] = 1; 1543 | vnext = &(vertices[vnext->next]); 1544 | size++; 1545 | } 1546 | monotonePolys->push_back(mpoly); 1547 | } 1548 | } 1549 | 1550 | // Cleanup. 1551 | delete[] vertices; 1552 | delete[] priority; 1553 | delete[] vertextypes; 1554 | delete[] edgeTreeIterators; 1555 | delete[] helpers; 1556 | delete[] used; 1557 | 1558 | if (error) { 1559 | return 0; 1560 | } else { 1561 | return 1; 1562 | } 1563 | } 1564 | 1565 | // Adds a diagonal to the doubly-connected list of vertices. 1566 | void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2, 1567 | TPPLVertexType *vertextypes, std::set::iterator *edgeTreeIterators, 1568 | std::set *edgeTree, long *helpers) { 1569 | long newindex1, newindex2; 1570 | 1571 | newindex1 = *numvertices; 1572 | (*numvertices)++; 1573 | newindex2 = *numvertices; 1574 | (*numvertices)++; 1575 | 1576 | vertices[newindex1].p = vertices[index1].p; 1577 | vertices[newindex2].p = vertices[index2].p; 1578 | 1579 | vertices[newindex2].next = vertices[index2].next; 1580 | vertices[newindex1].next = vertices[index1].next; 1581 | 1582 | vertices[vertices[index2].next].previous = newindex2; 1583 | vertices[vertices[index1].next].previous = newindex1; 1584 | 1585 | vertices[index1].next = newindex2; 1586 | vertices[newindex2].previous = index1; 1587 | 1588 | vertices[index2].next = newindex1; 1589 | vertices[newindex1].previous = index2; 1590 | 1591 | // Update all relevant structures. 1592 | vertextypes[newindex1] = vertextypes[index1]; 1593 | edgeTreeIterators[newindex1] = edgeTreeIterators[index1]; 1594 | helpers[newindex1] = helpers[index1]; 1595 | if (edgeTreeIterators[newindex1] != edgeTree->end()) { 1596 | edgeTreeIterators[newindex1]->index = newindex1; 1597 | } 1598 | vertextypes[newindex2] = vertextypes[index2]; 1599 | edgeTreeIterators[newindex2] = edgeTreeIterators[index2]; 1600 | helpers[newindex2] = helpers[index2]; 1601 | if (edgeTreeIterators[newindex2] != edgeTree->end()) { 1602 | edgeTreeIterators[newindex2]->index = newindex2; 1603 | } 1604 | } 1605 | 1606 | bool TPPLPartition::Below(TPPLPoint &p1, TPPLPoint &p2) { 1607 | if (p1.y < p2.y) { 1608 | return true; 1609 | } else if (p1.y == p2.y) { 1610 | if (p1.x < p2.x) { 1611 | return true; 1612 | } 1613 | } 1614 | return false; 1615 | } 1616 | 1617 | // Sorts in the falling order of y values, if y is equal, x is used instead. 1618 | bool TPPLPartition::VertexSorter::operator()(long index1, long index2) { 1619 | if (vertices[index1].p.y > vertices[index2].p.y) { 1620 | return true; 1621 | } else if (vertices[index1].p.y == vertices[index2].p.y) { 1622 | if (vertices[index1].p.x > vertices[index2].p.x) { 1623 | return true; 1624 | } 1625 | } 1626 | return false; 1627 | } 1628 | 1629 | bool TPPLPartition::ScanLineEdge::IsConvex(const TPPLPoint &p1, const TPPLPoint &p2, const TPPLPoint &p3) const { 1630 | tppl_float tmp; 1631 | tmp = (p3.y - p1.y) * (p2.x - p1.x) - (p3.x - p1.x) * (p2.y - p1.y); 1632 | if (tmp > 0) { 1633 | return 1; 1634 | } 1635 | 1636 | return 0; 1637 | } 1638 | 1639 | bool TPPLPartition::ScanLineEdge::operator<(const ScanLineEdge &other) const { 1640 | if (other.p1.y == other.p2.y) { 1641 | if (p1.y == p2.y) { 1642 | return (p1.y < other.p1.y); 1643 | } 1644 | return IsConvex(p1, p2, other.p1); 1645 | } else if (p1.y == p2.y) { 1646 | return !IsConvex(other.p1, other.p2, p1); 1647 | } else if (p1.y < other.p1.y) { 1648 | return !IsConvex(other.p1, other.p2, p1); 1649 | } else { 1650 | return IsConvex(p1, p2, other.p1); 1651 | } 1652 | } 1653 | 1654 | // Triangulates monotone polygon. 1655 | // Time complexity: O(n) 1656 | // Space complexity: O(n) 1657 | int TPPLPartition::TriangulateMonotone(TPPLPoly *inPoly, TPPLPolyList *triangles) { 1658 | if (!inPoly->Valid()) { 1659 | return 0; 1660 | } 1661 | 1662 | long i, i2, j, topindex, bottomindex, leftindex, rightindex, vindex; 1663 | TPPLPoint *points = NULL; 1664 | long numpoints; 1665 | TPPLPoly triangle; 1666 | 1667 | numpoints = inPoly->GetNumPoints(); 1668 | points = inPoly->GetPoints(); 1669 | 1670 | // Trivial case. 1671 | if (numpoints == 3) { 1672 | triangles->push_back(*inPoly); 1673 | return 1; 1674 | } 1675 | 1676 | topindex = 0; 1677 | bottomindex = 0; 1678 | for (i = 1; i < numpoints; i++) { 1679 | if (Below(points[i], points[bottomindex])) { 1680 | bottomindex = i; 1681 | } 1682 | if (Below(points[topindex], points[i])) { 1683 | topindex = i; 1684 | } 1685 | } 1686 | 1687 | // Check if the poly is really monotone. 1688 | i = topindex; 1689 | while (i != bottomindex) { 1690 | i2 = i + 1; 1691 | if (i2 >= numpoints) { 1692 | i2 = 0; 1693 | } 1694 | if (!Below(points[i2], points[i])) { 1695 | return 0; 1696 | } 1697 | i = i2; 1698 | } 1699 | i = bottomindex; 1700 | while (i != topindex) { 1701 | i2 = i + 1; 1702 | if (i2 >= numpoints) { 1703 | i2 = 0; 1704 | } 1705 | if (!Below(points[i], points[i2])) { 1706 | return 0; 1707 | } 1708 | i = i2; 1709 | } 1710 | 1711 | char *vertextypes = new char[numpoints]; 1712 | long *priority = new long[numpoints]; 1713 | 1714 | // Merge left and right vertex chains. 1715 | priority[0] = topindex; 1716 | vertextypes[topindex] = 0; 1717 | leftindex = topindex + 1; 1718 | if (leftindex >= numpoints) { 1719 | leftindex = 0; 1720 | } 1721 | rightindex = topindex - 1; 1722 | if (rightindex < 0) { 1723 | rightindex = numpoints - 1; 1724 | } 1725 | for (i = 1; i < (numpoints - 1); i++) { 1726 | if (leftindex == bottomindex) { 1727 | priority[i] = rightindex; 1728 | rightindex--; 1729 | if (rightindex < 0) { 1730 | rightindex = numpoints - 1; 1731 | } 1732 | vertextypes[priority[i]] = -1; 1733 | } else if (rightindex == bottomindex) { 1734 | priority[i] = leftindex; 1735 | leftindex++; 1736 | if (leftindex >= numpoints) { 1737 | leftindex = 0; 1738 | } 1739 | vertextypes[priority[i]] = 1; 1740 | } else { 1741 | if (Below(points[leftindex], points[rightindex])) { 1742 | priority[i] = rightindex; 1743 | rightindex--; 1744 | if (rightindex < 0) { 1745 | rightindex = numpoints - 1; 1746 | } 1747 | vertextypes[priority[i]] = -1; 1748 | } else { 1749 | priority[i] = leftindex; 1750 | leftindex++; 1751 | if (leftindex >= numpoints) { 1752 | leftindex = 0; 1753 | } 1754 | vertextypes[priority[i]] = 1; 1755 | } 1756 | } 1757 | } 1758 | priority[i] = bottomindex; 1759 | vertextypes[bottomindex] = 0; 1760 | 1761 | long *stack = new long[numpoints]; 1762 | long stackptr = 0; 1763 | 1764 | stack[0] = priority[0]; 1765 | stack[1] = priority[1]; 1766 | stackptr = 2; 1767 | 1768 | // For each vertex from top to bottom trim as many triangles as possible. 1769 | for (i = 2; i < (numpoints - 1); i++) { 1770 | vindex = priority[i]; 1771 | if (vertextypes[vindex] != vertextypes[stack[stackptr - 1]]) { 1772 | for (j = 0; j < (stackptr - 1); j++) { 1773 | if (vertextypes[vindex] == 1) { 1774 | triangle.Triangle(points[stack[j + 1]], points[stack[j]], points[vindex]); 1775 | } else { 1776 | triangle.Triangle(points[stack[j]], points[stack[j + 1]], points[vindex]); 1777 | } 1778 | triangles->push_back(triangle); 1779 | } 1780 | stack[0] = priority[i - 1]; 1781 | stack[1] = priority[i]; 1782 | stackptr = 2; 1783 | } else { 1784 | stackptr--; 1785 | while (stackptr > 0) { 1786 | if (vertextypes[vindex] == 1) { 1787 | if (IsConvex(points[vindex], points[stack[stackptr - 1]], points[stack[stackptr]])) { 1788 | triangle.Triangle(points[vindex], points[stack[stackptr - 1]], points[stack[stackptr]]); 1789 | triangles->push_back(triangle); 1790 | stackptr--; 1791 | } else { 1792 | break; 1793 | } 1794 | } else { 1795 | if (IsConvex(points[vindex], points[stack[stackptr]], points[stack[stackptr - 1]])) { 1796 | triangle.Triangle(points[vindex], points[stack[stackptr]], points[stack[stackptr - 1]]); 1797 | triangles->push_back(triangle); 1798 | stackptr--; 1799 | } else { 1800 | break; 1801 | } 1802 | } 1803 | } 1804 | stackptr++; 1805 | stack[stackptr] = vindex; 1806 | stackptr++; 1807 | } 1808 | } 1809 | vindex = priority[i]; 1810 | for (j = 0; j < (stackptr - 1); j++) { 1811 | if (vertextypes[stack[j + 1]] == 1) { 1812 | triangle.Triangle(points[stack[j]], points[stack[j + 1]], points[vindex]); 1813 | } else { 1814 | triangle.Triangle(points[stack[j + 1]], points[stack[j]], points[vindex]); 1815 | } 1816 | triangles->push_back(triangle); 1817 | } 1818 | 1819 | delete[] priority; 1820 | delete[] vertextypes; 1821 | delete[] stack; 1822 | 1823 | return 1; 1824 | } 1825 | 1826 | int TPPLPartition::Triangulate_MONO(TPPLPolyList *inpolys, TPPLPolyList *triangles) { 1827 | TPPLPolyList monotone; 1828 | TPPLPolyList::iterator iter; 1829 | 1830 | if (!MonotonePartition(inpolys, &monotone)) { 1831 | return 0; 1832 | } 1833 | for (iter = monotone.begin(); iter != monotone.end(); iter++) { 1834 | if (!TriangulateMonotone(&(*iter), triangles)) { 1835 | return 0; 1836 | } 1837 | } 1838 | return 1; 1839 | } 1840 | 1841 | int TPPLPartition::Triangulate_MONO(TPPLPoly *poly, TPPLPolyList *triangles) { 1842 | TPPLPolyList polys; 1843 | polys.push_back(*poly); 1844 | 1845 | return Triangulate_MONO(&polys, triangles); 1846 | } 1847 | -------------------------------------------------------------------------------- /src/polypartition.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #ifndef POLYPARTITION_H 25 | #define POLYPARTITION_H 26 | 27 | #include 28 | #include 29 | 30 | #ifndef tppl_float 31 | #define tppl_float double 32 | #endif 33 | #ifndef tppl_sqrt 34 | #define tppl_sqrt sqrt 35 | #endif 36 | 37 | enum TPPLOrientation { 38 | TPPL_ORIENTATION_CW = -1, 39 | TPPL_ORIENTATION_NONE = 0, 40 | TPPL_ORIENTATION_CCW = 1, 41 | }; 42 | 43 | enum TPPLVertexType { 44 | TPPL_VERTEXTYPE_REGULAR = 0, 45 | TPPL_VERTEXTYPE_START = 1, 46 | TPPL_VERTEXTYPE_END = 2, 47 | TPPL_VERTEXTYPE_SPLIT = 3, 48 | TPPL_VERTEXTYPE_MERGE = 4, 49 | }; 50 | 51 | // 2D point structure. 52 | struct TPPLPoint { 53 | tppl_float x; 54 | tppl_float y; 55 | // User-specified vertex identifier. Note that this isn't used internally 56 | // by the library, but will be faithfully copied around. 57 | int id; 58 | 59 | TPPLPoint operator+(const TPPLPoint &p) const { 60 | TPPLPoint r; 61 | r.x = x + p.x; 62 | r.y = y + p.y; 63 | return r; 64 | } 65 | 66 | TPPLPoint operator-(const TPPLPoint &p) const { 67 | TPPLPoint r; 68 | r.x = x - p.x; 69 | r.y = y - p.y; 70 | return r; 71 | } 72 | 73 | TPPLPoint operator*(const tppl_float f) const { 74 | TPPLPoint r; 75 | r.x = x * f; 76 | r.y = y * f; 77 | return r; 78 | } 79 | 80 | TPPLPoint operator/(const tppl_float f) const { 81 | TPPLPoint r; 82 | r.x = x / f; 83 | r.y = y / f; 84 | return r; 85 | } 86 | 87 | bool operator==(const TPPLPoint &p) const { 88 | return ((x == p.x) && (y == p.y)); 89 | } 90 | 91 | bool operator!=(const TPPLPoint &p) const { 92 | return !((x == p.x) && (y == p.y)); 93 | } 94 | }; 95 | 96 | // Polygon implemented as an array of points with a "hole" flag. 97 | class TPPLPoly { 98 | protected: 99 | TPPLPoint *points; 100 | long numpoints; 101 | bool hole; 102 | 103 | public: 104 | // Constructors and destructors. 105 | TPPLPoly(); 106 | ~TPPLPoly(); 107 | 108 | TPPLPoly(const TPPLPoly &src); 109 | TPPLPoly &operator=(const TPPLPoly &src); 110 | 111 | // Getters and setters. 112 | long GetNumPoints() const { 113 | return numpoints; 114 | } 115 | 116 | bool IsHole() const { 117 | return hole; 118 | } 119 | 120 | void SetHole(bool hole) { 121 | this->hole = hole; 122 | } 123 | 124 | TPPLPoint &GetPoint(long i) { 125 | return points[i]; 126 | } 127 | 128 | const TPPLPoint &GetPoint(long i) const { 129 | return points[i]; 130 | } 131 | 132 | TPPLPoint *GetPoints() { 133 | return points; 134 | } 135 | 136 | TPPLPoint &operator[](int i) { 137 | return points[i]; 138 | } 139 | 140 | const TPPLPoint &operator[](int i) const { 141 | return points[i]; 142 | } 143 | 144 | // Clears the polygon points. 145 | void Clear(); 146 | 147 | // Inits the polygon with numpoints vertices. 148 | void Init(long numpoints); 149 | 150 | // Creates a triangle with points p1, p2, and p3. 151 | void Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3); 152 | 153 | // Inverts the orfer of vertices. 154 | void Invert(); 155 | 156 | // Returns the orientation of the polygon. 157 | // Possible values: 158 | // TPPL_ORIENTATION_CCW: Polygon vertices are in counter-clockwise order. 159 | // TPPL_ORIENTATION_CW: Polygon vertices are in clockwise order. 160 | // TPPL_ORIENTATION_NONE: The polygon has no (measurable) area. 161 | TPPLOrientation GetOrientation() const; 162 | 163 | // Sets the polygon orientation. 164 | // Possible values: 165 | // TPPL_ORIENTATION_CCW: Sets vertices in counter-clockwise order. 166 | // TPPL_ORIENTATION_CW: Sets vertices in clockwise order. 167 | // TPPL_ORIENTATION_NONE: Reverses the orientation of the vertices if there 168 | // is one, otherwise does nothing (if orientation is already NONE). 169 | void SetOrientation(TPPLOrientation orientation); 170 | 171 | // Checks whether a polygon is valid or not. 172 | inline bool Valid() const { return this->numpoints >= 3; } 173 | }; 174 | 175 | #ifdef TPPL_ALLOCATOR 176 | typedef std::list TPPLPolyList; 177 | #else 178 | typedef std::list TPPLPolyList; 179 | #endif 180 | 181 | class TPPLPartition { 182 | protected: 183 | struct PartitionVertex { 184 | bool isActive; 185 | bool isConvex; 186 | bool isEar; 187 | 188 | TPPLPoint p; 189 | tppl_float angle; 190 | PartitionVertex *previous; 191 | PartitionVertex *next; 192 | 193 | PartitionVertex(); 194 | }; 195 | 196 | struct MonotoneVertex { 197 | TPPLPoint p; 198 | long previous; 199 | long next; 200 | }; 201 | 202 | class VertexSorter { 203 | MonotoneVertex *vertices; 204 | 205 | public: 206 | VertexSorter(MonotoneVertex *v) : 207 | vertices(v) {} 208 | bool operator()(long index1, long index2); 209 | }; 210 | 211 | struct Diagonal { 212 | long index1; 213 | long index2; 214 | }; 215 | 216 | #ifdef TPPL_ALLOCATOR 217 | typedef std::list DiagonalList; 218 | #else 219 | typedef std::list DiagonalList; 220 | #endif 221 | 222 | // Dynamic programming state for minimum-weight triangulation. 223 | struct DPState { 224 | bool visible; 225 | tppl_float weight; 226 | long bestvertex; 227 | }; 228 | 229 | // Dynamic programming state for convex partitioning. 230 | struct DPState2 { 231 | bool visible; 232 | long weight; 233 | DiagonalList pairs; 234 | }; 235 | 236 | // Edge that intersects the scanline. 237 | struct ScanLineEdge { 238 | mutable long index; 239 | TPPLPoint p1; 240 | TPPLPoint p2; 241 | 242 | // Determines if the edge is to the left of another edge. 243 | bool operator<(const ScanLineEdge &other) const; 244 | 245 | bool IsConvex(const TPPLPoint &p1, const TPPLPoint &p2, const TPPLPoint &p3) const; 246 | }; 247 | 248 | // Standard helper functions. 249 | bool IsConvex(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3); 250 | bool IsReflex(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3); 251 | bool IsInside(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p); 252 | 253 | bool InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p); 254 | bool InCone(PartitionVertex *v, TPPLPoint &p); 255 | 256 | int Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TPPLPoint &p22); 257 | 258 | TPPLPoint Normalize(const TPPLPoint &p); 259 | tppl_float Distance(const TPPLPoint &p1, const TPPLPoint &p2); 260 | 261 | // Helper functions for Triangulate_EC. 262 | void UpdateVertexReflexity(PartitionVertex *v); 263 | void UpdateVertex(PartitionVertex *v, PartitionVertex *vertices, long numvertices); 264 | 265 | // Helper functions for ConvexPartition_OPT. 266 | void UpdateState(long a, long b, long w, long i, long j, DPState2 **dpstates); 267 | void TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); 268 | void TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); 269 | 270 | // Helper functions for MonotonePartition. 271 | bool Below(TPPLPoint &p1, TPPLPoint &p2); 272 | void AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2, 273 | TPPLVertexType *vertextypes, std::set::iterator *edgeTreeIterators, 274 | std::set *edgeTree, long *helpers); 275 | 276 | // Triangulates a monotone polygon, used in Triangulate_MONO. 277 | int TriangulateMonotone(TPPLPoly *inPoly, TPPLPolyList *triangles); 278 | 279 | public: 280 | // Simple heuristic procedure for removing holes from a list of polygons. 281 | // It works by creating a diagonal from the right-most hole vertex 282 | // to some other visible vertex. 283 | // Time complexity: O(h*(n^2)), h is the # of holes, n is the # of vertices. 284 | // Space complexity: O(n) 285 | // params: 286 | // inpolys: 287 | // A list of polygons that can contain holes. 288 | // Vertices of all non-hole polys have to be in counter-clockwise order. 289 | // Vertices of all hole polys have to be in clockwise order. 290 | // outpolys: 291 | // A list of polygons without holes. 292 | // Returns 1 on success, 0 on failure. 293 | int RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys); 294 | 295 | // Triangulates a polygon by ear clipping. 296 | // Time complexity: O(n^2), n is the number of vertices. 297 | // Space complexity: O(n) 298 | // params: 299 | // poly: 300 | // An input polygon to be triangulated. 301 | // Vertices have to be in counter-clockwise order. 302 | // triangles: 303 | // A list of triangles (result). 304 | // Returns 1 on success, 0 on failure. 305 | int Triangulate_EC(TPPLPoly *poly, TPPLPolyList *triangles); 306 | 307 | // Triangulates a list of polygons that may contain holes by ear clipping 308 | // algorithm. It first calls RemoveHoles to get rid of the holes, and then 309 | // calls Triangulate_EC for each resulting polygon. 310 | // Time complexity: O(h*(n^2)), h is the # of holes, n is the # of vertices. 311 | // Space complexity: O(n) 312 | // params: 313 | // inpolys: 314 | // A list of polygons to be triangulated (can contain holes). 315 | // Vertices of all non-hole polys have to be in counter-clockwise order. 316 | // Vertices of all hole polys have to be in clockwise order. 317 | // triangles: 318 | // A list of triangles (result). 319 | // Returns 1 on success, 0 on failure. 320 | int Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles); 321 | 322 | // Creates an optimal polygon triangulation in terms of minimal edge length. 323 | // Time complexity: O(n^3), n is the number of vertices 324 | // Space complexity: O(n^2) 325 | // params: 326 | // poly: 327 | // An input polygon to be triangulated. 328 | // Vertices have to be in counter-clockwise order. 329 | // triangles: 330 | // A list of triangles (result). 331 | // Returns 1 on success, 0 on failure. 332 | int Triangulate_OPT(TPPLPoly *poly, TPPLPolyList *triangles); 333 | 334 | // Triangulates a polygon by first partitioning it into monotone polygons. 335 | // Time complexity: O(n*log(n)), n is the number of vertices. 336 | // Space complexity: O(n) 337 | // params: 338 | // poly: 339 | // An input polygon to be triangulated. 340 | // Vertices have to be in counter-clockwise order. 341 | // triangles: 342 | // A list of triangles (result). 343 | // Returns 1 on success, 0 on failure. 344 | int Triangulate_MONO(TPPLPoly *poly, TPPLPolyList *triangles); 345 | 346 | // Triangulates a list of polygons by first 347 | // partitioning them into monotone polygons. 348 | // Time complexity: O(n*log(n)), n is the number of vertices. 349 | // Space complexity: O(n) 350 | // params: 351 | // inpolys: 352 | // A list of polygons to be triangulated (can contain holes). 353 | // Vertices of all non-hole polys have to be in counter-clockwise order. 354 | // Vertices of all hole polys have to be in clockwise order. 355 | // triangles: 356 | // A list of triangles (result). 357 | // Returns 1 on success, 0 on failure. 358 | int Triangulate_MONO(TPPLPolyList *inpolys, TPPLPolyList *triangles); 359 | 360 | // Creates a monotone partition of a list of polygons that 361 | // can contain holes. Triangulates a set of polygons by 362 | // first partitioning them into monotone polygons. 363 | // Time complexity: O(n*log(n)), n is the number of vertices. 364 | // Space complexity: O(n) 365 | // params: 366 | // inpolys: 367 | // A list of polygons to be triangulated (can contain holes). 368 | // Vertices of all non-hole polys have to be in counter-clockwise order. 369 | // Vertices of all hole polys have to be in clockwise order. 370 | // monotonePolys: 371 | // A list of monotone polygons (result). 372 | // Returns 1 on success, 0 on failure. 373 | int MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monotonePolys); 374 | 375 | // Partitions a polygon into convex polygons by using the 376 | // Hertel-Mehlhorn algorithm. The algorithm gives at most four times 377 | // the number of parts as the optimal algorithm, however, in practice 378 | // it works much better than that and often gives optimal partition. 379 | // It uses triangulation obtained by ear clipping as intermediate result. 380 | // Time complexity O(n^2), n is the number of vertices. 381 | // Space complexity: O(n) 382 | // params: 383 | // poly: 384 | // An input polygon to be partitioned. 385 | // Vertices have to be in counter-clockwise order. 386 | // parts: 387 | // Resulting list of convex polygons. 388 | // Returns 1 on success, 0 on failure. 389 | int ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts); 390 | 391 | // Partitions a list of polygons into convex parts by using the 392 | // Hertel-Mehlhorn algorithm. The algorithm gives at most four times 393 | // the number of parts as the optimal algorithm, however, in practice 394 | // it works much better than that and often gives optimal partition. 395 | // It uses triangulation obtained by ear clipping as intermediate result. 396 | // Time complexity O(n^2), n is the number of vertices. 397 | // Space complexity: O(n) 398 | // params: 399 | // inpolys: 400 | // An input list of polygons to be partitioned. Vertices of 401 | // all non-hole polys have to be in counter-clockwise order. 402 | // Vertices of all hole polys have to be in clockwise order. 403 | // parts: 404 | // Resulting list of convex polygons. 405 | // Returns 1 on success, 0 on failure. 406 | int ConvexPartition_HM(TPPLPolyList *inpolys, TPPLPolyList *parts); 407 | 408 | // Optimal convex partitioning (in terms of number of resulting 409 | // convex polygons) using the Keil-Snoeyink algorithm. 410 | // For reference, see M. Keil, J. Snoeyink, "On the time bound for 411 | // convex decomposition of simple polygons", 1998. 412 | // Time complexity O(n^3), n is the number of vertices. 413 | // Space complexity: O(n^3) 414 | // params: 415 | // poly: 416 | // An input polygon to be partitioned. 417 | // Vertices have to be in counter-clockwise order. 418 | // parts: 419 | // Resulting list of convex polygons. 420 | // Returns 1 on success, 0 on failure. 421 | int ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts); 422 | }; 423 | 424 | #endif 425 | -------------------------------------------------------------------------------- /test/SConstruct: -------------------------------------------------------------------------------- 1 | #!python 2 | import os, subprocess 3 | 4 | opts = Variables([], ARGUMENTS) 5 | 6 | # Gets the standard flags CC, CCX, etc. 7 | env = DefaultEnvironment() 8 | 9 | # Define our options. Use future-proofed names for platforms. 10 | platform_array = ["", "windows", "macos", "linux"] 11 | opts.Add(EnumVariable("target", "Compilation target", "debug", ["d", "debug", "r", "release"])) 12 | opts.Add(EnumVariable("platform", "Compilation platform", "", platform_array)) 13 | opts.Add(EnumVariable("p", "Alias for 'platform'", "", platform_array)) 14 | opts.Add(BoolVariable("use_llvm", "Use the LLVM / Clang compiler", "no")) 15 | 16 | # Only support 64-bit systems. 17 | bits = 64 18 | 19 | # Updates the environment with the option variables. 20 | opts.Update(env) 21 | 22 | # Process platform arguments. 23 | if env["p"] != "": 24 | env["platform"] = env["p"] 25 | 26 | if env["platform"] == "": 27 | print("No valid target platform selected.") 28 | quit() 29 | 30 | # Process other arguments. 31 | if env["use_llvm"]: 32 | env["CXX"] = "clang++" 33 | 34 | env.Append(CCFLAGS=["-Wno-unused-result"]) 35 | 36 | # Check our platform specifics 37 | if env["platform"] == "macos": 38 | if env["target"] in ("debug", "d"): 39 | env.Append(CCFLAGS=["-g", "-O2", "-arch", "x86_64"]) 40 | env.Append(LINKFLAGS=["-arch", "x86_64"]) 41 | else: 42 | env.Append(CCFLAGS=["-g", "-O3", "-arch", "x86_64"]) 43 | env.Append(LINKFLAGS=["-arch", "x86_64"]) 44 | 45 | elif env["platform"] == "linux": 46 | if env["target"] in ("debug", "d"): 47 | env.Append(CCFLAGS=["-fPIC", "-g3", "-Og"]) 48 | else: 49 | env.Append(CCFLAGS=["-fPIC", "-g", "-O3"]) 50 | 51 | elif env["platform"] == "windows": 52 | # This makes sure to keep the session environment variables 53 | # on Windows, so that you can run scons in a VS 2017 prompt 54 | # and it will find all the required tools. 55 | env.Append(ENV=os.environ) 56 | 57 | env.Append(CCFLAGS=["-DWIN32", "-D_WIN32", "-D_WINDOWS", "-W3", "-GR", "-D_CRT_SECURE_NO_WARNINGS"]) 58 | if env["target"] in ("debug", "d"): 59 | env.Append(CCFLAGS=["-EHsc", "-D_DEBUG", "-MDd"]) 60 | else: 61 | env.Append(CCFLAGS=["-O2", "-EHsc", "-DNDEBUG", "-MD"]) 62 | 63 | # Tweak this if you want to use different folders, 64 | # or more folders, to store your source code in. 65 | env.Append(CPPPATH=["./", "../src/"]) 66 | sources = Glob("*.cpp") + Glob("../src/*.cpp") 67 | 68 | program = env.Program(target="./polypartition_test", source=sources) 69 | 70 | Default(program) 71 | 72 | # Generates help for the -h scons option. 73 | Help(opts.GenerateHelpText(env)) 74 | -------------------------------------------------------------------------------- /test/image.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "image.h" 30 | 31 | Image::Image(void) { 32 | width = 0; 33 | height = 0; 34 | data = NULL; 35 | } 36 | 37 | Image::Image(long width, long height) { 38 | this->width = width; 39 | this->height = height; 40 | data = (unsigned char *)malloc(3 * width * height); 41 | memset(data, 0, 3 * width * height); 42 | } 43 | 44 | Image::Image(const Image &src) { 45 | width = src.width; 46 | height = src.height; 47 | data = (unsigned char *)malloc(3 * width * height); 48 | memcpy(data, src.data, 3 * width * height); 49 | } 50 | 51 | Image &Image::operator=(const Image &src) { 52 | width = src.width; 53 | height = src.height; 54 | if (data) { 55 | free(data); 56 | } 57 | data = (unsigned char *)malloc(3 * width * height); 58 | memcpy(data, src.data, 3 * width * height); 59 | return *this; 60 | } 61 | 62 | Image::~Image(void) { 63 | if (data) { 64 | free(data); 65 | } 66 | } 67 | 68 | void Image::Init(long width, long height) { 69 | if (data) { 70 | free(data); 71 | } 72 | this->width = width; 73 | this->height = height; 74 | data = (unsigned char *)malloc(3 * width * height); 75 | memset(data, 0, 3 * width * height); 76 | } 77 | 78 | unsigned char Image::GetMeanGray() { 79 | long sum = 0; 80 | for (long i = 0; i < height; i++) { 81 | for (long j = 0; j < width; j++) { 82 | sum += GetPixelGray(j, i); 83 | } 84 | } 85 | return (unsigned char)(sum / (width * height)); 86 | } 87 | 88 | void Image::Binarize(unsigned char threshold) { 89 | unsigned char c; 90 | for (long i = 0; i < height; i++) { 91 | for (long j = 0; j < width; j++) { 92 | c = GetPixelGray(j, i); 93 | if (c > threshold) { 94 | SetPixelGray(j, i, 255); 95 | } else { 96 | SetPixelGray(j, i, 0); 97 | } 98 | } 99 | } 100 | } 101 | 102 | void Image::FlipHorizontal() { 103 | unsigned char *newdata; 104 | newdata = (unsigned char *)malloc(height * width * 3); 105 | for (long i = 0; i < height; i++) { 106 | for (long j = 0; j < width; j++) { 107 | long index1 = 3 * ((i * width) + j); 108 | long index2 = 3 * ((i * width) + width - j - 1); 109 | newdata[index1] = data[index2]; 110 | index1++; 111 | index2++; 112 | newdata[index1] = data[index2]; 113 | index1++; 114 | index2++; 115 | newdata[index1] = data[index2]; 116 | index1++; 117 | index2++; 118 | } 119 | } 120 | free(data); 121 | data = newdata; 122 | } 123 | 124 | void Image::FlipVertical() { 125 | unsigned char *newdata; 126 | newdata = (unsigned char *)malloc(height * width * 3); 127 | for (long i = 0; i < height; i++) { 128 | for (long j = 0; j < width; j++) { 129 | long index1 = 3 * ((i * width) + j); 130 | long index2 = 3 * (((height - i - 1) * width) + j); 131 | newdata[index1] = data[index2]; 132 | index1++; 133 | index2++; 134 | newdata[index1] = data[index2]; 135 | index1++; 136 | index2++; 137 | newdata[index1] = data[index2]; 138 | index1++; 139 | index2++; 140 | } 141 | } 142 | free(data); 143 | data = newdata; 144 | } 145 | 146 | Image::Pixel Image::GetPixelBilinear(float x, float y) { 147 | Image::Pixel c = { 0, 0, 0 }, c1, c2, c3, c4; 148 | if (x < 0) { 149 | return c; 150 | } 151 | if (y < 0) { 152 | return c; 153 | } 154 | if (x > (width - 1)) { 155 | return c; 156 | } 157 | if (y > (height - 1)) { 158 | return c; 159 | } 160 | long x1, y1; 161 | float dx, dy; 162 | x1 = (long)floor(x); 163 | y1 = (long)floor(y); 164 | dx = x - x1; 165 | dy = y - y1; 166 | c1 = GetPixelColor(x1, y1); 167 | c2 = GetPixelColor(x1 + 1, y1); 168 | c3 = GetPixelColor(x1, y1 + 1); 169 | c4 = GetPixelColor(x1 + 1, y1 + 1); 170 | c.R = (unsigned char)round(interpolate(c1.R, c2.R, c3.R, c4.R, dx, dy)); 171 | c.G = (unsigned char)round(interpolate(c1.G, c2.G, c3.G, c4.G, dx, dy)); 172 | c.B = (unsigned char)round(interpolate(c1.B, c2.B, c3.B, c4.B, dx, dy)); 173 | return (c); 174 | } 175 | 176 | float Image::interpolate(float x1, float x2, float x3, float x4, float dx, float dy) { 177 | float x5, x6; 178 | x5 = x2 * dx + x1 * (1 - dx); 179 | x6 = x4 * dx + x3 * (1 - dx); 180 | return (x6 * dy + x5 * (1 - dy)); 181 | } 182 | 183 | long Image::round(float x) { 184 | if ((x - floor(x)) < 0.5) { 185 | return (long)floor(x); 186 | } else { 187 | return (long)ceil(x); 188 | } 189 | } 190 | 191 | void Image::GetHistogramGray(long *histogram) { 192 | for (long i = 0; i < 256; i++) { 193 | histogram[i] = 0; 194 | } 195 | for (long i = 0; i < height; i++) { 196 | for (long j = 0; j < width; j++) { 197 | histogram[GetPixelGray(j, i)]++; 198 | } 199 | } 200 | } 201 | 202 | void Image::Invert() { 203 | long i, n = 3 * width * height; 204 | for (i = 0; i < n; i++) { 205 | data[i] = 255 - data[i]; 206 | } 207 | } 208 | 209 | Image Image::Crop(int posx, int posy, int width, int height) { 210 | Image resultimg; 211 | Image::Pixel rgb; 212 | resultimg.Init(width, height); 213 | long i, j; 214 | for (i = 0; i < height; i++) { 215 | for (j = 0; j < width; j++) { 216 | rgb = this->GetPixelColor(j + posx, i + posy); 217 | resultimg.SetPixelColor(j, i, rgb); 218 | } 219 | } 220 | return resultimg; 221 | } 222 | 223 | Image Image::Resize(int factor) { 224 | long width, height; 225 | int factor2; 226 | long sumR, sumG, sumB; 227 | 228 | width = this->width / factor; 229 | height = this->height / factor; 230 | factor2 = factor * factor; 231 | 232 | Image resultimg(width, height); 233 | Image::Pixel rgb; 234 | long i, j, i2, j2, minx, miny, maxx, maxy; 235 | 236 | for (i = 0; i < height; i++) { 237 | for (j = 0; j < width; j++) { 238 | minx = j * factor; 239 | miny = i * factor; 240 | maxx = minx + factor; 241 | maxy = miny + factor; 242 | 243 | sumR = 0; 244 | sumG = 0; 245 | sumB = 0; 246 | 247 | for (i2 = miny; i2 < maxy; i2++) { 248 | for (j2 = minx; j2 < maxx; j2++) { 249 | rgb = GetPixelColor(j2, i2); 250 | sumR += rgb.R; 251 | sumG += rgb.G; 252 | sumB += rgb.B; 253 | } 254 | } 255 | rgb.R = (unsigned char)round(sumR / ((float)factor2)); 256 | rgb.G = (unsigned char)round(sumG / ((float)factor2)); 257 | rgb.B = (unsigned char)round(sumB / ((float)factor2)); 258 | resultimg.SetPixelColor(j, i, rgb); 259 | } 260 | } 261 | return resultimg; 262 | } 263 | 264 | Image Image::Filter(float *filter, long filterwidth, long filterheight) { 265 | Image ret(width, height); 266 | long i1, j1, i2, j2; 267 | long fox, foy, filtersize; 268 | long x, y; 269 | float sumr, sumg, sumb; 270 | long offset1, offset2, offset3; 271 | unsigned char *data1, *data2; 272 | fox = filterwidth / 2; 273 | foy = filterheight / 2; 274 | filtersize = filterwidth * filterheight; 275 | data1 = ret.GetData(); 276 | data2 = data; 277 | for (i1 = 0; i1 < height; i1++) { 278 | for (j1 = 0; j1 < width; j1++) { 279 | offset1 = (i1 * width + j1) * 3; 280 | sumr = 0; 281 | sumg = 0; 282 | sumb = 0; 283 | for (i2 = -foy; i2 <= foy; i2++) { 284 | for (j2 = -fox; j2 <= fox; j2++) { 285 | x = j1 + j2; 286 | y = i1 + i2; 287 | if (x < 0) { 288 | x = 0; 289 | } 290 | if (y < 0) { 291 | y = 0; 292 | } 293 | if (x >= width) { 294 | x = width - 1; 295 | } 296 | if (y >= height) { 297 | y = height - 1; 298 | } 299 | offset2 = (y * width + x) * 3; 300 | offset3 = (i2 + foy) * filterwidth + j2 + fox; 301 | sumr += data2[offset2] * filter[offset3]; 302 | sumg += data2[offset2 + 1] * filter[offset3]; 303 | sumb += data2[offset2 + 2] * filter[offset3]; 304 | } 305 | } 306 | data1[offset1] = (unsigned char)round(sumr); 307 | data1[offset1 + 1] = (unsigned char)round(sumg); 308 | data1[offset1 + 2] = (unsigned char)round(sumb); 309 | } 310 | } 311 | return ret; 312 | } 313 | 314 | Image Image::GaussBlur(float sigma, long masksize) { 315 | Image ret; 316 | float *filter; 317 | long fsize; 318 | long i, j; 319 | long fo; 320 | float pi = 3.1415926538f; 321 | 322 | if (!masksize) { 323 | masksize = round(sigma) * 2 * 2 + 1; 324 | } 325 | 326 | fsize = masksize * masksize; 327 | fo = masksize / 2; 328 | filter = (float *)malloc(fsize * sizeof(float)); 329 | 330 | for (i = -fo; i <= fo; i++) { 331 | for (j = -fo; j <= fo; j++) { 332 | filter[(i + fo) * masksize + j + fo] = (1 / (2 * pi * sigma)) * exp(-(i * i + j * j) / (2 * sigma * sigma)); 333 | } 334 | } 335 | 336 | float sum = 0; 337 | for (i = 0; i < fsize; i++) { 338 | sum += filter[i]; 339 | } 340 | for (i = 0; i < fsize; i++) { 341 | filter[i] = filter[i] / sum; 342 | } 343 | 344 | ret = this->Filter(filter, masksize, masksize); 345 | 346 | delete filter; 347 | return ret; 348 | } 349 | 350 | void Image::Clear(Pixel color) { 351 | long x, y; 352 | for (y = 0; y < height; y++) { 353 | for (x = 0; x < width; x++) { 354 | SetPixelColor(x, y, color); 355 | } 356 | } 357 | } 358 | 359 | void Image::DrawLine(int x1, int y1, int x2, int y2, Pixel color) { 360 | long i; 361 | long y; 362 | long dx, dy; 363 | dx = labs(x2 - x1); 364 | dy = labs(y2 - y1); 365 | if (dx > dy) { 366 | if (x2 > x1) { 367 | for (i = x1; i <= x2; i++) { 368 | y = (long)(((y2 - (float)(y1)) / (x2 - x1)) * (i - x1) + y1); 369 | SetPixelColor(i, y, color); 370 | } 371 | } else { 372 | for (i = x2; i <= x1; i++) { 373 | y = (long)(((y1 - (float)(y2)) / (x1 - x2)) * (i - x2) + y2); 374 | SetPixelColor(i, y, color); 375 | } 376 | } 377 | } else { 378 | if (y2 > y1) { 379 | for (i = y1; i <= y2; i++) { 380 | y = (long)(((x2 - (float)(x1)) / (y2 - y1)) * (i - y1) + x1); 381 | SetPixelColor(y, i, color); 382 | } 383 | } else { 384 | for (i = y2; i <= y1; i++) { 385 | y = (long)(((x1 - (float)(x2)) / (y1 - y2)) * (i - y2) + x2); 386 | SetPixelColor(y, i, color); 387 | } 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /test/image.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #pragma once 25 | 26 | // A simple image class with some (very basic) image processing operations. 27 | class Image { 28 | friend class ImageIO; 29 | 30 | protected: 31 | unsigned char *data; 32 | long width; 33 | long height; 34 | 35 | long round(float x); 36 | float interpolate(float x1, float x2, float x3, float x4, float dx, float dy); 37 | 38 | public: 39 | struct Pixel { 40 | unsigned char B; 41 | unsigned char G; 42 | unsigned char R; 43 | }; 44 | 45 | // Constructors and destructor. 46 | Image(void); 47 | Image(long width, long height); 48 | Image(const Image &src); 49 | Image &operator=(const Image &src); 50 | ~Image(void); 51 | 52 | // Initializes the image of specified width and height. 53 | // All pixels are set to black. 54 | void Init(long width, long height); 55 | 56 | // Property getters. 57 | long GetWidth(); 58 | long GetHeight(); 59 | unsigned char *GetData(); 60 | 61 | // Pixel getters and setters. 62 | unsigned char GetPixelGray(long x, long y); 63 | unsigned char GetPixelRed(long x, long y); 64 | unsigned char GetPixelGreen(long x, long y); 65 | unsigned char GetPixelBlue(long x, long y); 66 | Image::Pixel GetPixelColor(long x, long y); 67 | void SetPixelGray(long x, long y, unsigned char c); 68 | void SetPixelColor(long x, long y, Image::Pixel rgb); 69 | void SetPixelRed(long x, long y, unsigned char c); 70 | void SetPixelGreen(long x, long y, unsigned char c); 71 | void SetPixelBlue(long x, long y, unsigned char c); 72 | 73 | // Returns the color of a pixel at real coordinates (x, y) 74 | // using bilinear interpolation. 75 | Image::Pixel GetPixelBilinear(float x, float y); 76 | 77 | // Some basic image processing functions. 78 | 79 | // Returns the mean value of pixel intensity. 80 | unsigned char GetMeanGray(); 81 | 82 | // Computes the histogram of image intensity 83 | // and returns it via histogram parameter. 84 | // params: 85 | // histogram: 86 | // An array of 256 components, used to return the histogram. 87 | void GetHistogramGray(long *histogram); 88 | 89 | // Binarizes the image (posterize with two levels). All pixels with the 90 | // intensity lower than threshold become black, and all others become white. 91 | void Binarize(unsigned char threshold); 92 | 93 | // Flips the image in horizontal direction. 94 | void FlipHorizontal(); 95 | 96 | // Flips the image in vertical direction. 97 | void FlipVertical(); 98 | 99 | // inverts image colors 100 | void Invert(); 101 | 102 | // Crops the image. Returns the sub-image with upper left corner at 103 | // (posx, posy) with the size of (width, height). 104 | Image Crop(int posx, int posy, int width, int height); 105 | 106 | // Resizes the image by an integer factor. 107 | // For example, if factor is 2, returns the image with size 108 | // (width/2, height/2). Each pixel in a new image is obtained as 109 | // an average of corresponding pixels in the original image. 110 | Image Resize(int factor); 111 | 112 | // Computes the convolution of image and a filter. 113 | // params: 114 | // filter: 115 | // An array containing the filter coefficients. 116 | // filterwidth: 117 | // The width of the filter array. 118 | // filterheight: 119 | // The height of the filter array. 120 | Image Filter(float *filter, long filterwidth, long filterheight); 121 | 122 | // Filters the image using Gaussian blur. 123 | // params: 124 | // sigma: 125 | // The standard deviation of the Gaussian filter. 126 | // masksize: 127 | // The size of the corresponding filter. 128 | // If set to 0, the masksize will be calculated as sigma * 2 * 2 + 1. 129 | Image GaussBlur(float sigma, long masksize = 0); 130 | 131 | // Paints the whole image with the given color. 132 | void Clear(Pixel color); 133 | 134 | // Draws a line from point (x1, y1) to point (x2, y2) in the given color. 135 | void DrawLine(int x1, int y1, int x2, int y2, Pixel color); 136 | }; 137 | 138 | inline unsigned char Image::GetPixelGray(long x, long y) { 139 | unsigned char c; 140 | long index = 3 * ((y * width) + x); 141 | c = (unsigned char)(((long)(data[index]) + (long)(data[index + 1]) + (long)(data[index + 2])) / 3); 142 | return c; 143 | } 144 | 145 | inline unsigned char Image::GetPixelRed(long x, long y) { 146 | long index = 3 * ((y * width) + x); 147 | return data[index]; 148 | } 149 | 150 | inline unsigned char Image::GetPixelGreen(long x, long y) { 151 | long index = 3 * ((y * width) + x) + 1; 152 | return data[index]; 153 | } 154 | 155 | inline unsigned char Image::GetPixelBlue(long x, long y) { 156 | long index = 3 * ((y * width) + x) + 2; 157 | return data[index]; 158 | } 159 | 160 | inline void Image::SetPixelRed(long x, long y, unsigned char c) { 161 | long index = 3 * ((y * width) + x); 162 | data[index] = c; 163 | } 164 | 165 | inline void Image::SetPixelGreen(long x, long y, unsigned char c) { 166 | long index = 3 * ((y * width) + x) + 1; 167 | data[index] = c; 168 | } 169 | 170 | inline void Image::SetPixelBlue(long x, long y, unsigned char c) { 171 | long index = 3 * ((y * width) + x) + 2; 172 | data[index] = c; 173 | } 174 | 175 | inline Image::Pixel Image::GetPixelColor(long x, long y) { 176 | Image::Pixel rgb; 177 | long index = 3 * ((y * width) + x); 178 | rgb.B = data[index]; 179 | rgb.G = data[index + 1]; 180 | rgb.R = data[index + 2]; 181 | return rgb; 182 | } 183 | 184 | inline void Image::SetPixelGray(long x, long y, unsigned char c) { 185 | long index = 3 * ((y * width) + x); 186 | data[index] = c; 187 | data[index + 1] = c; 188 | data[index + 2] = c; 189 | } 190 | 191 | inline void Image::SetPixelColor(long x, long y, Image::Pixel rgb) { 192 | if (x < 0) { 193 | return; 194 | } 195 | if (y < 0) { 196 | return; 197 | } 198 | if (x >= width) { 199 | return; 200 | } 201 | if (y >= height) { 202 | return; 203 | } 204 | long index = 3 * ((y * width) + x); 205 | data[index] = rgb.B; 206 | data[index + 1] = rgb.G; 207 | data[index + 2] = rgb.R; 208 | } 209 | 210 | inline long Image::GetWidth() { 211 | return width; 212 | } 213 | 214 | inline long Image::GetHeight() { 215 | return height; 216 | } 217 | 218 | inline unsigned char *Image::GetData() { 219 | return data; 220 | } 221 | -------------------------------------------------------------------------------- /test/imageio.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #define _CRT_SECURE_NO_WARNINGS 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "image.h" 33 | #include "imageio.h" 34 | 35 | const char *ImageIO::GetFileExtension(const char *filename) { 36 | // First remove the folder if present. 37 | const char *filename2, *tmp, *extension; 38 | filename2 = strrchr(filename, '/'); 39 | tmp = strrchr(filename, '\\'); 40 | if (tmp > filename2) { 41 | filename2 = tmp; 42 | } 43 | if (!filename2) { 44 | filename2 = filename; 45 | } 46 | // Now find the dot. 47 | extension = strrchr(filename2, '.'); 48 | if (extension) { 49 | extension++; 50 | } else { 51 | extension = filename2 + strlen(filename2); 52 | } 53 | return extension; 54 | } 55 | 56 | int ImageIO::GetImageType(const char *filename) { 57 | const char *extension = GetFileExtension(filename); 58 | if ((strcmp(extension, "bmp") == 0) || (strcmp(extension, "BMP") == 0)) { 59 | return IMGTYPE_BMP; 60 | } else if ((strcmp(extension, "ppm") == 0) || (strcmp(extension, "PPM") == 0)) { 61 | return IMGTYPE_PPM; 62 | } else if ((strcmp(extension, "pgm") == 0) || (strcmp(extension, "PGM") == 0)) { 63 | return IMGTYPE_PGM; 64 | } else if ((strcmp(extension, "raw") == 0) || (strcmp(extension, "RAW") == 0)) { 65 | return IMGTYPE_RAW; 66 | } else { 67 | return IMGTYPE_UNSUPPORTED; 68 | } 69 | } 70 | 71 | void ImageIO::LoadImage(const char *filename, Image *image) { 72 | int imgType = GetImageType(filename); 73 | if (imgType == IMGTYPE_UNSUPPORTED) { 74 | printf("Error loading image %s, unsupported image type!\n", filename); 75 | return; 76 | } 77 | LoadImage(filename, image, imgType); 78 | } 79 | 80 | void ImageIO::LoadImage(const char *filename, Image *image, int imageType) { 81 | switch (imageType) { 82 | case IMGTYPE_BMP: 83 | LoadImageBMP(filename, image); 84 | break; 85 | case IMGTYPE_PPM: 86 | LoadImagePPM(filename, image); 87 | break; 88 | case IMGTYPE_PGM: 89 | LoadImagePGM(filename, image); 90 | break; 91 | case IMGTYPE_RAW: 92 | LoadImageRAW(filename, image); 93 | break; 94 | } 95 | } 96 | 97 | void ImageIO::LoadImageBMP(const char *filename, Image *image) { 98 | FILE *fp = fopen(filename, "rb"); 99 | if (!fp) { 100 | printf("Error opening %s!\n", filename); 101 | return; 102 | } 103 | 104 | char header[2]; 105 | fread(header, 1, 2, fp); 106 | if (!((header[0] == 'B') && (header[1] == 'M'))) { 107 | fclose(fp); 108 | printf("Error loading image %s, wrong file format!\n", filename); 109 | return; 110 | } 111 | 112 | long bmOffset; 113 | fseek(fp, 10, SEEK_SET); 114 | fread(&bmOffset, 4, 1, fp); 115 | 116 | fseek(fp, 18, SEEK_SET); 117 | fread(&(image->width), 4, 1, fp); 118 | fread(&(image->height), 4, 1, fp); 119 | 120 | short bpp; 121 | fseek(fp, 28, SEEK_SET); 122 | fread(&bpp, 2, 1, fp); 123 | if (!((bpp == 8) || (bpp == 24))) { 124 | fclose(fp); 125 | printf("Error loading image %s, can only load BMP files with 8 or 24 bpp!\n", filename); 126 | return; 127 | } 128 | 129 | long compression; 130 | fread(&compression, 4, 1, fp); 131 | if (compression) { 132 | fclose(fp); 133 | printf("Error loading image %s, can only load uncompressed BMP files!\n", filename); 134 | return; 135 | } 136 | 137 | if (image->data) 138 | free(image->data); 139 | image->data = (unsigned char *)malloc(image->height * image->width * 3); 140 | 141 | if (bpp == 8) { 142 | // Bytes per row. 143 | long bpr, tmp; 144 | tmp = image->width % 4; 145 | if (tmp == 0) { 146 | bpr = image->width; 147 | } else { 148 | bpr = image->width + 4 - tmp; 149 | tmp = 4 - tmp; 150 | } 151 | 152 | long bmSize = bpr * image->height; 153 | unsigned char *buffer = (unsigned char *)malloc(bmSize); 154 | fseek(fp, bmOffset, SEEK_SET); 155 | fread(buffer, 1, bmSize, fp); 156 | 157 | long pos = 0; 158 | for (long i = 0; i < image->height; i++) { 159 | for (long j = 0; j < image->width; j++) { 160 | image->SetPixelGray(j, image->height - i - 1, buffer[pos]); 161 | pos++; 162 | } 163 | pos += tmp; 164 | } 165 | 166 | free(buffer); 167 | 168 | } else if (bpp == 24) { 169 | // Bytes per row. 170 | long bpr, tmp; 171 | tmp = (image->width * 3) % 4; 172 | if (tmp == 0) { 173 | bpr = image->width * 3; 174 | } else { 175 | bpr = image->width * 3 + 4 - tmp; 176 | tmp = 4 - tmp; 177 | } 178 | 179 | long bmSize = bpr * image->height; 180 | unsigned char *buffer = (unsigned char *)malloc(bmSize); 181 | fseek(fp, bmOffset, SEEK_SET); 182 | fread(buffer, 1, bmSize, fp); 183 | 184 | long pos = 0; 185 | Image::Pixel rgb; 186 | for (long i = 0; i < image->height; i++) { 187 | for (long j = 0; j < image->width; j++) { 188 | rgb.B = buffer[pos++]; 189 | rgb.G = buffer[pos++]; 190 | rgb.R = buffer[pos++]; 191 | image->SetPixelColor(j, image->height - i - 1, rgb); 192 | } 193 | pos += tmp; 194 | } 195 | 196 | free(buffer); 197 | } 198 | 199 | fclose(fp); 200 | } 201 | 202 | void ImageIO::SaveImage(const char *filename, Image *image) { 203 | int imgType = GetImageType(filename); 204 | if (imgType == IMGTYPE_UNSUPPORTED) { 205 | printf("Error saving image to %s, unknown image format!\n", filename); 206 | return; 207 | } 208 | SaveImage(filename, image, imgType); 209 | } 210 | 211 | void ImageIO::SaveImage(const char *filename, Image *image, int imageType) { 212 | switch (imageType) { 213 | case IMGTYPE_BMP: 214 | SaveImageBMP(filename, image); 215 | break; 216 | case IMGTYPE_PPM: 217 | SaveImagePPM(filename, image); 218 | break; 219 | case IMGTYPE_PGM: 220 | SaveImagePPM(filename, image); 221 | break; 222 | case IMGTYPE_RAW: 223 | SaveImageRAW(filename, image); 224 | break; 225 | } 226 | } 227 | 228 | void ImageIO::SaveImageBMP(const char *filename, Image *image) { 229 | FILE *fp = fopen(filename, "wb"); 230 | if (!fp) { 231 | printf("Error opening %s!\n", filename); 232 | return; 233 | } 234 | 235 | char header[2]; 236 | long tmpl; 237 | short tmps; 238 | 239 | // Header. 240 | header[0] = 'B'; 241 | header[1] = 'M'; 242 | fwrite(header, 2, 1, fp); 243 | 244 | long rowsize; 245 | long tmp = (image->width * 3) % 4; 246 | if (tmp == 0) { 247 | rowsize = image->width * 3; 248 | } else { 249 | rowsize = image->width * 3 + 4 - tmp; 250 | } 251 | unsigned char *row = (unsigned char *)malloc(rowsize); 252 | tmpl = 54 + rowsize * image->height; 253 | fwrite(&tmpl, 4, 1, fp); 254 | 255 | tmps = 0; 256 | fwrite(&tmps, 2, 1, fp); 257 | fwrite(&tmps, 2, 1, fp); 258 | 259 | // Offset to the beginning of BMP data. 260 | tmpl = 54; 261 | fwrite(&tmpl, 4, 1, fp); 262 | 263 | // Info header size. 264 | tmpl = 40; 265 | fwrite(&tmpl, 4, 1, fp); 266 | 267 | // Size. 268 | tmpl = image->width; 269 | fwrite(&tmpl, 4, 1, fp); 270 | tmpl = image->height; 271 | fwrite(&tmpl, 4, 1, fp); 272 | 273 | tmps = 1; 274 | fwrite(&tmps, 2, 1, fp); 275 | tmps = 24; // Bits per pixel (bpp). 276 | fwrite(&tmps, 2, 1, fp); 277 | tmpl = 0; 278 | fwrite(&tmpl, 4, 1, fp); 279 | fwrite(&tmpl, 4, 1, fp); 280 | fwrite(&tmpl, 4, 1, fp); 281 | fwrite(&tmpl, 4, 1, fp); 282 | fwrite(&tmpl, 4, 1, fp); 283 | fwrite(&tmpl, 4, 1, fp); 284 | 285 | // Actual bitmap data. 286 | for (long i = 0; i < image->height; i++) { 287 | memset(row, 0, rowsize); 288 | memcpy(row, image->data + (3 * image->width) * (image->height - i - 1), 3 * image->width); 289 | fwrite(row, rowsize, 1, fp); 290 | } 291 | 292 | free(row); 293 | 294 | fclose(fp); 295 | } 296 | 297 | void ImageIO::LoadImagePPM(const char *filename, Image *image) { 298 | FILE *fp = fopen(filename, "rb"); 299 | if (!fp) { 300 | printf("Error opening %s!\n", filename); 301 | return; 302 | } 303 | 304 | long filesize; 305 | fseek(fp, 0, SEEK_END); 306 | filesize = ftell(fp); 307 | fseek(fp, 0, SEEK_SET); 308 | 309 | unsigned char *buffer = (unsigned char *)malloc(filesize); 310 | fread(buffer, 1, filesize, fp); 311 | 312 | char id[1024]; 313 | long sizex, sizey, levels; 314 | sscanf((char *)buffer, "%s\n%ld %ld\n%ld\n", id, &sizex, &sizey, &levels); 315 | 316 | if ((strncmp(id, "P6", 2) != 0) || (levels != 255)) { 317 | free(buffer); 318 | fclose(fp); 319 | printf("Error loading image %s, wrong file format!\n", filename); 320 | return; 321 | } 322 | 323 | image->width = sizex; 324 | image->height = sizey; 325 | 326 | if (image->data) 327 | free(image->data); 328 | image->data = (unsigned char *)malloc(image->height * image->width * 3); 329 | 330 | long pos = filesize - sizex * sizey * 3; 331 | for (long i = 0; i < sizey; i++) { 332 | for (long j = 0; j < sizex; j++) { 333 | Image::Pixel rgb; 334 | rgb.R = buffer[pos++]; 335 | rgb.G = buffer[pos++]; 336 | rgb.B = buffer[pos++]; 337 | image->SetPixelColor(j, i, rgb); 338 | } 339 | } 340 | 341 | free(buffer); 342 | 343 | fclose(fp); 344 | } 345 | 346 | void ImageIO::LoadImagePGM(const char *filename, Image *image) { 347 | FILE *fp = fopen(filename, "rb"); 348 | if (!fp) { 349 | printf("Error opening %s!\n", filename); 350 | return; 351 | } 352 | 353 | long filesize; 354 | fseek(fp, 0, SEEK_END); 355 | filesize = ftell(fp); 356 | fseek(fp, 0, SEEK_SET); 357 | 358 | unsigned char *buffer = (unsigned char *)malloc(filesize); 359 | fread(buffer, 1, filesize, fp); 360 | 361 | char id[1024]; 362 | long sizex, sizey, levels; 363 | sscanf((char *)buffer, "%s\n%ld %ld\n%ld\n", id, &sizex, &sizey, &levels); 364 | 365 | if ((strncmp(id, "P5", 2) != 0) || (levels != 255)) { 366 | free(buffer); 367 | fclose(fp); 368 | printf("Error loading image %s, wrong file format!\n", filename); 369 | return; 370 | } 371 | 372 | image->width = sizex; 373 | image->height = sizey; 374 | 375 | if (image->data) { 376 | free(image->data); 377 | } 378 | image->data = (unsigned char *)malloc(image->height * image->width * 3); 379 | 380 | long pos = filesize - sizex * sizey; 381 | for (long i = 0; i < sizey; i++) { 382 | for (long j = 0; j < sizex; j++) { 383 | image->SetPixelGray(j, i, buffer[pos]); 384 | pos++; 385 | } 386 | } 387 | 388 | free(buffer); 389 | 390 | fclose(fp); 391 | } 392 | 393 | void ImageIO::SaveImagePPM(const char *filename, Image *image) { 394 | FILE *fp = fopen(filename, "wb"); 395 | if (!fp) { 396 | printf("Error opening %s!\n", filename); 397 | return; 398 | } 399 | 400 | fprintf(fp, "P6\n%ld %ld\n255\n", image->width, image->height); 401 | 402 | long sizex = image->width; 403 | long sizey = image->height; 404 | unsigned char *buffer = (unsigned char *)malloc(image->width * image->height * 3); 405 | long pos = 0; 406 | for (long i = 0; i < sizey; i++) { 407 | for (long j = 0; j < sizex; j++) { 408 | Image::Pixel rgb = image->GetPixelColor(j, i); 409 | buffer[pos++] = rgb.R; 410 | buffer[pos++] = rgb.G; 411 | buffer[pos++] = rgb.B; 412 | } 413 | } 414 | 415 | fwrite(buffer, 1, image->width * image->height * 3, fp); 416 | 417 | free(buffer); 418 | fclose(fp); 419 | } 420 | 421 | void ImageIO::SaveImagePGM(const char *filename, Image *image) { 422 | FILE *fp = fopen(filename, "wb"); 423 | if (!fp) { 424 | printf("Error opening %s!\n", filename); 425 | return; 426 | } 427 | 428 | fprintf(fp, "P5\n%ld %ld\n255\n", image->width, image->height); 429 | 430 | long sizex = image->width; 431 | long sizey = image->height; 432 | unsigned char *buffer = (unsigned char *)malloc(image->width * image->height); 433 | long pos = 0; 434 | for (long i = 0; i < sizey; i++) { 435 | for (long j = 0; j < sizex; j++) { 436 | buffer[pos++] = image->GetPixelGray(j, i); 437 | } 438 | } 439 | 440 | fwrite(buffer, 1, image->width * image->height, fp); 441 | 442 | free(buffer); 443 | fclose(fp); 444 | } 445 | 446 | void ImageIO::LoadImageRAW(const char *filename, Image *image, long width, long height) { 447 | FILE *fp = fopen(filename, "rb"); 448 | if (!fp) { 449 | printf("Error opening %s!\n", filename); 450 | return; 451 | } 452 | 453 | if ((width == 0) || (height == 0)) { 454 | long filesize; 455 | fseek(fp, 0, SEEK_END); 456 | filesize = ftell(fp); 457 | fseek(fp, 0, SEEK_SET); 458 | width = (long)sqrt((double)filesize); 459 | height = width; 460 | if ((height * width) != filesize) { 461 | fclose(fp); 462 | printf("Error loading image %s, wrong file format!\n", filename); 463 | return; 464 | } 465 | } 466 | 467 | image->width = width; 468 | image->height = height; 469 | if (image->data) { 470 | free(image->data); 471 | } 472 | image->data = (unsigned char *)malloc(image->height * image->width * 3); 473 | 474 | unsigned char *buffer = (unsigned char *)malloc(image->width * image->height); 475 | fread(buffer, 1, image->width * image->height, fp); 476 | 477 | long pos = 0; 478 | for (long i = 0; i < height; i++) { 479 | for (long j = 0; j < width; j++) { 480 | unsigned char c = buffer[pos++]; 481 | image->SetPixelGray(j, i, c); 482 | } 483 | } 484 | 485 | free(buffer); 486 | fclose(fp); 487 | } 488 | 489 | void ImageIO::SaveImageRAW(const char *filename, Image *image) { 490 | FILE *fp = fopen(filename, "wb"); 491 | if (!fp) { 492 | printf("Error opening %s!\n", filename); 493 | return; 494 | } 495 | 496 | long sizex = image->width; 497 | long sizey = image->height; 498 | unsigned char *buffer = (unsigned char *)malloc(image->width * image->height); 499 | 500 | long pos = 0; 501 | for (long i = 0; i < sizey; i++) { 502 | for (long j = 0; j < sizex; j++) { 503 | unsigned char c = image->GetPixelGray(j, i); 504 | buffer[pos++] = c; 505 | } 506 | } 507 | 508 | fwrite(buffer, 1, image->width * image->height, fp); 509 | 510 | free(buffer); 511 | fclose(fp); 512 | } 513 | -------------------------------------------------------------------------------- /test/imageio.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #pragma once 25 | 26 | #define IMGTYPE_RAW 0 27 | #define IMGTYPE_BMP 1 28 | #define IMGTYPE_PPM 2 29 | #define IMGTYPE_PGM 3 30 | #define IMGTYPE_UNSUPPORTED 999 31 | 32 | // Handles basic image file input/output. 33 | // Only supports uncompressed image formats (RAW, BMP, PPM, PGM). 34 | class ImageIO { 35 | protected: 36 | // Gets the file extension from the file name. 37 | const char *GetFileExtension(const char *filename); 38 | 39 | // Determines the image format from the file name. 40 | int GetImageType(const char *filename); 41 | 42 | public: 43 | // Loads the image from `filename` into `image`. 44 | // This method automatically determines the image format. 45 | void LoadImage(const char *filename, Image *image); 46 | 47 | // Loads the image from a file named `filename` into `image`, 48 | // using the format given as `imageType`. 49 | void LoadImage(const char *filename, Image *image, int imageType); 50 | 51 | // Saves the image into file named `filename`. 52 | // This method automatically determines the image format. 53 | void SaveImage(const char *filename, Image *image); 54 | 55 | // Saves the image into file named `filename`, 56 | // using the format given as `imageType`. 57 | void SaveImage(const char *filename, Image *image, int imageType); 58 | 59 | // Loads the uncompressed BMP image from `filename` into `image`. 60 | void LoadImageBMP(const char *filename, Image *image); 61 | // Saves the image into file named `filename` in uncompressed BMP format. 62 | void SaveImageBMP(const char *filename, Image *image); 63 | 64 | // Loads the PPM image from `filename` into `image`. 65 | void LoadImagePPM(const char *filename, Image *image); 66 | // Saves the image into file named `filename` in PPM format. 67 | void SaveImagePPM(const char *filename, Image *image); 68 | 69 | // Loads the PGM image from `filename` into `image`. 70 | void LoadImagePGM(const char *filename, Image *image); 71 | // Saves the image into file named `filename` in PGM format. 72 | void SaveImagePGM(const char *filename, Image *image); 73 | 74 | // Loads the image from the file named `filename`. 75 | // The file is assumed to be structured so that it only contains 76 | // an array of raw (gray) pixel values, as the file does not contain 77 | // the image width and height, those are passed as parameters to the 78 | // function. If width and height are 0, the image is assumed to be 79 | // square and the width and height are computed based on the file size. 80 | void LoadImageRAW(const char *filename, Image *image, long width = 0, long height = 0); 81 | 82 | // Saves the image to a file named `filename`. 83 | // Only the array of raw (gray) pixel values are stored, 84 | // without additional information such as image size. 85 | void SaveImageRAW(const char *filename, Image *image); 86 | }; 87 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* Copyright (c) 2011-2021 Ivan Fratric and contributors. */ 3 | /* */ 4 | /* Permission is hereby granted, free of charge, to any person obtaining */ 5 | /* a copy of this software and associated documentation files (the */ 6 | /* "Software"), to deal in the Software without restriction, including */ 7 | /* without limitation the rights to use, copy, modify, merge, publish, */ 8 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 9 | /* permit persons to whom the Software is furnished to do so, subject to */ 10 | /* the following conditions: */ 11 | /* */ 12 | /* The above copyright notice and this permission notice shall be */ 13 | /* included in all copies or substantial portions of the Software. */ 14 | /* */ 15 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 16 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 17 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 18 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 19 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 20 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 21 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 22 | /*************************************************************************/ 23 | 24 | #define _CRT_SECURE_NO_WARNINGS 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace std; 31 | 32 | #include "polypartition.h" 33 | 34 | #include "image.h" 35 | #include "imageio.h" 36 | 37 | void ReadPoly(FILE *fp, TPPLPoly *poly) { 38 | int i, numpoints, hole; 39 | float x, y; 40 | 41 | fscanf(fp, "%d\n", &numpoints); 42 | poly->Init(numpoints); 43 | 44 | fscanf(fp, "%d\n", &hole); 45 | if (hole) { 46 | poly->SetHole(true); 47 | } 48 | 49 | for (i = 0; i < numpoints; i++) { 50 | fscanf(fp, "%g %g\n", &x, &y); 51 | (*poly)[i].x = x; 52 | (*poly)[i].y = y; 53 | } 54 | } 55 | 56 | void ReadPoly(const char *filename, TPPLPoly *poly) { 57 | FILE *fp = fopen(filename, "r"); 58 | if (!fp) { 59 | printf("Error reading file %s\n", filename); 60 | return; 61 | } 62 | ReadPoly(fp, poly); 63 | fclose(fp); 64 | } 65 | 66 | void ReadPolyList(FILE *fp, list *polys) { 67 | int i, numpolys; 68 | TPPLPoly poly; 69 | 70 | polys->clear(); 71 | fscanf(fp, "%d\n", &numpolys); 72 | for (i = 0; i < numpolys; i++) { 73 | ReadPoly(fp, &poly); 74 | polys->push_back(poly); 75 | } 76 | } 77 | 78 | void ReadPolyList(const char *filename, list *polys) { 79 | FILE *fp = fopen(filename, "r"); 80 | if (!fp) { 81 | printf("Error reading file %s\n", filename); 82 | return; 83 | } 84 | ReadPolyList(fp, polys); 85 | fclose(fp); 86 | } 87 | 88 | void WritePoly(FILE *fp, TPPLPoly *poly) { 89 | int i, numpoints; 90 | numpoints = poly->GetNumPoints(); 91 | 92 | fprintf(fp, "%d\n", numpoints); 93 | 94 | if (poly->IsHole()) { 95 | fprintf(fp, "1\n"); 96 | } else { 97 | fprintf(fp, "0\n"); 98 | } 99 | 100 | for (i = 0; i < numpoints; i++) { 101 | fprintf(fp, "%g %g\n", (*poly)[i].x, (*poly)[i].y); 102 | } 103 | } 104 | 105 | void WritePoly(const char *filename, TPPLPoly *poly) { 106 | FILE *fp = fopen(filename, "w"); 107 | if (!fp) { 108 | printf("Error writing file %s\n", filename); 109 | return; 110 | } 111 | WritePoly(fp, poly); 112 | fclose(fp); 113 | } 114 | 115 | void WritePolyList(FILE *fp, list *polys) { 116 | list::iterator iter; 117 | 118 | fprintf(fp, "%ld\n", polys->size()); 119 | 120 | for (iter = polys->begin(); iter != polys->end(); iter++) { 121 | WritePoly(fp, &(*iter)); 122 | } 123 | } 124 | 125 | void WritePolyList(const char *filename, list *polys) { 126 | FILE *fp = fopen(filename, "w"); 127 | if (!fp) { 128 | printf("Error writing file %s\n", filename); 129 | return; 130 | } 131 | WritePolyList(fp, polys); 132 | fclose(fp); 133 | } 134 | 135 | void DrawPoly(Image *img, TPPLPoly *poly, tppl_float xmin, tppl_float xmax, tppl_float ymin, tppl_float ymax) { 136 | TPPLPoint p1, p2, p1img, p2img, polymin, imgmin; 137 | long i; 138 | Image::Pixel color = { 0, 0, 0 }; 139 | 140 | polymin.x = xmin; 141 | polymin.y = ymin; 142 | imgmin.x = 5; 143 | imgmin.y = 5; 144 | 145 | tppl_float polySizeX = xmax - xmin; 146 | tppl_float polySizeY = ymax - ymin; 147 | tppl_float imgSizeX = (tppl_float)img->GetWidth() - 10; 148 | tppl_float imgSizeY = (tppl_float)img->GetHeight() - 10; 149 | 150 | tppl_float scalex = 0; 151 | tppl_float scaley = 0; 152 | tppl_float scale; 153 | if (polySizeX > 0) { 154 | scalex = imgSizeX / polySizeX; 155 | } 156 | if (polySizeY > 0) { 157 | scaley = imgSizeY / polySizeY; 158 | } 159 | 160 | if (scalex > 0 && scalex < scaley) { 161 | scale = scalex; 162 | } else if (scaley > 0) { 163 | scale = scaley; 164 | } else { 165 | scale = 1; 166 | } 167 | 168 | for (i = 0; i < poly->GetNumPoints(); i++) { 169 | p1 = poly->GetPoint(i); 170 | p2 = poly->GetPoint((i + 1) % poly->GetNumPoints()); 171 | p1img = (p1 - polymin) * scale + imgmin; 172 | p2img = (p2 - polymin) * scale + imgmin; 173 | img->DrawLine((int)p1img.x, (int)p1img.y, (int)p2img.x, (int)p2img.y, color); 174 | } 175 | } 176 | 177 | void DrawPoly(const char *filename, TPPLPoly *poly) { 178 | Image img(500, 500); 179 | Image::Pixel white = { 255, 255, 255 }; 180 | img.Clear(white); 181 | ImageIO io; 182 | 183 | tppl_float xmin = std::numeric_limits::max(); 184 | tppl_float xmax = std::numeric_limits::min(); 185 | tppl_float ymin = std::numeric_limits::max(); 186 | tppl_float ymax = std::numeric_limits::min(); 187 | for (int i = 0; i < poly->GetNumPoints(); i++) { 188 | if (poly->GetPoint(i).x < xmin) { 189 | xmin = poly->GetPoint(i).x; 190 | } 191 | if (poly->GetPoint(i).x > xmax) { 192 | xmax = poly->GetPoint(i).x; 193 | } 194 | if (poly->GetPoint(i).y < ymin) { 195 | ymin = poly->GetPoint(i).y; 196 | } 197 | if (poly->GetPoint(i).y > ymax) { 198 | ymax = poly->GetPoint(i).y; 199 | } 200 | } 201 | 202 | DrawPoly(&img, poly, xmin, xmax, ymin, ymax); 203 | 204 | io.SaveImage(filename, &img); 205 | } 206 | 207 | void DrawPolyList(const char *filename, list *polys) { 208 | Image img(300, 450); 209 | Image::Pixel white = { 255, 255, 255 }; 210 | img.Clear(white); 211 | 212 | ImageIO io; 213 | list::iterator iter; 214 | 215 | tppl_float xmin = std::numeric_limits::max(); 216 | tppl_float xmax = std::numeric_limits::min(); 217 | tppl_float ymin = std::numeric_limits::max(); 218 | tppl_float ymax = std::numeric_limits::min(); 219 | for (iter = polys->begin(); iter != polys->end(); iter++) { 220 | for (int i = 0; i < iter->GetNumPoints(); i++) { 221 | if (iter->GetPoint(i).x < xmin) { 222 | xmin = iter->GetPoint(i).x; 223 | } 224 | if (iter->GetPoint(i).x > xmax) { 225 | xmax = iter->GetPoint(i).x; 226 | } 227 | if (iter->GetPoint(i).y < ymin) { 228 | ymin = iter->GetPoint(i).y; 229 | } 230 | if (iter->GetPoint(i).y > ymax) { 231 | ymax = iter->GetPoint(i).y; 232 | } 233 | } 234 | //if(iter->GetOrientation() == TPPL_CCW) printf("CCW\n"); 235 | //else if (iter->GetOrientation() == TPPL_CW) printf("CW\n"); 236 | //else printf("gfdgdg\n"); 237 | } 238 | //printf("\n"); 239 | 240 | for (iter = polys->begin(); iter != polys->end(); iter++) { 241 | DrawPoly(&img, &(*iter), xmin, xmax, ymin, ymax); 242 | } 243 | 244 | io.SaveImage(filename, &img); 245 | } 246 | 247 | bool ComparePoly(TPPLPoly *p1, TPPLPoly *p2) { 248 | long i, n = p1->GetNumPoints(); 249 | if (n != p2->GetNumPoints()) { 250 | return false; 251 | } 252 | for (i = 0; i < n; i++) { 253 | if ((*p1)[i] != (*p2)[i]) { 254 | return false; 255 | } 256 | } 257 | return true; 258 | } 259 | 260 | bool ComparePoly(list *polys1, list *polys2) { 261 | list::iterator iter1, iter2; 262 | long i, n = (long)polys1->size(); 263 | if (n != (signed)polys2->size()) { 264 | return false; 265 | } 266 | iter1 = polys1->begin(); 267 | iter2 = polys2->begin(); 268 | for (i = 0; i < n; i++) { 269 | if (!ComparePoly(&(*iter1), &(*iter2))) { 270 | return false; 271 | } 272 | iter1++; 273 | iter2++; 274 | } 275 | return true; 276 | } 277 | 278 | void GenerateTestData() { 279 | TPPLPartition pp; 280 | 281 | list testpolys, result, expectedResult; 282 | 283 | ReadPolyList("test_input.txt", &testpolys); 284 | 285 | DrawPolyList("test_input.bmp", &testpolys); 286 | 287 | pp.Triangulate_EC(&testpolys, &result); 288 | //pp.Triangulate_EC(&(*testpolys.begin()),&result); 289 | DrawPolyList("test_triangulate_EC.bmp", &result); 290 | WritePolyList("test_triangulate_EC.txt", &result); 291 | 292 | result.clear(); 293 | expectedResult.clear(); 294 | 295 | pp.Triangulate_OPT(&(*testpolys.begin()), &result); 296 | DrawPolyList("test_triangulate_OPT.bmp", &result); 297 | WritePolyList("test_triangulate_OPT.txt", &result); 298 | 299 | result.clear(); 300 | expectedResult.clear(); 301 | 302 | pp.Triangulate_MONO(&testpolys, &result); 303 | //pp.Triangulate_MONO(&(*testpolys.begin()),&result); 304 | DrawPolyList("test_triangulate_MONO.bmp", &result); 305 | WritePolyList("test_triangulate_MONO.txt", &result); 306 | 307 | result.clear(); 308 | expectedResult.clear(); 309 | 310 | pp.ConvexPartition_HM(&testpolys, &result); 311 | //pp.ConvexPartition_HM(&(*testpolys.begin()),&result); 312 | DrawPolyList("test_convexpartition_HM.bmp", &result); 313 | WritePolyList("test_convexpartition_HM.txt", &result); 314 | 315 | result.clear(); 316 | expectedResult.clear(); 317 | 318 | pp.ConvexPartition_OPT(&(*testpolys.begin()), &result); 319 | DrawPolyList("test_convexpartition_OPT.bmp", &result); 320 | WritePolyList("test_convexpartition_OPT.txt", &result); 321 | } 322 | 323 | /* 324 | int main() { 325 | TPPLPartition pp; 326 | list testpolys, result; 327 | 328 | ReadPolyList("failing_mono_clean - copy.txt", &testpolys); 329 | DrawPolyList("test.bmp", &testpolys); 330 | if (!pp.Triangulate_MONO(&testpolys, &result)) { 331 | printf("Error\n"); 332 | } 333 | DrawPolyList("test2.bmp", &result); 334 | } 335 | */ 336 | 337 | int main() { 338 | int failures = 0; 339 | TPPLPartition pp; 340 | 341 | list testpolys, result, expectedResult; 342 | 343 | ReadPolyList("test_input.txt", &testpolys); 344 | 345 | DrawPolyList("test_input.bmp", &testpolys); 346 | 347 | printf("Testing Triangulate_EC: "); 348 | pp.Triangulate_EC(&testpolys, &result); 349 | ReadPolyList("test_triangulate_EC.txt", &expectedResult); 350 | if (ComparePoly(&result, &expectedResult)) { 351 | printf("success\n"); 352 | } else { 353 | printf("failed\n"); 354 | failures++; 355 | } 356 | DrawPolyList("tri_ec.bmp", &result); 357 | 358 | result.clear(); 359 | expectedResult.clear(); 360 | 361 | printf("Testing Triangulate_OPT: "); 362 | pp.Triangulate_OPT(&(*testpolys.begin()), &result); 363 | ReadPolyList("test_triangulate_OPT.txt", &expectedResult); 364 | if (ComparePoly(&result, &expectedResult)) { 365 | printf("success\n"); 366 | } else { 367 | printf("failed\n"); 368 | failures++; 369 | } 370 | DrawPolyList("tri_opt.bmp", &result); 371 | 372 | result.clear(); 373 | expectedResult.clear(); 374 | 375 | printf("Testing Triangulate_MONO: "); 376 | pp.Triangulate_MONO(&testpolys, &result); 377 | ReadPolyList("test_triangulate_MONO.txt", &expectedResult); 378 | if (ComparePoly(&result, &expectedResult)) { 379 | printf("success\n"); 380 | } else { 381 | printf("failed\n"); 382 | failures++; 383 | } 384 | DrawPolyList("tri_mono.bmp", &result); 385 | 386 | result.clear(); 387 | expectedResult.clear(); 388 | 389 | printf("Testing ConvexPartition_HM: "); 390 | pp.ConvexPartition_HM(&testpolys, &result); 391 | ReadPolyList("test_convexpartition_HM.txt", &expectedResult); 392 | if (ComparePoly(&result, &expectedResult)) { 393 | printf("success\n"); 394 | } else { 395 | printf("failed\n"); 396 | failures++; 397 | } 398 | DrawPolyList("conv_hm.bmp", &result); 399 | 400 | result.clear(); 401 | expectedResult.clear(); 402 | 403 | printf("Testing ConvexPartition_OPT: "); 404 | pp.ConvexPartition_OPT(&(*testpolys.begin()), &result); 405 | ReadPolyList("test_convexpartition_OPT.txt", &expectedResult); 406 | if (ComparePoly(&result, &expectedResult)) { 407 | printf("success\n"); 408 | } else { 409 | printf("failed\n"); 410 | failures++; 411 | } 412 | DrawPolyList("conv_opt.bmp", &result); 413 | 414 | return failures; 415 | } 416 | -------------------------------------------------------------------------------- /test/test_convexpartition_HM.txt: -------------------------------------------------------------------------------- 1 | 21 2 | 3 3 | 0 4 | 179 196 5 | 189 172 6 | 189 242 7 | 3 8 | 0 9 | 125 191 10 | 153 197 11 | 132 221 12 | 5 13 | 0 14 | 150 266 15 | 132 221 16 | 179 196 17 | 189 242 18 | 196 310 19 | 5 20 | 0 21 | 230 99 22 | 230 80 23 | 254 79 24 | 254 98 25 | 235 163 26 | 4 27 | 0 28 | 212 144 29 | 230 99 30 | 235 163 31 | 212 173 32 | 6 33 | 0 34 | 163 138 35 | 212 144 36 | 212 173 37 | 189 172 38 | 159 161 39 | 141 138 40 | 5 41 | 0 42 | 50 98 43 | 50 79 44 | 74 80 45 | 74 99 46 | 69 163 47 | 4 48 | 0 49 | 69 163 50 | 74 99 51 | 92 144 52 | 92 173 53 | 5 54 | 0 55 | 115 172 56 | 92 173 57 | 92 144 58 | 141 138 59 | 159 161 60 | 4 61 | 0 62 | 189 172 63 | 179 196 64 | 150 183 65 | 159 161 66 | 4 67 | 0 68 | 253 377 69 | 208 377 70 | 208 355 71 | 228 358 72 | 4 73 | 0 74 | 96 355 75 | 96 377 76 | 51 377 77 | 76 358 78 | 3 79 | 0 80 | 228 358 81 | 254 361 82 | 253 377 83 | 5 84 | 0 85 | 219 301 86 | 228 358 87 | 208 355 88 | 196 310 89 | 189 242 90 | 3 91 | 0 92 | 51 377 93 | 50 361 94 | 76 358 95 | 5 96 | 0 97 | 108 310 98 | 96 355 99 | 76 358 100 | 85 301 101 | 115 242 102 | 4 103 | 0 104 | 150 266 105 | 108 310 106 | 115 242 107 | 132 221 108 | 4 109 | 0 110 | 132 221 111 | 115 242 112 | 115 172 113 | 125 191 114 | 3 115 | 0 116 | 159 161 117 | 125 191 118 | 115 172 119 | 4 120 | 0 121 | 163 125 122 | 163 138 123 | 141 138 124 | 141 125 125 | 9 126 | 0 127 | 152 71 128 | 170 75 129 | 179 87 130 | 178 108 131 | 163 125 132 | 141 125 133 | 126 108 134 | 125 87 135 | 134 75 136 | -------------------------------------------------------------------------------- /test/test_convexpartition_OPT.txt: -------------------------------------------------------------------------------- 1 | 17 2 | 9 3 | 0 4 | 170 75 5 | 179 87 6 | 178 108 7 | 163 125 8 | 141 125 9 | 126 108 10 | 125 87 11 | 134 75 12 | 152 71 13 | 4 14 | 0 15 | 163 125 16 | 163 138 17 | 141 138 18 | 141 125 19 | 5 20 | 0 21 | 163 138 22 | 212 144 23 | 212 173 24 | 189 172 25 | 141 138 26 | 6 27 | 0 28 | 189 172 29 | 189 242 30 | 150 266 31 | 115 242 32 | 115 172 33 | 141 138 34 | 4 35 | 0 36 | 212 144 37 | 230 99 38 | 235 163 39 | 212 173 40 | 4 41 | 0 42 | 115 172 43 | 92 173 44 | 92 144 45 | 141 138 46 | 3 47 | 0 48 | 150 266 49 | 108 310 50 | 115 242 51 | 3 52 | 0 53 | 189 242 54 | 196 310 55 | 150 266 56 | 5 57 | 0 58 | 230 99 59 | 230 80 60 | 254 79 61 | 254 98 62 | 235 163 63 | 4 64 | 0 65 | 92 173 66 | 69 163 67 | 74 99 68 | 92 144 69 | 4 70 | 0 71 | 108 310 72 | 96 355 73 | 85 301 74 | 115 242 75 | 4 76 | 0 77 | 189 242 78 | 219 301 79 | 208 355 80 | 196 310 81 | 5 82 | 0 83 | 69 163 84 | 50 98 85 | 50 79 86 | 74 80 87 | 74 99 88 | 4 89 | 0 90 | 96 355 91 | 96 377 92 | 76 358 93 | 85 301 94 | 4 95 | 0 96 | 219 301 97 | 228 358 98 | 208 377 99 | 208 355 100 | 4 101 | 0 102 | 96 377 103 | 51 377 104 | 50 361 105 | 76 358 106 | 4 107 | 0 108 | 228 358 109 | 254 361 110 | 253 377 111 | 208 377 112 | -------------------------------------------------------------------------------- /test/test_input.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 44 3 | 0 4 | 170 75 5 | 179 87 6 | 178 108 7 | 163 125 8 | 163 138 9 | 212 144 10 | 230 99 11 | 230 80 12 | 254 79 13 | 254 98 14 | 235 163 15 | 212 173 16 | 189 172 17 | 189 242 18 | 219 301 19 | 228 358 20 | 254 361 21 | 253 377 22 | 208 377 23 | 208 355 24 | 196 310 25 | 150 266 26 | 108 310 27 | 96 355 28 | 96 377 29 | 51 377 30 | 50 361 31 | 76 358 32 | 85 301 33 | 115 242 34 | 115 172 35 | 92 173 36 | 69 163 37 | 50 98 38 | 50 79 39 | 74 80 40 | 74 99 41 | 92 144 42 | 141 138 43 | 141 125 44 | 126 108 45 | 125 87 46 | 134 75 47 | 152 71 48 | 6 49 | 1 50 | 159 161 51 | 125 191 52 | 153 197 53 | 132 221 54 | 179 196 55 | 150 183 56 | -------------------------------------------------------------------------------- /test/test_input_format.txt: -------------------------------------------------------------------------------- 1 | 2 # Number of polygons defined in this file. 2 | 4 # Number of vertices in the first polygon. 3 | 0 # This polygon is NOT a hole. 4 | 77 479 # Position of a vertex, Y is down, must be clockwise order. 5 | 77 419 # Position of a vertex, Y is down, must be clockwise order. 6 | 137 419 # Position of a vertex, Y is down, must be clockwise order. 7 | 137 479 # Position of a vertex, Y is down, must be clockwise order. 8 | 4 # Number of vertices in the second polygon. 9 | 1 # This polygon IS a hole. 10 | 97 459 # Position of a vertex, Y is down, must be clockwise order. 11 | 117 459 # Position of a vertex, Y is down, must be clockwise order. 12 | 117 439 # Position of a vertex, Y is down, must be clockwise order. 13 | 97 439 # Position of a vertex, Y is down, must be clockwise order. 14 | -------------------------------------------------------------------------------- /test/test_triangulate_EC.txt: -------------------------------------------------------------------------------- 1 | 50 2 | 3 3 | 0 4 | 179 196 5 | 189 172 6 | 189 242 7 | 3 8 | 0 9 | 125 191 10 | 153 197 11 | 132 221 12 | 3 13 | 0 14 | 132 221 15 | 179 196 16 | 189 242 17 | 3 18 | 0 19 | 230 80 20 | 254 79 21 | 254 98 22 | 3 23 | 0 24 | 230 99 25 | 230 80 26 | 254 98 27 | 3 28 | 0 29 | 230 99 30 | 254 98 31 | 235 163 32 | 3 33 | 0 34 | 212 144 35 | 230 99 36 | 235 163 37 | 3 38 | 0 39 | 212 144 40 | 235 163 41 | 212 173 42 | 3 43 | 0 44 | 212 144 45 | 212 173 46 | 189 172 47 | 3 48 | 0 49 | 163 138 50 | 212 144 51 | 189 172 52 | 3 53 | 0 54 | 50 98 55 | 50 79 56 | 74 80 57 | 3 58 | 0 59 | 50 98 60 | 74 80 61 | 74 99 62 | 3 63 | 0 64 | 69 163 65 | 50 98 66 | 74 99 67 | 3 68 | 0 69 | 69 163 70 | 74 99 71 | 92 144 72 | 3 73 | 0 74 | 92 173 75 | 69 163 76 | 92 144 77 | 3 78 | 0 79 | 115 172 80 | 92 173 81 | 92 144 82 | 3 83 | 0 84 | 115 172 85 | 92 144 86 | 141 138 87 | 3 88 | 0 89 | 189 172 90 | 179 196 91 | 150 183 92 | 3 93 | 0 94 | 189 172 95 | 150 183 96 | 159 161 97 | 3 98 | 0 99 | 163 138 100 | 189 172 101 | 159 161 102 | 3 103 | 0 104 | 253 377 105 | 208 377 106 | 208 355 107 | 3 108 | 0 109 | 96 355 110 | 96 377 111 | 51 377 112 | 3 113 | 0 114 | 228 358 115 | 254 361 116 | 253 377 117 | 3 118 | 0 119 | 228 358 120 | 253 377 121 | 208 355 122 | 3 123 | 0 124 | 219 301 125 | 228 358 126 | 208 355 127 | 3 128 | 0 129 | 219 301 130 | 208 355 131 | 196 310 132 | 3 133 | 0 134 | 189 242 135 | 219 301 136 | 196 310 137 | 3 138 | 0 139 | 189 242 140 | 196 310 141 | 150 266 142 | 3 143 | 0 144 | 132 221 145 | 189 242 146 | 150 266 147 | 3 148 | 0 149 | 51 377 150 | 50 361 151 | 76 358 152 | 3 153 | 0 154 | 96 355 155 | 51 377 156 | 76 358 157 | 3 158 | 0 159 | 96 355 160 | 76 358 161 | 85 301 162 | 3 163 | 0 164 | 108 310 165 | 96 355 166 | 85 301 167 | 3 168 | 0 169 | 108 310 170 | 85 301 171 | 115 242 172 | 3 173 | 0 174 | 150 266 175 | 108 310 176 | 115 242 177 | 3 178 | 0 179 | 132 221 180 | 150 266 181 | 115 242 182 | 3 183 | 0 184 | 132 221 185 | 115 242 186 | 115 172 187 | 3 188 | 0 189 | 125 191 190 | 132 221 191 | 115 172 192 | 3 193 | 0 194 | 159 161 195 | 125 191 196 | 115 172 197 | 3 198 | 0 199 | 159 161 200 | 115 172 201 | 141 138 202 | 3 203 | 0 204 | 163 138 205 | 159 161 206 | 141 138 207 | 3 208 | 0 209 | 163 125 210 | 163 138 211 | 141 138 212 | 3 213 | 0 214 | 163 125 215 | 141 138 216 | 141 125 217 | 3 218 | 0 219 | 178 108 220 | 163 125 221 | 141 125 222 | 3 223 | 0 224 | 178 108 225 | 141 125 226 | 126 108 227 | 3 228 | 0 229 | 179 87 230 | 178 108 231 | 126 108 232 | 3 233 | 0 234 | 179 87 235 | 126 108 236 | 125 87 237 | 3 238 | 0 239 | 170 75 240 | 179 87 241 | 125 87 242 | 3 243 | 0 244 | 152 71 245 | 170 75 246 | 125 87 247 | 3 248 | 0 249 | 152 71 250 | 125 87 251 | 134 75 252 | -------------------------------------------------------------------------------- /test/test_triangulate_MONO.txt: -------------------------------------------------------------------------------- 1 | 50 2 | 3 3 | 0 4 | 212 144 5 | 92 144 6 | 163 138 7 | 3 8 | 0 9 | 163 138 10 | 92 144 11 | 141 138 12 | 3 13 | 0 14 | 163 138 15 | 141 138 16 | 163 125 17 | 3 18 | 0 19 | 163 125 20 | 141 138 21 | 141 125 22 | 3 23 | 0 24 | 163 125 25 | 141 125 26 | 178 108 27 | 3 28 | 0 29 | 178 108 30 | 141 125 31 | 126 108 32 | 3 33 | 0 34 | 178 108 35 | 126 108 36 | 179 87 37 | 3 38 | 0 39 | 179 87 40 | 126 108 41 | 125 87 42 | 3 43 | 0 44 | 179 87 45 | 125 87 46 | 170 75 47 | 3 48 | 0 49 | 170 75 50 | 125 87 51 | 134 75 52 | 3 53 | 0 54 | 170 75 55 | 134 75 56 | 152 71 57 | 3 58 | 0 59 | 50 361 60 | 96 377 61 | 51 377 62 | 3 63 | 0 64 | 76 358 65 | 96 377 66 | 50 361 67 | 3 68 | 0 69 | 96 377 70 | 76 358 71 | 96 355 72 | 3 73 | 0 74 | 108 310 75 | 96 355 76 | 76 358 77 | 3 78 | 0 79 | 108 310 80 | 76 358 81 | 85 301 82 | 3 83 | 0 84 | 108 310 85 | 85 301 86 | 150 266 87 | 3 88 | 0 89 | 189 242 90 | 150 266 91 | 85 301 92 | 3 93 | 0 94 | 189 242 95 | 85 301 96 | 115 242 97 | 3 98 | 0 99 | 132 221 100 | 189 242 101 | 115 242 102 | 3 103 | 0 104 | 179 196 105 | 189 242 106 | 132 221 107 | 3 108 | 0 109 | 189 242 110 | 179 196 111 | 189 172 112 | 3 113 | 0 114 | 179 196 115 | 150 183 116 | 189 172 117 | 3 118 | 0 119 | 189 172 120 | 150 183 121 | 159 161 122 | 3 123 | 0 124 | 235 163 125 | 189 172 126 | 159 161 127 | 3 128 | 0 129 | 212 144 130 | 235 163 131 | 159 161 132 | 3 133 | 0 134 | 230 99 135 | 235 163 136 | 212 144 137 | 3 138 | 0 139 | 235 163 140 | 230 99 141 | 254 98 142 | 3 143 | 0 144 | 254 98 145 | 230 99 146 | 230 80 147 | 3 148 | 0 149 | 254 98 150 | 230 80 151 | 254 79 152 | 3 153 | 0 154 | 212 173 155 | 189 172 156 | 235 163 157 | 3 158 | 0 159 | 253 377 160 | 208 377 161 | 254 361 162 | 3 163 | 0 164 | 228 358 165 | 254 361 166 | 208 377 167 | 3 168 | 0 169 | 228 358 170 | 208 377 171 | 208 355 172 | 3 173 | 0 174 | 196 310 175 | 228 358 176 | 208 355 177 | 3 178 | 0 179 | 228 358 180 | 196 310 181 | 219 301 182 | 3 183 | 0 184 | 219 301 185 | 196 310 186 | 150 266 187 | 3 188 | 0 189 | 219 301 190 | 150 266 191 | 189 242 192 | 3 193 | 0 194 | 125 191 195 | 153 197 196 | 132 221 197 | 3 198 | 0 199 | 125 191 200 | 132 221 201 | 115 242 202 | 3 203 | 0 204 | 125 191 205 | 115 242 206 | 115 172 207 | 3 208 | 0 209 | 125 191 210 | 115 172 211 | 159 161 212 | 3 213 | 0 214 | 115 172 215 | 69 163 216 | 159 161 217 | 3 218 | 0 219 | 212 144 220 | 159 161 221 | 69 163 222 | 3 223 | 0 224 | 92 144 225 | 212 144 226 | 69 163 227 | 3 228 | 0 229 | 74 99 230 | 92 144 231 | 69 163 232 | 3 233 | 0 234 | 74 99 235 | 69 163 236 | 50 98 237 | 3 238 | 0 239 | 74 99 240 | 50 98 241 | 74 80 242 | 3 243 | 0 244 | 74 80 245 | 50 98 246 | 50 79 247 | 3 248 | 0 249 | 92 173 250 | 69 163 251 | 115 172 252 | -------------------------------------------------------------------------------- /test/test_triangulate_OPT.txt: -------------------------------------------------------------------------------- 1 | 42 2 | 3 3 | 0 4 | 170 75 5 | 179 87 6 | 152 71 7 | 3 8 | 0 9 | 179 87 10 | 178 108 11 | 152 71 12 | 3 13 | 0 14 | 178 108 15 | 141 125 16 | 152 71 17 | 3 18 | 0 19 | 178 108 20 | 163 125 21 | 141 125 22 | 3 23 | 0 24 | 141 125 25 | 125 87 26 | 152 71 27 | 3 28 | 0 29 | 163 125 30 | 163 138 31 | 141 125 32 | 3 33 | 0 34 | 141 125 35 | 126 108 36 | 125 87 37 | 3 38 | 0 39 | 125 87 40 | 134 75 41 | 152 71 42 | 3 43 | 0 44 | 163 138 45 | 141 138 46 | 141 125 47 | 3 48 | 0 49 | 163 138 50 | 189 172 51 | 141 138 52 | 3 53 | 0 54 | 163 138 55 | 212 144 56 | 189 172 57 | 3 58 | 0 59 | 189 172 60 | 115 172 61 | 141 138 62 | 3 63 | 0 64 | 212 144 65 | 212 173 66 | 189 172 67 | 3 68 | 0 69 | 189 172 70 | 189 242 71 | 115 172 72 | 3 73 | 0 74 | 115 172 75 | 92 144 76 | 141 138 77 | 3 78 | 0 79 | 212 144 80 | 235 163 81 | 212 173 82 | 3 83 | 0 84 | 189 242 85 | 115 242 86 | 115 172 87 | 3 88 | 0 89 | 115 172 90 | 92 173 91 | 92 144 92 | 3 93 | 0 94 | 212 144 95 | 254 98 96 | 235 163 97 | 3 98 | 0 99 | 189 242 100 | 150 266 101 | 115 242 102 | 3 103 | 0 104 | 92 173 105 | 69 163 106 | 92 144 107 | 3 108 | 0 109 | 212 144 110 | 230 99 111 | 254 98 112 | 3 113 | 0 114 | 189 242 115 | 196 310 116 | 150 266 117 | 3 118 | 0 119 | 150 266 120 | 108 310 121 | 115 242 122 | 3 123 | 0 124 | 69 163 125 | 50 98 126 | 92 144 127 | 3 128 | 0 129 | 230 99 130 | 230 80 131 | 254 98 132 | 3 133 | 0 134 | 189 242 135 | 219 301 136 | 196 310 137 | 3 138 | 0 139 | 108 310 140 | 85 301 141 | 115 242 142 | 3 143 | 0 144 | 50 98 145 | 74 99 146 | 92 144 147 | 3 148 | 0 149 | 230 80 150 | 254 79 151 | 254 98 152 | 3 153 | 0 154 | 219 301 155 | 208 355 156 | 196 310 157 | 3 158 | 0 159 | 108 310 160 | 96 355 161 | 85 301 162 | 3 163 | 0 164 | 50 98 165 | 74 80 166 | 74 99 167 | 3 168 | 0 169 | 219 301 170 | 228 358 171 | 208 355 172 | 3 173 | 0 174 | 96 355 175 | 76 358 176 | 85 301 177 | 3 178 | 0 179 | 50 98 180 | 50 79 181 | 74 80 182 | 3 183 | 0 184 | 228 358 185 | 208 377 186 | 208 355 187 | 3 188 | 0 189 | 96 355 190 | 96 377 191 | 76 358 192 | 3 193 | 0 194 | 228 358 195 | 253 377 196 | 208 377 197 | 3 198 | 0 199 | 96 377 200 | 51 377 201 | 76 358 202 | 3 203 | 0 204 | 228 358 205 | 254 361 206 | 253 377 207 | 3 208 | 0 209 | 51 377 210 | 50 361 211 | 76 358 212 | --------------------------------------------------------------------------------