├── .clang-format ├── .gitattributes ├── .gitignore ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── THIRD-PARTY-LICENSES ├── UnitTests ├── CMakeLists.txt ├── gtest_main.cc ├── src │ ├── test_cached_polymesh3_loader.cpp │ └── test_xmesh_timing.cpp ├── stdafx.cpp └── stdafx.h ├── build.py ├── conanfile.py ├── src └── xmesh │ ├── cached_polymesh3_loader.cpp │ └── xmesh_timing.cpp ├── stdafx.cpp ├── stdafx.h └── xmesh ├── cached_polymesh3_loader.hpp ├── fractional_index_iterator.hpp └── xmesh_timing.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | Standard: c++17 3 | UseTab: Never 4 | ColumnLimit: 120 5 | IndentWidth: 4 6 | BreakBeforeBraces: Attach 7 | NamespaceIndentation: None 8 | AlwaysBreakTemplateDeclarations: true 9 | SpacesInParentheses: true 10 | SpaceBeforeParens: Never 11 | BreakConstructorInitializersBeforeComma: true 12 | PointerAlignment: Left 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | cmake_minimum_required( VERSION 3.15 FATAL_ERROR ) 4 | 5 | project( XMeshCore ) 6 | 7 | find_package( thinkboxcmlibrary REQUIRED ) 8 | include( ThinkboxCMLibrary ) 9 | include( PrecompiledHeader ) 10 | 11 | option(BUILD_UNIT_TESTS "Build unit tests" ON) 12 | option(BUILD_WITH_TBB "Build with Intel TBB" ON) 13 | 14 | add_library( xmeshcore STATIC ) 15 | 16 | target_include_directories( xmeshcore PUBLIC 17 | $ 18 | $ 19 | ) 20 | 21 | file( GLOB_RECURSE H_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 22 | "xmesh/*.h" 23 | "xmesh/*.hpp" 24 | ) 25 | 26 | file( GLOB_RECURSE CXX_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 27 | "src/xmesh/*.cpp" 28 | ) 29 | 30 | target_sources( xmeshcore PRIVATE 31 | stdafx.cpp 32 | stdafx.h 33 | ${H_FILES} 34 | ${CXX_FILES} 35 | ) 36 | 37 | # The Conan version of Boost was built with this, and it changes the library names. 38 | # As a result, we need to set this to tell Boost to look for the right libraries to 39 | # link against. 40 | target_compile_definitions( xmeshcore PUBLIC BOOST_AUTO_LINK_SYSTEM ) 41 | 42 | set_target_properties( xmeshcore PROPERTIES PROJECT_LABEL "XMeshCore" ) 43 | 44 | find_package( thinkboxlibrary REQUIRED ) 45 | find_package( Boost REQUIRED ) 46 | find_package( OpenEXR REQUIRED ) 47 | find_package( tinyxml2 REQUIRED ) 48 | find_package( ZLIB REQUIRED ) 49 | 50 | target_include_directories( xmeshcore PUBLIC ${thinkboxlibrary_INCLUDE_DIRS} ) 51 | target_include_directories( xmeshcore PUBLIC ${Boost_INCLUDE_DIRS} ) 52 | target_include_directories( xmeshcore PUBLIC ${OpenEXR_INCLUDE_DIRS} ) 53 | target_include_directories( xmeshcore PUBLIC ${tinyxml2_INCLUDE_DIRS} ) 54 | target_include_directories( xmeshcore PUBLIC ${ZLIB_INCLUDE_DIRS} ) 55 | 56 | target_link_libraries( xmeshcore INTERFACE thinkboxlibrary::thinkboxlibrary ) 57 | target_link_libraries( xmeshcore INTERFACE Boost::Boost ) 58 | target_link_libraries( xmeshcore INTERFACE OpenEXR::OpenEXR ) 59 | target_link_libraries( xmeshcore INTERFACE tinyxml2::tinyxml2 ) 60 | target_link_libraries( xmeshcore INTERFACE ZLIB::ZLIB ) 61 | 62 | if(BUILD_WITH_TBB) 63 | target_compile_definitions( xmeshcore PRIVATE TBB_AVAILABLE ) 64 | find_package( TBB REQUIRED ) 65 | target_include_directories( xmeshcore PUBLIC ${TBB_INCLUDE_DIRS} ) 66 | target_link_libraries( xmeshcore INTERFACE TBB::tbb ) 67 | endif() 68 | 69 | frantic_default_source_groups( xmeshcore HEADERDIR xmesh SOURCEDIR src/xmesh ) 70 | frantic_common_platform_setup( xmeshcore ) 71 | 72 | add_precompiled_header( xmeshcore stdafx.h SOURCE_CXX stdafx.cpp ) 73 | 74 | # Disable optimization for the RelWithDebInfo configuration on Windows. 75 | # This allows breakpoints to be hit reliably when debugging in Visual Studio. 76 | if( WIN32 ) 77 | target_compile_options( xmeshcore PRIVATE "$<$:/O2>$<$:/Od>" ) 78 | endif() 79 | 80 | if( BUILD_UNIT_TESTS ) 81 | add_subdirectory( UnitTests ) 82 | endif() 83 | 84 | install( DIRECTORY xmesh 85 | DESTINATION include 86 | FILES_MATCHING PATTERN "*.hpp" 87 | ) 88 | install( TARGETS xmeshcore 89 | RUNTIME DESTINATION bin 90 | LIBRARY DESTINATION lib 91 | ARCHIVE DESTINATION lib 92 | ) 93 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to XMesh 2 | 3 | Thanks for your interest in contributing to XMesh! ❤️ 4 | 5 | This document describes how to set up a development environment and submit your contributions. Please read it carefully 6 | and let us know if it's not up-to-date (even better, submit a PR with your corrections ;-)). 7 | 8 | - [Prerequisites](#prerequisites) 9 | - [Building](#building) 10 | - [Testing](#testing) 11 | - [Publishing to Local Conan Cache](#publishing-to-local-conan-cache) 12 | - [Building Multiple Configurations](#building-multiple-configurations) 13 | - [Pull Requests](#pull-requests) 14 | - [Pull Request Checklist](#pull-request-checklist) 15 | - [Step 1: Open Issue](#step-1-open-issue) 16 | - [Step 2: Design (optional)](#step-2-design-optional) 17 | - [Step 3: Work your Magic](#step-3-work-your-magic) 18 | - [Step 4: Commit](#step-4-commit) 19 | - [Step 5: Pull Request](#step-5-pull-request) 20 | - [Step 6: Merge](#step-6-merge) 21 | 22 | ## Prerequisites 23 | 24 | You will need Python 3.10 or later and the C++ compiler for your platform installed before you are able to build XMesh. The compilers used for each platform are as follows: 25 | 26 | * Windows: Visual C++ 14.1 (Visual Studio 2017) or later 27 | * macOS: Clang 10.0 or later 28 | * Linux: GCC 7 or later 29 | 30 | You will then need to install Conan. You can do this by running: 31 | 32 | ```bash 33 | pip install conan conan_package_tools 34 | ``` 35 | 36 | Conan is a C++ package manager that is used to install the 3rd party dependencies. 37 | 38 | XMesh uses the C++17 standard. 39 | 40 | You will need to build the following dependencies to your local Conan cache: 41 | 42 | * https://github.com/aws/thinkbox-cm-library 43 | * https://github.com/aws/thinkbox-library 44 | 45 | ## Building 46 | 47 | From the project root directory run the following commands to install the dependencies and build XMesh: 48 | 49 | ```bash 50 | conan install . --install-folder build 51 | conan build . --build-folder build 52 | ``` 53 | 54 | If you wish to generate a development environment without building the package immediately, you can add `--configure` to the `conan build` command. 55 | 56 | If you are using Windows, once run, you can open `build/XMesh.sln` in Visual Studio to use Visual Studio for development and debugging. 57 | 58 | 59 | ### Testing 60 | 61 | You can run the project's unit tests after [building](#building) the project by running the following command from the project root directory on Windows: 62 | 63 | ```bash 64 | build/UnitTests/Release/test_thinkboxlibrary.exe 65 | ``` 66 | 67 | ### Publishing to Local Conan Cache 68 | 69 | If you need to publish build artifacts to your local Conan cache manually, after completing the [building](#building) steps, you can run the following commands to package XMesh and publish it to your local Conan cache: 70 | 71 | ```bash 72 | conan package . --install-folder build --build-folder build --package-folder build/package 73 | conan export-pkg . --package-folder build/package 74 | ``` 75 | 76 | ### Building Multiple Configurations 77 | 78 | To quickly build all supported configurations on the current platform you can run: 79 | 80 | ``` 81 | python build.py 82 | ``` 83 | 84 | This will build the configurations and publish them to your local conan cache. You can add the `--dry-run` flag to preview the configurations that will be built without building them. 85 | 86 | These artifacts will only be built and published to your local Conan cache if there is not already an artifact with the same name and version in the cache. 87 | 88 | ### Pull Requests 89 | 90 | #### Pull Request Checklist 91 | 92 | - Testing 93 | - Unit test added (prefer not to modify an existing test, otherwise, it's probably a breaking change) 94 | - Title and Description 95 | - __Change type__: title prefixed with **fix**, **feat** and module name in parens, which will appear in changelog 96 | - __Title__: use lower-case and doesn't end with a period 97 | - __Breaking?__: last paragraph: "BREAKING CHANGE: " 98 | - __Issues__: Indicate issues fixed via: "**Fixes #xxx**" or "**Closes #xxx**" 99 | 100 | #### Step 1: Open Issue 101 | 102 | If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in 103 | advance, because sometimes, someone is already working in this space, so maybe it's worth collaborating with them 104 | instead of duplicating the efforts. 105 | 106 | #### Step 2: Design (optional) 107 | 108 | In some cases, it is useful to seek for feedback by iterating on a design document. This is useful 109 | when you plan a big change or feature, or you want advice on what would be the best path forward. 110 | 111 | Sometimes, the GitHub issue is sufficient for such discussions, and can be sufficient to get 112 | clarity on what you plan to do. Sometimes, a design document would work better, so people can provide 113 | iterative feedback. 114 | 115 | In such cases, use the GitHub issue description to collect **requirements** and 116 | **use cases** for your feature. 117 | 118 | #### Step 3: Work your Magic 119 | 120 | Work your magic. Here are some guidelines: 121 | 122 | - Coding style: 123 | - Code should conform to the style defined in .clang-format 124 | - Every change requires a unit test 125 | - Try to maintain a single feature/bugfix per pull request. It's okay to introduce a little bit of housekeeping 126 | changes along the way, but try to avoid conflating multiple features. Eventually all these are going to go into a 127 | single commit, so you can use that to frame your scope. 128 | 129 | #### Step 4: Commit 130 | 131 | Create a commit with the proposed changes: 132 | 133 | - Commit title and message (and PR title and description) must adhere to [conventionalcommits](https://www.conventionalcommits.org). 134 | - The title must begin with `feat: title`, `fix: title`, `refactor: title` or 135 | `chore: title`. 136 | - Title should be lowercase. 137 | - No period at the end of the title. 138 | 139 | - Commit message should describe _motivation_. Think about your code reviewers and what information they need in 140 | order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so 141 | it will be easier to follow. 142 | 143 | - Commit message should indicate which issues are fixed: `fixes #` or `closes #`. 144 | 145 | - Shout out to collaborators. 146 | 147 | - If not obvious (i.e. from unit tests), describe how you verified that your change works. 148 | 149 | - If this commit includes breaking changes, they must be listed at the end in the following format (notice how multiple breaking changes should be formatted): 150 | 151 | ``` 152 | BREAKING CHANGE: Description of what broke and how to achieve this behavior now 153 | - **module-name:** Another breaking change 154 | - **module-name:** Yet another breaking change 155 | ``` 156 | 157 | #### Step 5: Pull Request 158 | 159 | - Push to a personal GitHub fork. 160 | - Submit a Pull Request on GitHub. A reviewer will later be assigned by the maintainers. 161 | - Please follow the PR checklist written above. We trust our contributors to self-check, and this helps that process! 162 | - Discuss review comments and iterate until you get at least one "Approve". When iterating, push new commits to the 163 | same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints 164 | for you when you finalize your merge commit message. 165 | - Make sure to update the PR title/description if things change. The PR title/description are going to be used as the 166 | commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. 167 | 168 | #### Step 6: Merge 169 | 170 | - Make sure your PR builds successfully 171 | - Once approved and tested, a maintainer will squash-merge to master and will use your PR title/description as the 172 | commit message. 173 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | XMesh 2 | Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMesh 2 | 3 | ## Overview 4 | 5 | XMesh contains code to support certain features of XMesh plugins such as [ThinkboxXMeshMY](). Most of the code for reading from and writing to the XMesh file format can be found in [ThinkboxThinkboxLibrary](). 6 | 7 | ## Table of Contents 8 | 9 | - [Reporting Bugs/Feature Requests](#reporting-bugs/feature-requests) 10 | - [Security issue notifications](#security-issue-notifications) 11 | - [Contributing](#contributing) 12 | - [Code of Conduct](#code-of-conduct) 13 | - [Licensing](#licensing) 14 | 15 | ## Reporting Bugs/Feature Requests 16 | 17 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 18 | 19 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 20 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 21 | 22 | - A reproducible test case or series of steps 23 | - The version of our code being used 24 | - Any modifications you've made relevant to the bug 25 | - Anything unusual about your environment or deployment 26 | 27 | ## Security issue notifications 28 | 29 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 30 | 31 | ## Contributing 32 | 33 | Contributions to XMesh are encouraged. If you want to fix a problem, or want to enhance the library in any way, then 34 | we are happy to accept your contribution. Information on contributing to XMesh can be found 35 | [in CONTRIBUTING.md](CONTRIBUTING.md). 36 | 37 | ## Code of Conduct 38 | 39 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 40 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 41 | opensource-codeofconduct@amazon.com with any additional questions or comments. 42 | 43 | ## Licensing 44 | 45 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 46 | 47 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 48 | -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES: -------------------------------------------------------------------------------- 1 | ** gtest -- https://github.com/google/googletest 2 | Copyright 2006, Google Inc. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following disclaimer 13 | in the documentation and/or other materials provided with the 14 | distribution. 15 | * Neither the name of Google Inc. nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | // modification, are permitted provided that the following conditions are 31 | // met: 32 | // 33 | // * Redistributions of source code must retain the above copyright 34 | // notice, this list of conditions and the following disclaimer. 35 | // * Redistributions in binary form must reproduce the above 36 | // copyright notice, this list of conditions and the following disclaimer 37 | // in the documentation and/or other materials provided with the 38 | // distribution. 39 | // * Neither the name of Google Inc. nor the names of its 40 | // contributors may be used to endorse or promote products derived from 41 | // this software without specific prior written permission. 42 | // 43 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | -------------------------------------------------------------------------------- /UnitTests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | cmake_minimum_required( VERSION 3.15 FATAL_ERROR ) 4 | project( XMeshCoreTest ) 5 | 6 | find_package( thinkboxcmlibrary REQUIRED ) 7 | include( ThinkboxCMLibrary ) 8 | include( PrecompiledHeader ) 9 | 10 | add_executable( test_xmesh "" ) 11 | target_include_directories( test_xmesh 12 | PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ) 13 | 14 | target_link_libraries( test_xmesh PRIVATE xmeshcore ) 15 | 16 | find_package( GTest REQUIRED ) 17 | target_include_directories( test_xmesh PRIVATE ${GTest_INCLUDE_DIRS} ) 18 | target_link_libraries( test_xmesh PRIVATE GTest::GTest ) 19 | 20 | file( GLOB_RECURSE H_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 21 | "*.h" 22 | "*.hpp" 23 | ) 24 | 25 | file( GLOB_RECURSE CXX_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 26 | "*.cpp" 27 | "*.cc" 28 | ) 29 | 30 | target_sources( test_xmesh PRIVATE 31 | stdafx.cpp 32 | stdafx.h 33 | ${H_FILES} 34 | ${CXX_FILES} 35 | ) 36 | 37 | frantic_common_platform_setup( test_xmesh ) 38 | frantic_default_source_groups( test_xmesh SOURCEDIR src ) 39 | frantic_link_apple_core_libraries( test_xmesh ) 40 | 41 | add_precompiled_header( test_xmesh stdafx.h SOURCE_CXX stdafx.cpp ) 42 | 43 | # Disable optimization for the RelWithDebInfo configuration on Windows. 44 | # This allows breakpoints to be hit reliably when debugging in Visual Studio. 45 | if( WIN32 ) 46 | target_compile_options( test_xmesh PRIVATE "$<$:/O2>$<$:/Od>" ) 47 | endif() 48 | -------------------------------------------------------------------------------- /UnitTests/gtest_main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2006, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | #include "stdafx.h" 31 | 32 | #include 33 | 34 | #include 35 | 36 | GTEST_API_ int main( int argc, char** argv ) { 37 | printf( "Running main() from gtest_main.cc\n" ); 38 | testing::InitGoogleTest( &argc, argv ); 39 | return RUN_ALL_TESTS(); 40 | } 41 | -------------------------------------------------------------------------------- /UnitTests/src/test_cached_polymesh3_loader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "stdafx.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | using namespace boost::filesystem; 21 | 22 | using namespace frantic::channels; 23 | using namespace frantic::geometry; 24 | using namespace frantic::graphics; 25 | 26 | using namespace xmesh; 27 | 28 | namespace { 29 | 30 | polymesh3_ptr create_test_mesh() { 31 | polymesh3_builder builder; 32 | 33 | builder.add_vertex( 0, 0, 0 ); 34 | builder.add_vertex( 1, 0, 0 ); 35 | builder.add_vertex( 1, 1, 0 ); 36 | 37 | int indices[] = { 0, 1, 2 }; 38 | 39 | builder.add_polygon( indices, 3 ); 40 | 41 | polymesh3_ptr mesh = builder.finalize(); 42 | 43 | raw_byte_buffer buffer; 44 | buffer.resize( 3 * sizeof( vector3f ) ); 45 | vector3f* v = reinterpret_cast( buffer.begin() ); 46 | v[0] = vector3f( 0, 0, 1 ); 47 | v[1] = vector3f( 0, 0, 1 ); 48 | v[2] = vector3f( 0, 0, 1 ); 49 | 50 | mesh->add_vertex_channel( _T("Velocity"), data_type_float32, 3, buffer ); 51 | 52 | buffer.resize( 3 * sizeof( vector3f ) ); 53 | v = reinterpret_cast( buffer.begin() ); 54 | v[0] = vector3f( 1, 0, 0 ); 55 | v[1] = vector3f( 0, 1, 0 ); 56 | v[2] = vector3f( 0, 0, 1 ); 57 | mesh->add_vertex_channel( _T("Color"), data_type_float32, 3, buffer ); 58 | 59 | buffer.resize( 3 * sizeof( vector3f ) ); 60 | v = reinterpret_cast( buffer.begin() ); 61 | v[0] = vector3f( 0, 0, 1 ); 62 | v[1] = vector3f( 0, 0, 1 ); 63 | v[2] = vector3f( 0, 0, 1 ); 64 | mesh->add_vertex_channel( _T("Normal"), data_type_float32, 3, buffer ); 65 | 66 | buffer.resize( 2 * sizeof( vector3f ) ); 67 | v = reinterpret_cast( buffer.begin() ); 68 | v[0] = vector3f( 1, 0, 0 ); 69 | v[1] = vector3f( 2, 0, 0 ); 70 | std::vector faces( 3 ); 71 | faces[0] = 0; 72 | faces[1] = 1; 73 | faces[2] = 0; 74 | mesh->add_vertex_channel( _T("TextureCoord"), data_type_float32, 3, buffer, &faces ); 75 | 76 | buffer.resize( sizeof( boost::uint16_t ) ); 77 | boost::uint16_t* i = reinterpret_cast( buffer.begin() ); 78 | i[0] = 4; 79 | mesh->add_face_channel( _T("MaterialID"), data_type_uint16, 1, buffer ); 80 | 81 | buffer.resize( sizeof( boost::int32_t ) ); 82 | boost::int32_t* i32 = reinterpret_cast( buffer.begin() ); 83 | i32[0] = 1; 84 | mesh->add_face_channel( _T("SmoothingGroup"), data_type_int32, 1, buffer ); 85 | 86 | return mesh; 87 | } 88 | 89 | } // anonymous namespace 90 | 91 | TEST( CachedPolymesh3Loader, LoadObj ) { 92 | path tempDir = temp_directory_path() / unique_path(); 93 | create_directory( tempDir ); 94 | 95 | frantic::files::scoped_file_cleanup cleanup; 96 | cleanup.add( tempDir ); 97 | 98 | const frantic::tstring filename = frantic::files::to_tstring( tempDir / "temp.obj" ); 99 | 100 | polymesh3_ptr mesh = create_test_mesh(); 101 | 102 | // Filter to keep only channels that are supported by OBJ 103 | channel_propagation_policy cpp( true ); 104 | cpp.add_channel( _T("Normal") ); 105 | cpp.add_channel( _T("TextureCoord") ); 106 | 107 | { 108 | std::vector channels; 109 | mesh->get_vertex_channel_names( channels ); 110 | BOOST_FOREACH( const frantic::tstring& channel, channels ) { 111 | if( !cpp.is_channel_included( channel ) ) { 112 | mesh->erase_vertex_channel( channel ); 113 | } 114 | } 115 | } 116 | 117 | { 118 | std::vector channels; 119 | mesh->get_face_channel_names( channels ); 120 | BOOST_FOREACH( const frantic::tstring& channel, channels ) { 121 | if( !cpp.is_channel_included( channel ) ) { 122 | mesh->erase_face_channel( channel ); 123 | } 124 | } 125 | } 126 | 127 | EXPECT_TRUE( mesh->has_vertex_channel( _T("TextureCoord") ) ); 128 | 129 | write_obj_polymesh_file( filename, mesh ); 130 | 131 | xmesh_metadata metadata; 132 | 133 | cached_polymesh3_loader loader; 134 | const_polymesh3_ptr loadedMesh = loader.load( filename, &metadata, LOAD_POLYMESH3_MASK::ALL ); 135 | 136 | EXPECT_TRUE( is_equal( mesh, loadedMesh ) ); 137 | } 138 | 139 | TEST( CachedPolymesh3Loader, LoadEmptyXMesh ) { 140 | path tempDir = temp_directory_path() / unique_path(); 141 | create_directory( tempDir ); 142 | 143 | frantic::files::scoped_file_cleanup cleanup; 144 | cleanup.add( tempDir ); 145 | 146 | const frantic::tstring filename = frantic::files::to_tstring( tempDir / "temp.xmesh" ); 147 | 148 | polymesh3_builder builder; 149 | 150 | polymesh3_ptr mesh = builder.finalize(); 151 | 152 | raw_byte_buffer buffer; 153 | std::vector faces; 154 | 155 | mesh->add_vertex_channel( _T("Velocity"), data_type_float32, 3, buffer ); 156 | mesh->add_vertex_channel( _T("TextureCoord"), data_type_float32, 3, buffer, &faces ); 157 | mesh->add_face_channel( _T("MaterialID"), data_type_uint16, 1, buffer ); 158 | 159 | write_xmesh_polymesh_file( filename, mesh, 0 ); 160 | 161 | xmesh_metadata metadata; 162 | 163 | cached_polymesh3_loader loader; 164 | const_polymesh3_ptr loadedMesh = loader.load( filename, &metadata, LOAD_POLYMESH3_MASK::ALL ); 165 | 166 | EXPECT_TRUE( is_equal( mesh, loadedMesh ) ); 167 | } 168 | 169 | TEST( CachedPolymesh3Loader, LoadXMesh ) { 170 | path tempDir = temp_directory_path() / unique_path(); 171 | create_directory( tempDir ); 172 | 173 | frantic::files::scoped_file_cleanup cleanup; 174 | cleanup.add( tempDir ); 175 | 176 | polymesh3_ptr meshA = create_test_mesh(); 177 | polymesh3_ptr meshB = create_test_mesh(); 178 | { 179 | // change some of meshB's channel values 180 | 181 | polymesh3_vertex_accessor verts = meshB->get_vertex_accessor( _T("verts") ); 182 | for( std::size_t i = 0; i < verts.vertex_count(); ++i ) { 183 | verts.get_vertex( i ).z = 1; 184 | } 185 | 186 | polymesh3_vertex_accessor color = meshB->get_vertex_accessor( _T("Color") ); 187 | for( std::size_t i = 0; i < color.vertex_count(); ++i ) { 188 | color.get_vertex( i ).set( 0, 1, 0 ); 189 | } 190 | 191 | raw_byte_buffer buffer; 192 | buffer.resize( 2 * sizeof( vector3f ) ); 193 | vector3f* v = reinterpret_cast( buffer.begin() ); 194 | v[0] = vector3f( 1, 0, 0 ); 195 | v[1] = vector3f( 2, 0, 0 ); 196 | std::vector faces( 3 ); 197 | faces[0] = 1; 198 | faces[1] = 0; 199 | faces[2] = 1; 200 | meshB->erase_vertex_channel( _T("TextureCoord") ); 201 | meshB->add_vertex_channel( _T("TextureCoord"), data_type_float32, 3, buffer, &faces ); 202 | 203 | polymesh3_face_accessor materialID = 204 | meshB->get_face_accessor( _T("MaterialID") ); 205 | materialID.get_face( 0 ) = 8; 206 | } 207 | 208 | const frantic::tstring file0 = frantic::files::to_tstring( tempDir / "moving_triangle_0000.xmesh" ); 209 | const frantic::tstring file1 = frantic::files::to_tstring( tempDir / "moving_triangle_0001.xmesh" ); 210 | 211 | xmesh_sequence_saver xss; 212 | xss.write_xmesh( meshA, file0 ); 213 | xss.write_xmesh( meshB, file1 ); 214 | 215 | xmesh_metadata metadata; 216 | 217 | cached_polymesh3_loader loader; 218 | const_polymesh3_ptr mesh0 = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::ALL ); 219 | polymesh3_ptr mesh( const_cast( mesh0.get() ) ); 220 | EXPECT_TRUE( is_equal( meshA, mesh ) ); 221 | 222 | mesh0 = loader.load( file1, &metadata, LOAD_POLYMESH3_MASK::ALL ); 223 | mesh.reset( const_cast( mesh0.get() ) ); 224 | EXPECT_TRUE( is_equal( meshB, mesh ) ); 225 | 226 | { 227 | cached_polymesh3_loader loader; 228 | const_polymesh3_ptr mesh = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::BOX ); 229 | EXPECT_EQ( 0, mesh->vertex_count() ); 230 | EXPECT_EQ( 0, mesh->face_count() ); 231 | } 232 | 233 | { 234 | cached_polymesh3_loader loader; 235 | const_polymesh3_ptr mesh = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::VERTS ); 236 | EXPECT_EQ( 3, mesh->vertex_count() ); 237 | EXPECT_EQ( 0, mesh->face_count() ); 238 | EXPECT_EQ( vector3f( 0, 0, 0 ), mesh->get_vertex( 0 ) ); 239 | EXPECT_EQ( vector3f( 1, 0, 0 ), mesh->get_vertex( 1 ) ); 240 | EXPECT_EQ( vector3f( 1, 1, 0 ), mesh->get_vertex( 2 ) ); 241 | EXPECT_FALSE( mesh->has_vertex_channel( _T("Velocity") ) ); 242 | EXPECT_FALSE( mesh->has_vertex_channel( _T("Color") ) ); 243 | } 244 | 245 | { 246 | cached_polymesh3_loader loader; 247 | const_polymesh3_ptr mesh = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::FACES ); 248 | EXPECT_EQ( 3, mesh->vertex_count() ); 249 | EXPECT_EQ( 1, mesh->face_count() ); 250 | EXPECT_EQ( vector3f( 0, 0, 0 ), mesh->get_vertex( 0 ) ); 251 | EXPECT_EQ( vector3f( 1, 0, 0 ), mesh->get_vertex( 1 ) ); 252 | EXPECT_EQ( vector3f( 1, 1, 0 ), mesh->get_vertex( 2 ) ); 253 | } 254 | 255 | { 256 | cached_polymesh3_loader loader; 257 | const_polymesh3_ptr mesh = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::VELOCITY ); 258 | EXPECT_EQ( 3, mesh->vertex_count() ); 259 | EXPECT_EQ( 0, mesh->face_count() ); 260 | EXPECT_EQ( vector3f( 0, 0, 0 ), mesh->get_vertex( 0 ) ); 261 | EXPECT_EQ( vector3f( 1, 0, 0 ), mesh->get_vertex( 1 ) ); 262 | EXPECT_EQ( vector3f( 1, 1, 0 ), mesh->get_vertex( 2 ) ); 263 | EXPECT_TRUE( mesh->has_vertex_channel( _T("Velocity") ) ); 264 | } 265 | 266 | { 267 | cached_polymesh3_loader loader; 268 | const_polymesh3_ptr mesh = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::MAPS ); 269 | EXPECT_EQ( 3, mesh->vertex_count() ); 270 | EXPECT_EQ( 1, mesh->face_count() ); 271 | EXPECT_EQ( vector3f( 0, 0, 0 ), mesh->get_vertex( 0 ) ); 272 | EXPECT_EQ( vector3f( 1, 0, 0 ), mesh->get_vertex( 1 ) ); 273 | EXPECT_EQ( vector3f( 1, 1, 0 ), mesh->get_vertex( 2 ) ); 274 | EXPECT_FALSE( mesh->has_vertex_channel( _T("Velocity") ) ); 275 | EXPECT_TRUE( mesh->has_vertex_channel( _T("Color") ) ); 276 | EXPECT_TRUE( mesh->has_vertex_channel( _T("Normal") ) ); 277 | EXPECT_TRUE( mesh->has_vertex_channel( _T("TextureCoord") ) ); 278 | EXPECT_TRUE( mesh->has_face_channel( _T("MaterialID") ) ); 279 | EXPECT_TRUE( mesh->has_face_channel( _T("SmoothingGroup") ) ); 280 | } 281 | 282 | { 283 | cached_polymesh3_loader loader; 284 | const_polymesh3_ptr mesh = loader.load( file0, &metadata, LOAD_POLYMESH3_MASK::STATIC_MESH ); 285 | EXPECT_EQ( 3, mesh->vertex_count() ); 286 | EXPECT_EQ( 1, mesh->face_count() ); 287 | EXPECT_EQ( vector3f( 0, 0, 0 ), mesh->get_vertex( 0 ) ); 288 | EXPECT_EQ( vector3f( 1, 0, 0 ), mesh->get_vertex( 1 ) ); 289 | EXPECT_EQ( vector3f( 1, 1, 0 ), mesh->get_vertex( 2 ) ); 290 | EXPECT_FALSE( mesh->has_vertex_channel( _T("Velocity") ) ); 291 | EXPECT_TRUE( mesh->has_vertex_channel( _T("Color") ) ); 292 | EXPECT_TRUE( mesh->has_vertex_channel( _T("Normal") ) ); 293 | EXPECT_TRUE( mesh->has_vertex_channel( _T("TextureCoord") ) ); 294 | EXPECT_TRUE( mesh->has_face_channel( _T("MaterialID") ) ); 295 | EXPECT_TRUE( mesh->has_face_channel( _T("SmoothingGroup") ) ); 296 | } 297 | } 298 | 299 | TEST( CachedPolymesh3Loader, PopulateXMeshBoundBoxMetadata ) { 300 | path tempDir = temp_directory_path() / unique_path(); 301 | create_directory( tempDir ); 302 | 303 | frantic::files::scoped_file_cleanup cleanup; 304 | cleanup.add( tempDir ); 305 | 306 | const frantic::tstring file = frantic::files::to_tstring( tempDir / "box.xmesh" ); 307 | 308 | { 309 | polymesh3_builder builder; 310 | 311 | builder.add_vertex( 0, 0, 0 ); 312 | builder.add_vertex( 1, 0, 0 ); 313 | builder.add_vertex( 1, 1, 1 ); 314 | 315 | int indices[] = { 0, 1, 2 }; 316 | 317 | builder.add_polygon( indices, 3 ); 318 | 319 | polymesh3_ptr mesh = builder.finalize(); 320 | 321 | write_xmesh_polymesh_file( file, mesh ); 322 | } 323 | 324 | // Currently, write_xmesh_polymesh_file() doesn't write boundbox metadata. 325 | // It isn't a problem if it does, but I want some way to test that 326 | // cached_polymesh3_loader::load() populates the boundbox metadata even 327 | // when the loaded file doesn't have such metadata. 328 | { 329 | xmesh_metadata metadata; 330 | read_xmesh_metadata( file, metadata ); 331 | EXPECT_FALSE( metadata.has_boundbox() ); 332 | } 333 | 334 | xmesh_metadata metadata; 335 | cached_polymesh3_loader loader; 336 | loader.load( file, &metadata, LOAD_POLYMESH3_MASK::BOX ); 337 | EXPECT_TRUE( metadata.has_boundbox() ); 338 | EXPECT_EQ( boundbox3f( vector3f( 0 ), vector3f( 1 ) ), metadata.get_boundbox() ); 339 | } 340 | 341 | TEST( CachedPolymesh3Loader, PopulateObjBoundBoxMetadata ) { 342 | path tempDir = temp_directory_path() / unique_path(); 343 | create_directory( tempDir ); 344 | 345 | frantic::files::scoped_file_cleanup cleanup; 346 | cleanup.add( tempDir ); 347 | 348 | const frantic::tstring file = frantic::files::to_tstring( tempDir / "box.obj" ); 349 | 350 | { 351 | polymesh3_builder builder; 352 | 353 | builder.add_vertex( 0, 0, 0 ); 354 | builder.add_vertex( 1, 0, 0 ); 355 | builder.add_vertex( 1, 1, 1 ); 356 | 357 | int indices[] = { 0, 1, 2 }; 358 | 359 | builder.add_polygon( indices, 3 ); 360 | 361 | polymesh3_ptr mesh = builder.finalize(); 362 | 363 | write_obj_polymesh_file( file, mesh ); 364 | } 365 | 366 | xmesh_metadata metadata; 367 | cached_polymesh3_loader loader; 368 | loader.load( file, &metadata, LOAD_POLYMESH3_MASK::BOX ); 369 | EXPECT_TRUE( metadata.has_boundbox() ); 370 | EXPECT_EQ( boundbox3f( vector3f( 0 ), vector3f( 1 ) ), metadata.get_boundbox() ); 371 | } 372 | -------------------------------------------------------------------------------- /UnitTests/src/test_xmesh_timing.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "stdafx.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | using namespace frantic::files; 12 | using namespace xmesh; 13 | 14 | class xmesh_timing_test : public xmesh_timing { 15 | protected: 16 | double evaluate_playback_graph( double frame ) const { return frame; } 17 | }; 18 | 19 | TEST( XMeshTiming, GetFrameVelocityOffset ) { 20 | xmesh_timing_test timing; 21 | 22 | frame_set frames; 23 | frames.add_frame( 0 ); 24 | frames.add_frame( 0.5 ); 25 | frames.add_frame( 1 ); 26 | 27 | xmesh_timing::range_region rangeRegion; 28 | double sampleFrame; 29 | double sampleOffset; 30 | 31 | timing.get_frame_velocity_offset( 0.4, frames, rangeRegion, sampleFrame, sampleOffset ); 32 | 33 | EXPECT_EQ( xmesh_timing::RANGE_INSIDE, rangeRegion ); 34 | EXPECT_EQ( 0, sampleFrame ); 35 | // Lower precision because the code uses float internally. 36 | // TODO: Consider changing to double internally, or change offset to use 37 | // float externally? 38 | EXPECT_FLOAT_EQ( 0.4f, static_cast( sampleOffset ) ); 39 | } 40 | 41 | TEST( XMeshTiming, GetSubframeVelocityOffset ) { 42 | xmesh_timing_test timing; 43 | 44 | frame_set frames; 45 | frames.add_frame( 0 ); 46 | frames.add_frame( 0.5 ); 47 | frames.add_frame( 1 ); 48 | 49 | xmesh_timing::range_region rangeRegion; 50 | double sampleFrame; 51 | double sampleOffset; 52 | 53 | timing.get_subframe_velocity_offset( 0.4, frames, rangeRegion, sampleFrame, sampleOffset ); 54 | 55 | EXPECT_EQ( xmesh_timing::RANGE_INSIDE, rangeRegion ); 56 | EXPECT_EQ( 0.5, sampleFrame ); 57 | EXPECT_FLOAT_EQ( -0.1f, static_cast( sampleOffset ) ); 58 | } 59 | 60 | TEST( XMeshTiming, GetFrameInterpolation ) { 61 | xmesh_timing_test timing; 62 | 63 | frame_set frames; 64 | frames.add_frame( 0 ); 65 | frames.add_frame( 0.5 ); 66 | frames.add_frame( 1 ); 67 | 68 | xmesh_timing::range_region rangeRegion; 69 | std::pair sampleFrames; 70 | double sampleOffset; 71 | 72 | timing.get_frame_interpolation( 0.4, frames, rangeRegion, sampleFrames, sampleOffset ); 73 | 74 | EXPECT_EQ( xmesh_timing::RANGE_INSIDE, rangeRegion ); 75 | EXPECT_EQ( std::make_pair( 0.0, 1.0 ), sampleFrames ); 76 | EXPECT_FLOAT_EQ( 0.4f, static_cast( sampleOffset ) ); 77 | } 78 | 79 | TEST( XMeshTiming, GetSubframeInterpolation ) { 80 | xmesh_timing_test timing; 81 | 82 | frame_set frames; 83 | frames.add_frame( 0 ); 84 | frames.add_frame( 0.5 ); 85 | frames.add_frame( 1 ); 86 | 87 | xmesh_timing::range_region rangeRegion; 88 | std::pair sampleFrames; 89 | double sampleOffset; 90 | 91 | timing.get_subframe_interpolation( 0.4, frames, rangeRegion, sampleFrames, sampleOffset ); 92 | 93 | EXPECT_EQ( xmesh_timing::RANGE_INSIDE, rangeRegion ); 94 | EXPECT_EQ( std::make_pair( 0.0, 0.5 ), sampleFrames ); 95 | EXPECT_FLOAT_EQ( 0.8f, static_cast( sampleOffset ) ); 96 | } 97 | -------------------------------------------------------------------------------- /UnitTests/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "stdafx.h" 4 | -------------------------------------------------------------------------------- /UnitTests/stdafx.h: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from __future__ import annotations 4 | from typing import Any 5 | from cpt.packager import ConanMultiPackager 6 | 7 | import argparse 8 | import platform 9 | import pprint 10 | 11 | 12 | COMMON_PACKAGER_ARGS: dict[str, Any] = { 13 | 'build_types': ['Release'], 14 | 'archs': ['x86_64'], 15 | 'build_policy': 'missing' 16 | } 17 | 18 | WINDOWS_PACKAGER_ARGS: dict[str, Any] = { 19 | 'visual_versions': ['15', '16'], 20 | 'visual_runtimes': ['MD'] 21 | } 22 | 23 | LINUX_PACKAGER_ARGS: dict[str, Any] = { 24 | 'gcc_versions': ['7'] 25 | } 26 | 27 | MACOS_PACKAGER_ARGS: dict[str, Any] = { 28 | 'apple_clang_versions': ['10.0', '10.2'] 29 | } 30 | 31 | def parse_arguments() -> argparse.Namespace: 32 | parser = argparse.ArgumentParser() 33 | parser.add_argument('-u', '--username', default=None, help='The Conan username to use for the built package.') 34 | parser.add_argument('-c', '--channel', default=None, help='The Conan channel to use for the built package.') 35 | parser.add_argument('-o', '--option', action='append', dest='options', help='Specify package options to be used by the build.') 36 | parser.add_argument('--dry-run', action='store_true', help='Print the configurations that would be built without actually building them.') 37 | return parser.parse_args() 38 | 39 | def main() -> None: 40 | args = parse_arguments() 41 | 42 | packager_args = { 43 | 'username': args.username, 44 | 'channel': args.channel, 45 | 'options': args.options 46 | } 47 | packager_args.update(COMMON_PACKAGER_ARGS) 48 | 49 | if platform.system() == 'Windows': 50 | packager_args.update(WINDOWS_PACKAGER_ARGS) 51 | elif platform.system() == 'Linux': 52 | packager_args.update(LINUX_PACKAGER_ARGS) 53 | elif platform.system() == 'Darwin': 54 | packager_args.update(MACOS_PACKAGER_ARGS) 55 | else: 56 | raise Exception('Platform not supported.') 57 | 58 | builder = ConanMultiPackager(**packager_args) 59 | builder.add_common_builds(pure_c=False) 60 | 61 | # Remove the legacy libstdc++ build. 62 | if platform.system() == 'Linux': 63 | builder.remove_build_if(lambda build: build.settings['compiler.libcxx'] == 'libstdc++') 64 | 65 | if args.dry_run: 66 | pprint.pprint(builder.builds, indent=4) 67 | else: 68 | builder.run() 69 | 70 | 71 | if __name__ == '__main__': 72 | main() 73 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import os 4 | from conans import ConanFile, CMake 5 | 6 | SETTINGS: list[str] = [ 7 | 'os', 8 | 'compiler', 9 | 'build_type', 10 | 'arch' 11 | ] 12 | 13 | TOOL_REQUIRES: list[str]= [ 14 | 'cmake/3.24.1', 15 | 'thinkboxcmlibrary/1.0.0' 16 | ] 17 | 18 | REQUIRES: list[str] = [ 19 | 'thinkboxlibrary/1.0.0', 20 | 'tinyxml2/9.0.0' 21 | ] 22 | 23 | class XMeshCoreConan(ConanFile): 24 | name: str = 'xmeshcore' 25 | version: str = '1.0.0' 26 | license: str = 'Apache-2.0' 27 | description: str = 'Shared XMesh code.' 28 | settings: list[str] = SETTINGS 29 | requires: list[str] = REQUIRES 30 | tool_requires: list[str] = TOOL_REQUIRES 31 | generators: str | list[str] = 'cmake_find_package' 32 | 33 | def build(self) -> None: 34 | cmake = CMake(self) 35 | cmake.configure() 36 | cmake.build() 37 | 38 | def imports(self) -> None: 39 | # Copy tbb DLLs to the UnitTests binary directory 40 | self.copy('*.dll', dst='UnitTests/Release', src='bin') 41 | 42 | def export_sources(self) -> None: 43 | self.copy('**.h', src='', dst='') 44 | self.copy('**.hpp', src='', dst='') 45 | self.copy('**.cpp', src='', dst='') 46 | self.copy('**.cc', src='', dst='') 47 | self.copy('CMakeLists.txt', src='', dst='') 48 | self.copy('UnitTests/CMakeLists.txt', src='', dst='') 49 | self.copy('NOTICE.txt', src='', dst='') 50 | self.copy('LICENSE.txt', src='', dst='') 51 | self.copy('THIRD-PARTY-LICENSES', src='', dst='') 52 | 53 | def package(self) -> None: 54 | cmake = CMake(self) 55 | cmake.install() 56 | 57 | with open(os.path.join(self.source_folder, 'NOTICE.txt'), 'r', encoding='utf8') as notice_file: 58 | notice_contents = notice_file.readlines() 59 | with open(os.path.join(self.source_folder, 'LICENSE.txt'), 'r', encoding='utf8') as license_file: 60 | license_contents = license_file.readlines() 61 | with open(os.path.join(self.source_folder, 'THIRD-PARTY-LICENSES'), 'r', encoding='utf8') as third_party_file: 62 | third_party_contents = third_party_file.readlines() 63 | os.makedirs(os.path.join(self.package_folder, 'licenses'), exist_ok=True) 64 | with open(os.path.join(self.package_folder, 'licenses', 'LICENSE'), 'w', encoding='utf8') as cat_license_file: 65 | cat_license_file.writelines(notice_contents) 66 | cat_license_file.writelines(license_contents) 67 | cat_license_file.writelines(third_party_contents) 68 | 69 | def deploy(self) -> None: 70 | self.copy("*", dst="lib", src="lib") 71 | self.copy("*", dst="include", src="include") 72 | 73 | def package_info(self) -> None: 74 | self.cpp_info.libs = ["xmeshcore"] 75 | -------------------------------------------------------------------------------- /src/xmesh/cached_polymesh3_loader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "stdafx.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifndef FRANTIC_DISABLE_THREADS 15 | #ifdef TBB_AVAILABLE 16 | #include 17 | #include 18 | #include 19 | #include 20 | #else 21 | #include 22 | #include 23 | #endif 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | using namespace frantic::geometry; 34 | using namespace xmesh; 35 | 36 | #ifdef FRANTIC_POLYMESH_COPY_ON_WRITE 37 | 38 | namespace { 39 | 40 | #if !defined( FRANTIC_DISABLE_THREADS ) && !defined( TBB_AVAILABLE ) 41 | 42 | #pragma message( "Using boost threads" ) 43 | 44 | // A simple thread pool for saving xmesh channels in parallel. 45 | class thread_pool { 46 | boost::asio::io_service m_service; 47 | boost::shared_ptr m_work; 48 | boost::thread_group m_threads; 49 | boost::thread* m_thread; 50 | 51 | public: 52 | thread_pool( std::size_t threadCount ) 53 | : m_service( std::max( threadCount, 1 ) ) 54 | , m_work( new boost::asio::io_service::work( m_service ) ) { 55 | threadCount = std::max( threadCount, 1 ); 56 | for( std::size_t i = 0; i < threadCount; ++i ) { 57 | m_thread = m_threads.create_thread( boost::bind( &boost::asio::io_service::run, &m_service ) ); 58 | } 59 | } 60 | 61 | ~thread_pool() { 62 | m_work.reset(); 63 | m_threads.join_all(); 64 | } 65 | 66 | template 67 | void schedule( F task ) { 68 | m_service.post( task ); 69 | } 70 | }; 71 | 72 | #elif !defined( FRANTIC_DISABLE_THREADS ) && defined( TBB_AVAILABLE ) 73 | 74 | #pragma message( "Using TBB threads" ) 75 | 76 | class enumerate_functions : public tbb::filter { 77 | public: 78 | enumerate_functions( std::vector>& functions ) 79 | : tbb::filter( tbb::filter::serial ) 80 | , m_index( 0 ) 81 | , m_functions( functions ) {} 82 | 83 | void* operator()( void* ) { 84 | if( m_index == m_functions.size() ) { 85 | return 0; 86 | } 87 | 88 | void* result = &m_functions[m_index]; 89 | ++m_index; 90 | return result; 91 | } 92 | 93 | private: 94 | std::size_t m_index; 95 | std::vector>& m_functions; 96 | }; 97 | 98 | class invoke_functions : public tbb::filter { 99 | public: 100 | invoke_functions() 101 | : tbb::filter( tbb::filter::parallel ) {} 102 | 103 | void* operator()( void* item ) { 104 | if( item ) { 105 | boost::function* f = reinterpret_cast*>( item ); 106 | ( *f )(); 107 | } 108 | return 0; 109 | } 110 | }; 111 | 112 | #else 113 | 114 | #pragma message( "Threads are disabled" ) 115 | 116 | class thread_pool { 117 | public: 118 | thread_pool( std::size_t /*threadCount*/ ) {} 119 | 120 | template 121 | void schedule( F task ) { 122 | task(); 123 | } 124 | }; 125 | 126 | #endif 127 | 128 | void parallel_invoke_functions( std::vector>& functions, std::size_t threadCount ) { 129 | #if !defined( FRANTIC_DISABLE_THREADS ) && defined( TBB_AVAILABLE ) 130 | tbb::task_scheduler_init taskScheduler; 131 | 132 | tbb::pipeline pipeline; 133 | 134 | enumerate_functions enumerateFunctions( functions ); 135 | invoke_functions invokeFunctions; 136 | 137 | pipeline.add_filter( enumerateFunctions ); 138 | pipeline.add_filter( invokeFunctions ); 139 | 140 | pipeline.run( std::max( 1, static_cast( threadCount ) ) ); 141 | #else 142 | thread_pool threadpool( std::max( 1, static_cast( threadCount ) ) ); 143 | 144 | BOOST_FOREACH( boost::function& f, functions ) { 145 | threadpool.schedule( f ); 146 | } 147 | #endif 148 | } 149 | 150 | } // anonymous namespace 151 | 152 | cached_polymesh3_loader::cached_polymesh3_loader() { 153 | // const int maxThreads = 4; 154 | // set_thread_count( std::max( 1, static_cast( std::min( maxThreads, 155 | // boost::thread::hardware_concurrency() ) ) ) ); 156 | set_thread_count( 1 ); 157 | } 158 | 159 | typedef frantic::threads::SynchronizedQueue error_queue_t; 160 | 161 | class xmesh_consistency_error : public std::runtime_error { 162 | public: 163 | xmesh_consistency_error( const std::string& message ) 164 | : std::runtime_error( message ) {} 165 | }; 166 | 167 | static void load_vertex_channel( const xmesh_reader& reader, const frantic::tstring& name, char* pData, 168 | frantic::channels::data_type_t expectedType, std::size_t expectedArity, 169 | std::size_t expectedCount, error_queue_t& errorQueue ) { 170 | std::string errMsg; 171 | try { 172 | reader.load_vertex_channel( name, pData, expectedType, expectedArity, expectedCount ); 173 | } catch( std::exception& e ) { 174 | errMsg = e.what(); 175 | } catch( ... ) { 176 | errMsg = "load_vertex_channel Error: An unknown exception occurred."; 177 | } 178 | if( !errMsg.empty() ) { 179 | errorQueue.enter( errMsg ); 180 | } 181 | } 182 | 183 | static void load_face_channel( const xmesh_reader& reader, const frantic::tstring& name, char* pData, 184 | frantic::channels::data_type_t expectedType, std::size_t expectedArity, 185 | std::size_t expectedCount, error_queue_t& errorQueue ) { 186 | std::string errMsg; 187 | try { 188 | reader.load_face_channel( name, pData, expectedType, expectedArity, expectedCount ); 189 | } catch( std::exception& e ) { 190 | errMsg = e.what(); 191 | } catch( ... ) { 192 | errMsg = "load_face_channel Error: An unknown exception occurred."; 193 | } 194 | if( !errMsg.empty() ) { 195 | errorQueue.enter( errMsg ); 196 | } 197 | } 198 | 199 | static void load_vertex_channel_faces_cache( const xmesh_reader& reader, const frantic::tstring& name, 200 | polymesh3_cache_face_entry& cache, std::size_t expectedCount, 201 | error_queue_t& errorQueue ) { 202 | std::string errMsg; 203 | try { 204 | std::vector faceIndexBuffer( expectedCount ); 205 | if( expectedCount > 0 ) { 206 | reader.load_vertex_channel_faces( name, reinterpret_cast( &faceIndexBuffer[0] ), expectedCount ); 207 | } 208 | std::size_t numFaces = 0; 209 | std::vector facesEndIndexBuffer; 210 | facesEndIndexBuffer.resize( 0 ); 211 | facesEndIndexBuffer.reserve( 5000 ); 212 | int* geomFaceIndexBuffer = expectedCount > 0 ? &faceIndexBuffer[0] : 0; 213 | for( std::size_t i = 0; i < expectedCount; ++i ) { 214 | if( geomFaceIndexBuffer[i] < 0 ) { 215 | geomFaceIndexBuffer[i] = -geomFaceIndexBuffer[i] - 1; 216 | facesEndIndexBuffer.push_back( (int)( i + 1 ) ); 217 | ++numFaces; 218 | } 219 | } 220 | polymesh3_channel_faces channelFaces( faceIndexBuffer ); 221 | polymesh3_channel_faces channelFacesEnd( facesEndIndexBuffer ); 222 | cache.faces = channelFaces; 223 | cache.facesEnd = channelFacesEnd; 224 | cache.numFaces = numFaces; 225 | } catch( std::exception& e ) { 226 | errMsg = e.what(); 227 | } catch( ... ) { 228 | errMsg = "load_vertex_channel_faces_cache Error: An unknown exception occurred."; 229 | } 230 | if( !errMsg.empty() ) { 231 | errorQueue.enter( errMsg ); 232 | } 233 | } 234 | 235 | static void assert_expected_cache_data( const polymesh3_cache& cache, 236 | const boost::filesystem::path::string_type& filename, 237 | frantic::channels::data_type_t expectedType, std::size_t expectedArity, 238 | std::size_t expectedCount ) { 239 | polymesh3_cache::data_cache_t::const_iterator it = cache.data.find( filename ); 240 | if( it == cache.data.end() ) { 241 | throw std::runtime_error( "assert_expected_cache_data: Missing cache entry for file \"" + 242 | frantic::strings::to_string( filename ) + "\"" ); 243 | } 244 | const polymesh3_cache_data_entry& cacheEntry = it->second; 245 | if( !cacheEntry.data.is_valid() ) { 246 | throw std::runtime_error( "assert_expected_cache_data: Cache entry for file \"" + 247 | frantic::strings::to_string( filename ) + "\" is NULL" ); 248 | } 249 | if( cacheEntry.data.type() != expectedType ) { 250 | throw xmesh_consistency_error( 251 | "assert_expected_cache_data: File \"" + frantic::strings::to_string( filename ) + 252 | "\" has the wrong data type. Expected " + 253 | frantic::strings::to_string( frantic::channels::channel_data_type_str( expectedType ) ) + " but got " + 254 | frantic::strings::to_string( frantic::channels::channel_data_type_str( cacheEntry.data.type() ) ) + 255 | " instead." ); 256 | } 257 | if( cacheEntry.data.arity() != expectedArity ) { 258 | throw xmesh_consistency_error( "assert_expected_cache_data: File \"" + frantic::strings::to_string( filename ) + 259 | "\" has the wrong arity. Expected " + 260 | boost::lexical_cast( expectedArity ) + " but got " + 261 | boost::lexical_cast( cacheEntry.data.arity() ) + " instead." ); 262 | } 263 | const std::size_t primitiveSize = 264 | frantic::channels::sizeof_channel_data_type( cacheEntry.data.type() ) * cacheEntry.data.arity(); 265 | if( primitiveSize == 0 ) { 266 | throw xmesh_consistency_error( "assert_expected_cache_data: File \"" + frantic::strings::to_string( filename ) + 267 | "\" has primitive size 0." ); 268 | } 269 | if( cacheEntry.data.element_size() == 0 ) { 270 | throw xmesh_consistency_error( "assert_expected_cache_data: File \"" + frantic::strings::to_string( filename ) + 271 | "\" has element size 0." ); 272 | } 273 | const std::size_t count = cacheEntry.data.get().size() / primitiveSize; 274 | if( count != expectedCount ) { 275 | throw xmesh_consistency_error( "assert_expected_cache_data: File \"" + frantic::strings::to_string( filename ) + 276 | "\" has the data count. Expected " + 277 | boost::lexical_cast( expectedCount ) + " but got " + 278 | boost::lexical_cast( count ) + " instead." ); 279 | } 280 | } 281 | 282 | static void assert_expected_cache_faces( const polymesh3_cache& cache, 283 | const boost::filesystem::path::string_type& filename, 284 | std::size_t expectedCount ) { 285 | polymesh3_cache::faces_cache_t::const_iterator it = cache.faces.find( filename ); 286 | if( it == cache.faces.end() ) { 287 | throw std::runtime_error( "assert_expected_cache_faces: Missing faces cache entry for file \"" + 288 | frantic::strings::to_string( filename ) + "\"" ); 289 | } 290 | const polymesh3_cache_face_entry& cacheEntry = it->second; 291 | if( !cacheEntry.faces.is_valid() ) { 292 | throw std::runtime_error( "assert_expected_cache_faces: Faces cache entry for file \"" + 293 | frantic::strings::to_string( filename ) + "\" is NULL" ); 294 | } 295 | if( !cacheEntry.facesEnd.is_valid() ) { 296 | throw std::runtime_error( "assert_expected_cache_faces: Faces end cache entry for file \"" + 297 | frantic::strings::to_string( filename ) + "\" is NULL" ); 298 | } 299 | if( cacheEntry.faces.get().size() != expectedCount ) { 300 | throw xmesh_consistency_error( 301 | "assert_expected_cache_faces: File \"" + frantic::strings::to_string( filename ) + 302 | "\" has incorrect data count. Expected " + boost::lexical_cast( expectedCount ) + 303 | " but got " + boost::lexical_cast( cacheEntry.faces.get().size() ) + " instead." ); 304 | } 305 | } 306 | 307 | static void get_keep_channel_refs( xmesh_reader& reader, polymesh3_cache& cache, 308 | const std::vector& vertexChannelNames, bool loadVertexChannelFaces, 309 | const std::vector& faceChannelNames, 310 | std::vector& keepData, 311 | std::vector& keepFaces ) { 312 | BOOST_FOREACH( const frantic::tstring& vertexChannelName, vertexChannelNames ) { 313 | const xmesh_vertex_channel& ch = reader.get_vertex_channel( vertexChannelName ); 314 | std::size_t numChannelFaceElements = ch.get_face_count(); 315 | 316 | { 317 | polymesh3_cache::data_cache_t::iterator i = cache.data.find( ch.get_vertex_file_path().native() ); 318 | if( i != cache.data.end() ) { 319 | keepData.push_back( i->second.data ); 320 | } 321 | } 322 | 323 | if( loadVertexChannelFaces && numChannelFaceElements > 0 ) { 324 | polymesh3_cache::faces_cache_t::iterator i = cache.faces.find( ch.get_face_file_path().native() ); 325 | if( i != cache.faces.end() ) { 326 | keepFaces.push_back( i->second.faces ); 327 | } 328 | } 329 | } 330 | 331 | BOOST_FOREACH( const frantic::tstring& faceChannelName, faceChannelNames ) { 332 | const xmesh_face_channel& ch = reader.get_face_channel( faceChannelName ); 333 | 334 | polymesh3_cache::data_cache_t::iterator i = cache.data.find( ch.get_face_file_path().native() ); 335 | if( i != cache.data.end() ) { 336 | keepData.push_back( i->second.data ); 337 | } 338 | } 339 | } 340 | 341 | void load_missing_files( const frantic::tstring& filenameForErrorMessage, xmesh_reader& reader, 342 | const polymesh3_cache& cache, const std::vector& vertexChannelNames, 343 | bool loadVertexChannelFaces, const std::vector& faceChannelNames, 344 | std::size_t threadCount, 345 | std::map& missingData, 346 | std::map& missingFaces ) { 347 | using namespace frantic::channels; 348 | using boost::tie; 349 | 350 | typedef std::map missing_data_t; 351 | typedef std::map missing_faces_t; 352 | 353 | const xmesh_vertex_channel& geomCh = reader.get_vertex_channel( _T("verts") ); 354 | const std::size_t numVerts = geomCh.get_vertex_count(); 355 | const std::size_t numFaceElements = geomCh.get_face_count(); 356 | 357 | error_queue_t errorQueue; 358 | { // scope for loadFunctions 359 | std::vector> loadFunctions; 360 | 361 | bool didInsert = false; 362 | 363 | BOOST_FOREACH( const frantic::tstring& vertexChannelName, vertexChannelNames ) { 364 | const xmesh_vertex_channel& ch = reader.get_vertex_channel( vertexChannelName ); 365 | const std::size_t numChannelVerts = ch.get_vertex_count(); 366 | const std::size_t numChannelFaceElements = ch.get_face_count(); 367 | const std::pair typeInfo = ch.get_vertex_type(); 368 | 369 | if( numChannelFaceElements > 0 ) { 370 | if( numChannelFaceElements != numFaceElements ) 371 | throw std::runtime_error( 372 | "load_polymesh_file() The channel \"" + frantic::strings::to_string( vertexChannelName ) + 373 | "\" did not have the same custom face layout as the geometry channel in file \"" + 374 | frantic::strings::to_string( filenameForErrorMessage ) + "\"" ); 375 | } else { 376 | if( numChannelVerts != numVerts ) 377 | throw std::runtime_error( 378 | "load_polymesh_file() The channel \"" + frantic::strings::to_string( vertexChannelName ) + 379 | "\" did not have the same vertex layout as the geometry channel in file \"" + 380 | frantic::strings::to_string( filenameForErrorMessage ) + "\"" ); 381 | } 382 | 383 | { // scope for data 384 | polymesh3_cache::data_cache_t::const_iterator j = cache.data.find( ch.get_vertex_file_path().native() ); 385 | if( j == cache.data.end() ) { 386 | missing_data_t::iterator i = missingData.find( ch.get_vertex_file_path().native() ); 387 | if( i == missingData.end() ) { 388 | { 389 | frantic::graphics::raw_byte_buffer buffer; 390 | buffer.resize( frantic::channels::sizeof_channel_data_type( typeInfo.first ) * 391 | typeInfo.second * numChannelVerts ); 392 | polymesh3_channel_data channelData( buffer, typeInfo.first, typeInfo.second ); 393 | boost::tuples::tie( i, didInsert ) = 394 | missingData.insert( polymesh3_cache::data_cache_t::value_type( 395 | ch.get_vertex_file_path().native(), channelData ) ); 396 | } 397 | loadFunctions.push_back( 398 | boost::bind( load_vertex_channel, boost::ref( reader ), vertexChannelName, 399 | i->second.data.get_writable().ptr_at( 0 ), typeInfo.first, typeInfo.second, 400 | numChannelVerts, boost::ref( errorQueue ) ) ); 401 | } 402 | } 403 | } 404 | 405 | if( loadVertexChannelFaces && numChannelFaceElements > 0 ) { // scope for faces 406 | polymesh3_cache::faces_cache_t::const_iterator j = cache.faces.find( ch.get_face_file_path().native() ); 407 | if( j == cache.faces.end() ) { 408 | missing_faces_t::iterator i = missingFaces.find( ch.get_face_file_path().native() ); 409 | if( i == missingFaces.end() ) { 410 | std::vector buffer( numChannelFaceElements ); 411 | boost::tuples::tie( i, didInsert ) = 412 | missingFaces.insert( polymesh3_cache::faces_cache_t::value_type( 413 | ch.get_face_file_path().native(), polymesh3_cache_face_entry( buffer ) ) ); 414 | loadFunctions.push_back( boost::bind( load_vertex_channel_faces_cache, boost::ref( reader ), 415 | vertexChannelName, boost::ref( i->second ), 416 | numChannelFaceElements, boost::ref( errorQueue ) ) ); 417 | } 418 | } 419 | } 420 | } 421 | 422 | BOOST_FOREACH( const frantic::tstring& faceChannelName, faceChannelNames ) { 423 | const xmesh_face_channel& ch = reader.get_face_channel( faceChannelName ); 424 | const std::size_t numChannelFaces = ch.get_face_count(); 425 | const std::pair typeInfo = ch.get_face_type(); 426 | 427 | polymesh3_cache::data_cache_t::const_iterator j = cache.data.find( ch.get_face_file_path().native() ); 428 | if( j == cache.data.end() ) { 429 | missing_data_t::iterator i = missingData.find( ch.get_face_file_path().native() ); 430 | if( i == missingData.end() ) { 431 | { 432 | frantic::graphics::raw_byte_buffer buffer; 433 | buffer.resize( frantic::channels::sizeof_channel_data_type( typeInfo.first ) * typeInfo.second * 434 | numChannelFaces ); 435 | polymesh3_channel_data channelData( buffer, typeInfo.first, typeInfo.second ); 436 | boost::tuples::tie( i, didInsert ) = 437 | missingData.insert( polymesh3_cache::data_cache_t::value_type( 438 | ch.get_face_file_path().native(), channelData ) ); 439 | } 440 | if( !didInsert ) { 441 | throw std::runtime_error( "Internal error: no insert performed for new cache entry" ); 442 | } 443 | loadFunctions.push_back( boost::bind( load_face_channel, boost::ref( reader ), faceChannelName, 444 | i->second.data.get_writable().ptr_at( 0 ), typeInfo.first, 445 | typeInfo.second, numChannelFaces, 446 | boost::ref( errorQueue ) ) ); 447 | } 448 | } 449 | } 450 | 451 | parallel_invoke_functions( loadFunctions, threadCount ); 452 | } 453 | 454 | // Check for thread errors before we proceed 455 | if( errorQueue.size() ) { 456 | std::string errMsg; 457 | bool success = errorQueue.leave( errMsg ); 458 | if( success ) { 459 | throw std::runtime_error( errMsg ); 460 | } else { 461 | throw std::runtime_error( "An unknown error occurred while attempting to retrieve worker error message." ); 462 | } 463 | } 464 | } 465 | 466 | void cache_missing_files( const frantic::tstring& filenameForErrorMessage, xmesh_reader& reader, polymesh3_cache& cache, 467 | const std::vector& vertexChannels, bool loadVertexChannelFaces, 468 | const std::vector& faceChannels, std::size_t threadCount ) { 469 | typedef std::map missing_data_t; 470 | missing_data_t missingData; 471 | typedef std::map missing_faces_t; 472 | missing_faces_t missingFaces; 473 | 474 | load_missing_files( filenameForErrorMessage, reader, cache, vertexChannels, loadVertexChannelFaces, faceChannels, 475 | threadCount, missingData, missingFaces ); 476 | 477 | // insert after loading is finished, 478 | // so we don't insert if an exception occurred during load 479 | for( missing_data_t::iterator i = missingData.begin(); i != missingData.end(); ++i ) { 480 | cache.data[i->first] = i->second; 481 | } 482 | for( missing_faces_t::iterator i = missingFaces.begin(); i != missingFaces.end(); ++i ) { 483 | cache.faces[i->first] = i->second; 484 | } 485 | } 486 | 487 | int cached_polymesh3_loader::find_xmesh_cache_entry_index( const boost::filesystem::path::string_type& filename ) { 488 | for( std::size_t i = 0; i < m_xmeshCache.size(); ++i ) { 489 | if( m_xmeshCache[i] && m_xmeshCache[i]->filename == filename ) { 490 | return static_cast( i ); 491 | } 492 | } 493 | return -1; 494 | } 495 | 496 | xmesh_cache_entry& 497 | cached_polymesh3_loader::get_xmesh_cache_entry( const boost::filesystem::path::string_type& filename ) { 498 | int fileIndexOrNegative = find_xmesh_cache_entry_index( filename ); 499 | 500 | std::size_t fileIndex; 501 | if( fileIndexOrNegative >= 0 ) { 502 | fileIndex = static_cast( fileIndexOrNegative ); 503 | } else { 504 | // create a cache entry 505 | boost::shared_ptr cacheEntry( 506 | new xmesh_cache_entry( boost::filesystem::path( filename ).native() ) ); 507 | 508 | cacheEntry->filename = filename; 509 | xmesh_metadata metadata; 510 | read_xmesh_metadata( filename, cacheEntry->metadata ); 511 | 512 | // replace the last entry with the new entry 513 | fileIndex = m_xmeshCache.size() - 1; 514 | m_xmeshCache[fileIndex] = cacheEntry; 515 | } 516 | 517 | // move the cache entry to the top of the cache 518 | for( std::size_t i = fileIndex; i > 0; --i ) { 519 | m_xmeshCache[i].swap( m_xmeshCache[i - 1] ); 520 | } 521 | 522 | return *( m_xmeshCache[0] ); 523 | } 524 | 525 | frantic::geometry::const_polymesh3_ptr 526 | cached_polymesh3_loader::build_polymesh3_from_cache( xmesh_reader& reader, polymesh3_cache& cache, 527 | std::vector& vertexChannelNames, 528 | std::vector& faceChannelNames, bool loadVerts, 529 | bool loadFaces, const frantic::tstring& filename ) { 530 | // build the polymesh 531 | const xmesh_vertex_channel& geomCh = reader.get_vertex_channel( _T("verts") ); 532 | 533 | const std::size_t numVerts = loadVerts ? geomCh.get_vertex_count() : 0; 534 | const std::size_t numFaceElements = loadFaces ? geomCh.get_face_count() : 0; 535 | const std::size_t numPolygons = loadFaces ? cache.faces[geomCh.get_face_file_path().native()].numFaces : 0; 536 | 537 | polymesh3_channel_data geomVertData; 538 | if( numVerts > 0 ) { 539 | geomVertData = cache.data[geomCh.get_vertex_file_path().native()].data; 540 | } else { 541 | frantic::graphics::raw_byte_buffer buffer; 542 | geomVertData = polymesh3_channel_data( buffer, frantic::channels::data_type_float32, 3 ); 543 | } 544 | polymesh3_channel_faces geomFacesData = 545 | ( numFaceElements > 0 ) ? cache.faces[geomCh.get_face_file_path().native()].faces : polymesh3_channel_faces(); 546 | polymesh3_channel_faces geomFacesEndData = ( numFaceElements > 0 ) 547 | ? cache.faces[geomCh.get_face_file_path().native()].facesEnd 548 | : polymesh3_channel_faces(); 549 | 550 | const_shared_polymesh3_builder builder( geomVertData, geomFacesData, geomFacesEndData ); 551 | 552 | const_polymesh3_ptr result = builder.finalize(); 553 | polymesh3_const_vertex_accessor geomAcc = 554 | result->get_const_vertex_accessor( _T("verts") ); 555 | 556 | // check the polymesh channels and add them to the polymesh 557 | BOOST_FOREACH( const frantic::tstring& vertexChannelName, vertexChannelNames ) { 558 | if( vertexChannelName == _T("verts") ) { 559 | continue; 560 | } 561 | const xmesh_vertex_channel& ch = reader.get_vertex_channel( vertexChannelName ); 562 | const std::size_t numChannelVerts = ch.get_vertex_count(); 563 | const std::size_t numChannelFaceElements = ch.get_face_count(); 564 | const std::pair typeInfo = ch.get_vertex_type(); 565 | 566 | assert_expected_cache_data( cache, ch.get_vertex_file_path().native(), typeInfo.first, typeInfo.second, 567 | numChannelVerts ); 568 | 569 | if( loadFaces && numChannelFaceElements > 0 ) { 570 | if( numChannelFaceElements != numFaceElements ) { 571 | throw xmesh_consistency_error( 572 | "load_polymesh3_file() The channel \"" + frantic::strings::to_string( vertexChannelName ) + 573 | "\" had mismatched polygon index count compared to the geometry channel in file \"" + 574 | frantic::strings::to_string( filename ) + "\"" ); 575 | } 576 | 577 | assert_expected_cache_faces( cache, ch.get_face_file_path().native(), numChannelFaceElements ); 578 | 579 | // const std::vector & faceBuffer = cache.faces[ch.get_face_file_path()].faces.get(); 580 | const std::vector& faceEndBuffer = cache.faces[ch.get_face_file_path().native()].facesEnd.get(); 581 | 582 | // const int * channelFaceIndexBuffer = &faceBuffer[0]; 583 | for( std::size_t curPoly = 0; curPoly < numPolygons; ++curPoly ) { 584 | std::size_t curPolySize = 585 | ( curPoly == 0 ) ? faceEndBuffer[0] : ( faceEndBuffer[curPoly] - faceEndBuffer[curPoly - 1] ); 586 | if( curPolySize != geomAcc.get_face_degree( curPoly ) ) 587 | throw xmesh_consistency_error( 588 | "load_polymesh_file() The channel \"" + frantic::strings::to_string( vertexChannelName ) + 589 | "\" had mismatched polygon sizes compared to the geometry channel in file \"" + 590 | frantic::strings::to_string( filename ) + "\"" ); 591 | } 592 | 593 | polymesh3_cache_data_entry& cacheDataEntry = cache.data[ch.get_vertex_file_path().native()]; 594 | builder.add_vertex_channel( vertexChannelName, cacheDataEntry.data, 595 | &cache.faces[ch.get_face_file_path().native()].faces ); 596 | } else { 597 | polymesh3_cache_data_entry& cacheDataEntry = cache.data[ch.get_vertex_file_path().native()]; 598 | builder.add_vertex_channel( vertexChannelName, cacheDataEntry.data ); 599 | } 600 | } 601 | 602 | BOOST_FOREACH( const frantic::tstring& faceChannelName, faceChannelNames ) { 603 | const xmesh_face_channel& ch = reader.get_face_channel( faceChannelName ); 604 | const std::size_t numChannelFaces = ch.get_face_count(); 605 | const std::pair typeInfo = ch.get_face_type(); 606 | 607 | assert_expected_cache_data( cache, ch.get_face_file_path().native(), typeInfo.first, typeInfo.second, 608 | numChannelFaces ); 609 | 610 | if( numChannelFaces != numPolygons ) { 611 | throw xmesh_consistency_error( 612 | "load_polymesh_file() The face channel \"" + frantic::strings::to_string( faceChannelName ) + 613 | "\" did not have one entry per-face in file \"" + frantic::strings::to_string( filename ) + "\"" ); 614 | } 615 | 616 | polymesh3_cache_data_entry& cacheDataEntry = cache.data[ch.get_face_file_path().native()]; 617 | builder.add_face_channel( faceChannelName, cacheDataEntry.data ); 618 | } 619 | 620 | return builder.finalize(); 621 | } 622 | 623 | frantic::geometry::const_polymesh3_ptr 624 | cached_polymesh3_loader::load_xmesh( const frantic::tstring& filename, xmesh_metadata* outMetadata, int loadMask ) { 625 | polymesh3_cache& cache = m_cache; 626 | 627 | xmesh_cache_entry& readerCache = 628 | get_xmesh_cache_entry( boost::filesystem::path( frantic::strings::to_wstring( filename ) ).native() ); 629 | 630 | xmesh_reader& reader = readerCache.reader; 631 | xmesh_metadata& metadata = readerCache.metadata; 632 | 633 | if( loadMask & LOAD_POLYMESH3_MASK::BOX ) { 634 | if( !metadata.has_boundbox() ) { 635 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 636 | } 637 | } 638 | 639 | if( loadMask & LOAD_POLYMESH3_MASK::FACES ) { 640 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 641 | } 642 | 643 | if( loadMask & LOAD_POLYMESH3_MASK::VELOCITY ) { 644 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 645 | } 646 | 647 | if( loadMask & LOAD_POLYMESH3_MASK::MAPS ) { 648 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 649 | loadMask |= LOAD_POLYMESH3_MASK::FACES; 650 | } 651 | 652 | const bool loadVerts = ( loadMask & LOAD_POLYMESH3_MASK::VERTS ) != 0; 653 | const bool loadFaces = ( loadMask & LOAD_POLYMESH3_MASK::FACES ) != 0; 654 | 655 | // Get vertex channels 656 | std::vector vertexChannelNames; 657 | // all of the channels in vertexChannelNames, plus sometimes Velocity 658 | // because I don't want it repeatedly loaded and unloaded while scrubbing 659 | std::vector keepVertexChannelNames; 660 | { 661 | std::vector allVertexChannelNames; 662 | reader.get_vertex_channel_names( allVertexChannelNames ); 663 | vertexChannelNames.reserve( allVertexChannelNames.size() + 1 ); 664 | keepVertexChannelNames.reserve( allVertexChannelNames.size() + 1 ); 665 | BOOST_FOREACH( const frantic::tstring& channelName, allVertexChannelNames ) { 666 | if( channelName == _T("Velocity") ) { 667 | if( loadMask & LOAD_POLYMESH3_MASK::VELOCITY ) { 668 | vertexChannelNames.push_back( channelName ); 669 | } else if( loadVerts ) { 670 | keepVertexChannelNames.push_back( channelName ); 671 | } 672 | } else if( loadMask & LOAD_POLYMESH3_MASK::MAPS ) { 673 | vertexChannelNames.push_back( channelName ); 674 | } 675 | } 676 | } 677 | if( loadVerts ) { 678 | vertexChannelNames.push_back( _T("verts") ); 679 | } 680 | keepVertexChannelNames.insert( keepVertexChannelNames.end(), vertexChannelNames.begin(), vertexChannelNames.end() ); 681 | 682 | // Get face channels 683 | std::vector faceChannelNames; 684 | if( loadMask & LOAD_POLYMESH3_MASK::FACES ) { 685 | reader.get_face_channel_names( faceChannelNames ); 686 | } 687 | std::vector keepFaceChannelNames( faceChannelNames ); 688 | 689 | // Grab a reference to all channels that we need for the current mesh 690 | // before we evict cache entries 691 | { // scope for channel references 692 | std::vector keepData; 693 | std::vector keepFaces; 694 | get_keep_channel_refs( reader, cache, keepVertexChannelNames, loadFaces, keepFaceChannelNames, keepData, 695 | keepFaces ); 696 | 697 | // Evict unreferenced cache entries 698 | // TODO: Keep unreferenced cache entries with some memory limit and eviction policy 699 | // TODO: Reuse memory allocated for evicted entries? 700 | std::vector evictData; 701 | for( polymesh3_cache::data_cache_t::iterator i = cache.data.begin(); i != cache.data.end(); ++i ) { 702 | if( !i->second.data.is_valid() ) { 703 | evictData.push_back( i->first ); 704 | } else if( !i->second.data.is_shared() ) { 705 | evictData.push_back( i->first ); 706 | } 707 | } 708 | BOOST_FOREACH( const boost::filesystem::path::string_type& channelFileName, evictData ) { 709 | cache.data.erase( channelFileName ); 710 | } 711 | std::vector evictFaces; 712 | for( polymesh3_cache::faces_cache_t::iterator i = cache.faces.begin(); i != cache.faces.end(); ++i ) { 713 | if( !i->second.faces.is_valid() || !i->second.facesEnd.is_valid() ) { 714 | evictFaces.push_back( i->first ); 715 | } else if( !i->second.faces.is_shared() ) { 716 | evictFaces.push_back( i->first ); 717 | } 718 | } 719 | BOOST_FOREACH( const boost::filesystem::path::string_type& channelFileName, evictFaces ) { 720 | cache.faces.erase( channelFileName ); 721 | } 722 | } 723 | 724 | cache_missing_files( filename, reader, cache, vertexChannelNames, loadFaces, faceChannelNames, m_threadCount ); 725 | 726 | frantic::geometry::const_polymesh3_ptr result = build_polymesh3_from_cache( 727 | reader, cache, vertexChannelNames, faceChannelNames, loadVerts, loadFaces, filename ); 728 | 729 | if( loadMask & LOAD_POLYMESH3_MASK::BOX ) { 730 | if( !metadata.has_boundbox() ) { 731 | frantic::graphics::boundbox3f bbox; 732 | if( result->has_vertex_channel( _T("verts") ) ) { 733 | frantic::geometry::polymesh3_const_vertex_accessor acc = 734 | result->get_const_vertex_accessor( _T("verts") ); 735 | for( std::size_t i = 0; i < acc.vertex_count(); ++i ) { 736 | bbox += acc.get_vertex( i ); 737 | } 738 | } 739 | metadata.set_boundbox( bbox ); 740 | } 741 | } 742 | 743 | if( outMetadata ) { 744 | *outMetadata = metadata; 745 | } 746 | 747 | return result; 748 | } 749 | 750 | frantic::geometry::const_polymesh3_ptr cached_polymesh3_loader::load( const frantic::tstring& filename, 751 | frantic::geometry::xmesh_metadata* outMetadata, 752 | int loadMask ) { 753 | using namespace frantic::channels; 754 | 755 | FF_LOG( debug ) << "Loading file: \"" << filename << "\"" << std::endl; 756 | 757 | boost::timer timer; 758 | 759 | frantic::geometry::const_polymesh3_ptr result; 760 | 761 | if( outMetadata ) { 762 | outMetadata->clear(); 763 | } 764 | 765 | const frantic::tstring type = frantic::strings::to_lower( frantic::files::extension_from_path( filename ) ); 766 | if( type == _T(".obj") ) { 767 | result = load_obj_polymesh_file( filename ); 768 | } else if( type == _T(".xmesh") ) { 769 | result = load_xmesh( filename, outMetadata, loadMask ); 770 | } else { 771 | throw std::runtime_error( 772 | "cached_polymesh3_loader::load() Didn't recognize the file format of the input mesh file \"" + 773 | frantic::strings::to_string( filename ) + "\"" ); 774 | } 775 | 776 | if( !result ) { 777 | throw std::runtime_error( "cached_polymesh3_loader::load() Result is NULL" ); 778 | } 779 | 780 | if( ( loadMask & LOAD_POLYMESH3_MASK::BOX ) && outMetadata && !outMetadata->has_boundbox() ) { 781 | frantic::graphics::boundbox3f bbox; 782 | if( result->has_vertex_channel( _T("verts") ) ) { 783 | frantic::geometry::polymesh3_const_vertex_accessor acc = 784 | result->get_const_vertex_accessor( _T("verts") ); 785 | for( std::size_t i = 0; i < acc.vertex_count(); ++i ) { 786 | bbox += acc.get_vertex( i ); 787 | } 788 | } 789 | outMetadata->set_boundbox( bbox ); 790 | } 791 | 792 | FF_LOG( debug ) << "Load time [s]: " << boost::basic_format( _T("%.3f") ) % timer.elapsed() 793 | << std::endl; 794 | 795 | return result; 796 | } 797 | 798 | void cached_polymesh3_loader::set_thread_count( std::size_t numThreads ) { 799 | if( numThreads >= 1 ) { 800 | m_threadCount = numThreads; 801 | } else { 802 | m_threadCount = 1; 803 | } 804 | } 805 | 806 | void cached_polymesh3_loader::clear_cache() { 807 | m_cache.data.clear(); 808 | m_cache.faces.clear(); 809 | for( std::size_t i = 0; i < m_xmeshCache.size(); ++i ) { 810 | m_xmeshCache[i].reset(); 811 | } 812 | } 813 | 814 | #else 815 | 816 | namespace { 817 | 818 | int fill_load_mask_dependencies( int loadMask ) { 819 | if( loadMask & LOAD_POLYMESH3_MASK::FACES ) { 820 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 821 | } 822 | 823 | if( loadMask & LOAD_POLYMESH3_MASK::VELOCITY ) { 824 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 825 | } 826 | 827 | if( loadMask & LOAD_POLYMESH3_MASK::MAPS ) { 828 | loadMask |= LOAD_POLYMESH3_MASK::VERTS; 829 | loadMask |= LOAD_POLYMESH3_MASK::FACES; 830 | } 831 | 832 | return loadMask; 833 | } 834 | 835 | bool get_load_faces( int loadMask ) { 836 | return ( fill_load_mask_dependencies( loadMask ) & LOAD_POLYMESH3_MASK::FACES ) != 0; 837 | } 838 | 839 | frantic::channels::channel_propagation_policy get_channel_propagation_policy( int loadMask ) { 840 | loadMask = fill_load_mask_dependencies( loadMask ); 841 | 842 | frantic::channels::channel_propagation_policy cpp; 843 | 844 | if( loadMask & LOAD_POLYMESH3_MASK::MAPS ) { 845 | cpp.set_to_exclude_policy(); 846 | if( ( loadMask & LOAD_POLYMESH3_MASK::VELOCITY ) == 0 ) { 847 | cpp.add_channel( _T("Velocity") ); 848 | } 849 | } else { 850 | cpp.set_to_include_policy(); 851 | if( loadMask & LOAD_POLYMESH3_MASK::VELOCITY ) { 852 | cpp.add_channel( _T("Velocity") ); 853 | } 854 | } 855 | 856 | return cpp; 857 | } 858 | 859 | frantic::geometry::polymesh3_ptr load_polymesh3( const frantic::tstring& filename, xmesh_metadata* outMetadata, 860 | int loadMask ) { 861 | frantic::geometry::polymesh3_ptr result; 862 | 863 | const frantic::tstring type = frantic::strings::to_lower( frantic::files::extension_from_path( filename ) ); 864 | if( type == _T(".obj") ) { 865 | result = load_obj_polymesh_file( filename ); 866 | if( outMetadata ) { 867 | outMetadata->clear(); 868 | } 869 | } else if( type == _T(".xmesh") ) { 870 | bool done = false; 871 | frantic::geometry::xmesh_reader reader( filename ); 872 | if( outMetadata ) { 873 | *outMetadata = reader.get_metadata(); 874 | } 875 | if( loadMask == LOAD_POLYMESH3_MASK::BOX && outMetadata && outMetadata->has_boundbox() ) { 876 | frantic::geometry::polymesh3_builder builder; 877 | result = builder.finalize(); 878 | done = true; 879 | } 880 | if( !done ) { 881 | result = frantic::geometry::load_xmesh_polymesh_file( reader, get_channel_propagation_policy( loadMask ), 882 | get_load_faces( loadMask ) ); 883 | } 884 | } else { 885 | throw std::runtime_error( "load_polymesh3: Didn't recognize the file format of the input mesh file \"" + 886 | frantic::strings::to_string( filename ) + "\"" ); 887 | } 888 | 889 | if( outMetadata && !outMetadata->has_boundbox() ) { 890 | frantic::graphics::boundbox3f bbox; 891 | if( result->has_vertex_channel( _T("verts") ) ) { 892 | frantic::geometry::polymesh3_const_vertex_accessor acc = 893 | result->get_const_vertex_accessor( _T("verts") ); 894 | for( std::size_t i = 0; i < acc.vertex_count(); ++i ) { 895 | bbox += acc.get_vertex( i ); 896 | } 897 | } 898 | outMetadata->set_boundbox( bbox ); 899 | } 900 | 901 | return result; 902 | } 903 | 904 | } // anonymous namespace 905 | 906 | cached_polymesh3_loader::cached_polymesh3_loader() 907 | : m_cachedMask( 0 ) {} 908 | 909 | frantic::geometry::const_polymesh3_ptr cached_polymesh3_loader::load( const frantic::tstring& filename, 910 | xmesh_metadata* outMetadata, int loadMask ) { 911 | frantic::geometry::polymesh3_ptr result; 912 | 913 | boost::unique_lock lock( m_cachedMeshMutex ); 914 | 915 | const int missingMask = ~m_cachedMask & loadMask; 916 | 917 | if( filename == m_cachedMeshFilename && missingMask == 0 ) { 918 | result = m_cachedMesh; 919 | } else { 920 | lock.unlock(); 921 | xmesh_metadata metadata; 922 | result = load_polymesh3( filename, &metadata, loadMask ); 923 | lock.lock(); 924 | m_cachedMesh = result; 925 | m_cachedMeshMetadata = metadata; 926 | m_cachedMeshFilename = filename; 927 | m_cachedMask = loadMask; 928 | } 929 | 930 | if( outMetadata ) { 931 | *outMetadata = m_cachedMeshMetadata; 932 | } 933 | 934 | return result; 935 | } 936 | 937 | void cached_polymesh3_loader::set_thread_count( std::size_t /*numThreads*/ ) {} 938 | 939 | void cached_polymesh3_loader::clear_cache() { 940 | boost::unique_lock lock( m_cachedMeshMutex ); 941 | m_cachedMeshFilename.clear(); 942 | m_cachedMesh.reset(); 943 | } 944 | 945 | #endif 946 | -------------------------------------------------------------------------------- /src/xmesh/xmesh_timing.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "stdafx.h" 4 | 5 | #ifndef _BOOL 6 | #define _BOOL 7 | #endif 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | using namespace xmesh; 15 | 16 | namespace { 17 | 18 | bool get_nearest_subframe( const frantic::files::frame_set& frameSet, double frame, double* pOutFrame, 19 | const bool useWholeFrames = false ) { 20 | std::pair range; 21 | float alpha; 22 | double outFrame; 23 | 24 | if( frameSet.empty() ) { 25 | return false; 26 | } 27 | 28 | if( useWholeFrames ) { 29 | if( frameSet.get_nearest_wholeframe_interval( frame, range, alpha ) ) { 30 | if( alpha < 0.5f ) { 31 | outFrame = range.first; 32 | } else { 33 | outFrame = range.second; 34 | } 35 | } else { 36 | return false; 37 | } 38 | } else { 39 | if( frameSet.frame_exists( frame ) ) { 40 | outFrame = frame; 41 | } else if( frameSet.get_nearest_subframe_interval( frame, range, alpha ) ) { 42 | if( alpha < 0.5f ) { 43 | outFrame = range.first; 44 | } else { 45 | outFrame = range.second; 46 | } 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | if( pOutFrame ) { 53 | *pOutFrame = outFrame; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | } // anonymous namespace 60 | 61 | xmesh_timing::xmesh_timing() 62 | : m_frameOffset( 0 ) 63 | , m_limitToRange( false ) 64 | , m_frameRange( 0, 0 ) {} 65 | 66 | xmesh_timing::~xmesh_timing() {} 67 | 68 | void xmesh_timing::set_offset( double frameOffset ) { m_frameOffset = frameOffset; } 69 | 70 | void xmesh_timing::set_range( double startFrame, double endFrame ) { 71 | if( !( startFrame <= endFrame ) ) { 72 | throw std::runtime_error( "set_range Error: startFrame must be less than or equal to endFrame" ); 73 | } 74 | m_limitToRange = true; 75 | m_frameRange = std::pair( startFrame, endFrame ); 76 | } 77 | 78 | void xmesh_timing::set_sequence_name( const frantic::tstring& sequenceName ) { m_sequenceName = sequenceName; } 79 | 80 | /* 81 | double xmesh_timing::evaluate_playback_graph( double frame ) const{ 82 | return frame; 83 | } 84 | */ 85 | 86 | void xmesh_timing::get_frame_velocity_offset( double frame, const frantic::files::frame_set& frameSet, 87 | range_region& rangeInterval, double& sampleFrame, 88 | double& sampleOffset ) const { 89 | xmesh_extrapolation_info info = get_extrapolation_info( frame, frameSet, true ); 90 | rangeInterval = info.rangeRegion; 91 | sampleFrame = info.frame; 92 | sampleOffset = info.offset; 93 | } 94 | 95 | void xmesh_timing::get_subframe_velocity_offset( double frame, const frantic::files::frame_set& frameSet, 96 | range_region& rangeInterval, double& sampleFrame, 97 | double& sampleOffset ) const { 98 | xmesh_extrapolation_info info = get_extrapolation_info( frame, frameSet, false ); 99 | rangeInterval = info.rangeRegion; 100 | sampleFrame = info.frame; 101 | sampleOffset = info.offset; 102 | } 103 | 104 | double xmesh_timing::get_time_derivative( double frame, double frameStep ) const { 105 | if( frameStep == 0 ) { 106 | throw std::runtime_error( "Frame step cannot be zero when computing time derivative." ); 107 | } 108 | 109 | double timeDerivative; 110 | 111 | if( m_limitToRange ) { 112 | const double evalFrame = get_timing_data_without_limit_to_range( frame ); 113 | const double evalWholeFrame = get_timing_data_without_limit_to_range( 114 | frantic::math::round( frame ) ); // Note: not necessarily a whole frame after retiming 115 | 116 | // To match get_extrapolation_info, make sure both evalFrame and evalWholeFrame in range. (Corresponds to 117 | // inFrame and inWholeFrame.) 118 | if( evalFrame >= m_frameRange.first && evalFrame <= m_frameRange.second && 119 | evalWholeFrame >= m_frameRange.first && evalWholeFrame <= m_frameRange.second ) { 120 | double intervalStart = get_timing_data_without_limit_to_range( frame - ( frameStep / 2 ) ); 121 | double intervalEnd = get_timing_data_without_limit_to_range( frame + ( frameStep / 2 ) ); 122 | timeDerivative = ( intervalEnd - intervalStart ) / frameStep; 123 | } else { 124 | timeDerivative = 0; 125 | } 126 | } else { 127 | double intervalStart = get_timing_data_without_limit_to_range( frame - ( frameStep / 2 ) ); 128 | double intervalEnd = get_timing_data_without_limit_to_range( frame + ( frameStep / 2 ) ); 129 | timeDerivative = ( intervalEnd - intervalStart ) / frameStep; 130 | } 131 | 132 | return timeDerivative; 133 | } 134 | 135 | void xmesh_timing::get_frame_interpolation( double frame, const frantic::files::frame_set& frameSet, 136 | range_region& rangeInterval, std::pair& sampleFrames, 137 | double& alpha ) const { 138 | xmesh_interpolation_info info = get_interpolation_info( frame, frameSet, false ); 139 | rangeInterval = info.rangeRegion; 140 | sampleFrames = info.frames; 141 | alpha = info.alpha; 142 | } 143 | 144 | void xmesh_timing::get_subframe_interpolation( double frame, const frantic::files::frame_set& frameSet, 145 | range_region& rangeInterval, std::pair& sampleFrames, 146 | double& alpha ) const { 147 | xmesh_interpolation_info info = get_interpolation_info( frame, frameSet, true ); 148 | rangeInterval = info.rangeRegion; 149 | sampleFrames = info.frames; 150 | alpha = info.alpha; 151 | } 152 | 153 | const frantic::tstring& xmesh_timing::get_sequence_name() const { return m_sequenceName; } 154 | 155 | xmesh_timing::xmesh_extrapolation_info xmesh_timing::get_extrapolation_info( double outFrame, 156 | const frantic::files::frame_set& frameSet, 157 | bool useConsistentSubframeSamples ) const { 158 | // "out" times correspond to times in the viewport or render, before the frameOffset etc. are applied 159 | // "in" times correspond to times in the frameSet, after frameOffset etc. are applied 160 | 161 | if( frameSet.empty() ) { 162 | throw std::runtime_error( "No files found in sequence: \"" + 163 | frantic::strings::to_string( get_sequence_name() ) + "\"" ); 164 | } 165 | 166 | const double outWholeFrame = frantic::math::round( outFrame ); 167 | 168 | const double inFrame = get_timing_data_without_limit_to_range( outFrame ); 169 | 170 | // The input frame corresponding to the outWholeFrame. 171 | // This is not necessarily a whole frame! 172 | const double inWholeFrame = get_timing_data_without_limit_to_range( outWholeFrame ); 173 | 174 | range_region rangeRegion = RANGE_INSIDE; 175 | 176 | // look for a time close to this in the frameSet 177 | double requestFrame = inFrame; 178 | if( m_limitToRange && inFrame < m_frameRange.first ) { 179 | if( inWholeFrame < m_frameRange.first ) { 180 | rangeRegion = RANGE_BEFORE; 181 | requestFrame = m_frameRange.first; 182 | } else { 183 | requestFrame = inWholeFrame; 184 | } 185 | } else if( m_limitToRange && inFrame > m_frameRange.second ) { 186 | if( inWholeFrame > m_frameRange.second ) { 187 | rangeRegion = RANGE_AFTER; 188 | requestFrame = m_frameRange.second; 189 | } else { 190 | requestFrame = inWholeFrame; 191 | } 192 | } else { 193 | if( useConsistentSubframeSamples ) { 194 | requestFrame = inWholeFrame; 195 | } else { 196 | requestFrame = inFrame; 197 | } 198 | } 199 | 200 | // a time close to requestFrame in the frameSet 201 | double inSampleFrame; 202 | bool foundSampleFrame = get_nearest_subframe( frameSet, requestFrame, &inSampleFrame ); 203 | if( !foundSampleFrame ) { 204 | throw std::runtime_error( 205 | "An appropriate frame to offset for time " + boost::lexical_cast( inWholeFrame ) + 206 | " could not be found in the sequence: \"" + frantic::strings::to_string( get_sequence_name() ) + "\"" ); 207 | } 208 | 209 | xmesh_timing::xmesh_extrapolation_info result; 210 | result.rangeRegion = rangeRegion; 211 | result.frame = inSampleFrame; 212 | result.offset = ( rangeRegion == RANGE_INSIDE ? ( inFrame - inSampleFrame ) : 0 ); 213 | return result; 214 | } 215 | 216 | xmesh_timing::xmesh_interpolation_info xmesh_timing::get_interpolation_info( double frame, 217 | const frantic::files::frame_set& frameSet, 218 | bool useSubframes ) const { 219 | if( frameSet.empty() ) { 220 | throw std::runtime_error( "No files found in sequence: \"" + 221 | frantic::strings::to_string( get_sequence_name() ) + "\"" ); 222 | } 223 | 224 | double inFrame = get_timing_data_without_limit_to_range( frame ); 225 | range_region rangeRegion = RANGE_INSIDE; 226 | 227 | if( m_limitToRange ) { 228 | if( inFrame < m_frameRange.first ) { 229 | rangeRegion = RANGE_BEFORE; 230 | } else if( inFrame > m_frameRange.second ) { 231 | rangeRegion = RANGE_AFTER; 232 | } 233 | inFrame = frantic::math::clamp( inFrame, m_frameRange.first, m_frameRange.second ); 234 | } 235 | 236 | std::pair frameBracket; 237 | float alpha; 238 | bool foundFrames; 239 | if( useSubframes ) { 240 | foundFrames = frameSet.get_nearest_subframe_interval( inFrame, frameBracket, alpha ); 241 | } else { 242 | foundFrames = frameSet.get_nearest_wholeframe_interval( inFrame, frameBracket, alpha ); 243 | } 244 | if( !foundFrames ) { 245 | throw std::runtime_error( 246 | "Appropriate frames to interpolate at time " + boost::lexical_cast( frame ) + 247 | " could not be found in the sequence: \"" + frantic::strings::to_string( get_sequence_name() ) + "\"" ); 248 | } 249 | 250 | xmesh_timing::xmesh_interpolation_info result; 251 | result.rangeRegion = rangeRegion; 252 | result.frames = frameBracket; 253 | result.alpha = alpha; 254 | return result; 255 | } 256 | 257 | double xmesh_timing::get_timing_data_without_limit_to_range( double frame ) const { 258 | frame = evaluate_playback_graph( frame ); 259 | 260 | frame += m_frameOffset; 261 | 262 | return frame; 263 | } 264 | -------------------------------------------------------------------------------- /stdafx.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // stdafx.cpp : source file that includes just the standard includes 4 | // XMeshCore.pch will be the pre-compiled header 5 | // stdafx.obj will contain the pre-compiled type information 6 | 7 | #include "stdafx.h" 8 | 9 | // TODO: reference any additional headers you need in STDAFX.H 10 | // and not in this file 11 | -------------------------------------------------------------------------------- /stdafx.h: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | // stdafx.h : include file for standard system include files, 4 | // or project specific include files that are used frequently, but 5 | // are changed infrequently 6 | // 7 | 8 | #pragma once 9 | 10 | #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. 11 | #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. 12 | #endif 13 | 14 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 15 | 16 | // TODO: reference additional headers your program requires here 17 | -------------------------------------------------------------------------------- /xmesh/cached_polymesh3_loader.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #define FRANTIC_POLYMESH_COPY_ON_WRITE 13 | 14 | namespace xmesh { 15 | 16 | namespace LOAD_POLYMESH3_MASK { 17 | enum load_polymesh3_mask { 18 | VERTS = 1, 19 | FACES = 2, 20 | VELOCITY = 4, 21 | MAPS = 8, 22 | BOX = 16, 23 | // 24 | STATIC_MESH = VERTS + FACES + MAPS + BOX, 25 | ALL = VERTS + FACES + VELOCITY + MAPS + BOX 26 | }; 27 | }; 28 | 29 | class polymesh3_loader_interface { 30 | public: 31 | virtual frantic::geometry::const_polymesh3_ptr 32 | load( const frantic::tstring& filename, frantic::geometry::xmesh_metadata* outMetadata, int loadMask ) = 0; 33 | virtual void set_thread_count( std::size_t numThreads ) = 0; 34 | virtual void clear_cache() = 0; 35 | }; 36 | 37 | #ifdef FRANTIC_POLYMESH_COPY_ON_WRITE 38 | 39 | struct xmesh_cache_entry { 40 | boost::filesystem::path::string_type filename; 41 | frantic::geometry::xmesh_reader reader; 42 | frantic::geometry::xmesh_metadata metadata; 43 | 44 | xmesh_cache_entry( const boost::filesystem::path::string_type& path ) 45 | : reader( path ) {} 46 | }; 47 | 48 | struct polymesh3_cache_data_entry { 49 | frantic::geometry::polymesh3_channel_data data; 50 | 51 | polymesh3_cache_data_entry() {} 52 | 53 | polymesh3_cache_data_entry( frantic::geometry::polymesh3_channel_data data ) 54 | : data( data ) {} 55 | }; 56 | 57 | struct polymesh3_cache_face_entry { 58 | frantic::geometry::polymesh3_channel_faces faces; 59 | frantic::geometry::polymesh3_channel_faces facesEnd; 60 | std::size_t numFaces; 61 | 62 | polymesh3_cache_face_entry() 63 | : numFaces( 0 ) {} 64 | 65 | polymesh3_cache_face_entry( std::vector& facesIn ) 66 | : numFaces( 0 ) 67 | , faces( facesIn ) {} 68 | }; 69 | 70 | struct polymesh3_cache { 71 | typedef boost::unordered_map data_cache_t; 72 | typedef boost::unordered_map faces_cache_t; 73 | 74 | data_cache_t data; 75 | faces_cache_t faces; 76 | }; 77 | 78 | class cached_polymesh3_loader : public polymesh3_loader_interface { 79 | public: 80 | cached_polymesh3_loader(); 81 | frantic::geometry::const_polymesh3_ptr load( const frantic::tstring& filename, 82 | frantic::geometry::xmesh_metadata* outMetadata, int loadMask ); 83 | void set_thread_count( std::size_t numThreads ); 84 | void clear_cache(); 85 | 86 | private: 87 | polymesh3_cache m_cache; 88 | boost::array, 2> m_xmeshCache; 89 | std::size_t m_threadCount; 90 | 91 | // return the filename's index in m_xmeshCache, or -1 if it is not cached 92 | int find_xmesh_cache_entry_index( const boost::filesystem::path::string_type& filename ); 93 | 94 | frantic::geometry::const_polymesh3_ptr 95 | build_polymesh3_from_cache( frantic::geometry::xmesh_reader& reader, polymesh3_cache& cache, 96 | std::vector& vertexChannelNames, 97 | std::vector& faceChannelNames, bool loadVerts, bool loadFaces, 98 | const frantic::tstring& filename ); 99 | 100 | frantic::geometry::const_polymesh3_ptr load_xmesh( const frantic::tstring& filename, 101 | frantic::geometry::xmesh_metadata* outMetadata, int loadMask ); 102 | 103 | xmesh_cache_entry& get_xmesh_cache_entry( const boost::filesystem::path::string_type& filename ); 104 | }; 105 | 106 | #else 107 | 108 | class cached_polymesh3_loader : public polymesh3_loader_interface { 109 | public: 110 | cached_polymesh3_loader(); 111 | frantic::geometry::const_polymesh3_ptr load( const frantic::tstring& filename, 112 | frantic::geometry::xmesh_metadata* outMetadata, int loadMask ); 113 | void set_thread_count( std::size_t numThreads ); 114 | void clear_cache(); 115 | 116 | private: 117 | typedef boost::mutex mutex_t; 118 | 119 | mutex_t m_cachedMeshMutex; 120 | frantic::tstring m_cachedMeshFilename; 121 | frantic::geometry::xmesh_metadata m_cachedMeshMetadata; 122 | frantic::geometry::polymesh3_ptr m_cachedMesh; 123 | int m_cachedMask; 124 | }; 125 | 126 | #endif 127 | 128 | } // namespace xmesh 129 | -------------------------------------------------------------------------------- /xmesh/fractional_index_iterator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace xmesh { 11 | 12 | /** 13 | * Iterate over a fraction of the indices within a range [0..count). 14 | * The returned indices are approximately evenly distributed within the range. 15 | */ 16 | class fractional_index_iterator { 17 | std::size_t m_index; 18 | std::size_t m_acc; 19 | std::size_t m_count; 20 | std::size_t m_fractionalCount; 21 | 22 | void find_valid_index() { 23 | for( ; m_index < m_count; ++m_index ) { 24 | m_acc += m_fractionalCount; 25 | if( m_acc >= m_count ) { 26 | m_acc -= m_count; 27 | return; 28 | } 29 | } 30 | 31 | m_index = std::numeric_limits::max(); 32 | } 33 | 34 | public: 35 | fractional_index_iterator( std::size_t count, float fraction ) 36 | : m_index( 0 ) 37 | , m_acc( 0 ) 38 | , m_count( count ) 39 | , m_fractionalCount( 40 | frantic::math::clamp( static_cast( fraction * count ), 0, count ) ) { 41 | find_valid_index(); 42 | } 43 | 44 | fractional_index_iterator() 45 | : m_index( std::numeric_limits::max() ) {} 46 | 47 | std::size_t operator*() const { return m_index; } 48 | 49 | bool operator==( const fractional_index_iterator& other ) const { return m_index == other.m_index; } 50 | 51 | bool operator!=( const fractional_index_iterator& other ) const { return m_index != other.m_index; } 52 | 53 | fractional_index_iterator& operator++() { 54 | if( m_index != std::numeric_limits::max() ) { 55 | ++m_index; 56 | find_valid_index(); 57 | } 58 | return *this; 59 | } 60 | 61 | std::size_t num_indices() const { return m_fractionalCount; } 62 | }; 63 | 64 | } // namespace xmesh 65 | -------------------------------------------------------------------------------- /xmesh/xmesh_timing.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include 4 | 5 | namespace frantic { 6 | namespace files { 7 | 8 | class frame_set; 9 | 10 | } 11 | } // namespace frantic 12 | 13 | namespace xmesh { 14 | 15 | class xmesh_timing { 16 | public: 17 | xmesh_timing(); 18 | virtual ~xmesh_timing(); 19 | 20 | void set_offset( double frameOffset ); 21 | void set_range( double startFrame, double endFrame ); 22 | 23 | // for error messages 24 | void set_sequence_name( const frantic::tstring& sequenceName ); 25 | 26 | enum range_region { RANGE_BEFORE = -1, RANGE_INSIDE = 0, RANGE_AFTER = 1 }; 27 | 28 | void get_frame_velocity_offset( double frame, const frantic::files::frame_set& frameSet, range_region& rangeRegion, 29 | double& sampleFrame, double& sampleOffset ) const; 30 | void get_subframe_velocity_offset( double frame, const frantic::files::frame_set& frameSet, 31 | range_region& rangeRegion, double& sampleFrame, double& sampleOffset ) const; 32 | double get_time_derivative( double frame, double frameStep ) const; 33 | 34 | void get_frame_interpolation( double frame, const frantic::files::frame_set& frameSet, range_region& rangeRegion, 35 | std::pair& sampleFrames, double& sampleOffset ) const; 36 | void get_subframe_interpolation( double frame, const frantic::files::frame_set& frameSet, range_region& rangeRegion, 37 | std::pair& sampleFrames, double& sampleOffset ) const; 38 | 39 | protected: 40 | virtual double evaluate_playback_graph( double frame ) const = 0; 41 | 42 | const frantic::tstring& get_sequence_name() const; 43 | 44 | private: 45 | struct xmesh_extrapolation_info { 46 | range_region rangeRegion; 47 | double frame; 48 | double offset; 49 | }; 50 | 51 | struct xmesh_interpolation_info { 52 | range_region rangeRegion; 53 | std::pair frames; 54 | double alpha; 55 | }; 56 | 57 | xmesh_extrapolation_info get_extrapolation_info( double outFrame, const frantic::files::frame_set& frameSet, 58 | bool useConsistentSubframeSamples ) const; 59 | xmesh_interpolation_info get_interpolation_info( double frame, const frantic::files::frame_set& frameSet, 60 | bool useSubframes ) const; 61 | 62 | double get_timing_data_without_limit_to_range( double frame ) const; 63 | 64 | double m_frameOffset; 65 | 66 | bool m_limitToRange; 67 | std::pair m_frameRange; 68 | 69 | frantic::tstring m_sequenceName; 70 | }; 71 | 72 | } // namespace xmesh 73 | --------------------------------------------------------------------------------