├── .editorconfig ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build-test.yml │ ├── deploy.yaml │ └── greetings.yaml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── Paramdigma.Core.sln ├── Paramdigma.Core.sln.DotSettings ├── README.md ├── coverage └── cobertura.xml ├── docfx_project ├── .gitignore ├── api │ ├── .gitignore │ └── index.md ├── articles │ ├── intro.md │ └── toc.yml ├── docfx.json ├── index.md └── toc.yml ├── src ├── Collections │ ├── Interval.cs │ └── Matrix{T}.cs ├── Curves │ ├── Geodesics.cs │ └── LevelSets.cs ├── Data │ └── Settings.json ├── Exceptions │ └── UnsetGeometryException.cs ├── Extensions │ └── Lists.cs ├── Geometry │ ├── Base │ │ ├── BaseCurve.cs │ │ ├── BasePoint.cs │ │ └── InvalidCurveException.cs │ ├── Box.cs │ ├── Circle.cs │ ├── Cylinder.cs │ ├── Interfaces │ │ ├── ICurve.cs │ │ ├── ISurface.cs │ │ └── IVector.cs │ ├── Intersect │ │ ├── Intersect.cs │ │ └── IntersectErrors.cs │ ├── Line.cs │ ├── Line2d.cs │ ├── Mesh.cs │ ├── MeshCorner.cs │ ├── MeshEdge.cs │ ├── MeshFace.cs │ ├── MeshGeometry.cs │ ├── MeshHalfEdge.cs │ ├── MeshPoint.cs │ ├── MeshTopology.cs │ ├── MeshVertex.cs │ ├── NurbsCalculator.cs │ ├── NurbsCurve.cs │ ├── NurbsSurface.cs │ ├── Plane.cs │ ├── Point2d.cs │ ├── Point3d.cs │ ├── Point4d.cs │ ├── Polyline.cs │ ├── Polyline2d.cs │ ├── Ray.cs │ ├── Ray2d.cs │ ├── Rectangle2d.cs │ ├── Sphere.cs │ ├── Torus.cs │ ├── Vector2d.cs │ ├── Vector3d.cs │ └── VectorNd.cs ├── IO │ ├── CSVReader.cs │ ├── CSVWritter.cs │ ├── OBJMeshData.cs │ ├── OBJReader.cs │ ├── OBJWritter.cs │ ├── OFFMeshData.cs │ ├── OFFReader.cs │ ├── OFFResult.cs │ └── OffWriter.cs ├── LinearAlgebra │ ├── Complex.cs │ ├── LeastSquaresLinearFit.cs │ └── Triplet.cs ├── Optimization │ ├── GradientDescent.cs │ ├── GradientDescentOptions.cs │ ├── GradientDescentResult.cs │ ├── KMeansCluster.cs │ └── KMeansClustering.cs ├── Paramdigma.Core.Rules.ruleset ├── Paramdigma.Core.csproj ├── Spatial │ ├── Delaunay.cs │ ├── DelaunayEdge.cs │ ├── DelaunayPoint.cs │ ├── DelaunayTriangle.cs │ ├── Octree.cs │ ├── PointCloud.cs │ ├── PointCloudMember.cs │ └── Quadtree.cs └── Utility │ ├── Convert.cs │ └── Settings.cs └── tests ├── Collections └── IntervalTests.cs ├── Curves └── LevelSetsTests.cs ├── Data └── meshes │ ├── cube.off │ ├── parabolicCyclide.off │ ├── sphere.off │ └── test.off ├── Extensions └── ListExtensionsTests.cs ├── Geometry ├── 2D │ ├── DelaunayTests.cs │ ├── Line2dTests.cs │ ├── Point2dTests.cs │ ├── Polyline2dTests.cs │ ├── Ray2dTests.cs │ └── Vector2dTests.cs ├── 3D │ ├── BoxTests.cs │ ├── CircleTests.cs │ ├── CurveBaseTests.cs │ ├── CylinderTests.cs │ ├── Intersect3dTests.cs │ ├── LineTests.cs │ ├── MeshCornerTests.cs │ ├── MeshFaceTests.cs │ ├── MeshGeometryTests.cs │ ├── MeshPointTests.cs │ ├── MeshTests.cs │ ├── MeshTopologyTests.cs │ ├── MeshVertexTests.cs │ ├── NurbsCurveData.cs │ ├── NurbsCurveTests.cs │ ├── NurbsSurfaceTests.cs │ ├── PlaneTests.cs │ ├── Point3dData.cs │ ├── Point3dTests.cs │ ├── Point4dTests.cs │ ├── Polyline3dTests.cs │ ├── Ray3dTests.cs │ ├── SphereTests.cs │ ├── Vector3dTests.cs │ ├── VectorEntity_Tests.cs │ └── VectorNd_Tests.cs └── SpatialStructures │ └── QuadTreeTests.cs ├── Optimization ├── GradientDescentOptionsTests.cs ├── GradientDescentTests.cs └── KMeansClusteringTests.cs ├── Paramdigma.Core.Tests.csproj ├── RhinoConversions.cs └── Utilities ├── JsonFileDataAttribute.cs └── ResourcesTests.cs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global owners 2 | * @AlanRynne 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at alan@rynne.es. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: your_title 5 | labels: your_label 6 | assignees: your_username 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Run '...' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem.s 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: Feature Request 5 | labels: your_labels 6 | assignees: your_username 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master, develop, ci/* ] 6 | pull_request: 7 | branches: [ master, develop ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: 🛎 Checkout repo 16 | uses: actions/checkout@v2 17 | - name: 🚧 Setup .NET Core 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 3.1.101 21 | - name: ⚙️ Install dependencies 22 | run: dotnet restore 23 | - name: 🏗 Build 24 | run: dotnet build --configuration Release --no-restore 25 | - name: 🧪 Test 26 | run: dotnet test --no-restore /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=../coverage/opencover.xml 27 | - name: 📚 Push to Codecov.io 28 | uses: codecov/codecov-action@v1 29 | with: 30 | file: coverage/opencover.xml 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy docs 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | build_and_publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: 🛎 Checkout repo 14 | uses: actions/checkout@v2 15 | 16 | - name: 🚧 Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | source-url: https://nuget.pkg.github.com/paramdigma/index.json 21 | env: 22 | NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | 24 | - name: ⚙️ Install dependencies 25 | run: dotnet restore 26 | 27 | - name: 🏗 Build 28 | run: dotnet build --configuration Release --no-restore 29 | 30 | - name: 🧪 Test 31 | run: dotnet test --no-restore 32 | 33 | - name: 🗜 Compress build files # This would actually build your project, using zip for an example artifact 34 | run: zip --junk-paths ./Paramdigma.Core.zip ./src/bin/Release/netstandard2.0/* 35 | 36 | - name: 📘 Create Release 37 | id: create_release 38 | uses: actions/create-release@v1 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | tag_name: ${{ github.ref }} 43 | release_name: Release ${{ github.ref }} 44 | draft: false 45 | prerelease: true 46 | - name: 📜 Upload Release Asset 47 | id: upload-release-asset 48 | uses: actions/upload-release-asset@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 53 | asset_path: ./Paramdigma.Core.zip 54 | asset_name: Paramdigma.Core.zip 55 | asset_content_type: application/zip 56 | 57 | - name: 📦 Create the package 58 | run: dotnet pack --configuration Release src/Paramdigma.Core.csproj 59 | - name: 🚀 Publish the package to GPR 60 | run: dotnet nuget push src/bin/Release/*.nupkg -k ${PUSH_TOKEN} 61 | env: 62 | PUSH_TOKEN: ${{secrets.GITHUB_TOKEN}} 63 | 64 | deploy_docs: 65 | runs-on: ubuntu-latest 66 | name: Docs build & deploy 67 | needs: build_and_publish 68 | steps: 69 | - uses: actions/checkout@v2 70 | name: 🛎 Checkout 71 | - run: "rm .gitignore && ls ." 72 | name: 🗑 Remove .gitignore file 73 | - uses: nikeee/docfx-action@master 74 | name: 📚 Build Docs 75 | with: 76 | args: docfx_project/docfx.json 77 | - name: 🚀 Deploy 78 | uses: JamesIves/github-pages-deploy-action@releases/v3 79 | with: 80 | BRANCH: gh-pages # The branch the action should deploy to. 81 | FOLDER: docs/ # The folder the action should deploy. 82 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yaml: -------------------------------------------------------------------------------- 1 | name: Greet first-time contributors 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'This is your first issue! Thanks for taking the time to do this!!' 13 | pr-message: 'This is your first PR! Thanks for making Paramdigma.Core a little better!' 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "ban.spellright", 8 | "ms-dotnettools.csharp", 9 | "Gruntfuggly.todo-tree", 10 | "brainfit.vscode-coverage-highlighter", 11 | "josefpihrt-vscode.snippetica-csharp", 12 | ], 13 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 14 | "unwantedRecommendations": [ 15 | 16 | ] 17 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "name": "Run Tests", 8 | "type": "coreclr", 9 | "request": "launch", 10 | "preLaunchTask": "build", 11 | // If you have changed target frameworks, make sure to update the program path. 12 | "program": "dotnet", 13 | "args": [ 14 | "test", 15 | "/p:CollectCoverage=true", 16 | "/p:CoverletOutputFormat=cobertura", 17 | "/p:CoverletOutput=../coverage/cobertura.xml", 18 | ], 19 | "cwd": "${workspaceFolder}/tests", 20 | "console": "internalConsole", 21 | "stopAtEntry": false, 22 | "logging": { 23 | "engineLogging": false, 24 | "moduleLoad": false, 25 | "browserStdOut": false 26 | } 27 | }] 28 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [{ 4 | "label": "build", 5 | "command": "dotnet", 6 | "type": "process", 7 | "args": [ 8 | "build", 9 | "${workspaceFolder}/tests/Paramdigma.Core.Tests.csproj" 10 | ], 11 | "problemMatcher": [] 12 | }, 13 | { 14 | "label": "build-docs", 15 | "command": "docfx", 16 | "type": "shell", 17 | "args": [ 18 | "docfx_project/docfx.json" 19 | ], 20 | }, 21 | { 22 | "label": "serve-docs", 23 | "command": "docfx", 24 | "type": "shell", 25 | "args": [ 26 | "docfx_project/docfx.json", 27 | "--serve" 28 | ], 29 | "isBackground": true, 30 | "problemMatcher": [] 31 | }, 32 | ] 33 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.1.1] - 21-Nov-2020 9 | 10 | Leftover change caused CI pipeline to crash. This should fix it! 11 | 12 | ## [0.1.0] - 21-Nov-2020 13 | 14 | So... another couple of release notes missing! 😅 What's important? 15 | 16 | - Added NURBS support in curves and surfaces. 17 | - Added some spatial search algorithms. 18 | - Improved testing and coverage. 19 | 20 | ## [0.0.6] - 14-June-2020 21 | 22 | Yup! Versioning is hard... I didn't do a good job of keeping track of the changes, so version `0.0.2 -> 0.0.6` will go under the *"major improvements"* category. I promise to do better next time! 🤞🏻 23 | 24 | ## [0.0.1] - 10-Feb-2020 25 | 26 | Initial beta release with many many many things pending implementation still. Just main structure and functionality in place. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alan Rynne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Paramdigma.Core.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramdigma.Core", "src\Paramdigma.Core.csproj", "{FD765C61-21BE-47EF-A9D0-3098534472F2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramdigma.Core.Tests", "tests\Paramdigma.Core.Tests.csproj", "{C2382EC6-F096-4E84-8057-A1507E62310F}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Debug|x64.Build.0 = Debug|Any CPU 27 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Debug|x86.Build.0 = Debug|Any CPU 29 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Release|x64.ActiveCfg = Release|Any CPU 32 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Release|x64.Build.0 = Release|Any CPU 33 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Release|x86.ActiveCfg = Release|Any CPU 34 | {FD765C61-21BE-47EF-A9D0-3098534472F2}.Release|x86.Build.0 = Release|Any CPU 35 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Debug|x64.Build.0 = Debug|Any CPU 39 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Debug|x86.Build.0 = Debug|Any CPU 41 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Release|x64.ActiveCfg = Release|Any CPU 44 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Release|x64.Build.0 = Release|Any CPU 45 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Release|x86.ActiveCfg = Release|Any CPU 46 | {C2382EC6-F096-4E84-8057-A1507E62310F}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /Paramdigma.Core.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | 7 | 8 | True 9 | True 10 | True 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Architectrual Geometry Library for .Net 2 | 3 | ![Project Status](https://img.shields.io/badge/status-Under%20Development-red.svg) 4 | [![Target Framework](https://img.shields.io/badge/Target%20Framework-.NetStandard2.0-blueviolet.svg)](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) 5 | [![License](https://img.shields.io/github/license/Paramdigma/Core.svg)](https://github.com/Paramdigma/Core/blob/master/LICENSE) 6 | 7 | ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/paramdigma/core?sort=semver) 8 | ![Main language](https://img.shields.io/github/languages/top/Paramdigma/Core.svg) 9 | ![Code Size](https://img.shields.io/github/languages/code-size/Paramdigma/Core.svg) 10 | 11 | **Paramdigma.Core** is _(or will be)_ an independent and open source library for **_Architectural Geometry_** algorithms developed by @AlanRynne. 12 | 13 | The core idea is to create a complete package of 3D entities and functions that could be easily connected to different software solutions, via secondary projects that will act as wrappers for the library. 14 | 15 | Currently, we are starting development of: 16 | 17 | - [McNeel Rhinoceros/Grasshopper](https://github.com/paramdigma/core.grasshopper) 18 | - [Autodesk Revit/Dynamo](https://github.com/paramdigma/core.dynamo) 19 | 20 | If you are looking for just a geometry library to plug into a project we also provide a NuGet package. 21 | - [Paramdigma.Core NuGet Package (GPR hosted)](https://github.com/Paramdigma/Core/packages/268763) 22 | 23 | > Github Package Registry is kind of new... If you don't know how to setup GPR for your projects, follow the first step in [THIS GUIDE](https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-dotnet-cli-for-use-with-github-packages#authenticating-with-a-personal-access-token). Once that is done, you are good to go! 🚀 24 | 25 | ## Status 26 | 27 | | | `master` | `develop` | 28 | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 29 | | _CI Status_ | [![Build Status](https://travis-ci.com/Paramdigma/Core.svg?branch=master)](https://travis-ci.com/Paramdigma/Core) | [![Build Status](https://travis-ci.com/Paramdigma/Core.svg?branch=develop)](https://travis-ci.com/Paramdigma/Core) | 30 | | _Code Quality_ | [![CodeFactor](https://www.codefactor.io/repository/github/Paramdigma/Core/badge/master)](https://www.codefactor.io/repository/github/Paramdigma/Core/overview/master) | [![CodeFactor](https://www.codefactor.io/repository/github/Paramdigma/Core/badge/develop)](https://www.codefactor.io/repository/github/Paramdigma/Core/overview/develop) | 31 | | _Test Coverage_ | [![codecov](https://codecov.io/gh/Paramdigma/Core/branch/master/graph/badge.svg)](https://codecov.io/gh/Paramdigma/Core/branch/master) | [![codecov](https://codecov.io/gh/Paramdigma/Core/branch/develop/graph/badge.svg)](https://codecov.io/gh/Paramdigma/Core/branch/develop) | 32 | 33 | ## Usage 34 | 35 | > This section will be written very shortly! 36 | 37 | ## Documentation 38 | 39 | You can find the Docfx built documentation for the latest release on the 'master' branch at: 40 | 41 | [https://paramdigma.com/Core/](https://paramdigma.com/Core/) 42 | 43 | > I'm planning on supporting multiple versions on the docs in the future, for now, only the latest release will be documented. 44 | > 45 | > For previous releases, you can always build the docs locally with DocFx. 46 | 47 | ## Contributing 48 | 49 | I haven't developed any contributing guidelines, although the `master` branch is _push protected_ and is connected to Travis-CI so all contributions should pass build tests. 50 | 51 | If you want to contribute to this library, feel free to fork this repo and create a pull request with any modifications. 52 | 53 | The makes heavy use of GitHub Actions automation capabilities to test and deploy the library. Reach out to @AlanRynne for any doubts on this. -------------------------------------------------------------------------------- /docfx_project/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | docs 10 | -------------------------------------------------------------------------------- /docfx_project/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /docfx_project/api/index.md: -------------------------------------------------------------------------------- 1 | # PLACEHOLDER 2 | TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*! 3 | -------------------------------------------------------------------------------- /docfx_project/articles/intro.md: -------------------------------------------------------------------------------- 1 | # Add your introductions here! 2 | -------------------------------------------------------------------------------- /docfx_project/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Introduction 2 | href: intro.md 3 | -------------------------------------------------------------------------------- /docfx_project/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ 7 | "**.csproj" 8 | ], 9 | "src": "../src/" 10 | } 11 | ], 12 | "dest": "api", 13 | "disableGitFeatures": false, 14 | "disableDefaultFilter": false 15 | } 16 | ], 17 | "build": { 18 | "content": [ 19 | { 20 | "files": [ 21 | "api/**.yml", 22 | "api/index.md" 23 | ] 24 | }, 25 | { 26 | "files": [ 27 | "articles/**.md", 28 | "articles/**/toc.yml", 29 | "toc.yml", 30 | "*.md" 31 | ] 32 | } 33 | ], 34 | "resource": [ 35 | { 36 | "files": [ 37 | "images/**" 38 | ] 39 | } 40 | ], 41 | "overwrite": [ 42 | { 43 | "files": [ 44 | "apidoc/**.md" 45 | ], 46 | "exclude": [ 47 | "obj/**", 48 | "docs/**" 49 | ] 50 | } 51 | ], 52 | "dest": "../docs", 53 | "globalMetadataFiles": [], 54 | "fileMetadataFiles": [], 55 | "template": [ 56 | "statictoc" 57 | ], 58 | "postProcessors": [], 59 | "markdownEngineName": "markdig", 60 | "noLangKeyword": false, 61 | "keepFileLink": false, 62 | "cleanupCacheHistory": false, 63 | "disableGitFeatures": false 64 | } 65 | } -------------------------------------------------------------------------------- /docfx_project/index.md: -------------------------------------------------------------------------------- 1 | # Architectrual Geometry Library for .Net 2 | 3 | ![Project Status](https://img.shields.io/badge/status-Under%20Development-red.svg) 4 | ![Target Framework](https://img.shields.io/badge/target-.NetStandard2.1-blueviolet.svg) 5 | [![License](https://img.shields.io/github/license/Paramdigma/Core.svg)](https://github.com/Paramdigma/Core/blob/development/LICENSE) 6 | 7 | ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/paramdigma/core?sort=semver) 8 | ![GitHub commits since latest release (by SemVer)](https://img.shields.io/github/commits-since/paramdigma/core/latest/master?sort=semver) 9 | ![Main language](https://img.shields.io/github/languages/top/Paramdigma/Core.svg) 10 | ![Code Size](https://img.shields.io/github/languages/code-size/Paramdigma/Core.svg) 11 | 12 | **Paramdigma.Core** is _(or will be)_ an independent and open source library for **_Architectural Geometry_** algorithms developed by @AlanRynne. 13 | 14 | The core idea is to create a complete package of 3D entities and functions that could be easily connected to different software solutions, via secondary projects that will act as wrappers for the library. 15 | 16 | Currently, we are starting development of: 17 | 18 | - [McNeel Rhinoceros/Grasshopper](https://github.com/paramdigma/core.grasshopper) 19 | - [Autodesk Revit/Dynamo](https://github.com/paramdigma/core.dynamo) 20 | 21 | If you are looking for just a geometry library to plug into a project we also provide a NuGet package. 22 | - [Paramdigma.Core NuGet Package (GPR hosted)](https://github.com/Paramdigma/Core/packages/268763) 23 | 24 | > Github Package Registry is kind of new... If you don't know how to setup GPR for your projects, follow the first step in [THIS GUIDE](https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-dotnet-cli-for-use-with-github-packages#authenticating-with-a-personal-access-token). Once that is done, you are good to go! 🚀 25 | 26 | ## Status 27 | 28 | | | `master` | `develop` | 29 | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 30 | | _CI Status_ | [![Build Status](https://travis-ci.com/Paramdigma/Core.svg?branch=master)](https://travis-ci.com/Paramdigma/Core) | [![Build Status](https://travis-ci.com/Paramdigma/Core.svg?branch=develop)](https://travis-ci.com/Paramdigma/Core) | 31 | | _Code Quality_ | [![CodeFactor](https://www.codefactor.io/repository/github/Paramdigma/Core/badge/master)](https://www.codefactor.io/repository/github/Paramdigma/Core/overview/master) | [![CodeFactor](https://www.codefactor.io/repository/github/Paramdigma/Core/badge/develop)](https://www.codefactor.io/repository/github/Paramdigma/Core/overview/develop) | 32 | | _Test Coverage_ | [![codecov](https://codecov.io/gh/Paramdigma/Core/branch/master/graph/badge.svg)](https://codecov.io/gh/Paramdigma/Core/branch/master) | [![codecov](https://codecov.io/gh/Paramdigma/Core/branch/develop/graph/badge.svg)](https://codecov.io/gh/Paramdigma/Core/branch/develop) | 33 | 34 | ## Usage 35 | 36 | > This section will be written very shortly! 37 | 38 | ## Documentation 39 | 40 | You can find the Docfx built documentation for the latest relase on the 'master' branch at: 41 | 42 | [https://paramdigma.com/Core/](https://paramdigma.com/Core/) 43 | 44 | > I'm planning on supporting multiple versions on the docs in the future, for now, only the latest release will be documented. 45 | > 46 | > For previous releases, you can always build the docs locally with DocFx. 47 | 48 | ## Contributing 49 | 50 | I haven't developed any contributing guidelines, although the `master` branch is _push protected_ and is connected to Travis-CI so all contributions should pass build tests. 51 | 52 | If you want to contribute to this library, feel free to fork this repo and create a pull request with any modifications. 53 | 54 | The makes heavy use of GitHub Actions automation capabilities to test and deploy the library. Reach out to @AlanRynne for any doubts on this. -------------------------------------------------------------------------------- /docfx_project/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Articles 2 | href: articles/ 3 | - name: Api Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | -------------------------------------------------------------------------------- /src/Curves/Geodesics.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | 4 | namespace Paramdigma.Core.Curves 5 | { 6 | /// 7 | /// Static class to compute geodeesics on triangular meshes. 8 | /// 9 | public static class Geodesics 10 | { 11 | /// 12 | /// Computes a geodesic on a mesh given a starting point and an initial direction. 13 | /// Returns true if successfull and false if something went wrong. 14 | /// 15 | /// Point. 16 | /// Direction. 17 | /// Mesh. 18 | /// Maximum iterations. 19 | /// Geodesic curves. 20 | /// True if successful. 21 | public static bool StartDir( 22 | MeshPoint meshPoint, 23 | Vector3d vector, 24 | Mesh mesh, 25 | int maxIter, 26 | out List geodesic) 27 | { 28 | // Get initial face on the mesh 29 | var initialFace = mesh.Faces[meshPoint.FaceIndex]; 30 | 31 | // Start iteration 32 | // Create variables for current iteration step 33 | var thisFace = initialFace; 34 | var thisPoint = new Point3d(); 35 | var thisDirection = vector; 36 | 37 | var iter = 0; 38 | var geodPoints = new List(); 39 | do 40 | { 41 | var ray = new Ray(thisPoint, thisDirection); 42 | 43 | // Find intersection between ray and boundary 44 | Intersect3D.RayFacePerimeter(ray, thisFace, out var nextPoint, out var halfEdge); 45 | 46 | // Intersection method should check for correct direction using sign of dot product 47 | 48 | // Add point to pointlist 49 | geodPoints.Add(nextPoint); 50 | 51 | // Walk to next face 52 | var nextFace = halfEdge.Twin.Face; 53 | 54 | // Flip vector to next face 55 | var perpVector = Vector3d.CrossProduct( 56 | thisDirection, 57 | MeshGeometry.FaceNormal(thisFace)); 58 | var nextVector = Vector3d.CrossProduct( 59 | MeshGeometry.FaceNormal(nextFace), 60 | perpVector); 61 | 62 | // Assign iteration variables to current 63 | thisPoint = nextPoint; 64 | thisFace = nextFace; 65 | thisDirection = nextVector; 66 | 67 | // Increase counter 68 | iter++; 69 | } while (iter < maxIter); 70 | 71 | // Assign outputs 72 | geodesic = geodPoints; 73 | return true; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/Data/Settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DefaultTesselation": 10, 3 | "Tolerance": 0.0000001 4 | } -------------------------------------------------------------------------------- /src/Exceptions/UnsetGeometryException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Paramdigma.Core.Exceptions 4 | { 5 | /// 6 | /// Represents errors that ocur when using a geometry that has the 'isUnset' flag set to true. 7 | /// 8 | public class UnsetGeometryException : Exception 9 | { 10 | /// 11 | public UnsetGeometryException() { } 12 | 13 | 14 | /// 15 | public UnsetGeometryException(string message) : base(message) { } 16 | 17 | 18 | /// 19 | public UnsetGeometryException(string message, Exception innerException) : base( 20 | message, 21 | innerException) { } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Extensions/Lists.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Paramdigma.Core.Extensions 5 | { 6 | /// 7 | /// Static class holding some utility methods regarding object collections. 8 | /// 9 | public static class Lists 10 | { 11 | /// 12 | /// Initializes a new list full of objects initialized with their default constructor. 13 | /// 14 | /// Number of objects in the list. 15 | /// Type of object in the list. 16 | /// //TODO. 17 | public static List RepeatedDefault(int count) => Repeated(default(T), count); 18 | 19 | 20 | /// 21 | /// Initializes a new list full of objects initialized the values of the specified instance of T. 22 | /// 23 | /// Object to insert on every index of the list. 24 | /// Number of objects in the list. 25 | /// Type of object in the list. 26 | /// //TODO. 27 | public static List Repeated(T value, int count) 28 | { 29 | var repeated = new List(count); 30 | repeated.AddRange(Enumerable.Repeat(value, count)); 31 | return repeated; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Geometry/Base/BaseCurve.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Collections; 2 | 3 | #pragma warning disable 1591 4 | 5 | namespace Paramdigma.Core.Geometry 6 | { 7 | /// 8 | /// Represents a generic curve. This class is abstract and all curve classes should inherit from 9 | /// it. 10 | /// 11 | public abstract class BaseCurve 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | protected BaseCurve() => this.Domain = Interval.Unit; 17 | // Public properties 18 | 19 | 20 | /// 21 | /// Gets or sets the curve's domain. 22 | /// 23 | public Interval Domain { get; set; } 24 | 25 | /// 26 | /// Gets a value indicating whether the curve is valid. 27 | /// 28 | /// True if Valid. 29 | public bool IsValid => this.CheckValidity(); 30 | 31 | public double Length => this.ComputeLength(); 32 | 33 | 34 | /// 35 | /// Compute a point along the curve at a specified parameter. 36 | /// 37 | /// Parameter. 38 | /// Point at the parameter specified. 39 | public abstract Point3d PointAt(double t); 40 | 41 | 42 | /// 43 | /// Compute the tangent vector along the curve at a specified parameter. 44 | /// 45 | /// Parameter. 46 | /// Tangent vector at the parameter specified. 47 | public abstract Vector3d TangentAt(double t); 48 | 49 | 50 | /// 51 | /// Compute normal vector along the curve at a specified parameter. 52 | /// 53 | /// Parameter. 54 | /// Normal vector at the parameter specified. 55 | public abstract Vector3d NormalAt(double t); 56 | 57 | 58 | /// 59 | /// Compute a binormal vector along the curve at a specified parameter. 60 | /// 61 | /// Parameter. 62 | /// Binormal vector at the parameter specified. 63 | public abstract Vector3d BinormalAt(double t); 64 | 65 | 66 | /// 67 | /// Compute the perpendicular frame along the curve at a specified parameter. 68 | /// 69 | /// Parameter. 70 | /// Perpendicular plane at the parameter specified. 71 | public abstract Plane FrameAt(double t); 72 | 73 | 74 | /// 75 | /// Checks the validity of the curve. 76 | /// 77 | /// True if valid. 78 | public abstract bool CheckValidity(); 79 | 80 | 81 | /// 82 | /// Computes the length of the curve. 83 | /// 84 | /// Length as number. 85 | protected abstract double ComputeLength(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/Geometry/Base/InvalidCurveException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Exception for invalid curve. 7 | /// 8 | public class InvalidCurveException : Exception 9 | { 10 | /// 11 | public InvalidCurveException() { } 12 | 13 | 14 | /// 15 | public InvalidCurveException(string message) 16 | : base(message) { } 17 | 18 | 19 | /// 20 | public InvalidCurveException(string message, Exception innerException) 21 | : base(message, innerException) { } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Geometry/Box.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Collections; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Represents a 3D box. 7 | /// 8 | public class Box 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// Base plane of the box. 14 | /// Range of values in the X axis. 15 | /// Range of values in the Y axis. 16 | /// Range of values in the Z axis. 17 | public Box(Plane plane, Interval domainX, Interval domainY, Interval domainZ) 18 | { 19 | this.Plane = plane; 20 | this.DomainX = domainX; 21 | this.DomainY = domainY; 22 | this.DomainZ = domainZ; 23 | } 24 | 25 | 26 | /// 27 | /// Initializes a new instance of the class from 2 corners. Both corners will 28 | /// form the diagonal of 29 | /// the box. 30 | /// 31 | /// Lower left corner point. 32 | /// Upper right corner point. 33 | public Box(Point3d lower, Point3d upper) 34 | { 35 | this.Plane = Plane.WorldXY; 36 | this.DomainX = new Interval(lower.X, upper.X); 37 | this.DomainY = new Interval(lower.Y, upper.Y); 38 | this.DomainZ = new Interval(lower.Z, upper.Z); 39 | } 40 | 41 | 42 | /// 43 | /// Gets or sets the box's base plane. 44 | /// 45 | /// . 46 | public Plane Plane { get; set; } 47 | 48 | /// 49 | /// Gets or sets the box's X axis domain. 50 | /// 51 | /// . 52 | public Interval DomainX { get; set; } 53 | 54 | /// 55 | /// Gets or sets the box's Y axis domain. 56 | /// 57 | /// . 58 | public Interval DomainY { get; set; } 59 | 60 | /// 61 | /// Gets or sets the box's Z axis domain. 62 | /// 63 | /// . 64 | public Interval DomainZ { get; set; } 65 | 66 | /// 67 | /// Gets the corner point with lowest values. 68 | /// 69 | /// . 70 | public Point3d Min => new Point3d( 71 | this.DomainX.Start, 72 | this.DomainY.Start, 73 | this.DomainZ.Start); 74 | 75 | /// 76 | /// Gets the corner point with highest values. 77 | /// 78 | /// . 79 | public Point3d Max => new Point3d(this.DomainX.End, this.DomainY.End, this.DomainZ.End); 80 | 81 | /// 82 | /// Gets the center point of the box. 83 | /// 84 | /// . 85 | public Point3d Center => new Point3d( 86 | this.DomainX.RemapFromUnit(0.5), 87 | this.DomainY.RemapFromUnit(0.5), 88 | this.DomainZ.RemapFromUnit(0.5)); 89 | } 90 | } -------------------------------------------------------------------------------- /src/Geometry/Circle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry.Interfaces; 3 | 4 | namespace Paramdigma.Core.Geometry 5 | { 6 | /// 7 | /// Represents a planar circle curve. 8 | /// 9 | public class Circle : ICurve 10 | { 11 | /// 12 | /// The base plane for the circle. 13 | /// 14 | public Plane Plane; 15 | 16 | /// 17 | /// The radius of the circle. 18 | /// 19 | public double Radius; 20 | 21 | 22 | /// 23 | /// Initializes a new instance of by it's plane and radius. 24 | /// 25 | /// The plane to draw the circle at. 26 | /// The desired radius of the circle. 27 | public Circle(Plane plane, double radius) 28 | { 29 | this.Plane = plane; 30 | this.Radius = radius; 31 | } 32 | 33 | 34 | /// 35 | public Point3d PointAt(double t) 36 | { 37 | var radians = t * 2 * Math.PI; 38 | var x = this.Radius * Math.Cos(radians); 39 | var y = this.Radius * Math.Sin(radians); 40 | return this.Plane.PointAt(x, y, 0); 41 | } 42 | 43 | 44 | /// 45 | public Vector3d TangentAt(double t) => this.NormalAt(t).Cross(this.Plane.ZAxis); 46 | 47 | 48 | /// 49 | public Vector3d NormalAt(double t) => (this.Plane.Origin - this.PointAt(t)).Unit(); 50 | 51 | 52 | /// 53 | public Vector3d BinormalAt(double t) => this.TangentAt(t).Cross(this.NormalAt(t)); 54 | 55 | 56 | /// 57 | public Plane FrameAt(double t) => new Plane( 58 | this.PointAt(t), 59 | this.NormalAt(t), 60 | this.BinormalAt(t), 61 | this.TangentAt(t)); 62 | } 63 | } -------------------------------------------------------------------------------- /src/Geometry/Cylinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Collections; 3 | using Paramdigma.Core.Geometry.Interfaces; 4 | 5 | namespace Paramdigma.Core.Geometry 6 | { 7 | /// 8 | /// Represents a cylindrical surface. 9 | /// 10 | public class Cylinder : ISurface 11 | { 12 | /// 13 | /// Initializes a new instance of the class from it's individual 14 | /// components. 15 | /// 16 | /// The plane of the cylinder. 17 | /// The radius of the cylinder. 18 | /// The cylinder height range. 19 | /// Throws when radius is smaller or equal to 0 20 | /// Throws when the height domain is tiny. 21 | /// Throws when the passed plane is null 22 | public Cylinder(Plane plane, double radius, Interval domain) 23 | { 24 | if (radius <= 0) 25 | throw new ArgumentOutOfRangeException(nameof(radius)); 26 | if (domain.Length < Settings.Tolerance) 27 | throw new ArgumentException("Height length is tiny."); 28 | this.Plane = plane ?? throw new ArgumentNullException(nameof(plane)); 29 | this.Radius = radius; 30 | this.HeightRange = domain; 31 | this.DomainU = Interval.Unit; 32 | this.DomainV = Interval.Unit; 33 | } 34 | 35 | 36 | /// 37 | /// Gets or sets the base plane of the cylinder. 38 | /// 39 | /// . 40 | public Plane Plane { get; set; } 41 | 42 | /// 43 | /// Gets or sets the radius of the cylinder. 44 | /// 45 | /// . 46 | public double Radius { get; set; } 47 | 48 | /// 49 | /// Gets or sets the height range of the cylinder. 50 | /// 51 | /// . 52 | public Interval HeightRange { get; set; } 53 | 54 | /// 55 | /// Gets the cylinder height. 56 | /// 57 | public double Height => this.HeightRange.Length; 58 | 59 | /// 60 | public Interval DomainU { get; set; } 61 | 62 | /// 63 | public Interval DomainV { get; set; } 64 | 65 | 66 | /// 67 | public Plane FrameAt(double u, double v) 68 | { 69 | this.CheckParameters(u, v); 70 | throw new NotImplementedException(); 71 | } 72 | 73 | 74 | /// 75 | /// Compute the distance from a point to this cylinder. 76 | /// 77 | /// Point to compute with. 78 | /// Number representing the distance. 79 | public double DistanceTo(Point3d point) => throw new NotImplementedException(); 80 | 81 | 82 | /// 83 | /// Compute the closes point of a point in this cylinder. 84 | /// 85 | /// Point to compute with. 86 | /// Point3d instance of the closest point in the cylinder. 87 | public Point3d ClosestPointTo(Point3d point) => throw new NotImplementedException(); 88 | 89 | 90 | /// 91 | public Vector3d NormalAt(double u, double v) 92 | { 93 | this.CheckParameters(u, v); 94 | throw new NotImplementedException(); 95 | } 96 | 97 | 98 | /// 99 | public Point3d PointAt(double u, double v) 100 | { 101 | this.CheckParameters(u, v); 102 | 103 | double x, y, z; 104 | var rho = this.Radius; 105 | var phi = this.DomainU.Remap(u, new Interval(0, 2 * Math.PI)); 106 | x = rho * Math.Cos(phi); 107 | y = rho * Math.Sin(phi); 108 | z = v; 109 | return this.Plane.PointAt(x, y, z); 110 | } 111 | 112 | 113 | private void CheckParameters(double u, double v) 114 | { 115 | if (!this.DomainU.Contains(u)) 116 | throw new Exception("Parameter U must be contained inside domain"); 117 | if (!this.DomainV.Contains(v)) 118 | throw new Exception("Parameter V must be contained inside domain"); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/Geometry/Interfaces/ICurve.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Geometry.Interfaces 2 | { 3 | /// 4 | /// Base interface that all curve entities must implement. 5 | /// 6 | public interface ICurve 7 | { 8 | /// 9 | /// Computes the point on the curve at the specified parameter. 10 | /// 11 | /// Parameter. 12 | /// Point on curve. 13 | Point3d PointAt(double t); 14 | 15 | 16 | /// 17 | /// Computes the tangent vector on the curve at the specified parameter. 18 | /// 19 | /// Parameter. 20 | /// Tangent on curve. 21 | Vector3d TangentAt(double t); 22 | 23 | 24 | /// 25 | /// Computes the normal vector on the curve at the specified parameter. 26 | /// 27 | /// Parameter. 28 | /// Normal on curve. 29 | Vector3d NormalAt(double t); 30 | 31 | 32 | /// 33 | /// Computes the binormal vector on the curve at the specified parameter. 34 | /// 35 | /// Parameter. 36 | /// Binormal vector on curve. 37 | Vector3d BinormalAt(double t); 38 | 39 | 40 | /// 41 | /// Computes the perpendicular frame on the curve at the specified parameter. 42 | /// 43 | /// Parameter. 44 | /// Perpendicular frame on curve. 45 | Plane FrameAt(double t); 46 | } 47 | } -------------------------------------------------------------------------------- /src/Geometry/Interfaces/ISurface.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Collections; 2 | 3 | namespace Paramdigma.Core.Geometry.Interfaces 4 | { 5 | /// 6 | /// Base interface that all surface interface must implement. 7 | /// 8 | public interface ISurface 9 | { 10 | /// 11 | /// Gets the domain in the U direction. 12 | /// 13 | /// . 14 | Interval DomainU { get; } 15 | 16 | /// 17 | /// Gets the domain in the V direction. 18 | /// 19 | /// . 20 | Interval DomainV { get; } 21 | 22 | 23 | /// 24 | /// Compute a point at the specified surface coordinates. 25 | /// 26 | /// U coordinate. 27 | /// V coordinate. 28 | /// . 29 | Point3d PointAt(double u, double v); 30 | 31 | 32 | /// 33 | /// Compute the normal at the specified surface coordinates. 34 | /// 35 | /// U coordinate. 36 | /// V coordinate. 37 | /// Normal vector. 38 | Vector3d NormalAt(double u, double v); 39 | 40 | 41 | /// 42 | /// Compute the tangent plane at the specified surface coordinates. 43 | /// 44 | /// U coordinate. 45 | /// V coordinate. 46 | /// Tangent plane. 47 | Plane FrameAt(double u, double v); 48 | 49 | 50 | /// 51 | /// Compute the distance between this surface and a point. 52 | /// 53 | /// Point to compute distance to. 54 | /// Number representing the distance. 55 | double DistanceTo(Point3d point); 56 | 57 | 58 | /// 59 | /// Compute the projection of a point on this surface. 60 | /// 61 | /// Point to compute distance to. 62 | /// Projected 3d point on the surface. 63 | Point3d ClosestPointTo(Point3d point); 64 | } 65 | } -------------------------------------------------------------------------------- /src/Geometry/Interfaces/IVector.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Geometry.Interfaces 2 | { 3 | /// 4 | /// Base interface that all vector entities must implement. 5 | /// 6 | public interface IVector { } 7 | } -------------------------------------------------------------------------------- /src/Geometry/Intersect/IntersectErrors.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | 3 | #pragma warning disable 1591 4 | 5 | namespace Paramdigma.Core 6 | { 7 | /// 8 | /// Class contains all 3D related intersection methods. 9 | /// 10 | public static partial class Intersect3D 11 | { 12 | public enum LineLineIntersectionStatus 13 | { 14 | NoIntersection, Point, Error 15 | } 16 | 17 | public enum LinePlaneIntersectionStatus 18 | { 19 | NoIntersection, Point, OnPlane 20 | } 21 | 22 | public enum RayFacePerimeterIntersectionStatus 23 | { 24 | NoIntersection, Point, Error 25 | } 26 | 27 | public struct LineLineIntersectionResult 28 | { 29 | public double Distance { get; set; } 30 | 31 | public double ParamA { get; set; } 32 | 33 | public double ParamB { get; set; } 34 | 35 | public Point3d PointA { get; set; } 36 | 37 | public Point3d PointB { get; set; } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Geometry/Line.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Represents a 3D Line. 7 | /// 8 | public class Line : BaseCurve 9 | { 10 | /// 11 | /// Initializes a new instance of the class from two points. 12 | /// 13 | /// Start point. 14 | /// End point. 15 | public Line(Point3d startPoint, Point3d endPoint) 16 | { 17 | this.StartPoint = startPoint; 18 | this.EndPoint = endPoint; 19 | } 20 | 21 | 22 | /// 23 | /// Initializes a new instance of the class from an origin point, a direction 24 | /// and a specified 25 | /// length. 26 | /// 27 | /// Start point of the line. 28 | /// Direction of the line (length will not be taken into account). 29 | /// Length of the line. 30 | public Line(Point3d origin, Vector3d direction, double length) 31 | { 32 | this.StartPoint = origin; 33 | this.EndPoint = origin + direction.Unit() * length; 34 | } 35 | 36 | 37 | /// 38 | /// Gets or sets the lines's start point. 39 | /// 40 | public Point3d StartPoint { get; set; } 41 | 42 | /// 43 | /// Gets or sets the line's end point. 44 | /// 45 | public Point3d EndPoint { get; set; } 46 | 47 | 48 | /// 49 | /// Checks if line is valid. 50 | /// 51 | /// True if valid. 52 | public override bool CheckValidity() => this.Length >= Settings.Tolerance; 53 | 54 | 55 | /// 56 | /// Computes thepoint at the given parameter. 57 | /// 58 | /// Parameter of the point. Must be between 0 and 1. 59 | /// Point at specified parameter. 60 | public override Point3d PointAt(double t) => 61 | this.StartPoint + this.Domain.RemapToUnit(t) * (this.EndPoint - this.StartPoint); 62 | 63 | 64 | /// 65 | /// Computes the tangent at the given parameter. 66 | /// 67 | /// Parameter of the tangent. Must be between 0 and 1. 68 | /// Tangent at specified parameter. 69 | public override Vector3d TangentAt(double t) 70 | { 71 | var tangent = this.EndPoint - this.StartPoint; 72 | tangent.Unitize(); 73 | return tangent; 74 | } 75 | 76 | 77 | /// 78 | /// Computes the normal at the given parameter. 79 | /// 80 | /// Parameter of the normal vector. Must be between 0 and 1. 81 | /// Normal vector at specified parameter. 82 | public override Vector3d NormalAt(double t) 83 | { 84 | var tangent = this.TangentAt(t); 85 | var v = Math.Abs(tangent.Dot(Vector3d.UnitZ) - 1) < Settings.Tolerance 86 | ? Vector3d.UnitX 87 | : Vector3d.UnitZ; 88 | return tangent.Cross(v); 89 | } 90 | 91 | 92 | /// 93 | /// Computes the bi-normal vector at the given parameter. 94 | /// 95 | /// Parameter of the bi-normal vector. Must be between 0 and 1. 96 | /// Bi-normal vector at specified parameter. 97 | public override Vector3d BinormalAt(double t) => 98 | Vector3d.CrossProduct(this.TangentAt(t), this.NormalAt(t)); 99 | 100 | 101 | /// 102 | /// Computes the perpendicular frame at the given parameter. 103 | /// 104 | /// Parameter of the frame. Must be between 0 and 1. 105 | /// Frame at specified parameter. 106 | public override Plane FrameAt(double t) => new Plane( 107 | this.PointAt(t), 108 | this.TangentAt(t), 109 | this.NormalAt(t), 110 | this.BinormalAt(t)); 111 | 112 | 113 | /// 114 | /// Computes the length of the line. 115 | /// 116 | /// Line length. 117 | protected override double ComputeLength() => this.StartPoint.DistanceTo(this.EndPoint); 118 | 119 | 120 | /// 121 | /// Explicitly converts a line to it's vector representation. 122 | /// 123 | /// Line to convert. 124 | /// Vector defining the line direction and length. 125 | public static explicit operator Vector3d(Line line) => line.EndPoint - line.StartPoint; 126 | } 127 | } -------------------------------------------------------------------------------- /src/Geometry/Line2d.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Collections; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Represents a 2-dimensional line. 7 | /// 8 | public class Line2d 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// Start point of the line. 14 | /// End point of the line. 15 | public Line2d(Point2d startPoint, Point2d endPoint) 16 | { 17 | this.StartPoint = startPoint; 18 | this.EndPoint = endPoint; 19 | this.Domain = new Interval(0, this.Length); 20 | } 21 | 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The start point of the line. 27 | /// Direction. The length of the vector will determine the end point. 28 | /// New 2d line instance. 29 | public Line2d(Point2d startPoint, Vector2d direction) 30 | : this(startPoint, startPoint + direction) { } 31 | 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | /// Start point. 37 | /// Direction (length of vector will be disregarded). 38 | /// Desired length of the line. 39 | /// New 2d line instance. 40 | public Line2d(Point2d startPoint, Vector2d direction, double length) 41 | : this(startPoint, direction.Unit() * length) { } 42 | 43 | 44 | /// 45 | /// Gets or sets the start point of the line. 46 | /// 47 | /// 3D Point. 48 | public Point2d StartPoint { get; set; } 49 | 50 | /// 51 | /// Gets or sets the end point of the line. 52 | /// 53 | /// 3D Point. 54 | public Point2d EndPoint { get; set; } 55 | 56 | /// 57 | /// Gets or sets the line's domain. 58 | /// 59 | /// Interval. 60 | public Interval Domain { get; set; } 61 | 62 | /// 63 | /// Gets the vector representation of the line. 64 | /// 65 | public Vector2d Vector => 66 | this; // Implicit line to vector conversion (this property exists just for convenience and readability) 67 | 68 | /// 69 | /// Gets the length of the line. 70 | /// 71 | public double Length => this.Vector.Length; 72 | 73 | 74 | /// 75 | /// Implicit conversion from line to vector. 76 | /// 77 | /// Line to be transformed into vector. 78 | public static implicit operator Vector2d(Line2d line) => line.EndPoint - line.StartPoint; 79 | 80 | 81 | /// 82 | /// Computes if a given point is at the left, right or on the current line. 83 | /// 84 | /// Point to test. 85 | /// 86 | /// >0 for point left of the line 87 | /// =0 for point on the line 88 | /// bigger 0 for point right of the line. 89 | /// 90 | public double IsLeft(Point2d point) 91 | { 92 | Vector2d v1 = this; 93 | var v2 = point - this.StartPoint; 94 | return v1.PerpProduct(v2); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/Geometry/MeshCorner.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Geometry 2 | { 3 | /// 4 | /// Represents a corner of a given mesh face. 5 | /// 6 | public class MeshCorner 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public MeshCorner() => this.Index = -1; 12 | 13 | 14 | /// 15 | /// Gets or sets the corner's first half-edge. 16 | /// 17 | public MeshHalfEdge HalfEdge { get; set; } 18 | 19 | /// 20 | /// Gets or sets the index of the mesh corner. 21 | /// 22 | public int Index { get; set; } 23 | 24 | /// 25 | /// Gets the mesh corner vertex. 26 | /// 27 | public MeshVertex Vertex => this.HalfEdge.Prev.Vertex; 28 | 29 | /// 30 | /// Gets the face the mesh corner belongs to. 31 | /// 32 | public MeshFace Face => this.HalfEdge.Face; 33 | 34 | /// 35 | /// Gets the next corner. 36 | /// 37 | public MeshCorner Next => this.HalfEdge.Next.Corner; 38 | 39 | /// 40 | /// Gets the previous corner. 41 | /// 42 | public MeshCorner Prev => this.HalfEdge.Prev.Corner; 43 | } 44 | } -------------------------------------------------------------------------------- /src/Geometry/MeshEdge.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Edge class representing a full edge of a half-edge mesh. 7 | /// A full edge contains 2 half-edges. 8 | /// 9 | public class MeshEdge 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public MeshEdge() => this.Index = -1; 15 | 16 | 17 | /// 18 | /// Gets or sets the half-edge linked to this edge. 19 | /// 20 | public MeshHalfEdge HalfEdge { get; set; } 21 | 22 | /// 23 | /// Gets or sets the index of this Mesh Edge. 24 | /// 25 | public int Index { get; set; } 26 | 27 | /// 28 | /// Gets a value indicating whether the mesh edge lies on a boundary. 29 | /// 30 | public bool OnBoundary => this.HalfEdge.OnBoundary || this.HalfEdge.Twin.OnBoundary; 31 | 32 | 33 | /// 34 | /// Gets the adjacent vertices of this given edge. 35 | /// 36 | /// 37 | public List AdjacentVertices() 38 | { 39 | var vertices = new List {this.HalfEdge.Vertex, this.HalfEdge.Twin.Vertex}; 40 | return vertices; 41 | } 42 | 43 | 44 | /// 45 | /// Gets the adjacent faces of this edge. 46 | /// 47 | /// 48 | public List AdjacentFaces() 49 | { 50 | var faces = new List 51 | { 52 | this.HalfEdge.AdjacentFace, this.HalfEdge.Twin.AdjacentFace 53 | }; 54 | return faces; 55 | } 56 | 57 | 58 | /// 59 | /// Gets the adjacent edges of this edge. 60 | /// 61 | /// 62 | public List AdjacentEdges() 63 | { 64 | var edges = new List(); 65 | edges.AddRange(this.HalfEdge.Vertex.AdjacentEdges()); 66 | edges.AddRange(this.HalfEdge.Twin.Vertex.AdjacentEdges()); 67 | return edges; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Geometry/MeshHalfEdge.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Geometry 2 | { 3 | /// 4 | /// Represents a mesh half-edge. 5 | /// 6 | public class MeshHalfEdge 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public MeshHalfEdge() => this.Index = -1; 12 | 13 | 14 | /// 15 | /// Gets or sets the vertex linked to this half-edge. 16 | /// 17 | public MeshVertex Vertex { get; set; } 18 | 19 | /// 20 | /// Gets or sets the edge linked to this half-edge. 21 | /// 22 | public MeshEdge Edge { get; set; } 23 | 24 | /// 25 | /// Gets or sets the face linked to this half-edge. 26 | /// 27 | public MeshFace Face { get; set; } 28 | 29 | /// 30 | /// Gets or sets the corner linked to this half-edge. 31 | /// 32 | public MeshCorner Corner { get; set; } 33 | 34 | /// 35 | /// Gets or sets the next half-edge in a face. 36 | /// 37 | public MeshHalfEdge Next { get; set; } 38 | 39 | /// 40 | /// Gets or sets the previous half-edge in a face. 41 | /// 42 | public MeshHalfEdge Prev { get; set; } 43 | 44 | /// 45 | /// Gets or sets the opposite half-edge. 46 | /// 47 | public MeshHalfEdge Twin { get; set; } 48 | 49 | /// 50 | /// Gets or sets a value indicating whether the half-edge lies on a boundary. 51 | /// 52 | public bool OnBoundary { get; set; } 53 | 54 | /// 55 | /// Gets or sets the half-edge index. 56 | /// 57 | public int Index { get; set; } 58 | 59 | /// 60 | /// Gets the previous vertex of the half-edge. 61 | /// 62 | public MeshVertex PreviousVertex => this.Twin.Vertex; 63 | 64 | /// 65 | /// Gets the opposite face of the half-edge. 66 | /// 67 | public MeshFace AdjacentFace => this.Twin.Face; 68 | 69 | 70 | /// 71 | /// Gets the string representation of the half-edge. 72 | /// 73 | /// 74 | public override string ToString() => "Half-edge " + this.Index; 75 | } 76 | } -------------------------------------------------------------------------------- /src/Geometry/MeshPoint.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Geometry 2 | { 3 | /// 4 | /// Represents a point on a mesh as it's face index and barycentric coordinatees. 5 | /// 6 | public class MeshPoint 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | /// Face Index. 12 | /// U coordinate. 13 | /// V coordinate. 14 | /// Z coordinate. 15 | public MeshPoint(int faceIndex, double u, double v, double w) 16 | { 17 | this.FaceIndex = faceIndex; 18 | this.U = u; 19 | this.V = v; 20 | this.W = w; 21 | } 22 | 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// 3D Point. 28 | /// Mesh face. 29 | public MeshPoint(MeshFace face, Point3d point) 30 | { 31 | var adj = face.AdjacentVertices(); 32 | var bary = Convert.Point3dToBarycentric(point, adj[0], adj[1], adj[2]); 33 | this.U = bary[0]; 34 | this.V = bary[1]; 35 | this.W = bary[2]; 36 | } 37 | 38 | 39 | /// 40 | /// Gets or sets the index of the face this point lies in. 41 | /// 42 | public int FaceIndex { get; set; } 43 | 44 | /// 45 | /// Gets or sets the U coordinate at the face. 46 | /// 47 | public double U { get; set; } 48 | 49 | /// 50 | /// Gets or sets the V coordinate at the face. 51 | /// 52 | public double V { get; set; } 53 | 54 | /// 55 | /// Gets or sets the W coordinate at the face. 56 | /// 57 | public double W { get; set; } 58 | 59 | 60 | /// 61 | /// Converts a mesh point into a string. 62 | /// 63 | /// String representation of the mesh point. 64 | public override string ToString() => 65 | "MeshPoint{ " + this.FaceIndex + "; " + this.U + ", " + this.V + ", " + this.W + " }"; 66 | } 67 | } -------------------------------------------------------------------------------- /src/Geometry/NurbsCurve.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Paramdigma.Core.Geometry 6 | { 7 | /// 8 | /// 9 | public class NurbsCurve : BaseCurve 10 | { 11 | /// 12 | /// The control points of the nurbs curve. 13 | /// 14 | public List ControlPoints; 15 | 16 | /// 17 | /// The degree of the curve. 18 | /// 19 | public int Degree; 20 | 21 | /// 22 | /// The nurbs curve knot vector. 23 | /// 24 | public List Knots; 25 | 26 | 27 | /// 28 | /// Initializes a new instance of by it's control points and degree. 29 | /// 30 | /// The control points to create the curve with. 31 | /// The desired degree of the curve. Degree cannot be > (ControlPoints - 1) 32 | public NurbsCurve(List controlPoints, int degree) 33 | { 34 | this.ControlPoints = controlPoints; 35 | this.Knots = NurbsCalculator.CreateUniformKnotVector(controlPoints.Count, degree) 36 | .ToList(); 37 | this.Degree = degree; 38 | } 39 | 40 | 41 | /// 42 | /// Gets the count of the control points - 1. 43 | /// 44 | private int N => this.ControlPoints.Count - 1; 45 | 46 | /// 47 | /// The start point of the curve. 48 | /// 49 | public Point3d StartPoint => this.PointAt(this.Domain.Start); 50 | 51 | /// 52 | /// The end point of the curve. 53 | /// 54 | public Point3d EndPoint => this.PointAt(this.Domain.End); 55 | 56 | /// 57 | /// The tangent vector at the start of the curve. 58 | /// 59 | public Vector3d StartTangent => this.TangentAt(this.Domain.Start); 60 | 61 | /// 62 | /// The tangent vector at the end of the curve. 63 | /// 64 | public Vector3d EndTangent => this.TangentAt(this.Domain.End); 65 | 66 | 67 | /// 68 | /// Computes the specific amount of derivatives on the specified parameter. 69 | /// 70 | /// Parameter to compute derivatives at. 71 | /// Number of derivatives to compute. 72 | /// Array containing the 73 | private IList DerivativesAt(double t, int count) => 74 | NurbsCalculator.NurbsCurveDerivs( 75 | this.N, 76 | this.Degree, 77 | this.Knots, 78 | this.ControlPoints, 79 | t, 80 | count 81 | ); 82 | 83 | 84 | /// 85 | public override Point3d PointAt(double t) => 86 | NurbsCalculator.CurvePoint(this.N, this.Degree, this.Knots, this.ControlPoints, t); 87 | 88 | 89 | /// 90 | public override Vector3d TangentAt(double t) => this.DerivativesAt(t, 1)[1].Unit(); 91 | 92 | 93 | /// 94 | public override Vector3d NormalAt(double t) => this.DerivativesAt(t, 2)[2].Unit(); 95 | 96 | 97 | /// 98 | public override Vector3d BinormalAt(double t) => this.DerivativesAt(t, 3)[3].Unit(); 99 | 100 | 101 | /// 102 | public override Plane FrameAt(double t) 103 | { 104 | var ders = this.DerivativesAt(t, 3); 105 | return new Plane(( Point3d ) ders[0], ders[1], ders[2], ders[3]); 106 | } 107 | 108 | 109 | /// 110 | public override bool CheckValidity() => true; 111 | 112 | 113 | /// 114 | protected override double ComputeLength() => throw new NotImplementedException(); 115 | } 116 | } -------------------------------------------------------------------------------- /src/Geometry/Ray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Infinite 3d ray starting at a point. 7 | /// 8 | public class Ray 9 | { 10 | /// 11 | /// Initializes a new instance of the class with origin and direction. 12 | /// 13 | /// Point representing the origin of the ray. 14 | /// Vector representing the direction of the ray. 15 | public Ray(Point3d origin, Vector3d direction) 16 | { 17 | this.Origin = origin ?? throw new ArgumentNullException(nameof(origin)); 18 | this.Direction = direction ?? throw new ArgumentNullException(nameof(direction)); 19 | } 20 | 21 | 22 | /// 23 | /// Gets or sets the origin point of the ray. 24 | /// 25 | public Point3d Origin { get; set; } 26 | 27 | /// 28 | /// Gets or sets the direction vector of the ray. 29 | /// 30 | public Vector3d Direction { get; set; } 31 | 32 | 33 | /// 34 | /// Computes a point in the ray at the given parameter. 35 | /// 36 | /// Parameter to obtain point. 37 | /// Returns a point at the specified parameter of the Ray. 38 | public Point3d PointAt(double t) => this.Origin + t * this.Direction; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Geometry/Ray2d.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Paramdigma.Core.Geometry 4 | { 5 | /// 6 | /// Represents an infinite 2-dimensional ray. 7 | /// 8 | public class Ray2d 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// Origin point. 14 | /// Direction vector. 15 | public Ray2d(Point2d origin, Vector2d direction) 16 | { 17 | this.Origin = origin ?? throw new ArgumentNullException(nameof(origin)); 18 | this.Direction = direction ?? throw new ArgumentNullException(nameof(direction)); 19 | } 20 | 21 | 22 | /// 23 | /// Gets or sets the origin of the ray. 24 | /// 25 | /// Origin point. 26 | public Point2d Origin { get; set; } 27 | 28 | /// 29 | /// Gets or sets the direction of the ray as a unit vector. 30 | /// 31 | /// Direction vector. 32 | public Vector2d Direction { get; set; } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Geometry/Sphere.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Collections; 3 | using Paramdigma.Core.Geometry.Interfaces; 4 | 5 | namespace Paramdigma.Core.Geometry 6 | { 7 | /// 8 | /// Represents a spherical surface. 9 | /// 10 | public class Sphere : ISurface 11 | { 12 | /// 13 | /// Initializes a new instance of given it's base plane and radius. 14 | /// 15 | /// 16 | /// 17 | /// Throws when radius is smaller than 0. 18 | public Sphere(Plane plane, double radius) 19 | { 20 | if (Math.Abs(radius) < Settings.Tolerance) 21 | throw new ArithmeticException("Can't create a sphere of 0 radius."); 22 | this.Plane = plane; 23 | this.Radius = radius; 24 | this.DomainU = Interval.Unit; 25 | this.DomainV = Interval.Unit; 26 | } 27 | 28 | 29 | /// 30 | /// Initializes a new instance of around the World origin with unit radius. 31 | /// 32 | public Sphere() : this(Plane.WorldXY, 1) { } 33 | 34 | 35 | /// 36 | /// Gets or sets the base plane of the sphere. 37 | /// 38 | /// . 39 | public Plane Plane { get; set; } 40 | 41 | /// 42 | /// Gets or sets the radius of the sphere. 43 | /// 44 | /// . 45 | public double Radius { get; set; } 46 | 47 | /// 48 | public Interval DomainU { get; set; } 49 | 50 | /// 51 | public Interval DomainV { get; set; } 52 | 53 | 54 | /// 55 | public double DistanceTo(Point3d point) => 56 | this.Plane.Origin.DistanceTo(point) - this.Radius; 57 | 58 | 59 | /// 60 | public Point3d ClosestPointTo(Point3d point) => 61 | this.Plane.Origin + (point - this.Plane.Origin).Unit() * this.Radius; 62 | 63 | 64 | /// 65 | public Plane FrameAt(double u, double v) => throw new NotImplementedException(); 66 | 67 | 68 | /// 69 | public Vector3d NormalAt(double u, double v) => 70 | (this.PointAt(u, v) - this.Plane.Origin).Unit(); 71 | 72 | 73 | /// 74 | public Point3d PointAt(double u, double v) 75 | { 76 | double x, y, z; 77 | var tau = new Interval(0, Math.PI).RemapFromUnit(v); 78 | var rho = new Interval(0, 2 * Math.PI).RemapFromUnit(u); 79 | x = this.Radius * Math.Sin(tau) * Math.Cos(rho); 80 | y = this.Radius * Math.Sin(tau) * Math.Sin(rho); 81 | z = this.Radius * Math.Cos(tau); 82 | return this.Plane.PointAt(x, y, z); 83 | } 84 | 85 | 86 | /// 87 | /// Returns the closest point on the sphere as a 2D point containing it's UV coordinates. 88 | /// 89 | /// Point to find closest to 90 | /// UV Parameter of the closest point as a Point2d instance. 91 | public Point2d ClosestParam(Point3d pt) 92 | { 93 | var rho = Math.Atan(pt.Y / pt.X); 94 | var tau = Math.Atan(Math.Sqrt(pt.X * pt.X + pt.Y * pt.Y) / pt.Z); 95 | var u = new Interval(0, 2 * Math.PI).RemapToUnit(rho); 96 | var v = new Interval(0, Math.PI).RemapToUnit(tau); 97 | return new Point2d(u, v); 98 | } 99 | 100 | 101 | /// 102 | /// Computes the point at a specified parameter, provided as a instance. 103 | /// 104 | /// parameter coordinates. 105 | /// instance of the specified point. 106 | public Point3d PointAt(Point2d uvPoint) => this.PointAt(uvPoint.X, uvPoint.Y); 107 | } 108 | } -------------------------------------------------------------------------------- /src/Geometry/Torus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Collections; 3 | using Paramdigma.Core.Geometry.Interfaces; 4 | 5 | namespace Paramdigma.Core.Geometry 6 | { 7 | /// 8 | /// Represents a toroidal surface. 9 | /// 10 | public class Torus : ISurface 11 | { 12 | /// 13 | /// Initializes a new instance of the class from a plane and two radii. 14 | /// 15 | /// The torus base plane. 16 | /// The torus major radius. 17 | /// The torus minor radius. 18 | public Torus(Plane plane, double majorRadius, double minorRadius) 19 | { 20 | this.Plane = plane; 21 | this.MajorRadius = majorRadius; 22 | this.MinorRadius = minorRadius; 23 | } 24 | 25 | 26 | /// 27 | /// Gets or sets the torus base plane. 28 | /// 29 | /// . 30 | public Plane Plane { get; set; } 31 | 32 | /// 33 | /// Gets or sets the torus major radius. 34 | /// 35 | /// . 36 | public double MajorRadius { get; set; } 37 | 38 | /// 39 | /// Gets or sets the torus minor radius. 40 | /// 41 | /// . 42 | public double MinorRadius { get; set; } 43 | 44 | /// 45 | public Interval DomainU { get; set; } 46 | 47 | /// 48 | public Interval DomainV { get; set; } 49 | 50 | 51 | /// 52 | public Plane FrameAt(double u, double v) => throw new NotImplementedException(); 53 | 54 | 55 | /// 56 | public double DistanceTo(Point3d point) => throw new NotImplementedException(); 57 | 58 | 59 | /// 60 | public Point3d ClosestPointTo(Point3d point) => throw new NotImplementedException(); 61 | 62 | 63 | /// 64 | public Vector3d NormalAt(double u, double v) => throw new NotImplementedException(); 65 | 66 | 67 | /// 68 | public Point3d PointAt(double u, double v) => throw new NotImplementedException(); 69 | } 70 | } -------------------------------------------------------------------------------- /src/IO/CSVReader.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace Paramdigma.Core.IO 4 | { 5 | /// 6 | /// CSV File reader. 7 | /// 8 | public static class CsvReader { } 9 | } -------------------------------------------------------------------------------- /src/IO/CSVWritter.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace Paramdigma.Core.IO 4 | { 5 | /// 6 | /// CSV File Writter. 7 | /// 8 | public static class CsvWritter { } 9 | } -------------------------------------------------------------------------------- /src/IO/OBJMeshData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | 4 | #pragma warning disable 1591 5 | 6 | namespace Paramdigma.Core.IO 7 | { 8 | public struct OBJMeshData 9 | { 10 | public OBJMeshData( 11 | List vertices, 12 | List> faces, 13 | List> edges, 14 | List> textureCoords, 15 | List> faceTextureCoords, 16 | List normals) 17 | { 18 | this.Vertices = vertices; 19 | this.Faces = faces; 20 | this.Edges = edges; 21 | this.TextureCoords = textureCoords; 22 | this.FaceTextureCoords = faceTextureCoords; 23 | this.Normals = normals; 24 | } 25 | 26 | 27 | public List Vertices { get; } 28 | 29 | public List> Faces { get; } 30 | 31 | public List> Edges { get; } 32 | 33 | public List> TextureCoords { get; } 34 | 35 | public List> FaceTextureCoords { get; } 36 | 37 | public List Normals { get; } 38 | } 39 | } -------------------------------------------------------------------------------- /src/IO/OBJReader.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace Paramdigma.Core.IO 4 | { 5 | /// 6 | /// OBJ File Reader. 7 | /// 8 | public static class ObjReader { } 9 | } -------------------------------------------------------------------------------- /src/IO/OBJWritter.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace Paramdigma.Core.IO 4 | { 5 | /// 6 | /// OBJ File Writter. 7 | /// 8 | public class ObjWritter { } 9 | } -------------------------------------------------------------------------------- /src/IO/OFFMeshData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | 4 | #pragma warning disable 1591 5 | 6 | namespace Paramdigma.Core.IO 7 | { 8 | /// 9 | /// Class containing the resulting mesh data extracted from an .OFF file. 10 | /// 11 | public class OffMeshData 12 | { 13 | /// 14 | /// Gets or sets the mesh vertices. 15 | /// 16 | public List Vertices { get; set; } 17 | 18 | /// 19 | /// Gets or sets the mesh face indices. 20 | /// 21 | public List> Faces { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/IO/OFFReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Paramdigma.Core.Geometry; 4 | 5 | #pragma warning disable 1591 6 | 7 | namespace Paramdigma.Core.IO 8 | { 9 | /// OFF Reader class. 10 | public static class OffReader 11 | { 12 | public static OffResult ReadMeshFromFile(string filePath, out OffMeshData data) 13 | { 14 | var lines = File.ReadAllLines(filePath); 15 | data = new OffMeshData(); 16 | 17 | // Check if first line states OFF format 18 | if (lines[0] != "OFF") 19 | return OffResult.IncorrectFormat; 20 | 21 | // Get second line and extract number of vertices and faces 22 | var initialData = lines[1].Split(' '); 23 | if (!int.TryParse(initialData[0], out var nVertex)) 24 | return OffResult.IncorrectFormat; 25 | 26 | if (!int.TryParse(initialData[1], out var nFaces)) 27 | return OffResult.IncorrectFormat; 28 | 29 | // Check if length of lines correct 30 | if (nVertex + nFaces + 2 != lines.Length) 31 | return OffResult.IncorrectFormat; 32 | 33 | // Iterate through all the lines containing the mesh data 34 | const int start = 2; 35 | var vertices = new List(); 36 | var faces = new List>(); 37 | 38 | for (var i = start; i < lines.Length; i++) 39 | { 40 | if (i < nVertex + start) 41 | { 42 | // Extract vertices 43 | var coords = new List(); 44 | 45 | // Iterate over the string fragments and convert them to numbers 46 | foreach (var ptStr in lines[i].Split(' ')) 47 | { 48 | if (!double.TryParse(ptStr, out var ptCoord)) 49 | return OffResult.IncorrectVertex; 50 | coords.Add(ptCoord); 51 | } 52 | 53 | vertices.Add(new Point3d(coords[0], coords[1], coords[2])); 54 | } 55 | else if (i < nVertex + nFaces + start) 56 | { 57 | // Extract faces 58 | // In OFF, faces come with a first number determining the number of vertices in that face 59 | var vertexIndexes = new List(); 60 | 61 | var faceStrings = lines[i].Split(' '); 62 | 63 | // Get first int that represents vertex count of face 64 | if (!int.TryParse(faceStrings[0], out var _)) 65 | return OffResult.IncorrectFace; 66 | 67 | for (var f = 1; f < faceStrings.Length; f++) 68 | { 69 | if (!int.TryParse(faceStrings[f], out var vertIndex)) 70 | return OffResult.IncorrectFace; 71 | 72 | vertexIndexes.Add(vertIndex); 73 | } 74 | 75 | faces.Add(vertexIndexes); 76 | } 77 | } 78 | 79 | // Set data output 80 | data.Vertices = vertices; 81 | data.Faces = faces; 82 | 83 | return OffResult.Ok; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/IO/OFFResult.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | namespace Paramdigma.Core.IO 4 | { 5 | /// 6 | /// Enum containing the result of the OFF conversion. 7 | /// 8 | public enum OffResult 9 | { 10 | Ok, 11 | IncorrectFormat, 12 | IncorrectVertex, 13 | IncorrectFace, 14 | NonMatchingVerticesSize, 15 | NonMatchingFacesSize, 16 | FileNotFound 17 | } 18 | } -------------------------------------------------------------------------------- /src/IO/OffWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Paramdigma.Core.Geometry; 3 | 4 | #pragma warning disable 1591 5 | 6 | namespace Paramdigma.Core.IO 7 | { 8 | /// OFF format writer class. 9 | public static class OffWriter 10 | { 11 | /// 12 | /// Write a Half-Edge mesh to a .OFF file. 13 | /// 14 | /// Half-edge mesh to export. 15 | /// Path to save the file to. 16 | /// 17 | public static OffResult WriteMeshToFile(Mesh mesh, string filePath) 18 | { 19 | var offLines = new string[mesh.Vertices.Count + mesh.Faces.Count + 2]; 20 | 21 | const string offHead = "OFF"; 22 | offLines[0] = offHead; 23 | var offCount = mesh.Vertices.Count + " " + mesh.Faces.Count + " 0"; 24 | offLines[1] = offCount; 25 | 26 | var count = 2; 27 | foreach (var vertex in mesh.Vertices) 28 | { 29 | var vText = vertex.X + " " + vertex.Y + " " + vertex.Z; 30 | offLines[count] = vText; 31 | count++; 32 | } 33 | 34 | foreach (var face in mesh.Faces) 35 | { 36 | if (!face.IsBoundaryLoop()) 37 | { 38 | var vertices = face.AdjacentVertices(); 39 | var faceString = vertices.Count.ToString(); 40 | 41 | foreach (var v in face.AdjacentVertices()) 42 | faceString = faceString + " " + v.Index; 43 | 44 | offLines[count] = faceString; 45 | count++; 46 | } 47 | } 48 | 49 | File.WriteAllLines(filePath, offLines); 50 | return OffResult.Ok; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/LinearAlgebra/LeastSquaresLinearFit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Paramdigma.Core.Geometry; 4 | 5 | namespace Paramdigma.Core.LinearAlgebra 6 | { 7 | /// 8 | /// Fit a line through a set of 2-dimensional points. 9 | /// 10 | public static class LeastSquaresLinearFit 11 | { 12 | // Find the least squares linear fit. 13 | // Return the total error. 14 | // Found at: http://csharphelper.com/blog/2014/10/find-a-linear-least-squares-fit-for-a-set-of-points-in-c/ 15 | 16 | 17 | /// 18 | /// Find the least squares best fitting line to the given points. 19 | /// 20 | /// The points to fit the line through. 21 | /// Height. 22 | /// Slope. 23 | /// 24 | public static double FindLinearLeastSquaresFit( 25 | List points, 26 | out double m, 27 | out double b) 28 | { 29 | // Perform the calculation. 30 | // Find the values S1, Sx, Sy, Sxx, and Sxy. 31 | double s1 = points.Count; 32 | double sx = 0, sy = 0, sxx = 0, sxy = 0; 33 | 34 | foreach (var pt in points) 35 | { 36 | sx += pt.X; 37 | sy += pt.Y; 38 | sxx += pt.X * pt.X; 39 | sxy += pt.X * pt.Y; 40 | } 41 | 42 | // Solve for m and b. 43 | m = (sxy * s1 - sx * sy) / (sxx * s1 - sx * sx); 44 | b = (sxy * sx - sy * sxx) / (sx * sx - s1 * sxx); 45 | 46 | return Math.Sqrt(ErrorSquared(points, m, b)); 47 | } 48 | 49 | 50 | // Return the error squared. 51 | private static double ErrorSquared(List points, double m, double b) 52 | { 53 | double total = 0; 54 | foreach (var pt in points) 55 | { 56 | var dy = pt.Y - (m * pt.X + b); 57 | total += dy * dy; 58 | } 59 | 60 | return total; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/LinearAlgebra/Triplet.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | #pragma warning disable 1591 4 | 5 | namespace Paramdigma.Core.LinearAlgebra 6 | { 7 | /// 8 | /// Represents a set of data in a sparse matrix. 9 | /// 10 | public class Triplet 11 | { 12 | // Constructor 13 | public Triplet(int m, int n) 14 | { 15 | this.M = m; 16 | this.N = n; 17 | this.Values = new List(); 18 | } 19 | // Public fields 20 | 21 | 22 | /// 23 | /// Gets values held by this triplet. 24 | /// 25 | /// 26 | public List Values { get; } 27 | 28 | public int M { get; } 29 | 30 | public int N { get; } 31 | 32 | 33 | // Methods 34 | public void AddEntry(double value, int m, int n) 35 | { 36 | var tD = new TripletData {Value = value, Row = m, Column = n}; 37 | 38 | this.Values.Add(tD); 39 | } 40 | } 41 | 42 | public struct TripletData 43 | { 44 | public int Row { get; set; } 45 | 46 | public int Column { get; set; } 47 | 48 | public double Value { get; set; } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Optimization/GradientDescentOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Optimization 2 | { 3 | /// 4 | /// Contains the different options of a Gradient Descent minimization. 5 | /// 6 | public struct GradientDescentOptions 7 | { 8 | /// 9 | /// Threshold to stop minimization. 10 | /// 11 | public double Limit; 12 | 13 | /// 14 | /// Maximum iterations for the gradient descent. 15 | /// 16 | public int MaxIterations; 17 | 18 | /// 19 | /// Step size to compute partial derivatives. 20 | /// 21 | public double DerivativeStep; 22 | 23 | /// 24 | /// Scaling factor for the gradient. Effectively speeds up or down the minimization. 25 | /// 26 | public double LearningRate; 27 | 28 | /// 29 | /// Minimum error to consider the results acceptable. 30 | /// 31 | public double ErrorThreshold; 32 | 33 | 34 | /// 35 | /// Initializes a new instance of the struct given an 36 | /// existing one. 37 | /// 38 | /// Options to duplicate. 39 | public GradientDescentOptions(GradientDescentOptions options) 40 | { 41 | this.Limit = options.Limit; 42 | this.MaxIterations = options.MaxIterations; 43 | this.DerivativeStep = options.DerivativeStep; 44 | this.LearningRate = options.LearningRate; 45 | this.ErrorThreshold = options.ErrorThreshold; 46 | } 47 | 48 | 49 | // TODO: Fill in this fields! 50 | 51 | 52 | /// 53 | /// Initializes a new instance of the struct given all it's 54 | /// values individually. 55 | /// 56 | /// 57 | /// 58 | /// 59 | /// 60 | /// 61 | public GradientDescentOptions( 62 | double threshold, 63 | int maxIterations, 64 | double derivativeStep, 65 | double learningRate, 66 | double errorThreshold) 67 | { 68 | this.Limit = threshold; 69 | this.MaxIterations = maxIterations; 70 | this.DerivativeStep = derivativeStep; 71 | this.LearningRate = learningRate; 72 | this.ErrorThreshold = errorThreshold; 73 | } 74 | 75 | 76 | /// 77 | /// Gets a GradientDescentOptions instance with the default values. 78 | /// 79 | public static GradientDescentOptions Default => 80 | new GradientDescentOptions(0.001, 10000, 0.01, 20, .01); 81 | 82 | /// 83 | /// Gets a GradientDescentOptions instance with small values. 84 | /// 85 | /// 86 | public static GradientDescentOptions DefaultSmall => 87 | new GradientDescentOptions(0.0001, 10000, 0.02, 40, .001); 88 | } 89 | } -------------------------------------------------------------------------------- /src/Optimization/GradientDescentResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Paramdigma.Core.Optimization 4 | { 5 | /// 6 | /// Contains the result values of a Gradient Descent minimization. 7 | /// 8 | public struct GradientDescentResult 9 | { 10 | /// 11 | /// Resulting values after gradient descen minimization. 12 | /// 13 | public List Values; 14 | 15 | /// 16 | /// Final gradient descent error. 17 | /// 18 | public double Error; 19 | 20 | /// 21 | /// Final length of the gradient. 22 | /// 23 | public double GradientLength; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Optimization/KMeansCluster.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using Paramdigma.Core.Geometry; 4 | 5 | namespace Paramdigma.Core.Optimization 6 | { 7 | /// 8 | /// Represents a vector cluster for the K-Means Clustering Algorithm. 9 | /// 10 | public class KMeansCluster : IList 11 | { 12 | private readonly IList list = new List(); 13 | 14 | /// 15 | /// Gets or sets the vector at the given index. 16 | /// 17 | /// Index of the desired object. 18 | public VectorNd this[int index] 19 | { 20 | get => this.list[index]; 21 | set => this.list[index] = value; 22 | } 23 | 24 | /// 25 | /// Gets the amount of clusters. 26 | /// 27 | public int Count => this.list.Count; 28 | 29 | /// 30 | /// Gets a value indicating whether the cluster is readOnly. 31 | /// 32 | public bool IsReadOnly => this.list.IsReadOnly; 33 | 34 | 35 | /// 36 | /// Add a new vector to the cluster. 37 | /// 38 | /// Vector to add. 39 | public void Add(VectorNd item) => this.list.Add(item); 40 | 41 | 42 | /// 43 | public void Clear() => this.list.Clear(); 44 | 45 | 46 | /// 47 | public bool Contains(VectorNd item) => this.list.Contains(item); 48 | 49 | 50 | /// 51 | public void CopyTo(VectorNd[] array, int arrayIndex) => this.list.CopyTo(array, arrayIndex); 52 | 53 | 54 | /// 55 | public IEnumerator GetEnumerator() => this.list.GetEnumerator(); 56 | 57 | 58 | /// 59 | public int IndexOf(VectorNd item) => this.list.IndexOf(item); 60 | 61 | 62 | /// 63 | public void Insert(int index, VectorNd item) => this.list.Insert(index, item); 64 | 65 | 66 | /// 67 | public bool Remove(VectorNd item) => this.list.Remove(item); 68 | 69 | 70 | /// 71 | public void RemoveAt(int index) => this.list.RemoveAt(index); 72 | 73 | 74 | IEnumerator IEnumerable.GetEnumerator() => this.list.GetEnumerator(); 75 | 76 | 77 | /// 78 | /// Computes the average of this cluster. 79 | /// 80 | /// Average vector of the current cluster. 81 | public VectorNd Average() 82 | { 83 | if (this.list.Count == 0) 84 | return new VectorNd(0); 85 | if (this.list.Count == 1) 86 | return this.list[0]; 87 | 88 | var result = new VectorNd(this.list[0].Dimension); 89 | foreach (var vector in this.list) 90 | result += vector; 91 | 92 | result /= this.list.Count; 93 | return result; 94 | } 95 | 96 | 97 | /// 98 | public override string ToString() => "Paramdigma.Core.Cluster[" + this.Count + "]"; 99 | } 100 | } -------------------------------------------------------------------------------- /src/Paramdigma.Core.Rules.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | c 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Paramdigma.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Computational Geometry library for .NET 7 | netstandard2.0 8 | Paramdigma Core 9 | https://paramdigma.com/Core/ 10 | https://github.com/Paramdigma/Core/blob/master/LICENSE 11 | git 12 | 8 13 | 0.1.2 14 | 15 | 16 | 17 | Paramdigma.Core 18 | 0.1.1 19 | Alan Rynne 20 | Paramdigma 21 | Computational Geometry library for .Net 22 | https://github.com/Paramdigma/Core 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | Paramdigma.Core.Rules.ruleset 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Spatial/Delaunay.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Paramdigma.Core.Spatial; 4 | 5 | namespace Paramdigma.Core.Geometry 6 | { 7 | /// 8 | /// Class holding all the delaunay and Voronoi classes in 2 dimensions. 9 | /// 10 | public static class Delaunay 11 | { 12 | /// 13 | /// Compute the delaunay triangulation of a given list of points. 14 | /// 15 | /// Points to find delaunay tessellation. 16 | /// Border to start from. 17 | /// List of . 18 | public static IEnumerable Compute( 19 | IEnumerable points, 20 | IEnumerable border) 21 | { 22 | var triangulation = new List(border); 23 | foreach (var point in points) 24 | { 25 | var badTriangles = FindBadTriangles(point, triangulation).ToList(); 26 | var polygon = FindHoleBoundaries(badTriangles); 27 | foreach (var triangle in badTriangles) 28 | { 29 | foreach (var vertex in triangle.Vertices) 30 | vertex.AdjacentTriangles.Remove(triangle); 31 | 32 | if (triangulation.Contains(triangle)) 33 | triangulation.Remove(triangle); 34 | } 35 | 36 | triangulation.AddRange( 37 | polygon.Select( 38 | edge => new DelaunayTriangle(point, (DelaunayPoint)edge.StartPoint, (DelaunayPoint)edge.EndPoint))); 39 | } 40 | 41 | return triangulation; 42 | } 43 | 44 | 45 | /// 46 | /// Computes the Voronoi diagram of a given Delaunay triangulation as a list of 47 | /// instances. 48 | /// 49 | /// Delaunay triangulation. 50 | /// Collection of lines representing the Voronoi cells. 51 | public static IEnumerable Voronoi(IEnumerable triangulation) 52 | => 53 | from triangle in triangulation 54 | from neighbour in triangle.TrianglesWithSharedEdges() 55 | select new Line2d(triangle.Circumcenter, neighbour.Circumcenter); 56 | 57 | 58 | private static IEnumerable FindHoleBoundaries( 59 | IEnumerable badTriangles) 60 | { 61 | var boundaryEdges = new List(); 62 | var duplicateEdges = new List(); 63 | foreach (var triangle in badTriangles) 64 | { 65 | for (var i = 0; i < triangle.Vertices.Count; i++) 66 | { 67 | var e = new DelaunayEdge( 68 | triangle.Vertices[i], 69 | triangle.Vertices[(i + 1) % triangle.Vertices.Count]); 70 | if (!boundaryEdges.Contains(e)) 71 | boundaryEdges.Add(e); 72 | else 73 | duplicateEdges.Add(e); 74 | } 75 | } 76 | 77 | for (var i = boundaryEdges.Count - 1; i >= 0; i--) 78 | { 79 | var e = boundaryEdges[i]; 80 | if (duplicateEdges.Contains(e)) 81 | boundaryEdges.Remove(e); 82 | } 83 | 84 | return boundaryEdges; 85 | } 86 | 87 | 88 | private static IEnumerable FindBadTriangles( 89 | Point2d point, 90 | IEnumerable triangles) 91 | => triangles.Where(triangle => triangle.IsPointInsideCircumcircle(point)); 92 | } 93 | } -------------------------------------------------------------------------------- /src/Spatial/DelaunayEdge.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | 3 | namespace Paramdigma.Core.Spatial 4 | { 5 | /// 6 | /// Represents a connection between two points in a Delaunay triangulation. 7 | /// 8 | public class DelaunayEdge: Line2d 9 | { 10 | 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// Start point. 15 | /// End point. 16 | public DelaunayEdge(DelaunayPoint startPoint, DelaunayPoint endPoint): base(startPoint, endPoint) 17 | { 18 | } 19 | 20 | 21 | /// 22 | public override bool Equals(object obj) 23 | { 24 | if (!(obj is DelaunayEdge edge)) 25 | return false; 26 | var samePoints = this.StartPoint == edge.StartPoint && this.EndPoint == edge.EndPoint; 27 | var samePointsReversed = 28 | this.StartPoint == edge.EndPoint && this.EndPoint == edge.StartPoint; 29 | return samePoints || samePointsReversed; 30 | } 31 | 32 | 33 | /// 34 | public override int GetHashCode() 35 | { 36 | var hCode = ( int ) this.StartPoint.X 37 | ^ ( int ) this.StartPoint.Y 38 | ^ ( int ) this.EndPoint.X 39 | ^ ( int ) this.EndPoint.Y; 40 | return hCode.GetHashCode(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Spatial/DelaunayPoint.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | 4 | namespace Paramdigma.Core.Spatial 5 | { 6 | /// 7 | /// Represents a point in a delaunay triangulation with adjacency information. 8 | /// 9 | public class DelaunayPoint : Point2d 10 | { 11 | /// 12 | /// List of adjacent triangles of this point. 13 | /// 14 | public readonly List AdjacentTriangles; 15 | 16 | 17 | /// 18 | /// Initializes a new instance of the class from it's coordinates. 19 | /// 20 | /// X Coordinate. 21 | /// Y Coordinate. 22 | public DelaunayPoint(double x, double y) : base(x, y) => 23 | this.AdjacentTriangles = new List(); 24 | 25 | 26 | /// 27 | /// Initializes a new instance of the class from a 28 | /// instance. 29 | /// 30 | /// Point to create from. 31 | public DelaunayPoint(Point2d point) : base(point.X, point.Y) { } 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /src/Spatial/Octree.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Spatial 2 | { 3 | /// 4 | /// Class for computing Octree spatial searches. 5 | /// 6 | public class Octree 7 | { 8 | // TODO: This class is empty! 9 | } 10 | } -------------------------------------------------------------------------------- /src/Spatial/PointCloud.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Paramdigma.Core.Spatial 5 | { 6 | /// 7 | /// Represents a collection of points with a color assigned to them. 8 | /// TODO: This is only a basic data structure for now. 9 | /// 10 | public class PointCloud : IList 11 | { 12 | /// 13 | /// Constructs a new point-cloud from a list of point cloud members.. 14 | /// 15 | /// List of point-cloud members. 16 | public PointCloud(List points) => this.Points = points; 17 | 18 | 19 | private List Points { get; } 20 | 21 | 22 | /// 23 | public IEnumerator GetEnumerator() => this.Points.GetEnumerator(); 24 | 25 | 26 | IEnumerator IEnumerable.GetEnumerator() => (( IEnumerable ) this.Points).GetEnumerator(); 27 | 28 | 29 | /// 30 | public void Add(PointCloudMember item) => this.Points.Add(item); 31 | 32 | 33 | /// 34 | public void Clear() => this.Points.Clear(); 35 | 36 | 37 | /// 38 | public bool Contains(PointCloudMember item) => this.Points.Contains(item); 39 | 40 | 41 | /// 42 | public void CopyTo(PointCloudMember[] array, int arrayIndex) => 43 | this.Points.CopyTo(array, arrayIndex); 44 | 45 | 46 | /// 47 | public bool Remove(PointCloudMember item) => this.Points.Remove(item); 48 | 49 | 50 | /// 51 | public int Count => this.Points.Count; 52 | 53 | /// 54 | public bool IsReadOnly => (( ICollection ) this.Points).IsReadOnly; 55 | 56 | 57 | /// 58 | public int IndexOf(PointCloudMember item) => this.Points.IndexOf(item); 59 | 60 | 61 | /// 62 | public void Insert(int index, PointCloudMember item) => this.Points.Insert(index, item); 63 | 64 | 65 | /// 66 | public void RemoveAt(int index) => this.Points.RemoveAt(index); 67 | 68 | 69 | /// 70 | public PointCloudMember this[int index] 71 | { 72 | get => this.Points[index]; 73 | set => this.Points[index] = value; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/Spatial/PointCloudMember.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Paramdigma.Core.Geometry; 3 | 4 | namespace Paramdigma.Core.Spatial 5 | { 6 | /// 7 | /// Class representing a point contained in a point cloud. 8 | /// 9 | public class PointCloudMember : BasePoint 10 | { 11 | /// 12 | /// Gets or sets the color at this point. 13 | /// 14 | /// The current color if set, defaults to white. 15 | public Color Color { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Spatial/Quadtree.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | 4 | namespace Paramdigma.Core.Spatial 5 | { 6 | /// 7 | /// Class to compute 2 dimensional spatial searches by quad subdivision. 8 | /// 9 | public class QuadTree 10 | { 11 | /// 12 | /// Boundary of this QuadTree. 13 | /// 14 | public readonly Rectangle2d Boundary; 15 | 16 | /// 17 | /// Gets or sets the list of points of this QuadTree. 18 | /// 19 | public readonly List Points; 20 | 21 | private readonly double threshold; 22 | 23 | private QuadTree northEast; 24 | private QuadTree northWest; 25 | private QuadTree southEast; 26 | private QuadTree southWest; 27 | 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// Boundary of this QuadTree. 33 | /// Smallest allowed dimension. 34 | public QuadTree(Rectangle2d boundary, double threshold) 35 | { 36 | this.Boundary = boundary; 37 | this.Points = new List(); 38 | this.threshold = threshold; 39 | } 40 | 41 | 42 | /// 43 | /// Insert a point in the QuadTree. 44 | /// 45 | /// Point to insert. 46 | /// True if point was inserted. 47 | public bool Insert(Point2d point) 48 | { 49 | if (!this.Boundary.ContainsPoint(point)) 50 | return false; 51 | 52 | if (this.Boundary.XDomain.Length < this.threshold 53 | || this.Boundary.YDomain.Length < this.threshold) 54 | { 55 | this.Points.Add(point); 56 | return true; 57 | } 58 | 59 | this.Subdivide(); 60 | 61 | if (this.northEast.Insert(point) 62 | || this.northWest.Insert(point) 63 | || this.southWest.Insert(point) 64 | || this.southEast.Insert(point)) 65 | return true; 66 | 67 | return false; 68 | } 69 | 70 | 71 | /// 72 | /// Query the QuadTree for all points contained in this range. 73 | /// 74 | /// Range to look for. 75 | /// Points contained in the range. 76 | public List QueryRange(Rectangle2d range) 77 | { 78 | var pointsInRange = new List(); 79 | 80 | if (!this.Boundary.IntersectsBox(range)) 81 | return pointsInRange; 82 | 83 | this.Points.ForEach( 84 | pt => 85 | { 86 | if (range.ContainsPoint(pt)) 87 | pointsInRange.Add(pt); 88 | }); 89 | 90 | // If we reached threshold return 91 | if (this.Boundary.XDomain.Length < this.threshold 92 | || this.Boundary.YDomain.Length < this.threshold) 93 | return pointsInRange; 94 | 95 | if (this.southWest != null) 96 | { 97 | pointsInRange.AddRange(this.southWest.QueryRange(range)); 98 | pointsInRange.AddRange(this.southEast.QueryRange(range)); 99 | pointsInRange.AddRange(this.northWest.QueryRange(range)); 100 | pointsInRange.AddRange(this.northEast.QueryRange(range)); 101 | } 102 | 103 | return pointsInRange; 104 | } 105 | 106 | 107 | private void Subdivide() 108 | { 109 | this.southWest = new QuadTree( 110 | new Rectangle2d(this.Boundary.BottomLeft, this.Boundary.Center), 111 | this.threshold); 112 | this.northWest = new QuadTree( 113 | new Rectangle2d(this.Boundary.MidLeft, this.Boundary.MidTop), 114 | this.threshold); 115 | this.southEast = new QuadTree( 116 | new Rectangle2d(this.Boundary.MidBottom, this.Boundary.MidRight), 117 | this.threshold); 118 | this.northEast = new QuadTree( 119 | new Rectangle2d(this.Boundary.Center, this.Boundary.TopRight), 120 | this.threshold); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/Utility/Convert.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | 3 | namespace Paramdigma.Core 4 | { 5 | /// 6 | /// Static class to handle unit and type conversions. 7 | /// 8 | public static class Convert 9 | { 10 | /// 11 | /// Compute barycentric coordinates (u, v, w) for 12 | /// point p with respect to triangle (a, b, c). 13 | /// 14 | /// Point to convert. 15 | /// First point of triangle. 16 | /// Second point of triangle. 17 | /// Third point of triangle. 18 | /// 19 | public static double[] Point3dToBarycentric(Point3d p, Point3d a, Point3d b, Point3d c) 20 | { 21 | Vector3d v0 = b - a, v1 = c - a, v2 = p - a; 22 | 23 | var den = v0.X * v1.Y - v1.X * v0.Y; 24 | 25 | var v = (v2.X * v1.Y - v1.X * v2.Y) / den; 26 | var w = (v0.X * v2.Y - v2.X * v0.Y) / den; 27 | var u = 1.0 - v - w; 28 | 29 | double[] result = {u, v, w}; 30 | 31 | return result; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Utility/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using Newtonsoft.Json; 5 | 6 | namespace Paramdigma.Core 7 | { 8 | /// 9 | /// Multi-layered struct that holds the library settings. 10 | /// 11 | public static class Settings 12 | { 13 | private static int tesselationLevel = 10; 14 | 15 | /// 16 | /// Gets the minimum value allowed when using this library. 17 | /// 18 | public static double Tolerance { get; private set; } = 0.0000001; 19 | 20 | /// 21 | /// Gets how many decimals are allowed when using the library. 22 | /// 23 | public static int MaxDecimals 24 | { 25 | get 26 | { 27 | var t = Tolerance.ToString("N14"); 28 | return t.Substring(t.IndexOf(".", StringComparison.Ordinal) + 1).IndexOf("1", StringComparison.Ordinal) + 1; 29 | } 30 | } 31 | 32 | 33 | /// 34 | /// Gets the default tessellation level when converting nurbs to meshes. 35 | /// 36 | /// Integer representing the default tessellation level. 37 | public static int GetDefaultTesselationLevel() => tesselationLevel; 38 | 39 | 40 | /// 41 | /// Sets the default tessellation level when converting nurbs to meshes. 42 | /// 43 | private static void SetDefaultTesselationLevel(int value) => tesselationLevel = value; 44 | 45 | 46 | /// 47 | /// Modifies the tolerance and computes the maxDecimals value accordingly. 48 | /// 49 | /// Desired tolerance. 50 | public static void SetTolerance(double tolerance) => Tolerance = tolerance; 51 | 52 | 53 | /// 54 | /// Reset the Settings to it's default values. 55 | /// 56 | public static void Reset() 57 | { 58 | var assembly = typeof(Settings).GetTypeInfo().Assembly; 59 | using (var stream = 60 | assembly.GetManifestResourceStream("Paramdigma.Core.Data.Settings.json")) 61 | { 62 | using (var reader = new StreamReader( 63 | stream ?? throw new InvalidOperationException("Could not get settings."))) 64 | { 65 | var result = reader.ReadToEnd(); 66 | var json = JsonConvert.DeserializeObject(result); 67 | SetTolerance(json.Tolerance); 68 | SetDefaultTesselationLevel(json.DefaultTesselation); 69 | } 70 | } 71 | } 72 | } 73 | 74 | /// 75 | /// This struct holds the settings from the embedded json file. It is only used to reset. 76 | /// 77 | internal struct EmbeddedSettings 78 | { 79 | public double Tolerance; 80 | public int DefaultTesselation; 81 | } 82 | } -------------------------------------------------------------------------------- /tests/Collections/IntervalTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Collections; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests 6 | { 7 | public class IntervalTests 8 | { 9 | [Fact] 10 | public void Can_CheckAndModifyDirection() 11 | { 12 | var i = new Interval(1, 4); 13 | var dir = i.HasInvertedDirection; 14 | i.FlipDirection(); 15 | var dir2 = i.HasInvertedDirection; 16 | Assert.True(dir != dir2); 17 | } 18 | 19 | 20 | [Fact] 21 | public void Can_CheckContainment() 22 | { 23 | var i = new Interval(0.455, 4.134); 24 | const double n1 = 0.0; 25 | const double n2 = 5.0; 26 | const double n3 = 2.33; 27 | Assert.False(i.Contains(n1)); 28 | Assert.False(i.Contains(n2)); 29 | Assert.True(i.Contains(n3)); 30 | } 31 | 32 | 33 | [Fact] 34 | public void Can_CropNumbers() 35 | { 36 | var i = new Interval(0.455, 4.134); 37 | const double n1 = 0.0; 38 | const double n2 = 5.0; 39 | const double n3 = 2.33; 40 | var n1C = i.Crop(n1); 41 | var n2C = i.Crop(n2); 42 | var n3C = i.Crop(n3); 43 | Assert.True(Math.Abs(n1C - i.Start) < Settings.Tolerance); 44 | Assert.True(Math.Abs(n2C - i.End) < Settings.Tolerance); 45 | Assert.True(Math.Abs(n3C - n3) < Settings.Tolerance); 46 | } 47 | 48 | 49 | [Fact] 50 | public void Can_RemapNumbers() 51 | { 52 | var i = new Interval(1, 3); 53 | const double n = 2.0; 54 | var nMap = i.RemapToUnit(n); 55 | Assert.True(Math.Abs(nMap - 0.5) < Settings.Tolerance); 56 | var nRemap = i.RemapFromUnit(nMap); 57 | Assert.True(Math.Abs(n - nRemap) < Settings.Tolerance); 58 | } 59 | 60 | 61 | [Fact] 62 | public void CanCreate_Interval() 63 | { 64 | var i0 = Interval.Unit; 65 | Assert.Equal(1, i0.Length); 66 | Assert.Throws(() => new Interval(double.NaN, 1)); 67 | Assert.Throws(() => new Interval(0, double.NaN)); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /tests/Curves/LevelSetsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Curves; 3 | using Paramdigma.Core.Geometry; 4 | using Xunit; 5 | 6 | namespace Paramdigma.Core.Tests.Curves 7 | { 8 | public class LevelSetsTests 9 | { 10 | public Mesh Triangle 11 | { 12 | get 13 | { 14 | var key = "scalar-1"; 15 | var ptA = new MeshVertex(0, 0, 0); 16 | ptA.UserValues[key] = 0; 17 | var ptB = new MeshVertex(1, 0, 0); 18 | ptB.UserValues[key] = 0; 19 | var ptC = new MeshVertex(0.5, 1, 1); 20 | ptC.UserValues[key] = 1; 21 | var vertices = new List {ptA, ptB, ptC}; 22 | var face = new List {0, 1, 2}; 23 | var mesh = new Mesh(vertices, new List> {face}); 24 | return mesh; 25 | } 26 | } 27 | 28 | 29 | [Fact] 30 | public void CanCompute_GradientInFace_ReturnsValidVector() 31 | { 32 | var c = LevelSets.ComputeGradientField("scalar-1", this.Triangle); 33 | Assert.Equal(this.Triangle.Faces.Count, c.Count); 34 | Assert.Equal(new Vector3d(0, -1, -1).Unit(), c[0].Unit()); 35 | } 36 | 37 | 38 | [Fact] 39 | public void CanCompute_LevelInFace_ReturnsValidLine() 40 | { 41 | LevelSets.ComputeLevels( 42 | "scalar-1", 43 | new List {0.5}, 44 | this.Triangle, 45 | out var levelSets); 46 | Assert.NotEmpty(levelSets); 47 | var v = ( Vector3d ) levelSets[0][0]; 48 | Assert.Equal(new Vector3d(1, 0, 0), v.Unit()); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /tests/Data/meshes/cube.off: -------------------------------------------------------------------------------- 1 | OFF 2 | 8 12 0 3 | -0.5 -0.5 -0.5 4 | 0.5 -0.5 -0.5 5 | 0.5 0.5 -0.5 6 | -0.5 0.5 -0.5 7 | -0.5 -0.5 0.5 8 | 0.5 -0.5 0.5 9 | 0.5 0.5 0.5 10 | -0.5 0.5 0.5 11 | 3 0 2 1 12 | 3 0 3 2 13 | 3 5 0 1 14 | 3 5 4 0 15 | 3 2 6 1 16 | 3 3 6 2 17 | 3 5 1 6 18 | 3 5 6 4 19 | 3 0 7 3 20 | 3 0 4 7 21 | 3 3 7 6 22 | 3 6 7 4 23 | -------------------------------------------------------------------------------- /tests/Data/meshes/sphere.off: -------------------------------------------------------------------------------- 1 | OFF 2 | 22 40 0 3 | 0 0 -10 4 | 5.87785243988037 0 -8.09016990661621 5 | 1.81635630130768 5.59016990661621 -8.09016990661621 6 | -4.75528240203857 3.45491504669189 -8.09016990661621 7 | -4.75528240203857 -3.45491504669189 -8.09016990661621 8 | 1.81635630130768 -5.59016990661621 -8.09016990661621 9 | 9.51056480407715 0 -3.09016990661621 10 | 2.93892621994019 9.04508495330811 -3.09016990661621 11 | -7.69420862197876 5.59016990661621 -3.09016990661621 12 | -7.69420862197876 -5.59016990661621 -3.09016990661621 13 | 2.93892621994019 -9.04508495330811 -3.09016990661621 14 | 9.51056480407715 0 3.09016990661621 15 | 2.93892621994019 9.04508495330811 3.09016990661621 16 | -7.69420862197876 5.59016990661621 3.09016990661621 17 | -7.69420862197876 -5.59016990661621 3.09016990661621 18 | 2.93892621994019 -9.04508495330811 3.09016990661621 19 | 5.87785243988037 0 8.09016990661621 20 | 1.81635630130768 5.59016990661621 8.09016990661621 21 | -4.75528240203857 3.45491504669189 8.09016990661621 22 | -4.75528240203857 -3.45491504669189 8.09016990661621 23 | 1.81635630130768 -5.59016990661621 8.09016990661621 24 | 0 0 10 25 | 3 1 0 2 26 | 3 2 0 3 27 | 3 3 0 4 28 | 3 4 0 5 29 | 3 5 0 1 30 | 3 7 1 2 31 | 3 8 2 3 32 | 3 9 3 4 33 | 3 10 4 5 34 | 3 6 5 1 35 | 3 12 6 7 36 | 3 13 7 8 37 | 3 14 8 9 38 | 3 14 9 10 39 | 3 11 10 6 40 | 3 17 11 12 41 | 3 18 12 13 42 | 3 19 13 14 43 | 3 19 14 15 44 | 3 16 15 11 45 | 3 21 16 17 46 | 3 21 17 18 47 | 3 21 18 19 48 | 3 21 19 20 49 | 3 21 20 16 50 | 3 6 1 7 51 | 3 7 2 8 52 | 3 8 3 9 53 | 3 9 4 10 54 | 3 10 5 6 55 | 3 11 6 12 56 | 3 12 7 13 57 | 3 13 8 14 58 | 3 14 10 15 59 | 3 15 10 11 60 | 3 16 11 17 61 | 3 17 12 18 62 | 3 18 13 19 63 | 3 19 15 20 64 | 3 20 15 16 65 | -------------------------------------------------------------------------------- /tests/Extensions/ListExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Extensions; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Extensions 6 | { 7 | public class ListExtensionsTests 8 | { 9 | [Fact] 10 | public void CanCreate_Repeated() 11 | { 12 | for (var i = 1; i < 10; i++) 13 | { 14 | var list2 = Lists.Repeated(new Point3d(1, 1, 1), 4); 15 | list2.ForEach(pt => Assert.Equal(new Point3d(1, 1, 1), pt)); 16 | } 17 | } 18 | 19 | 20 | [Fact] 21 | public void CanCreate_RepeatedDefault() 22 | { 23 | for (var i = 1; i < 10; i++) 24 | { 25 | var list2 = Lists.RepeatedDefault(i); 26 | Assert.True(list2.Count == i); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Geometry/2D/DelaunayTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Paramdigma.Core.Geometry; 5 | using Paramdigma.Core.Spatial; 6 | using Xunit; 7 | 8 | namespace Paramdigma.Core.Tests.Geometry 9 | { 10 | public class DelaunayTests 11 | { 12 | [Fact] 13 | public void CanComputeDelaunay() 14 | { 15 | var maxX = 100; 16 | var maxY = 100; 17 | var point0 = new DelaunayPoint(0, 0); 18 | var point1 = new DelaunayPoint(0, maxY); 19 | var point2 = new DelaunayPoint(maxX, maxY); 20 | var point3 = new DelaunayPoint(maxX, 0); 21 | var point4 = new DelaunayPoint(maxX / 2.0, maxY / 2.0); 22 | 23 | var triangle1 = new DelaunayTriangle(point0, point1, point2); 24 | var triangle2 = new DelaunayTriangle(point0, point2, point3); 25 | var border = new List {triangle1, triangle2}; 26 | 27 | var delaunay = Delaunay.Compute( 28 | new List 29 | { 30 | point0, point1, point2, point3, point4 31 | }, 32 | border 33 | ) 34 | .ToList(); 35 | Assert.True(delaunay.Count == 4); 36 | 37 | var voronoi = Delaunay.Voronoi(delaunay); 38 | // TODO: We expect 8 because it currently outputs the lines repeated twice. 39 | Assert.True(voronoi.ToList().Count == 8); 40 | } 41 | 42 | 43 | private List GeneratePoints( 44 | int ammount, 45 | double maxX, 46 | double maxY, 47 | out List border) 48 | { 49 | var point0 = new DelaunayPoint(0, 0); 50 | var point1 = new DelaunayPoint(0, maxY); 51 | var point2 = new DelaunayPoint(maxX, maxY); 52 | var point3 = new DelaunayPoint(maxX, 0); 53 | var triangle1 = new DelaunayTriangle(point0, point1, point2); 54 | var triangle2 = new DelaunayTriangle(point0, point2, point3); 55 | border = new List {triangle1, triangle2}; 56 | var rnd = new Random(); 57 | var points = new List(); 58 | for (var i = 0; i < ammount - 4; i++) 59 | points.Add(RandomPoint(rnd, 0, maxX)); 60 | return points; 61 | } 62 | 63 | 64 | private static DelaunayPoint RandomPoint( 65 | Random randGenerator, 66 | double minValue, 67 | double maxValue) 68 | { 69 | var range = maxValue - minValue; 70 | var randomPoint = new DelaunayPoint( 71 | randGenerator.NextDouble() * range + minValue, 72 | randGenerator.NextDouble() * range + minValue); 73 | return randomPoint; 74 | } 75 | 76 | 77 | [Fact] 78 | public void CanCompare_DelaunayEdges() 79 | { 80 | var edgeA = new DelaunayEdge(new DelaunayPoint(0, 0), new DelaunayPoint(1, 0)); 81 | var edgeB = new DelaunayEdge(new DelaunayPoint(0, 0), new DelaunayPoint(1, 0)); 82 | Assert.Equal(edgeA, edgeB); 83 | Assert.Equal(edgeA.GetHashCode(), edgeB.GetHashCode()); 84 | Assert.NotNull(edgeA); 85 | } 86 | 87 | 88 | [Fact] 89 | public void CanCreate_DelaunayPoint_FromPoint2d() 90 | { 91 | var pt = new Point2d(.5, .5); 92 | var dpt = new DelaunayPoint(pt); 93 | Assert.Equal(pt.X, dpt.X); 94 | Assert.Equal(pt.Y, dpt.Y); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /tests/Geometry/2D/Line2dTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | using Xunit; 3 | 4 | namespace Paramdigma.Core.Tests.Geometry 5 | { 6 | public class Line2dTests 7 | { 8 | [Fact] 9 | public void CanBe_Created() 10 | { 11 | var ptA = new Point2d(0, 1); 12 | var ptB = new Point2d(1, 0); 13 | var v = new Vector2d(1, 0); 14 | var line = new Line2d(ptA, ptB); 15 | var lineB = new Line2d(ptA, v); 16 | var lineC = new Line2d(ptA, v, 3); 17 | 18 | Assert.NotNull(line); 19 | Assert.NotNull(lineB); 20 | Assert.NotNull(lineC); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Geometry/2D/Point2dTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | using Xunit; 3 | 4 | namespace Paramdigma.Core.Tests.Geometry 5 | { 6 | public class Point2dTests 7 | { 8 | [Fact] 9 | public void Can_AddVector() 10 | { 11 | var pt = new Point2d(0, 1); 12 | Vector2d v = pt; 13 | var pt2 = pt + v; 14 | var expected = new Point2d(0, 2); 15 | Assert.True(pt2 == expected); 16 | } 17 | 18 | 19 | [Fact] 20 | public void CanBe_Added() 21 | { 22 | const double a = 3.3; 23 | const double b = 2.2; 24 | const double c = 4.11; 25 | var ptA = new Point2d(a, b); 26 | var ptB = new Point2d(b, c); 27 | var ptResult = new Point2d(a + b, b + c); 28 | Assert.True(ptA + ptB == ptResult); 29 | } 30 | 31 | 32 | [Fact] 33 | public void CanBe_ConvertedToVector() 34 | { 35 | var pt = new Point2d(0, 1); 36 | Vector2d v = pt; 37 | var pt2 = ( Point2d ) v; 38 | Assert.True(pt == pt2); 39 | } 40 | 41 | 42 | [Fact] 43 | public void CanBe_Divided() 44 | { 45 | const double a = 3.3; 46 | const double b = 2.2; 47 | const double m = 1.45; 48 | var ptA = new Point2d(a, b); 49 | var ptResult = new Point2d(a / m, b / m); 50 | Assert.True(ptA / m == ptResult); 51 | } 52 | 53 | 54 | [Fact] 55 | public void CanBe_Multiplied() 56 | { 57 | const double a = 3.3; 58 | const double b = 2.2; 59 | const double m = 1.45; 60 | var ptA = new Point2d(a, b); 61 | var ptResult = new Point2d(a * m, b * m); 62 | Assert.True(ptA * m == ptResult); 63 | Assert.True(m * ptA == ptResult); 64 | } 65 | 66 | 67 | [Fact] 68 | public void CanBe_Negated() 69 | { 70 | const double a = 3.3; 71 | const double b = 2.2; 72 | var ptA = new Point2d(a, b); 73 | var ptResult = new Point2d(-a, -b); 74 | Assert.True(-ptA == ptResult); 75 | } 76 | 77 | 78 | [Fact] 79 | public void CanBe_Substracted() 80 | { 81 | const double a = 3.3; 82 | const double b = 2.2; 83 | const double c = 4.11; 84 | var ptA = new Point2d(a, b); 85 | var ptB = new Point2d(b, c); 86 | var ptResult = new Point2d(a - b, b - c); 87 | Assert.True(ptA - ptB == ptResult); 88 | } 89 | 90 | 91 | [Fact] 92 | public void Create_FromPoint() 93 | { 94 | var expected = new Point2d(2.4, 2.5); 95 | var copy = new Point2d(expected); 96 | Assert.True(expected == copy); 97 | } 98 | 99 | 100 | [Fact] 101 | public void Create_Origin() 102 | { 103 | var origin = Point2d.Origin; 104 | var empty = new Point2d(); 105 | var expected = new Point2d(0, 0); 106 | 107 | Assert.True(origin == expected); 108 | Assert.True(empty == expected); 109 | } 110 | 111 | 112 | [Fact] 113 | public void EqualsAndHashCode_HaveConsistentResults() 114 | { 115 | var pt = new Point2d(1.00000009, -1); 116 | var pt2 = new Point2d(1, -1); 117 | var b1 = pt == pt2; 118 | var b2 = pt.GetHashCode() == pt2.GetHashCode(); 119 | Assert.True(b1 && b1 == b2); 120 | Assert.False(pt != pt2); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /tests/Geometry/2D/Polyline2dTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using Paramdigma.Core.Geometry; 5 | using Xunit; 6 | 7 | namespace Paramdigma.Core.Tests.Geometry 8 | { 9 | public class Polyline2dUnitSquareAndSegments : IEnumerable 10 | { 11 | private readonly Point2d pt1 = new Point2d(0, 0); 12 | 13 | private readonly Point2d pt2 = new Point2d(1, 0); 14 | 15 | private readonly Point2d pt3 = new Point2d(1, 1); 16 | 17 | private readonly Point2d pt4 = new Point2d(0, 1); 18 | 19 | 20 | public IEnumerator GetEnumerator() 21 | { 22 | yield return new object[] 23 | { 24 | new Polyline2d( 25 | new List {this.pt1, this.pt2, this.pt3, this.pt4}, 26 | false), 27 | 3 28 | }; 29 | yield return new object[] 30 | { 31 | new Polyline2d( 32 | new List {this.pt1, this.pt2, this.pt3, this.pt4}, 33 | true), 34 | 4 35 | }; 36 | } 37 | 38 | 39 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 40 | } 41 | 42 | public class Polyline2dDataSet : IEnumerable 43 | { 44 | private readonly Point2d pt1 = new Point2d(0, 0); 45 | 46 | private readonly Point2d pt2 = new Point2d(1, 0); 47 | 48 | private readonly Point2d pt3 = new Point2d(1, 1); 49 | 50 | private readonly Point2d pt4 = new Point2d(0, 1); 51 | 52 | 53 | public IEnumerator GetEnumerator() 54 | { 55 | yield return new object[] 56 | { 57 | new Polyline2d( 58 | new List {this.pt1, this.pt2, this.pt3, this.pt4}, 59 | false) 60 | }; 61 | } 62 | 63 | 64 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 65 | } 66 | 67 | public class Polyline2dTests 68 | { 69 | [Theory] 70 | [ClassData(typeof(Polyline2dUnitSquareAndSegments))] 71 | public void Constructor_ClosedOption_AddsVertexAndSegment( 72 | Polyline2d polyline, 73 | int expectedSegments) 74 | { 75 | Assert.True(polyline.Vertices.Count == expectedSegments + 1); 76 | Assert.True(polyline.Segments.Count == expectedSegments); 77 | } 78 | 79 | 80 | [Theory] 81 | [ClassData(typeof(Polyline2dUnitSquareAndSegments))] 82 | public void DefaultDomain_IsParametrizedByArcLength( 83 | Polyline2d polyline, 84 | double expectedLength) => Assert.True( 85 | Math.Abs(polyline.Domain.End - expectedLength) < Settings.Tolerance); 86 | 87 | 88 | [Theory] 89 | [ClassData(typeof(Polyline2dDataSet))] 90 | public void Reparametrize_SetsAllSegmentsDomain(Polyline2d polyline) 91 | { 92 | polyline.Reparametrize(); 93 | 94 | Assert.True( 95 | Math.Abs(polyline.Domain.End - polyline.Segments[^1].Domain.End) 96 | < Settings.Tolerance); 97 | Assert.True(Math.Abs(polyline.Domain.End - 1) < Settings.Tolerance); 98 | } 99 | 100 | 101 | [Theory] 102 | [ClassData(typeof(Polyline2dDataSet))] 103 | public void Check_IsClockwise(Polyline2d polyline) 104 | { 105 | polyline.IsClosed = true; 106 | var cond = polyline.IsClockwise(); 107 | Assert.False(cond); 108 | } 109 | 110 | 111 | [Theory] 112 | [ClassData(typeof(Polyline2dDataSet))] 113 | public void CanCompute_Area(Polyline2d polyline) 114 | { 115 | polyline.IsClosed = true; 116 | var area = polyline.Area(); 117 | Assert.True(Math.Abs(area - 1.0) < Settings.Tolerance); 118 | polyline.IsClosed = false; 119 | area = polyline.Area(); 120 | Assert.True(area == 0.0); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /tests/Geometry/2D/Ray2dTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class Ray2dTests 8 | { 9 | [Fact] 10 | public void CanCreate_AndThrowExceptions() 11 | { 12 | var ray = new Ray2d(Point2d.Origin, Vector2d.WorldX); 13 | Assert.Equal(Point2d.Origin, ray.Origin); 14 | Assert.Equal(Vector2d.WorldX, ray.Direction); 15 | 16 | Assert.Throws(() => new Ray2d(Point2d.Origin, null)); 17 | Assert.Throws(() => new Ray2d(null, Vector2d.WorldY)); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/BoxTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Collections; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class BoxTests 8 | { 9 | [Fact] 10 | public void CanCreate_FromCorners() 11 | { 12 | var box = new Box( 13 | Point3d.WorldOrigin, 14 | new Point3d(1, 1, 1) 15 | ); 16 | Assert.Equal(Plane.WorldXY, box.Plane); 17 | Assert.Equal(Interval.Unit, box.DomainX); 18 | Assert.Equal(Interval.Unit, box.DomainY); 19 | Assert.Equal(Interval.Unit, box.DomainZ); 20 | Assert.Equal(Point3d.WorldOrigin, box.Min); 21 | Assert.Equal(new Point3d(1, 1, 1), box.Max); 22 | Assert.Equal(new Point3d(.5, .5, .5), box.Center); 23 | } 24 | 25 | 26 | [Fact] 27 | public void CanCreate_FromPlaneAndDimensions() 28 | { 29 | var box = new Box(Plane.WorldXY, Interval.Unit, Interval.Unit, Interval.Unit); 30 | Assert.Equal(Plane.WorldXY, box.Plane); 31 | Assert.Equal(Interval.Unit, box.DomainX); 32 | Assert.Equal(Interval.Unit, box.DomainY); 33 | Assert.Equal(Interval.Unit, box.DomainZ); 34 | Assert.Equal(Point3d.WorldOrigin, box.Min); 35 | Assert.Equal(new Point3d(1, 1, 1), box.Max); 36 | Assert.Equal(new Point3d(.5, .5, .5), box.Center); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/CircleTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | using Xunit; 3 | 4 | namespace Paramdigma.Core.Tests.Geometry 5 | { 6 | public class CircleTests 7 | { 8 | private static Circle TestCircle => new Circle(Plane.WorldXY, 1); 9 | 10 | 11 | [Fact] 12 | public void CanCompute_BinormalAt() 13 | { 14 | Assert.Equal(TestCircle.BinormalAt(0), Vector3d.UnitZ); 15 | Assert.Equal(TestCircle.BinormalAt(0.25), Vector3d.UnitZ); 16 | Assert.Equal(TestCircle.BinormalAt(0.5), Vector3d.UnitZ); 17 | Assert.Equal(TestCircle.BinormalAt(0.75), Vector3d.UnitZ); 18 | } 19 | 20 | 21 | [Fact] 22 | public void CanCompute_FrameAt() => Assert.Equal( 23 | TestCircle.FrameAt(0), 24 | new Plane(TestCircle.PointAt(0), -Vector3d.UnitX, Vector3d.UnitZ)); 25 | 26 | 27 | [Fact] 28 | public void CanCompute_NormalAt() 29 | { 30 | Assert.Equal(TestCircle.NormalAt(0), -Vector3d.UnitX); 31 | Assert.Equal(TestCircle.NormalAt(0.25), -Vector3d.UnitY); 32 | Assert.Equal(TestCircle.NormalAt(0.5), Vector3d.UnitX); 33 | Assert.Equal(TestCircle.NormalAt(0.75), Vector3d.UnitY); 34 | } 35 | 36 | 37 | [Fact] 38 | public void CanCompute_PointAt() 39 | { 40 | Assert.Equal(TestCircle.PointAt(0), Vector3d.UnitX); 41 | Assert.Equal(TestCircle.PointAt(0.25), Vector3d.UnitY); 42 | Assert.Equal(TestCircle.PointAt(0.5), -Vector3d.UnitX); 43 | Assert.Equal(TestCircle.PointAt(0.75), -Vector3d.UnitY); 44 | } 45 | 46 | 47 | [Fact] 48 | public void CanCompute_TangentAt() 49 | { 50 | Assert.Equal(TestCircle.TangentAt(0), Vector3d.UnitY); 51 | Assert.Equal(TestCircle.TangentAt(0.25), -Vector3d.UnitX); 52 | Assert.Equal(TestCircle.TangentAt(0.5), -Vector3d.UnitY); 53 | Assert.Equal(TestCircle.TangentAt(0.75), Vector3d.UnitX); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/CurveBaseTests.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Tests.Geometry 2 | { 3 | public abstract class CurveBaseTests 4 | { 5 | public T TestCurve; 6 | 7 | public abstract void CanGet_Length(); 8 | 9 | public abstract void CanGet_PointAt(); 10 | 11 | public abstract void CanCheck_Validity(); 12 | 13 | public abstract void CanGet_Tangent(); 14 | 15 | public abstract void CanGet_Normal(); 16 | 17 | public abstract void CanGet_BiNormal(); 18 | 19 | public abstract void CanGet_PerpFrame(); 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/CylinderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Collections; 3 | using Paramdigma.Core.Geometry; 4 | using Xunit; 5 | 6 | namespace Paramdigma.Core.Tests.Geometry 7 | { 8 | public class CylinderTests 9 | { 10 | [Fact] 11 | private void CanCompute_PointAtCylinder() 12 | { 13 | var cyl = new Cylinder(Plane.WorldXY, 1, Interval.Unit); 14 | var actual = cyl.PointAt(0, 0); 15 | var expected = new Point3d(1, 0, 0); 16 | Assert.Equal(actual, expected); 17 | Assert.Throws(() => cyl.PointAt(-1, 0)); 18 | Assert.Throws(() => cyl.PointAt(0, -1)); 19 | } 20 | 21 | 22 | [Fact] 23 | public void CanCreate_FromDefaultConstructor() 24 | { 25 | var cyl = new Cylinder(Plane.WorldXY, 1, Interval.Unit); 26 | Assert.Equal(Plane.WorldXY, cyl.Plane); 27 | Assert.Equal(1, cyl.Radius); 28 | Assert.Equal(1, cyl.Height); 29 | } 30 | 31 | 32 | [Fact] 33 | public void CanCreate_FromPlaneHeightRadius() 34 | { 35 | var cyl = new Cylinder(Plane.WorldXY, 1, Interval.Unit); 36 | Assert.Equal(Plane.WorldXY, cyl.Plane); 37 | Assert.Equal(1, cyl.Radius); 38 | Assert.Equal(1, cyl.Height); 39 | } 40 | 41 | [Fact] 42 | public void CannotCreate_FromInvalidData() 43 | { 44 | Assert.Throws(() => new Cylinder(Plane.WorldXY, -1, Interval.Unit)); 45 | Assert.Throws(() => new Cylinder(Plane.WorldXY, 1, new Interval(0, Settings.Tolerance/2))); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/Intersect3dTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | using Xunit; 3 | 4 | namespace Paramdigma.Core.Tests.Geometry 5 | { 6 | public class Intersect3dTests 7 | { 8 | [Fact] 9 | public void CanIntersect_Line_Line() 10 | { 11 | var lineA = new Line(Point3d.WorldOrigin, new Point3d(1, 1, 1)); 12 | var lineB = new Line(new Point3d(1, 0, 0), new Point3d(0, 1, 1)); 13 | 14 | var status = Intersect3D.LineLine(lineA, lineB, out var result); 15 | Assert.Equal(Intersect3D.LineLineIntersectionStatus.Point, status); 16 | Assert.Equal(new Point3d(0.5, 0.5, 0.5), result.PointA); 17 | Assert.Equal(new Point3d(0.5, 0.5, 0.5), result.PointB); 18 | Assert.Equal(0.5, result.ParamA); 19 | Assert.Equal(0.5, result.ParamB); 20 | } 21 | 22 | 23 | [Fact] 24 | public void CanIntersect_Line_Plane() 25 | { 26 | var expected = new Point3d(.4, .23, 0); 27 | var a = new Point3d(.4, .23, 1); 28 | var b = new Point3d(.4, .23, -1); 29 | var lineA = new Line(a, b); 30 | var plane = Plane.WorldXY; 31 | 32 | var status = Intersect3D.LinePlane(lineA, plane, out var actual); 33 | Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.Point, status); 34 | Assert.Equal(expected, actual); 35 | } 36 | 37 | 38 | [Fact] 39 | public void CannotIntersect_Line_Plane_DoNotTouch() 40 | { 41 | var a = new Point3d(.4, .23, 1); 42 | var b = new Point3d(.4, .23, .2); 43 | var lineA = new Line(a, b); 44 | var plane = Plane.WorldXY; 45 | 46 | var status = Intersect3D.LinePlane(lineA, plane, out var actual); 47 | Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.NoIntersection, status); 48 | Assert.Null(actual); 49 | } 50 | 51 | 52 | [Fact] 53 | public void CannotIntersect_Line_Plane_DoNotTouchAndAreParallel() 54 | { 55 | var a = new Point3d(.4, .23, 1); 56 | var b = new Point3d(.9, .23, 1); 57 | var lineA = new Line(a, b); 58 | var plane = Plane.WorldXY; 59 | 60 | var status = Intersect3D.LinePlane(lineA, plane, out var actual); 61 | Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.NoIntersection, status); 62 | Assert.Null(actual); 63 | } 64 | 65 | 66 | [Fact] 67 | public void CannotIntersect_Line_Plane_Parallel() 68 | { 69 | var a = new Point3d(.6, .6, 0); 70 | var b = new Point3d(.4, .2, 0); 71 | var lineA = new Line(a, b); 72 | var plane = Plane.WorldXY; 73 | 74 | var status = Intersect3D.LinePlane(lineA, plane, out var actual); 75 | Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.OnPlane, status); 76 | Assert.Null(actual); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/LineTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class Line3dTests : CurveBaseTests 8 | { 9 | internal Line TestLine = new Line(Point3d.WorldOrigin, new Point3d(1, 1, 1)); 10 | 11 | 12 | [Fact] 13 | public override void CanCheck_Validity() => Assert.True(this.TestLine.IsValid); 14 | 15 | 16 | [Fact] 17 | public override void CanGet_BiNormal() 18 | { 19 | var biNorm = this.TestLine.BinormalAt(.5); 20 | Assert.True(biNorm != null); 21 | } 22 | 23 | 24 | [Fact] 25 | public override void CanGet_Length() => Assert.True( 26 | Math.Abs(this.TestLine.Length - Math.Sqrt(3)) < Settings.Tolerance); 27 | 28 | 29 | [Fact] 30 | public override void CanGet_Normal() 31 | { 32 | var norm = this.TestLine.NormalAt(.5); 33 | Assert.True(norm != null); 34 | 35 | var line = new Line(Point3d.WorldOrigin, Vector3d.UnitZ, 1); 36 | line.NormalAt(0.5); 37 | } 38 | 39 | 40 | [Fact] 41 | public override void CanGet_PerpFrame() 42 | { 43 | var biNorm = this.TestLine.FrameAt(.5); 44 | Assert.True(biNorm != null); 45 | } 46 | 47 | 48 | [Fact] 49 | public override void CanGet_PointAt() 50 | { 51 | var pt = this.TestLine.PointAt(.5); 52 | Assert.True(pt == new Point3d(0.5, 0.5, 0.5)); 53 | } 54 | 55 | 56 | [Fact] 57 | public override void CanGet_Tangent() 58 | { 59 | var biNorm = this.TestLine.TangentAt(.5); 60 | Assert.True(biNorm == new Vector3d(1, 1, 1).Unit()); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshCornerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | 8 | 9 | public class MeshCornerTests 10 | { 11 | 12 | public Mesh FlatTriangle 13 | { 14 | get 15 | { 16 | var ptA = new Point3d(0, 0, 0); 17 | var ptB = new Point3d(1, 0, 0); 18 | var ptC = new Point3d(1, 1, 0); 19 | var vertices = new List {ptA, ptB, ptC}; 20 | var face = new List {0, 1, 2}; 21 | var mesh = new Mesh(vertices, new List> {face}); 22 | return mesh; 23 | } 24 | } 25 | 26 | 27 | [Fact] 28 | public void HasPropertiesAssigned() 29 | { 30 | FlatTriangle.Corners.ForEach( 31 | corner => 32 | { 33 | Assert.NotNull(corner.Vertex); 34 | Assert.NotNull(corner.Face); 35 | Assert.NotNull(corner.Next); 36 | Assert.NotNull(corner.Prev); 37 | Assert.NotEqual(corner.Index, -1); 38 | }); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshFaceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class MeshFaceTests 8 | { 9 | private static Mesh FlatSquare 10 | { 11 | get 12 | { 13 | var ptA = new Point3d(0, 0, 0); 14 | var ptB = new Point3d(1, 0, 0); 15 | var ptC = new Point3d(1, 1, 0); 16 | var ptD = new Point3d(0, 1, 0); 17 | var vertices = new List {ptA, ptB, ptC, ptD}; 18 | var face1 = new List {0, 1, 3}; 19 | var face2 = new List {3, 1, 2}; 20 | var mesh = new Mesh(vertices, new List> {face1, face2}); 21 | return mesh; 22 | } 23 | } 24 | 25 | 26 | [Fact] 27 | public void CanCompute_FaceArea() => 28 | FlatSquare.Faces.ForEach(face => { Assert.Equal(0.5, face.Area); }); 29 | 30 | 31 | [Fact] 32 | public void CanCompute_FaceNormal() => 33 | FlatSquare.Faces.ForEach(face => { Assert.Equal(Vector3d.UnitZ, face.Normal); }); 34 | 35 | 36 | [Fact] 37 | public void CanCompute_FaceTopology() => 38 | FlatSquare.Faces.ForEach( 39 | face => 40 | { 41 | Assert.Single(face.AdjacentFaces()); 42 | Assert.Equal(3, face.AdjacentEdges().Count); 43 | Assert.Equal(3, face.AdjacentCorners().Count); 44 | Assert.Equal(3, face.AdjacentHalfEdges().Count); 45 | Assert.Equal(3, face.AdjacentVertices().Count); 46 | }); 47 | 48 | 49 | [Fact] 50 | public void CanConvert_ToString() => 51 | FlatSquare.Faces.ForEach(face => { Assert.NotNull(face.ToString()); }); 52 | 53 | 54 | [Fact] 55 | public void CanGet_Index() => 56 | FlatSquare.Faces.ForEach(face => { Assert.True(face.Index >= 0); }); 57 | } 58 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshGeometryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Paramdigma.Core.Geometry; 4 | using Xunit; 5 | 6 | namespace Paramdigma.Core.Tests.Geometry 7 | { 8 | public class MeshGeometryTests 9 | { 10 | public Mesh FlatSquare 11 | { 12 | get 13 | { 14 | var ptA = new Point3d(0, 0, 0); 15 | var ptB = new Point3d(1, 0, 0); 16 | var ptC = new Point3d(1, 1, 0); 17 | var ptD = new Point3d(0, 1, 0); 18 | var vertices = new List {ptA, ptB, ptC, ptD}; 19 | var face = new List {0, 1, 2, 3}; 20 | var mesh = new Mesh(vertices, new List> {face}); 21 | return mesh; 22 | } 23 | } 24 | 25 | public Mesh FlatTriangle 26 | { 27 | get 28 | { 29 | var ptA = new Point3d(0, 0, 0); 30 | var ptB = new Point3d(1, 0, 0); 31 | var ptC = new Point3d(1, 1, 0); 32 | var vertices = new List {ptA, ptB, ptC}; 33 | var face = new List {0, 1, 2}; 34 | var mesh = new Mesh(vertices, new List> {face}); 35 | return mesh; 36 | } 37 | } 38 | 39 | 40 | [Fact] 41 | private void CanCompute_CornerAngles() => 42 | this.FlatSquare.Corners.ForEach( 43 | corner => 44 | { 45 | var angle = MeshGeometry.Angle(corner); 46 | Assert.True(Math.Abs(angle - 0.5 * Math.PI) < Settings.Tolerance); 47 | }); 48 | 49 | 50 | [Fact] 51 | private void CanCompute_EdgeLengths() => 52 | this.FlatSquare.Edges.ForEach( 53 | edge => 54 | { 55 | var length = MeshGeometry.Length(edge); 56 | Assert.True(Math.Abs(length - 1) < Settings.Tolerance); 57 | }); 58 | 59 | 60 | [Fact] 61 | private void CanCompute_FaceArea() 62 | { 63 | var area = MeshGeometry.Area(this.FlatTriangle.Faces[0]); 64 | Assert.True(Math.Abs(area - 0.5) < Settings.Tolerance); 65 | } 66 | 67 | 68 | [Fact] 69 | private void CanCompute_FaceNormal() 70 | { 71 | var normal = MeshGeometry.FaceNormal(this.FlatTriangle.Faces[0]); 72 | Assert.Equal(Vector3d.UnitZ, normal); 73 | } 74 | 75 | 76 | [Fact] 77 | private void CanCompute_MeshArea() 78 | { 79 | var area = MeshGeometry.TotalArea(this.FlatTriangle); 80 | Assert.True(Math.Abs(area - 0.5) < Settings.Tolerance); 81 | } 82 | 83 | 84 | [Fact] 85 | private void CanCompute_VertexNormal() => 86 | this.FlatTriangle.Vertices.ForEach( 87 | vertex => 88 | { 89 | var normal = MeshGeometry.VertexNormalAngleWeighted(vertex); 90 | Assert.Equal(Vector3d.UnitZ, normal); 91 | normal = MeshGeometry.VertexNormalEquallyWeighted(vertex); 92 | Assert.Equal(Vector3d.UnitZ, normal); 93 | normal = MeshGeometry.VertexNormalAreaWeighted(vertex); 94 | Assert.Equal(Vector3d.UnitZ, normal); 95 | }); 96 | } 97 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshPointTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | 8 | 9 | public class MeshPointTests 10 | { 11 | 12 | public Mesh FlatTriangle 13 | { 14 | get 15 | { 16 | var ptA = new Point3d(0, 0, 0); 17 | var ptB = new Point3d(1, 0, 0); 18 | var ptC = new Point3d(1, 1, 0); 19 | var vertices = new List {ptA, ptB, ptC}; 20 | var face = new List {0, 1, 2}; 21 | var mesh = new Mesh(vertices, new List> {face}); 22 | return mesh; 23 | } 24 | } 25 | 26 | [Fact] 27 | public void CanConstruct_FromNumbers() 28 | { 29 | var pt = new MeshPoint(1, 0.4, 0.5, 0.6); 30 | Assert.Equal(1, pt.FaceIndex); 31 | Assert.Equal(0.4, pt.U); 32 | Assert.Equal(0.5, pt.V); 33 | Assert.Equal(0.6, pt.W); 34 | } 35 | 36 | [Fact] 37 | public void CanConstruct_FromEntities() 38 | { 39 | 40 | var pt = new MeshPoint(FlatTriangle.Faces[0],new Point3d(0.4,0.5,0.6)); 41 | Assert.Equal(0, pt.FaceIndex); 42 | Assert.NotNull(pt); 43 | } 44 | [Fact] 45 | public void CanConvert_ToString() 46 | { 47 | var pt = new MeshPoint(1, 0.4, 0.5, 0.6); 48 | Assert.NotNull(pt.ToString()); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class MeshTests 8 | { 9 | public Mesh FlatSquare 10 | { 11 | get 12 | { 13 | var ptA = new Point3d(0, 0, 0); 14 | var ptB = new Point3d(1, 0, 0); 15 | var ptC = new Point3d(1, 1, 0); 16 | var ptD = new Point3d(0, 1, 0); 17 | var vertices = new List {ptA, ptB, ptC, ptD}; 18 | var face = new List {0, 1, 2, 3}; 19 | var mesh = new Mesh(vertices, new List> {face}); 20 | return mesh; 21 | } 22 | } 23 | 24 | 25 | [Fact] 26 | public void CanCheck_QuadMesh() 27 | { 28 | var mesh = this.FlatSquare; 29 | Assert.True(mesh.IsQuadMesh()); 30 | Assert.False(mesh.IsNgonMesh()); 31 | Assert.False(mesh.IsTriangularMesh()); 32 | } 33 | 34 | 35 | [Fact] 36 | public void CanCompute_Boundary() 37 | { 38 | var mesh = this.FlatSquare; 39 | Assert.NotEmpty(mesh.Boundaries); 40 | Assert.Single(mesh.Boundaries); 41 | } 42 | 43 | 44 | [Fact] 45 | public void CanCompute_EulerCharacteristic() 46 | { 47 | var mesh = this.FlatSquare; 48 | Assert.Equal(1, mesh.EulerCharacteristic); 49 | } 50 | 51 | 52 | [Fact] 53 | public void CanConvert_ToString() 54 | { 55 | Assert.IsType(this.FlatSquare.ToString()); 56 | Assert.IsType(this.FlatSquare.GetMeshInfo()); 57 | } 58 | 59 | 60 | [Fact] 61 | public void CanCreate_Mesh() 62 | { 63 | var mesh = this.FlatSquare; 64 | Assert.NotNull(mesh); 65 | Assert.NotEmpty(mesh.Vertices); 66 | Assert.NotEmpty(mesh.Faces); 67 | } 68 | 69 | 70 | [Fact] 71 | public void CanDetect_IsolatedFaces() 72 | { 73 | var mesh = this.FlatSquare; 74 | Assert.False(mesh.HasIsolatedFaces()); 75 | } 76 | 77 | 78 | [Fact] 79 | public void CanDetect_IsolatedVertices() 80 | { 81 | var mesh = this.FlatSquare; 82 | Assert.False(mesh.HasIsolatedVertices()); 83 | 84 | mesh.Vertices.Add(new MeshVertex(3, 0, 0)); 85 | Assert.True(mesh.HasIsolatedVertices()); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshTopologyTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class MeshTopologyTests 8 | { 9 | public Mesh FlatSquare 10 | { 11 | get 12 | { 13 | var ptA = new Point3d(0, 0, 0); 14 | var ptB = new Point3d(1, 0, 0); 15 | var ptC = new Point3d(1, 1, 0); 16 | var ptD = new Point3d(0, 1, 0); 17 | var vertices = new List {ptA, ptB, ptC, ptD}; 18 | var face = new List {0, 1, 2, 3}; 19 | var mesh = new Mesh(vertices, new List> {face}); 20 | return mesh; 21 | } 22 | } 23 | 24 | public Mesh FlatTriangle 25 | { 26 | get 27 | { 28 | var ptA = new Point3d(0, 0, 0); 29 | var ptB = new Point3d(1, 0, 0); 30 | var ptC = new Point3d(1, 1, 0); 31 | var vertices = new List {ptA, ptB, ptC}; 32 | var face = new List {0, 1, 2}; 33 | var mesh = new Mesh(vertices, new List> {face}); 34 | return mesh; 35 | } 36 | } 37 | 38 | 39 | [Fact] 40 | public void CanCreate_MeshTopology() 41 | { 42 | // TODO: Improve this tests with better assertions. 43 | var topo = new MeshTopology(FlatSquare); 44 | 45 | Assert.Empty(topo.FaceFace); 46 | } 47 | 48 | [Fact] 49 | public void CanConvert_ToString() 50 | { 51 | // TODO: Improve this tests with better assertions. 52 | var topo = new MeshTopology(FlatSquare); 53 | 54 | Assert.NotNull(topo.TopologyDictToString(topo.FaceFace)); 55 | Assert.NotNull(topo.TopologyDictToString(topo.VertexVertex)); 56 | Assert.NotNull(topo.TopologyDictToString(topo.EdgeEdge)); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/MeshVertexTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class MeshVertexTests 8 | { 9 | public Mesh FlatSquare 10 | { 11 | get 12 | { 13 | var ptA = new Point3d(0, 0, 0); 14 | var ptB = new Point3d(1, 0, 0); 15 | var ptC = new Point3d(1, 1, 0); 16 | var ptD = new Point3d(0, 1, 0); 17 | var vertices = new List {ptA, ptB, ptC, ptD}; 18 | var face = new List {0, 1, 2, 3}; 19 | var mesh = new Mesh(vertices, new List> {face}); 20 | return mesh; 21 | } 22 | } 23 | 24 | 25 | [Fact] 26 | public void CanCompute_Adjacencies() => 27 | this.FlatSquare.Vertices.ForEach( 28 | vertex => 29 | { 30 | Assert.Single(vertex.AdjacentFaces()); 31 | Assert.Single(vertex.AdjacentCorners()); 32 | Assert.Equal(2, vertex.AdjacentVertices().Count); 33 | Assert.Equal(2, vertex.AdjacentEdges().Count); 34 | Assert.Equal(2, vertex.AdjacentHalfEdges().Count); 35 | Assert.Equal(2, vertex.Valence()); 36 | Assert.True(vertex.OnBoundary()); 37 | }); 38 | 39 | 40 | [Fact] 41 | public void CanConvert_ToString() => 42 | this.FlatSquare.Vertices.ForEach(vertex => { Assert.NotNull(vertex.ToString()); }); 43 | 44 | 45 | [Fact] 46 | public void CanCreate() 47 | { 48 | Assert.NotNull(new MeshVertex()); 49 | Assert.NotNull(new MeshVertex(Point3d.WorldOrigin)); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/NurbsCurveData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Paramdigma.Core.Tests.Geometry 5 | { 6 | public class NurbsCurveUnitParamData : IEnumerable 7 | { 8 | public IEnumerator GetEnumerator() 9 | { 10 | yield return new object[] {0}; 11 | yield return new object[] {0.1}; 12 | yield return new object[] {0.2}; 13 | yield return new object[] {0.3}; 14 | yield return new object[] {0.4}; 15 | yield return new object[] {0.5}; 16 | yield return new object[] {0.6}; 17 | yield return new object[] {0.7}; 18 | yield return new object[] {0.8}; 19 | yield return new object[] {0.9}; 20 | yield return new object[] {1.0}; 21 | } 22 | 23 | 24 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/NurbsCurveTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Paramdigma.Core.Geometry; 4 | using Paramdigma.Core.Tests.Conversions; 5 | using Xunit; 6 | using RG = Rhino.Geometry; 7 | 8 | namespace Paramdigma.Core.Tests.Geometry 9 | { 10 | public class NurbsCurveTests 11 | { 12 | private readonly List controlPoints = new List 13 | { 14 | new Point4d(0, 0, 0, 4), 15 | new Point4d(1, 3, 0, 3.5), 16 | new Point4d(1.4, 5, 0, 2), 17 | new Point4d(0, 7, 0, 1) 18 | }; 19 | 20 | private NurbsCurve Curve => new NurbsCurve(this.controlPoints, 3); 21 | 22 | private RG.NurbsCurve RhCurve 23 | { 24 | get 25 | { 26 | var rhcrv = RG.Curve.CreateControlPointCurve( 27 | this.controlPoints.Select(pt => pt.Position.ToRhino()), 28 | 3) 29 | .ToNurbsCurve(); 30 | for (var i = 0; i < this.controlPoints.Count; i++) 31 | rhcrv.Points.SetWeight(i, this.controlPoints[i].Weight); 32 | rhcrv.Domain = new RG.Interval(0, 1); 33 | return rhcrv.ToNurbsCurve(); 34 | } 35 | } 36 | 37 | 38 | [Theory] 39 | [InlineData(0)] 40 | [InlineData(0.1)] 41 | [InlineData(0.2)] 42 | [InlineData(1.0)] 43 | private void CanGet_PointAt(double t) 44 | { 45 | var point = this.Curve.PointAt(t); 46 | var rhPoint = this.RhCurve.PointAt(t); 47 | Assert.True(point.DistanceTo(rhPoint.ToCore()) < Settings.Tolerance); 48 | } 49 | 50 | 51 | [Theory] 52 | [InlineData(0)] 53 | [InlineData(0.1)] 54 | [InlineData(0.2)] 55 | [InlineData(1.0)] 56 | public void CanGet_TangentAt(double t) 57 | { 58 | var vector = this.Curve.TangentAt(t); 59 | var rhVector = this.RhCurve.TangentAt(t); 60 | Assert.True((vector - rhVector.ToCore()).Length < Settings.Tolerance); 61 | } 62 | 63 | 64 | [Theory] 65 | [ClassData(typeof(NurbsCurveUnitParamData))] 66 | public void CanGet_NormalAt(double t) 67 | { 68 | var vector = this.Curve.NormalAt(t); 69 | var rhVector = this.RhCurve.DerivativeAt(t, 2)[2]; 70 | var length = (vector - rhVector.ToCore().Unit()).Length; 71 | Assert.True(length < Settings.Tolerance); 72 | } 73 | 74 | 75 | [Theory] 76 | [InlineData(0)] 77 | [InlineData(0.1)] 78 | [InlineData(0.2)] 79 | [InlineData(1.0)] 80 | public void CanGet_BiNormalAt(double t) 81 | { 82 | var vector = this.Curve.BinormalAt(t); 83 | var rhVector = this.RhCurve.DerivativeAt(t, 3)[3]; 84 | var length = (vector - rhVector.ToCore().Unit()).Length; 85 | Assert.True(length < Settings.Tolerance); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/NurbsSurfaceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using Paramdigma.Core.Collections; 4 | using Paramdigma.Core.Geometry; 5 | using Paramdigma.Core.Tests.Conversions; 6 | using Xunit; 7 | using RG = Rhino.Geometry; 8 | 9 | namespace Paramdigma.Core.Tests.Geometry 10 | { 11 | public class NurbsSurfaceTests 12 | { 13 | [Theory] 14 | [ClassData(typeof(NurbsSurfaceUnitParamData))] 15 | public void CanGet_PointAt(double u, double v) 16 | { 17 | var surf = NurbsSurface.CreateFlatSurface(Interval.Unit, Interval.Unit, 4, 4); 18 | var pt = surf.PointAt(u, v); 19 | var rhSurf = surf.ToRhino(); 20 | var ptRh = rhSurf.PointAt(u, v); 21 | var distance = pt.DistanceTo(ptRh.ToCore()); 22 | Assert.True(distance < Settings.Tolerance); 23 | } 24 | 25 | 26 | [Theory] 27 | [ClassData(typeof(NurbsSurfaceUnitParamData))] 28 | public void CanGet_TangentAt(double u, double v) 29 | { 30 | var surf = NurbsSurface.CreateFlatSurface(Interval.Unit, Interval.Unit, 4, 4); 31 | var uT = surf.DerivativesAt(u, v, 1)[0, 1].Unit(); 32 | var vT = surf.DerivativesAt(u, v, 1)[1, 0].Unit(); 33 | var rhSurf = surf.ToRhino(); 34 | 35 | var uCrv = rhSurf.IsoCurve(1, v); 36 | uCrv.Domain = new RG.Interval(0, 1); 37 | var vCrv = rhSurf.IsoCurve(0, u); 38 | vCrv.Domain = new RG.Interval(0, 1); 39 | 40 | var uVector = uCrv.TangentAt(u); 41 | var vVector = vCrv.TangentAt(v); 42 | 43 | Assert.True((uVector.ToCore() - uT).Length <= Settings.Tolerance); 44 | Assert.True((vVector.ToCore() - vT).Length <= Settings.Tolerance); 45 | } 46 | 47 | 48 | [Theory] 49 | [ClassData(typeof(NurbsSurfaceUnitParamData))] 50 | public void CanGet_NormalAt(double u, double v) 51 | { 52 | var surf = NurbsSurface.CreateFlatSurface(Interval.Unit, Interval.Unit, 4, 4); 53 | var uT = surf.DerivativesAt(u, v, 1)[0, 1]; 54 | var vT = surf.DerivativesAt(u, v, 1)[1, 0]; 55 | var cross = vT.Cross(uT).Unit(); 56 | var rhSurf = surf.ToRhino(); 57 | var rhVector = rhSurf.NormalAt(u, v); 58 | rhVector.Unitize(); 59 | Assert.True((rhVector.ToCore() - cross).Length <= Settings.Tolerance); 60 | } 61 | } 62 | 63 | public class NurbsSurfaceUnitParamData : IEnumerable 64 | { 65 | public IEnumerator GetEnumerator() 66 | { 67 | yield return new object[] {0.0, 0.99}; 68 | yield return new object[] {0.1, .9}; 69 | yield return new object[] {0.2, .8}; 70 | yield return new object[] {0.3, .7}; 71 | yield return new object[] {0.4, .6}; 72 | yield return new object[] {0.5, .5}; 73 | yield return new object[] {0.6, .4}; 74 | yield return new object[] {0.7, .3}; 75 | yield return new object[] {0.8, .2}; 76 | yield return new object[] {0.9, .1}; 77 | yield return new object[] {0.99, .0}; 78 | yield return new object[] {1, .0}; 79 | } 80 | 81 | 82 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 83 | } 84 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/PlaneTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class PlaneTests 8 | { 9 | [Fact] 10 | public void CanBe_Cloned() 11 | { 12 | var pln = Plane.WorldXY; 13 | var plnB = pln.Clone(); 14 | var plnC = new Plane(pln); 15 | Assert.True(!ReferenceEquals(pln, plnB)); 16 | Assert.True(!ReferenceEquals(pln, plnC)); 17 | } 18 | 19 | 20 | [Fact] 21 | public void CanBe_Compared() 22 | { 23 | var expected = Plane.WorldXY; 24 | var actual = new Plane(Point3d.WorldOrigin, Vector3d.UnitX, Vector3d.UnitY); 25 | Assert.Equal(expected, actual); 26 | Assert.Equal(expected.GetHashCode(), actual.GetHashCode()); 27 | } 28 | 29 | 30 | [Fact] 31 | public void CanBe_Created() 32 | { 33 | var plane = new Plane(Point3d.WorldOrigin); 34 | var planeB = new Plane(Point3d.WorldOrigin, Vector3d.UnitZ, Vector3d.UnitX); 35 | 36 | var ptA = new Point3d(1, 0, 0); 37 | var ptB = new Point3d(0, 1, 0); 38 | var ptC = new Point3d(0, 0, 0); 39 | var planeC = new Plane(ptC, ptA, ptB); 40 | 41 | Assert.NotNull(plane); 42 | Assert.NotNull(planeB); 43 | Assert.NotNull(planeC); 44 | } 45 | 46 | 47 | [Fact] 48 | public void CanBe_Flipped() 49 | { 50 | var xy = Plane.WorldXY; 51 | var flipped = xy.Clone(); 52 | flipped.Flip(); 53 | Assert.Equal(xy.XAxis, flipped.YAxis); 54 | Assert.Equal(xy.YAxis, flipped.XAxis); 55 | Assert.Equal(xy.ZAxis, -flipped.ZAxis); 56 | } 57 | 58 | 59 | [Fact] 60 | public void CanCompute_ClosestPoint() 61 | { 62 | var pln = Plane.WorldXY; 63 | var pt = new Point3d(1, 1, 1); 64 | var expected = new Point3d(1, 1, 0); 65 | var result = pln.ClosestPoint(pt); 66 | var dist = pln.DistanceTo(pt); 67 | Assert.True(result == expected); 68 | Assert.True(Math.Abs(dist - 1) < Settings.Tolerance); 69 | } 70 | 71 | 72 | [Fact] 73 | public void CanCompute_Points() 74 | { 75 | var pln = Plane.WorldXY; 76 | var pt = pln.PointAt(1, 1); 77 | var expectedPt = new Point3d(1, 1, 0); 78 | var ptB = pln.PointAt(1, 1, 1); 79 | var expectedPtB = new Point3d(1, 1, 1); 80 | Assert.True(pt == expectedPt); 81 | Assert.True(ptB == expectedPtB); 82 | } 83 | 84 | 85 | [Fact] 86 | public void CanConvert_ToString() 87 | { 88 | var pln = Plane.WorldXY; 89 | Assert.NotNull(pln); 90 | } 91 | 92 | 93 | [Fact] 94 | public void CanCreate_SpecialPlanes() 95 | { 96 | var ptXY = Plane.WorldXY; 97 | var ptYZ = Plane.WorldYZ; 98 | var ptXZ = Plane.WorldXZ; 99 | Assert.NotNull(ptXY); 100 | Assert.NotNull(ptYZ); 101 | Assert.NotNull(ptXZ); 102 | } 103 | 104 | 105 | [Fact] 106 | public void CanRemap_FromXYPlane() 107 | { 108 | var yz = Plane.WorldYZ; 109 | var pt = new Point3d(0, 1, 1); 110 | var expected = new Point3d(1, 1, 0); 111 | var result = yz.RemapToPlaneSpace(pt); 112 | Assert.Equal(expected, result); 113 | } 114 | 115 | 116 | [Fact] 117 | public void CanRemap_ToXYPlane() 118 | { 119 | var yz = Plane.WorldYZ; 120 | var pt = new Point3d(1, 1, 0); 121 | var expected = new Point3d(0, 1, 1); 122 | var result = yz.RemapToWorldXYSpace(pt); 123 | Assert.Equal(expected, result); 124 | } 125 | 126 | 127 | [Fact] 128 | public void LinearPoints_ThrowError() 129 | { 130 | var ptA = new Point3d(0, 0, 0); 131 | var ptB = new Point3d(0.5, 0.5, 0.5); 132 | var ptC = new Point3d(1, 1, 1); 133 | 134 | Assert.Throws(() => new Plane(ptA, ptB, ptC)); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/Point3dData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using Paramdigma.Core.Geometry; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class Point3dEqualDataset : IEnumerable 8 | { 9 | public IEnumerator GetEnumerator() 10 | { 11 | yield return new object[] {new Point3d(1, 1, 1), new Point3d(1, 1, 1)}; 12 | yield return new object[] {new Point3d(2, 2, -1), new Point3d(2, 2, -1)}; 13 | } 14 | 15 | 16 | IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/Point4dTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | using Xunit; 3 | 4 | namespace Paramdigma.Core.Tests.Geometry 5 | { 6 | public class Point4dTests 7 | { 8 | [Fact] 9 | public void CanBe_Added() 10 | { 11 | const double a = 3.3; 12 | const double b = 2.2; 13 | const double c = 4.11; 14 | const double d = 1.344; 15 | 16 | var ptA = new Point4d(a, b, c, d); 17 | var ptB = new Point4d(b, c, a, d); 18 | var ptResult = new Point4d(a + b, b + c, c + a, d + d); 19 | Assert.True(ptA + ptB == ptResult); 20 | } 21 | 22 | 23 | [Fact] 24 | public void CanBe_Added_WithVector() 25 | { 26 | const double a = 3.3; 27 | const double b = 2.2; 28 | const double c = 4.11; 29 | const double d = 1.344; 30 | 31 | var ptA = new Point4d(a, b, c, d); 32 | var v = new Vector3d(b, c, a); 33 | var ptResult = new Point4d(a + b, b + c, c + a, d); 34 | Assert.True(ptA + v == ptResult); 35 | } 36 | 37 | 38 | [Fact] 39 | public void CanBe_Created() 40 | { 41 | var pt3 = new Point3d(1, 0, 0); 42 | var pt4 = new Point4d(1, 0, 0, 1); 43 | var pt42 = new Point4d(pt3); 44 | 45 | Assert.Equal(pt42, pt4); 46 | } 47 | 48 | 49 | [Fact] 50 | public void CanBe_Divided() 51 | { 52 | const double a = 3.3; 53 | const double b = 2.2; 54 | const double c = 4.11; 55 | const double d = 1.344; 56 | const double m = 1.45; 57 | var ptA = new Point4d(a, b, c, d); 58 | var ptResult = new Point4d(a / m, b / m, c / m, d / m); 59 | Assert.True(ptA / m == ptResult); 60 | } 61 | 62 | 63 | [Fact] 64 | public void CanBe_Multiplied() 65 | { 66 | const double a = 3.3; 67 | const double b = 2.2; 68 | const double c = 4.11; 69 | const double d = 1.344; 70 | const double m = 1.45; 71 | var ptA = new Point4d(a, b, c, d); 72 | var ptResult = new Point4d(a * m, b * m, c * m, d * m); 73 | Assert.True(ptA * m == ptResult); 74 | Assert.True(m * ptA == ptResult); 75 | } 76 | 77 | 78 | [Fact] 79 | public void CanBe_Negated() 80 | { 81 | const double a = 3.3; 82 | const double b = 2.2; 83 | const double c = 4.11; 84 | const double d = 1.344; 85 | 86 | var ptA = new Point4d(a, b, c, d); 87 | var ptResult = new Point4d(-a, -b, -c, d); 88 | Assert.True(-ptA == ptResult); 89 | } 90 | 91 | 92 | [Fact] 93 | public void CanBe_Subtracted() 94 | { 95 | const double a = 3.3; 96 | const double b = 2.2; 97 | const double c = 4.11; 98 | const double d = 1.344; 99 | 100 | var ptA = new Point4d(a, b, c, d); 101 | var ptB = new Point4d(b, c, a, d); 102 | var expected = new Point4d(a - b, b - c, c - a, d - d); 103 | var actual = ptA - ptB; 104 | Assert.Equal(expected, actual); 105 | } 106 | 107 | 108 | [Fact] 109 | public void CanCheck_Equality() 110 | { 111 | const double a = 3.3; 112 | const double b = 2.2; 113 | const double c = 4.11; 114 | const double d = 1.344; 115 | 116 | var ptA = new Point4d(a, b, c, d); 117 | var expectedEqual = new Point4d(a + Settings.Tolerance / 2, b, c, d); 118 | var expectedNotEqual = new Point4d(a + Settings.Tolerance * 2, b, c, d); 119 | Assert.True(ptA == expectedEqual); 120 | Assert.Equal(ptA.GetHashCode(), expectedEqual.GetHashCode()); 121 | Assert.True(ptA != expectedNotEqual); 122 | Assert.NotEqual(ptA.GetHashCode(), expectedNotEqual.GetHashCode()); 123 | Assert.False(ptA == null); 124 | } 125 | 126 | 127 | [Fact] 128 | public void CanCreate_FromPointAndWeight() 129 | { 130 | var pt = new Point3d(1, 0, 1); 131 | const int weight = 1; 132 | var pt4 = new Point4d(pt, weight); 133 | Assert.Equal(pt, pt4.Position); 134 | Assert.Equal(weight, pt4.Weight); 135 | } 136 | 137 | 138 | [Fact] 139 | public void CanToggle_IsUnset_OnWeightChange() 140 | { 141 | var pt = new Point4d(); 142 | Assert.True(pt.IsUnset); 143 | pt.Weight += 1; 144 | Assert.False(pt.IsUnset); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/Ray3dTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class Ray3dTests 8 | { 9 | [Fact] 10 | public void CanCompute_PointAt() 11 | { 12 | var ray = new Ray(Point3d.WorldOrigin, Vector3d.UnitX); 13 | var expected = new Point3d(3, 0, 0); 14 | var actual = ray.PointAt(3); 15 | Assert.Equal(expected, actual); 16 | } 17 | 18 | 19 | [Fact] 20 | public void CanCreate_AndThrowExceptions() 21 | { 22 | var ray = new Ray(Point3d.WorldOrigin, Vector3d.UnitX); 23 | Assert.Equal(Point3d.WorldOrigin, ray.Origin); 24 | Assert.Equal(Vector3d.UnitX, ray.Direction); 25 | 26 | Assert.Throws(() => new Ray(Point3d.WorldOrigin, null)); 27 | Assert.Throws(() => new Ray(null, Vector3d.UnitY)); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/SphereTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Collections; 3 | using Paramdigma.Core.Geometry; 4 | using Xunit; 5 | 6 | namespace Paramdigma.Core.Tests.Geometry 7 | { 8 | public class SphereTests 9 | { 10 | [Fact] 11 | public void CanCompute_FrameAtParameter() { } 12 | 13 | 14 | [Fact] 15 | public void CanCompute_NormalAtParameter() 16 | { 17 | var sphere = new Sphere(Plane.WorldXY, 1); 18 | var actual = sphere.NormalAt(0, 0); 19 | var expected = Vector3d.UnitZ; 20 | Assert.Equal(expected, actual); 21 | } 22 | 23 | 24 | [Fact] 25 | public void ComputeClosestParam_ThenPointAtParam_GivesSamePoint() 26 | { 27 | var radius = 4.55; 28 | var sphere = new Sphere(Plane.WorldXY, radius); 29 | var pt = new Point3d(radius, 0, 0); 30 | var param = sphere.ClosestParam(pt); 31 | Assert.Equal(new Point2d(0, 0.5), param); 32 | Assert.Equal(pt, sphere.PointAt(param)); 33 | } 34 | 35 | 36 | [Fact] 37 | public void ComputeClosestPoint_GivesAccurateResult() 38 | { 39 | var sphere = new Sphere(); 40 | var pt = new Point3d(4, 0, 0); 41 | Assert.Equal(new Point3d(1, 0, 0), sphere.ClosestPointTo(pt)); 42 | } 43 | 44 | 45 | [Fact] 46 | public void ComputeDistance_GivesAccurateResult() 47 | { 48 | var sphere = new Sphere(); 49 | var pt = new Point3d(4, 0, 0); 50 | Assert.Equal(3.0, sphere.DistanceTo(pt)); 51 | } 52 | 53 | 54 | [Fact] 55 | public void Create_SphereWithEmptyConstructor_ReturnsXYPlaneUnitSphere() 56 | { 57 | var sphere = new Sphere(); 58 | Assert.Equal(1, sphere.Radius); 59 | Assert.Equal(Plane.WorldXY, sphere.Plane); 60 | Assert.Equal(Interval.Unit, sphere.DomainU); 61 | Assert.Equal(Interval.Unit, sphere.DomainV); 62 | } 63 | 64 | 65 | [Fact] 66 | public void Create_SphereWithPlaneAndRadius_ReturnsValidSphere() 67 | { 68 | var sphere = new Sphere(Plane.WorldXY, 1); 69 | Assert.Equal(1, sphere.Radius); 70 | Assert.Equal(Plane.WorldXY, sphere.Plane); 71 | Assert.Equal(Interval.Unit, sphere.DomainU); 72 | Assert.Equal(Interval.Unit, sphere.DomainV); 73 | } 74 | 75 | 76 | [Fact] 77 | public void Create_SphereWithZeroRadius_ThrowsException() => 78 | Assert.Throws(() => new Sphere(Plane.WorldXY, 0)); 79 | } 80 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/Vector3dTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry 6 | { 7 | public class Vector3dTests 8 | { 9 | [Fact] 10 | public void Can_ComputeAngle() 11 | { 12 | var v1 = Vector3d.UnitX; 13 | var v2 = Vector3d.UnitY; 14 | var v3 = v1 + v2; 15 | var q = 0.25 * Math.PI; 16 | var a = Vector3d.Angle(v1, v2); 17 | var a2 = Vector3d.Angle(v1, v3); 18 | Assert.True(Math.Abs(a - 0.5 * Math.PI) < Settings.Tolerance); 19 | Assert.True(Math.Abs(a2 - q) <= Settings.Tolerance); 20 | } 21 | 22 | 23 | [Fact] 24 | public void CanBe_Added() 25 | { 26 | const double a = 3.3; 27 | const double b = 2.2; 28 | const double c = 4.11; 29 | var vA = new Vector3d(a, b, c); 30 | var vB = new Vector3d(b, c, a); 31 | var ptResult = new Vector3d(a + b, b + c, c + a); 32 | Assert.True(vA + vB == ptResult); 33 | } 34 | 35 | 36 | [Fact] 37 | public void CanBe_ConvertedToString() 38 | { 39 | var result = "Vector3d{ 1, 0, 0 }"; 40 | var v = Vector3d.UnitX; 41 | var s = v.ToString(); 42 | Assert.True(s == result); 43 | } 44 | 45 | 46 | [Fact] 47 | public void CanBe_Created() 48 | { 49 | var v = Vector3d.UnitY; 50 | var pt = new Point3d(0, 2, 2); 51 | var newV = new Vector3d(v); 52 | var newVp = new Vector3d(pt); 53 | 54 | Assert.True(v == newV); 55 | Assert.True(pt == newVp); 56 | } 57 | 58 | 59 | [Fact] 60 | public void CanBe_Divided() 61 | { 62 | const double a = 3.3; 63 | const double b = 2.2; 64 | const double c = 4.11; 65 | const double m = 1.45; 66 | var v = new Vector3d(a, b, c); 67 | var ptResult = new Vector3d(a / m, b / m, c / m); 68 | Assert.True(v / m == ptResult); 69 | } 70 | 71 | 72 | [Fact] 73 | public void CanBe_Multiplied() 74 | { 75 | const double a = 3.3; 76 | const double b = 2.2; 77 | const double c = 4.11; 78 | const double m = 1.45; 79 | var v = new Vector3d(a, b, c); 80 | var ptResult = new Vector3d(a * m, b * m, c * m); 81 | Assert.True(v * m == ptResult); 82 | } 83 | 84 | 85 | [Fact] 86 | public void CanBe_Negated() 87 | { 88 | const double a = 3.3; 89 | const double b = 2.2; 90 | const double c = 4.11; 91 | var v = new Vector3d(a, b, c); 92 | var ptResult = new Vector3d(-a, -b, -c); 93 | Assert.True(-v == ptResult); 94 | } 95 | 96 | 97 | [Fact] 98 | public void CanBe_Substracted() 99 | { 100 | const double a = 3.3; 101 | const double b = 2.2; 102 | const double c = 4.11; 103 | var vA = new Vector3d(a, b, c); 104 | var vB = new Vector3d(b, c, a); 105 | var ptResult = new Vector3d(a - b, b - c, c - a); 106 | Assert.True(vA - vB == ptResult); 107 | } 108 | 109 | 110 | [Fact] 111 | public void EqualsAndHashCode_HaveConsistentResults() 112 | { 113 | var v = new Vector3d(-1, 1, 1); 114 | var v2 = new Vector3d(-1.000000005, 1, 1); 115 | var b1 = v == v2; 116 | var b2 = v.GetHashCode() == v2.GetHashCode(); 117 | 118 | Assert.True(b1 && b1 == b2); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/VectorEntity_Tests.cs: -------------------------------------------------------------------------------- 1 | namespace Paramdigma.Core.Tests 2 | { 3 | public abstract class VectorEntityTests 4 | { 5 | public abstract void CanAdd(T a, T b, T expected); 6 | 7 | public abstract void CanAdd_Itself(T a, T b, T expected); 8 | 9 | public abstract void CanSubstract_New(T a, T b, T expected); 10 | 11 | public abstract void CanSubstract_ToItself(T a, T b, T expected); 12 | 13 | public abstract void CanMultiply_New(T a, double scalar, T expected); 14 | 15 | public abstract void CanMultiply_Itself(T a, double scalar, T expected); 16 | 17 | public abstract void CanDivide_New(T a, double scalar, T expected); 18 | 19 | public abstract void CanDivide_Itself(T a, double scalar, T expected); 20 | 21 | public abstract void IsEqual_WithinTolerance(T a, T b); 22 | 23 | public abstract void IsEqual_HasEqualHashcodes(T a, T b); 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Geometry/3D/VectorNd_Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Paramdigma.Core.Geometry; 4 | using Xunit; 5 | 6 | namespace Paramdigma.Core.Tests 7 | { 8 | public class VectorNdTests : VectorEntityTests 9 | { 10 | public static IEnumerable VectorAddData => new List 11 | { 12 | new object[] 13 | { 14 | new VectorNd(0, 0, 0, 9, 3), new VectorNd(4, 5, 6), new VectorNd(4, 5, 6, 9, 3) 15 | }, 16 | new object[] 17 | { 18 | new VectorNd(3, 5, 0, 3), 19 | new VectorNd( 20 | 4, 21 | 5, 22 | 6, 23 | 1, 24 | 3, 25 | 5), 26 | new VectorNd( 27 | 7, 28 | 10, 29 | 6, 30 | 4, 31 | 3, 32 | 5) 33 | } 34 | }; 35 | 36 | public static IEnumerable VectorDivideData => new List 37 | { 38 | new object[] {new VectorNd(0, 0, 0), 4, new VectorNd(0, 0, 0)}, 39 | new object[] {new VectorNd(3, 5, 1), 2, new VectorNd(1.5, 2.5, 0.5)} 40 | }; 41 | 42 | public static IEnumerable VectorMultiplyData => new List 43 | { 44 | new object[] {new VectorNd(0, 0, 0), 5, new VectorNd(0, 0, 0)}, 45 | new object[] {new VectorNd(3, 5, 2), 6, new VectorNd(18, 30, 12)} 46 | }; 47 | 48 | public static IEnumerable VectorSubstractData => new List 49 | { 50 | new object[] 51 | { 52 | new VectorNd(0, 0, 0), new VectorNd(4, 5, 6), new VectorNd(-4, -5, -6) 53 | }, 54 | new object[] {new VectorNd(3, 5, 0), new VectorNd(4, 5, 6), new VectorNd(-1, 0, -6)} 55 | }; 56 | 57 | public static IEnumerable IsEqualData => new List 58 | { 59 | new object[] {new VectorNd(3, 3, 3), new VectorNd(3, 3, 3 + 1E-12)}, 60 | new object[] {new VectorNd(4, 5, 6), new VectorNd(4 + 1E-9, 5 + 1E-8, 6)} 61 | }; 62 | 63 | 64 | [Theory] 65 | [MemberData(nameof(VectorAddData))] 66 | public override void CanAdd(VectorNd a, VectorNd b, VectorNd expected) 67 | { 68 | var c = a + b; 69 | Assert.Equal(c, expected); 70 | } 71 | 72 | 73 | [Theory] 74 | [MemberData(nameof(VectorAddData))] 75 | public override void CanAdd_Itself(VectorNd a, VectorNd b, VectorNd expected) => 76 | Assert.Equal(a + b, expected); 77 | 78 | 79 | [Theory] 80 | [MemberData(nameof(VectorDivideData))] 81 | public override void CanDivide_New(VectorNd a, double scalar, VectorNd expected) => 82 | Assert.Equal(a / scalar, expected); 83 | 84 | 85 | [Theory] 86 | [MemberData(nameof(VectorMultiplyData))] 87 | public override void CanMultiply_New(VectorNd a, double scalar, VectorNd expected) => 88 | Assert.Equal(a * scalar, expected); 89 | 90 | 91 | [Theory] 92 | [MemberData(nameof(VectorSubstractData))] 93 | public override void CanSubstract_New(VectorNd a, VectorNd b, VectorNd expected) => 94 | Assert.Equal(a - b, expected); 95 | 96 | 97 | [Theory] 98 | [MemberData(nameof(VectorSubstractData))] 99 | public override void CanSubstract_ToItself(VectorNd a, VectorNd b, VectorNd expected) => 100 | Assert.Equal(a - b, expected); 101 | 102 | 103 | [Theory] 104 | [MemberData(nameof(IsEqualData))] 105 | public override void IsEqual_HasEqualHashcodes(VectorNd a, VectorNd b) => 106 | Assert.Equal(a.GetHashCode(), b.GetHashCode()); 107 | 108 | 109 | [Theory] 110 | [MemberData(nameof(IsEqualData))] 111 | public override void IsEqual_WithinTolerance(VectorNd a, VectorNd b) => Assert.Equal(a, b); 112 | 113 | 114 | public override void CanMultiply_Itself(VectorNd a, double scalar, VectorNd expected) => 115 | throw new NotImplementedException(); 116 | 117 | 118 | public override void CanDivide_Itself(VectorNd a, double scalar, VectorNd expected) => 119 | throw new NotImplementedException(); 120 | } 121 | } -------------------------------------------------------------------------------- /tests/Geometry/SpatialStructures/QuadTreeTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Geometry; 2 | using Paramdigma.Core.Spatial; 3 | using Xunit; 4 | 5 | namespace Paramdigma.Core.Tests.Geometry.SpatialStructures 6 | { 7 | public class QuadTreeTests 8 | { 9 | [Fact] 10 | public void CanCreate_QuadTree() 11 | { 12 | var range = new Rectangle2d( 13 | Point2d.Origin, 14 | new Point2d(1, 1) 15 | ); 16 | 17 | var tree = new QuadTree(range, .26); 18 | var pt = new Point2d(0.35, 0.35); 19 | var low = new Point2d(0.3, 0.3); 20 | var high = new Point2d(0.4, 0.4); 21 | var check = tree.Insert(pt); 22 | Assert.True(check); 23 | var expected = tree.QueryRange( 24 | new Rectangle2d(low, high) 25 | ); 26 | Assert.Equal(pt, expected[0]); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Optimization/GradientDescentOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using Paramdigma.Core.Optimization; 2 | using Xunit; 3 | 4 | namespace Paramdigma.Core.Tests.Optimization 5 | { 6 | public class GradientDescentOptionsTests 7 | { 8 | [Fact] 9 | public void CanCreate_Default() 10 | { 11 | var actual = GradientDescentOptions.Default; 12 | var expected = new GradientDescentOptions( 13 | 0.001, 14 | 10000, 15 | 0.01, 16 | 20, 17 | .01 18 | ); 19 | Assert.Equal(expected, actual); 20 | } 21 | 22 | 23 | [Fact] 24 | public void CanCreate_DefaultSmall() 25 | { 26 | var actual = GradientDescentOptions.DefaultSmall; 27 | var expected = new GradientDescentOptions( 28 | 0.0001, 29 | 10000, 30 | 0.02, 31 | 40, 32 | .001 33 | ); 34 | Assert.Equal(expected, actual); 35 | } 36 | 37 | 38 | [Fact] 39 | public void CanCreate_FromExisting() 40 | { 41 | var expected = GradientDescentOptions.DefaultSmall; 42 | var actual = new GradientDescentOptions(expected); 43 | Assert.Equal(expected, actual); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Optimization/GradientDescentTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Paramdigma.Core.Geometry; 3 | using Paramdigma.Core.Optimization; 4 | using Xunit; 5 | 6 | namespace Paramdigma.Core.Tests.Optimization 7 | { 8 | public class GradientDescentTests 9 | { 10 | [Fact] 11 | public void GradientDescent_Line() 12 | { 13 | var line = new Line(Point3d.WorldOrigin, new Point3d(1, 1, 0)); 14 | var gd = new GradientDescent(GradientDescentOptions.Default) 15 | { 16 | Options = {MaxIterations = 1} 17 | }; 18 | var input = 1; 19 | 20 | gd.Options.MaxIterations = 100; 21 | gd.Options.LearningRate = 100; 22 | 23 | gd.Minimize( 24 | values => line.PointAt(values[0]).Y, 25 | new List {input} 26 | ); 27 | var err = gd.Result.Error <= gd.Options.ErrorThreshold; 28 | var value = gd.Result.Values[0] <= 0.01; 29 | var gLength = gd.Result.GradientLength <= gd.Options.Limit; 30 | 31 | Assert.True(err || value || gLength); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /tests/Optimization/KMeansClusteringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Paramdigma.Core.Collections; 5 | using Paramdigma.Core.Geometry; 6 | using Paramdigma.Core.Optimization; 7 | using Xunit; 8 | 9 | namespace Paramdigma.Core.Tests.Optimization 10 | { 11 | public class KMeansClusteringTests 12 | { 13 | private static IEnumerable CreateClusterAround( 14 | BasePoint pt, 15 | double radius, 16 | int count) 17 | { 18 | var cluster = new List(); 19 | var rnd = new Random(); 20 | var range = new Interval(-radius, radius); 21 | for (var i = 0; i < count; i++) 22 | { 23 | var x = pt.X + range.RemapFromUnit(rnd.NextDouble()); 24 | var y = pt.Y + range.RemapFromUnit(rnd.NextDouble()); 25 | var z = pt.Z + range.RemapFromUnit(rnd.NextDouble()); 26 | cluster.Add(new VectorNd(x, y, z)); 27 | } 28 | 29 | return cluster; 30 | } 31 | 32 | 33 | [Theory] 34 | [InlineData(4, 20)] 35 | [InlineData(6, 18)] 36 | [InlineData(10, 15)] 37 | public void KMeans_MainTest(int expectedClusters, int expectedClusterCount) 38 | { 39 | //Generate random vectors 40 | var vectors = new List(); 41 | var cir = new Circle(Plane.WorldXY, 10); 42 | var pts = new List(); 43 | 44 | for (var i = 0; i < expectedClusters; i++) 45 | { 46 | var pt = cir.PointAt(( double ) i / expectedClusters); 47 | pts.Add(pt); 48 | vectors.AddRange(CreateClusterAround(pt, 1, expectedClusterCount)); 49 | } 50 | 51 | Assert.True(vectors.Count == expectedClusters * expectedClusterCount); 52 | var eventCheck = false; 53 | // When 54 | var kMeans = new KMeansClustering(100, expectedClusters, vectors); 55 | kMeans.IterationCompleted += (sender, args) => 56 | { 57 | Assert.True(args.Iteration >= 0); 58 | Assert.True(args.Clusters.Count == expectedClusters); 59 | eventCheck = true; 60 | }; 61 | kMeans.Run(); 62 | //Assert the Iteration completed event has been raised 63 | Assert.True(eventCheck); 64 | // Then 65 | kMeans.Clusters.ForEach( 66 | cluster => 67 | { 68 | Assert.NotEmpty(cluster); 69 | var first = new Point3d(cluster[0][0], cluster[0][1], cluster[0][2]); 70 | var closest = pts.First(pt => pt.DistanceTo(first) <= 2); 71 | foreach (var vector in cluster) 72 | { 73 | var pt = new Point3d(vector[0], vector[1], vector[2]); 74 | var dist = pt.DistanceTo(closest); 75 | Assert.True(dist >= 0); 76 | } 77 | }); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /tests/Paramdigma.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | IDE0060 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/RhinoConversions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Paramdigma.Core.Geometry; 3 | using RG = Rhino.Geometry; 4 | 5 | namespace Paramdigma.Core.Tests.Conversions 6 | { 7 | public static class RhinoConversions 8 | { 9 | public static Point3d ToCore(this RG.Point3d point) => 10 | new Point3d(point.X, point.Y, point.Z); 11 | 12 | 13 | public static RG.Point3d ToRhino(this Point3d point) => 14 | new RG.Point3d(point.X, point.Y, point.Z); 15 | 16 | 17 | public static Vector3d ToCore(this RG.Vector3d vector) => 18 | new Vector3d(vector.X, vector.Y, vector.Z); 19 | 20 | 21 | public static RG.Vector3d ToRhino(this Vector3d vector) => 22 | new RG.Vector3d(vector.X, vector.Y, vector.Z); 23 | 24 | 25 | public static RG.NurbsCurve ToRhino(this NurbsCurve curve) => 26 | throw new NotImplementedException(); 27 | 28 | 29 | public static NurbsCurve ToCore(this RG.NurbsCurve curve) => 30 | throw new NotImplementedException(); 31 | 32 | 33 | public static RG.NurbsSurface ToRhino(this NurbsSurface surface) 34 | { 35 | // Create surface 36 | var surf = RG.NurbsSurface.Create( 37 | 3, 38 | false, 39 | surface.DegreeU + 1, // order is degree+1 40 | surface.DegreeV + 1, 41 | surface.ControlPoints.N, 42 | surface.ControlPoints.M); 43 | 44 | // Assign control points 45 | for (var i = 0; i < surface.ControlPoints.N; i++) 46 | { 47 | for (var j = 0; j < surface.ControlPoints.M; j++) 48 | { 49 | var pt = surface.ControlPoints[i, j]; 50 | surf.Points.SetPoint(i, j, pt.X, pt.Y, pt.Z, pt.Weight); 51 | } 52 | } 53 | 54 | // TODO: Add switch for periodic knots. 55 | 56 | // Create uniform knots. 57 | surf.KnotsU.CreateUniformKnots(1); 58 | surf.KnotsV.CreateUniformKnots(1); 59 | 60 | // Update surface interval. 61 | surf.SetDomain(0, new RG.Interval(0, 1)); 62 | surf.SetDomain(1, new RG.Interval(0, 1)); 63 | 64 | return surf; 65 | } 66 | 67 | 68 | public static NurbsSurface ToCore(this RG.NurbsSurface surface) => 69 | throw new NotImplementedException(); 70 | } 71 | } -------------------------------------------------------------------------------- /tests/Utilities/JsonFileDataAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using Xunit.Sdk; 8 | 9 | namespace Paramdigma.Core.Tests 10 | { 11 | /// 12 | /// Data Attribute to extract text data out of JSON files 13 | /// From: 14 | /// https://andrewlock.net/creating-a-custom-xunit-theory-test-dataattribute-to-load-data-from-json-files/ 15 | /// 16 | public class JsonFileDataAttribute : DataAttribute 17 | { 18 | private readonly string filePath; 19 | private readonly string propertyName; 20 | 21 | 22 | /// 23 | /// Load data from a JSON file as the data source for a theory 24 | /// 25 | /// The absolute or relative path to the JSON file to load 26 | public JsonFileDataAttribute(string filePath) 27 | : this(filePath, null) { } 28 | 29 | 30 | /// 31 | /// Load data from a JSON file as the data source for a theory 32 | /// 33 | /// The absolute or relative path to the JSON file to load 34 | /// 35 | /// The name of the property on the JSON file that contains the data for the 36 | /// test 37 | /// 38 | public JsonFileDataAttribute(string filePath, string propertyName) 39 | { 40 | this.filePath = filePath; 41 | this.propertyName = propertyName; 42 | } 43 | 44 | 45 | /// 46 | public override IEnumerable GetData(MethodInfo testMethod) 47 | { 48 | if (testMethod == null) 49 | throw new ArgumentNullException(nameof(testMethod)); 50 | 51 | // Get the absolute path to the JSON file 52 | var path = Path.IsPathRooted(this.filePath) 53 | ? this.filePath 54 | : Path.GetRelativePath(Directory.GetCurrentDirectory(), this.filePath); 55 | 56 | if (!File.Exists(path)) 57 | throw new ArgumentException($"Could not find file at path: {path}"); 58 | 59 | // Load the file 60 | var fileData = File.ReadAllText(this.filePath); 61 | 62 | if (string.IsNullOrEmpty(this.propertyName)) 63 | //whole file is the data 64 | return JsonConvert.DeserializeObject>(fileData); 65 | 66 | // Only use the specified property as the data 67 | var allData = JObject.Parse(fileData); 68 | var data = allData[this.propertyName]; 69 | return data.ToObject>(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /tests/Utilities/ResourcesTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using Xunit; 3 | using Xunit.Abstractions; 4 | 5 | namespace Paramdigma.Core.Tests 6 | { 7 | public class ResourcesTests 8 | { 9 | private readonly ITestOutputHelper testOutputHelper; 10 | 11 | 12 | public ResourcesTests(ITestOutputHelper testOutputHelper) => 13 | this.testOutputHelper = testOutputHelper; 14 | 15 | 16 | [Fact] 17 | public void ResetSettingsValues_FromResource() 18 | { 19 | this.testOutputHelper.WriteLine( 20 | Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); 21 | this.testOutputHelper.WriteLine(Settings.MaxDecimals.ToString()); 22 | this.testOutputHelper.WriteLine(Settings.GetDefaultTesselationLevel().ToString()); 23 | Settings.SetTolerance(0.1); 24 | this.testOutputHelper.WriteLine( 25 | Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); 26 | this.testOutputHelper.WriteLine(Settings.MaxDecimals.ToString()); 27 | Settings.Reset(); 28 | this.testOutputHelper.WriteLine( 29 | Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); 30 | this.testOutputHelper.WriteLine(Settings.MaxDecimals.ToString()); 31 | } 32 | } 33 | } --------------------------------------------------------------------------------