diff --git a/.editorconfig b/.editorconfig index df5e28a..b981a9d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,381 +1,80 @@ root=true -# Microsoft .NET properties -csharp_new_line_before_members_in_object_initializers=true - -########################################## -# File Extension Settings -########################################## - -# Visual Studio Solution Files -[*.sln] -indent_style=tab - -# Visual Studio XML Project Files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] -indent_size=2 - -# Various XML Configuration Files -[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] -indent_size=2 - -# JSON Files -[*.{json,json5}] -indent_size=2 - -# YAML Files -[*.{yml,yaml}] -indent_size=2 - -# Markdown Files -[*.md] -trim_trailing_whitespace=false - -# Web Files -[*.{htm,html,js,ts,tsx,css,sass,scss,less,svg,vue}] -indent_size=2 - -# Batch Files -[*.{cmd,bat}] -end_of_line=crlf - -# Bash Files -[*.sh] -end_of_line=lf - -########################################## -# .NET Language Conventions -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions -########################################## - -# .NET Code Style Settings -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings -[*.{cs,csx,cake,vb}] -# "this." and "Me." qualifiers -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me -dotnet_style_qualification_for_field=true:silent -dotnet_style_qualification_for_property=true:silent -dotnet_style_qualification_for_method=true:silent -dotnet_style_qualification_for_event=true:silent -# Language keywords instead of framework type names for type references -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords -dotnet_style_predefined_type_for_locals_parameters_members=true:warning -dotnet_style_predefined_type_for_member_access=true:warning -# Modifier preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers -dotnet_style_require_accessibility_modifiers=always:warning -csharp_preferred_modifier_order=public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async -visual_basic_preferred_modifier_order=Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async -dotnet_style_readonly_field=true:warning -# Parentheses preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences -dotnet_style_parentheses_in_arithmetic_binary_operators=always_for_clarity:suggestion -dotnet_style_parentheses_in_relational_binary_operators=always_for_clarity:suggestion -dotnet_style_parentheses_in_other_binary_operators=always_for_clarity:suggestion -dotnet_style_parentheses_in_other_operators=always_for_clarity:suggestion -# Expression-level preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences -dotnet_style_object_initializer=true:warning -dotnet_style_collection_initializer=true:warning -dotnet_style_explicit_tuple_names=true:warning -dotnet_style_prefer_inferred_tuple_names=true:warning -dotnet_style_prefer_inferred_anonymous_type_member_names=true:warning -dotnet_style_prefer_auto_properties=true:warning -dotnet_style_prefer_is_null_check_over_reference_equality_method=true:refactoring -dotnet_style_prefer_conditional_expression_over_assignment=false:refactoring -dotnet_style_prefer_conditional_expression_over_return=false:refactoring -dotnet_style_prefer_compound_assignment=true:warning -# Null-checking preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences -dotnet_style_coalesce_expression=true:warning -dotnet_style_null_propagation=true:warning -# Parameter preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences -dotnet_code_quality_unused_parameters=all:suggestion -# More style options (Undocumented) -# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 -dotnet_style_operator_placement_when_wrapping=end_of_line - -# C# Code Style Settings -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings -[*.{cs,csx,cake}] -# Implicit and explicit types -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types -csharp_style_var_for_built_in_types=true:refactoring -csharp_style_var_when_type_is_apparent=true:refactoring -csharp_style_var_elsewhere=true:refactoring -# Expression-bodied members -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members -csharp_style_expression_bodied_methods=true:refactoring -csharp_style_expression_bodied_constructors=true:refactoring -csharp_style_expression_bodied_operators=true:refactoring -csharp_style_expression_bodied_properties=true:refactoring -csharp_style_expression_bodied_indexers=true:refactoring -csharp_style_expression_bodied_accessors=true:refactoring -csharp_style_expression_bodied_lambdas=true:refactoring -csharp_style_expression_bodied_local_functions=true:refactoring -# Pattern matching -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching -csharp_style_pattern_matching_over_is_with_cast_check=true:suggestion -csharp_style_pattern_matching_over_as_with_null_check=true:suggestion -# Inlined variable declarations -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations -csharp_style_inlined_variable_declaration=true:warning -# Expression-level preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences -csharp_prefer_simple_default_expression=true:warning -# "Null" checking preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences -csharp_style_throw_expression=true:warning -csharp_style_conditional_delegate_call=true:warning -# Code block preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences -csharp_prefer_braces=false -# Unused value preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences -csharp_style_unused_value_expression_statement_preference=discard_variable:refactoring -csharp_style_unused_value_assignment_preference=discard_variable:refactoring -# Index and range preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences -csharp_style_prefer_index_operator=true:warning -csharp_style_prefer_range_operator=true:warning -# Miscellaneous preferences -# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences -csharp_style_deconstructed_variable_declaration=true:warning -csharp_style_pattern_local_over_anonymous_function=true:warning -csharp_using_directive_placement=outside_namespace:silent -csharp_prefer_static_local_function=true:warning -csharp_prefer_simple_using_statement=false:warning - -########################################## -# .NET Formatting Conventions -# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions -########################################## - -# Organize usings -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives -dotnet_sort_system_directives_first=true -# Newline options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options -csharp_new_line_before_open_brace=all -csharp_new_line_before_else=true -csharp_new_line_before_catch=true -csharp_new_line_before_finally=true -csharp_new_line_before_members_in_object_initializers=true -csharp_new_line_before_members_in_anonymous_types=true -csharp_new_line_between_query_expression_clauses=true - -# Indentation options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options -csharp_indent_case_contents=true -csharp_indent_switch_labels=true -csharp_indent_labels=no_change -csharp_indent_block_contents=true -csharp_indent_braces=false -csharp_indent_case_contents_when_block=false -# Spacing options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options -csharp_space_after_cast=false -csharp_space_after_keywords_in_control_flow_statements=true -csharp_space_between_parentheses=false -csharp_space_before_colon_in_inheritance_clause=true -csharp_space_after_colon_in_inheritance_clause=true -csharp_space_around_binary_operators=before_and_after -csharp_space_between_method_declaration_parameter_list_parentheses=false -csharp_space_between_method_declaration_empty_parameter_list_parentheses=false -csharp_space_between_method_declaration_name_and_open_parenthesis=false -csharp_space_between_method_call_parameter_list_parentheses=false -csharp_space_between_method_call_empty_parameter_list_parentheses=false -csharp_space_between_method_call_name_and_opening_parenthesis=false -csharp_space_after_comma=true -csharp_space_before_comma=false -csharp_space_after_dot=false -csharp_space_before_dot=false -csharp_space_after_semicolon_in_for_statement=true -csharp_space_before_semicolon_in_for_statement=false -csharp_space_around_declaration_statements=false -csharp_space_before_open_square_brackets=false -csharp_space_between_empty_square_brackets=false -csharp_space_between_square_brackets=false -# Wrapping options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options -csharp_preserve_single_line_statements=false -csharp_preserve_single_line_blocks=false - -########################################## -# .NET Naming Conventions -# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions -########################################## +[*] +# Most of the standard properties are supported +indent_size=4 +max_line_length=100 -[*.{cs,csx,cake,vb}] +# Most frequently used .NET-coding-convention properties are supported +csharp_space_between_parentheses=control_flow_statements,expressions,type_casts +csharp_style_var_for_built_in_types=true:suggestion -########################################## -# Styles -########################################## +# dotnet_diagnostic rules are supported +dotnet_diagnostic.cs1058.severity=suggestion -# camel_case_style - Define the camelCase style -dotnet_naming_style.camel_case_style.capitalization=camel_case -# pascal_case_style - Define the PascalCase style -dotnet_naming_style.pascal_case_style.capitalization=pascal_case -# first_upper_style - The first character must start with an upper-case character -dotnet_naming_style.first_upper_style.capitalization=first_word_upper -# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' -dotnet_naming_style.prefix_interface_with_i_style.capitalization=pascal_case -dotnet_naming_style.prefix_interface_with_i_style.required_prefix=I -# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' -dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization=pascal_case -dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix=T -# disallowed_style - Anything that has this style applied is marked as disallowed -# dotnet_naming_style.disallowed_style.capitalization=pascal_case -# dotnet_naming_style.disallowed_style.required_prefix=____RULE_VIOLATION____ -# dotnet_naming_style.disallowed_style.required_suffix=____RULE_VIOLATION____ -# internal_error_style - This style should never occur... if it does, it's indicates a bug in file or in the parser using the file -# dotnet_naming_style.internal_error_style.capitalization=pascal_case -# dotnet_naming_style.internal_error_style.required_prefix=____INTERNAL_ERROR____ -# dotnet_naming_style.internal_error_style.required_suffix=____INTERNAL_ERROR____ +# JetBrains Rider custom properties for code formatting styles +resharper_csharp_blank_lines_around_invocable=2 -########################################## -# .NET Design Guideline Field Naming Rules -# Naming rules for fields follow the .NET Framework design guidelines -# https://docs.microsoft.com/dotnet/standard/design-guidelines/index -########################################## +# JetBrains Rider custom properties for code syntax styles +csharp_default_private_modifier=explicit +braces_for_ifelse=not_required -# All public/protected/protected_internal constant fields must be PascalCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities=public, protected, protected_internal -dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers=const -dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds=field -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols=public_protected_constant_fields_group -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style=pascal_case_style -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity=warning - -# All public/protected/protected_internal static readonly fields must be PascalCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities=public, protected, protected_internal -dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers=static, readonly -dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds=field -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols=public_protected_static_readonly_fields_group -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style=pascal_case_style -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity=warning - -# No other public/protected/protected_internal fields are allowed -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities=public, protected, protected_internal -dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds=field -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols=other_public_protected_fields_group -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style=disallowed_style -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity=error - -########################################## -# StyleCop Field Naming Rules -# Naming rules for fields follow the StyleCop analyzers -# This does not override any rules using disallowed_style above -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers -########################################## - -# All constant fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities=public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers=const -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds=field -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols=stylecop_constant_fields_group -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style=pascal_case_style -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity=warning - -# All static readonly fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities=public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers=static, readonly -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds=field -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols=stylecop_static_readonly_fields_group -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style=pascal_case_style -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity=warning - -# No non-private instance fields are allowed -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities=public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds=field -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols=stylecop_fields_must_be_private_group -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style=disallowed_style -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity=error - -# Private fields must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md -dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities=private -dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds=field -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols=stylecop_private_fields_group -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style=camel_case_style -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity=warning - -# Local variables must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md -dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities=local -dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds=local -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols=stylecop_local_fields_group -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style=camel_case_style -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity=warning - -# This rule should never fire. However, it's included for at least two purposes: -# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. -# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). -dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities=* -dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds=field -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols=sanity_check_uncovered_field_case_group -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style=internal_error_style -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity=error - - -########################################## -# Other Naming Rules -########################################## - -# All of the following must be PascalCase: -# - Namespaces -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md -# - Classes and Enumerations -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md -# - Delegates -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types -# - Constructors, Properties, Events, Methods -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members -dotnet_naming_symbols.element_group.applicable_kinds=namespace, class, enum, struct, delegate, event, method, property -dotnet_naming_rule.element_rule.symbols=element_group -dotnet_naming_rule.element_rule.style=pascal_case_style -dotnet_naming_rule.element_rule.severity=warning - -# Interfaces use PascalCase and are prefixed with uppercase 'I' -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -dotnet_naming_symbols.interface_group.applicable_kinds=interface -dotnet_naming_rule.interface_rule.symbols=interface_group -dotnet_naming_rule.interface_rule.style=prefix_interface_with_i_style -dotnet_naming_rule.interface_rule.severity=warning - -# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -dotnet_naming_symbols.type_parameter_group.applicable_kinds=type_parameter -dotnet_naming_rule.type_parameter_rule.symbols=type_parameter_group -dotnet_naming_rule.type_parameter_rule.style=prefix_type_parameters_with_t_style -dotnet_naming_rule.type_parameter_rule.severity=warning - -# Function parameters use camelCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters -dotnet_naming_symbols.parameters_group.applicable_kinds=parameter -dotnet_naming_rule.parameters_rule.symbols=parameters_group -dotnet_naming_rule.parameters_rule.style=camel_case_style -dotnet_naming_rule.parameters_rule.severity=warning - -# Microsoft .NET properties -csharp_prefer_braces=false:none -csharp_space_after_keywords_in_control_flow_statements=true -csharp_space_between_method_call_parameter_list_parentheses=false -csharp_space_between_method_declaration_parameter_list_parentheses=false -csharp_space_between_parentheses=false +# JetBrains Rider custom properties for code inspections +resharper_possible_null_reference_exception_highlighting=error +resharper_replace_with_string_is_null_or_empty_highlighting=none # ReSharper properties -resharper_braces_redundant=true +resharper_accessor_owner_body=expression_body +resharper_align_linq_query=true +resharper_align_multiline_argument=true +resharper_align_multiline_calls_chain=true +resharper_align_multiline_expression=true +resharper_align_multiline_extends_list=true +resharper_align_multiline_for_stmt=true +resharper_align_multiline_parameter=true +resharper_align_multiple_declaration=true +resharper_align_multline_type_parameter_constrains=true +resharper_align_multline_type_parameter_list=true +resharper_align_tuple_components=true +resharper_allow_comment_after_lbrace=true +resharper_apply_auto_detected_rules=false +resharper_braces_for_dowhile=required_for_multiline +resharper_braces_for_fixed=required_for_multiline +resharper_braces_for_for=required_for_multiline +resharper_braces_for_foreach=required_for_multiline +resharper_braces_for_ifelse=required_for_multiline +resharper_braces_for_lock=required_for_multiline +resharper_braces_for_using=required_for_multiline +resharper_braces_for_while=required_for_multiline +resharper_csharp_blank_lines_around_region=2 +resharper_csharp_blank_lines_inside_region=2 +resharper_csharp_empty_block_style=together_same_line +resharper_csharp_int_align_comments=true +resharper_csharp_keep_blank_lines_in_code=1 +resharper_csharp_keep_blank_lines_in_declarations=1 +resharper_csharp_outdent_commas=true +resharper_csharp_outdent_dots=true +resharper_csharp_wrap_after_declaration_lpar=true +resharper_csharp_wrap_after_invocation_lpar=true +resharper_csharp_wrap_arguments_style=chop_if_long +resharper_csharp_wrap_before_binary_opsign=true +resharper_csharp_wrap_extends_list_style=chop_if_long +resharper_csharp_wrap_parameters_style=chop_if_long resharper_empty_block_style=together_same_line +resharper_indent_nested_fixed_stmt=true +resharper_indent_nested_foreach_stmt=true +resharper_indent_nested_for_stmt=true +resharper_indent_nested_lock_stmt=true +resharper_indent_nested_usings_stmt=true +resharper_indent_nested_while_stmt=true +resharper_keep_existing_embedded_arrangement=false +resharper_keep_existing_switch_expression_arrangement=false resharper_local_function_body=expression_body +resharper_max_array_initializer_elements_on_line=6 +resharper_max_enum_members_on_line=4 +resharper_max_formal_parameters_on_line=4 +resharper_max_initializer_elements_on_line=6 +resharper_max_invocation_arguments_on_line=6 +resharper_place_simple_embedded_statement_on_same_line=false resharper_space_within_catch_parentheses=false resharper_space_within_checked_parentheses=false resharper_space_within_foreach_parentheses=false @@ -385,38 +84,53 @@ resharper_space_within_parentheses=false resharper_space_within_switch_parentheses=false resharper_space_within_using_parentheses=false resharper_space_within_while_parentheses=false +resharper_wrap_array_initializer_style=chop_if_long +resharper_wrap_before_linq_expression=false +resharper_wrap_chained_method_calls=chop_if_long +resharper_wrap_enum_declaration=chop_if_long +resharper_wrap_linq_expressions=chop_if_long +resharper_xmldoc_wrap_lines=false -########################################## -# License -########################################## -# The following applies as to the .editorconfig file ONLY, and is -# included below for reference, per the requirements of the license -# corresponding to this .editorconfig file. -# See: https://github.com/RehanSaeed/EditorConfig -# -# MIT License -# -# Copyright (c) 2017-2019 Muhammad Rehan Saeed -# Copyright (c) 2019 Henry Gabryjelski -# -# Permission is hereby granted, free of charge, to any -# person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the -# Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, -# sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject -# to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -########################################## +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers=true +csharp_new_line_between_query_expression_clauses=true +csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_style_expression_bodied_accessors=true:suggestion +csharp_style_expression_bodied_constructors=true:none +csharp_style_expression_bodied_methods=true:none +csharp_style_expression_bodied_properties=true:suggestion +csharp_style_var_elsewhere=true:suggestion +csharp_style_var_when_type_is_apparent=true:suggestion +dotnet_diagnostic.sa1101.severity=none +dotnet_diagnostic.sa1124.severity=suggestion +dotnet_diagnostic.sa1128.severity=none +dotnet_diagnostic.sa1200.severity=none +dotnet_diagnostic.sa1201.severity=none +dotnet_diagnostic.sa1202.severity=none +dotnet_diagnostic.sa1203.severity=none +dotnet_diagnostic.sa1204.severity=none +dotnet_diagnostic.sa1401.severity=none +dotnet_diagnostic.sa1502.severity=none +dotnet_diagnostic.sa1503.severity=none +dotnet_diagnostic.sa1519.severity=none +dotnet_diagnostic.sa1600.severity=none +dotnet_diagnostic.sa1602.severity=none +dotnet_diagnostic.sa1610.severity=none +dotnet_diagnostic.sa1616.severity=none +dotnet_diagnostic.sa1633.severity=none +dotnet_diagnostic.sa1648.severity=none +dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion +dotnet_style_predefined_type_for_member_access=true:suggestion +dotnet_style_qualification_for_event=true:suggestion +dotnet_style_qualification_for_field=true:suggestion +dotnet_style_qualification_for_method=true:suggestion +dotnet_style_qualification_for_property=true:suggestion +dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion + +[*.{appxmanifest,asax,ascx,aspx,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style=space +indent_size=4 +tab_width=4 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bc73d5a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: csharp -mono: none -sudo: required -dist: xenial -dotnet: 3.0 - -script: - - dotnet restore - - dotnet build -c Release - - dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=../coverage/opencover.xml - -after_success: - - bash <(curl -s https://codecov.io/bash) -f "coverage/opencover.xml" - -branches: - only: - - master - - develop - - feature/* - -notifications: - slack: paramdigma:FtUcdwSDOynOHw2M479kXHxs#geometry-library diff --git a/CHANGELOG.md b/CHANGELOG.md index dee92d5..ae3db6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.1.0] - 21-Nov-2020 + +So... another couple of release notes missing! 😅 What's important? + +- Added NURBS support in curves and surfaces. +- Added some spatial search algorithms. +- Improved testing and coverage. ## [0.0.6] - 14-June-2020 diff --git a/Paramdigma.Core.sln.DotSettings b/Paramdigma.Core.sln.DotSettings index 0b65484..cfcafff 100644 --- a/Paramdigma.Core.sln.DotSettings +++ b/Paramdigma.Core.sln.DotSettings @@ -1,4 +1,12 @@  True + True + True + True + + True - True \ No newline at end of file + True + True + + \ No newline at end of file diff --git a/README.md b/README.md index 30c1f5c..1c90d8d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![License](https://img.shields.io/github/license/Paramdigma/Core.svg)](https://github.com/Paramdigma/Core/blob/master/LICENSE) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/paramdigma/core?sort=semver) -![GitHub commits since latest release (by SemVer)](https://img.shields.io/github/commits-since/paramdigma/core/latest/master?sort=semver) ![Main language](https://img.shields.io/github/languages/top/Paramdigma/Core.svg) ![Code Size](https://img.shields.io/github/languages/code-size/Paramdigma/Core.svg) @@ -37,7 +36,7 @@ If you are looking for just a geometry library to plug into a project we also pr ## Documentation -You can find the Docfx built documentation for the latest relase on the 'master' branch at: +You can find the Docfx built documentation for the latest release on the 'master' branch at: [https://paramdigma.com/Core/](https://paramdigma.com/Core/) diff --git a/src/Collections/Interval.cs b/src/Collections/Interval.cs index 4a7c82e..5a73b27 100644 --- a/src/Collections/Interval.cs +++ b/src/Collections/Interval.cs @@ -22,6 +22,7 @@ public Interval(double start, double end) this.End = end; } + /// /// Initializes a new instance of the struct from another interval. /// @@ -30,6 +31,7 @@ public Interval(double start, double end) public Interval(Interval interval) : this(interval.Start, interval.End) { } + /// /// Gets a new unit interval. /// @@ -40,21 +42,13 @@ public Interval(Interval interval) /// Gets or sets the starting value of the interval. /// /// Value will always be lower unless interval is inverted. - public double Start - { - get; - set; - } + public double Start { get; set; } /// /// Gets or sets the ending value of the interval. /// /// Value will always be the biggest unless interval is inverted. - public double End - { - get; - set; - } + public double End { get; set; } /// /// Gets the space between the start and end of the interval. @@ -66,6 +60,7 @@ public double End /// public bool HasInvertedDirection => this.Length < 0; + /// /// Crop a number so that it's contained on the given interval. /// @@ -81,6 +76,7 @@ public static double CropNumber(double number, Interval interval) return number; } + /// /// Remap a number from one interval to another. /// @@ -93,9 +89,10 @@ public static double RemapNumber(double number, Interval fromInterval, Interval var cropped = fromInterval.Contains(number) ? number : fromInterval.Crop(number); var proportion = (cropped - fromInterval.Start) / Math.Abs(fromInterval.Length); - return toInterval.Start + (toInterval.Length * proportion); + return toInterval.Start + toInterval.Length * proportion; } + /// /// Crop a number so that it is contained inside this interval. /// @@ -103,13 +100,16 @@ public static double RemapNumber(double number, Interval fromInterval, Interval /// Cropped number. public double Crop(double number) => CropNumber(number, this); + /// /// Remap a number from this interval to a given one. /// /// Number to remap. /// Interval to remap number to. /// Remapped number inside given interval. - public double Remap(double number, Interval toInterval) => RemapNumber(number, this, toInterval); + public double Remap(double number, Interval toInterval) => + RemapNumber(number, this, toInterval); + /// /// Remap a number from this interval to a unit interval. @@ -118,6 +118,7 @@ public static double RemapNumber(double number, Interval fromInterval, Interval /// Value remaped from 0 to 1. public double RemapToUnit(double number) => this.Remap(number, Unit); + /// /// Remap a number from a unit interval to this interval. /// @@ -125,6 +126,7 @@ public static double RemapNumber(double number, Interval fromInterval, Interval /// Remapped number. public double RemapFromUnit(double number) => Unit.Remap(number, this); + /// /// Check if a number is contained inside this interval. /// @@ -137,6 +139,7 @@ public bool Contains(double number) return min <= number && number <= max; } + /// /// Swap the Start and End values of this interval. /// diff --git a/src/Collections/Matrix{T}.cs b/src/Collections/Matrix{T}.cs index 3d489c8..e9570bb 100644 --- a/src/Collections/Matrix{T}.cs +++ b/src/Collections/Matrix{T}.cs @@ -14,12 +14,14 @@ public class Matrix // https://codereview.stackexchange.com/questions/194732/class-matrix-implementation private T[,] data; + /// /// Initializes a new instance of the class. /// /// Size of the square Matrix. public Matrix(int n) => this.data = new T[n, n]; + /// /// Initializes a new instance of the class of the specified size. /// @@ -27,23 +29,25 @@ public class Matrix /// Row size. public Matrix(int n, int m) => this.data = new T[n, m]; + /// /// Initializes a new instance of the class from a 2D array. /// /// 2D array of data. public Matrix(T[,] data) => this.data = data; + /// /// Gets columns. /// /// Number of columns on the Matrix. - public int N => this.data.GetUpperBound(0) + 1; + public int N => this.data.GetLength(0); /// /// Gets rows. /// /// Number of rows on the Matrix. - public int M => this.data.GetUpperBound(1) + 1; + public int M => this.data.GetLength(1); /// /// Gets a specific item in the matrix. @@ -52,6 +56,12 @@ public class Matrix /// Column. public ref T this[int row, int column] => ref this.data[row, column]; + /// + /// Gets the amount of items in this matrix. + /// + public int Count => this.data.Length; + + /// /// Get the row of a matrix at the specified index. /// @@ -66,6 +76,7 @@ public T[] Row(int n) return row; } + /// /// Get the column of a matrix at the specified index. /// @@ -80,34 +91,47 @@ public T[] Column(int m) return col; } + // ----- ORDERING METHODS ----- + /// /// Turns columns into rows and rows into columns. /// public void FlipMatrix() => throw - // TODO: Implement FlipMatrix() - new NotImplementedException(); + // TODO: Implement FlipMatrix() + new NotImplementedException(); + /// /// Increment Matrix column size by a specified amount. /// It accepts both increasing and decreasing the size. /// /// Positive or negative increment. - public void IncrementColumns(int incrementN) => this.ResizeMatrix(ref this.data, this.N + incrementN, this.M); + public void IncrementColumns(int incrementN) => this.ResizeMatrix( + ref this.data, + this.N + incrementN, + this.M); + /// /// Increment Matrix row size by a specified amount. /// It accepts both increasing and decreasing the size. /// /// Positive or negative increment. - public void IncrementRows(int incrementM) => this.ResizeMatrix(ref this.data, this.N, this.M + incrementM); + public void IncrementRows(int incrementM) => this.ResizeMatrix( + ref this.data, + this.N, + this.M + incrementM); + /// /// Increase or decrease the matrix size symetrically. /// /// Symetric increase/decrease. - public void IncrementMatrixSize(int symetricIncrement) => this.IncrementMatrixSize(symetricIncrement, symetricIncrement); + public void IncrementMatrixSize(int symetricIncrement) => + this.IncrementMatrixSize(symetricIncrement, symetricIncrement); + /// /// Increase or decrease the column size of the matrix. @@ -120,6 +144,7 @@ public void IncrementMatrixSize(int columnIncrement, int rowIncrement) this.IncrementRows(rowIncrement); } + /// /// Obtains all neighbour entities surrounding the specified matrix coordinates. /// @@ -135,6 +160,7 @@ public List GetAllNeighboursAt(int column, int row) return neighbours; } + /// /// Obtains corner neighbour entities surrounding the specified matrix coordinates. /// @@ -145,6 +171,7 @@ public List GetAllNeighboursAt(int column, int row) // TODO: Implement GetCornerNeighboursOfEntityAt() new NotImplementedException(); + /// /// Obtains contiguous neighbour entities surrounding the specified matrix coordinates. /// @@ -155,10 +182,12 @@ public List GetAllNeighboursAt(int column, int row) // TODO: Implement GetContiguousNeighboursOfEntityAt() new NotImplementedException(); + /// /// Resizes any given 2 dimensional array. /// It accepts smaller and bigger array outputs. - /// Obtained from: https://stackoverflow.com/questions/6539571/how-to-resize-multidimensional-2d-array-in-c . + /// Obtained from: + /// https://stackoverflow.com/questions/6539571/how-to-resize-multidimensional-2d-array-in-c . /// /// 2D Array to resize. /// Number of resulting columns in the array. diff --git a/src/Curves/Geodesics.cs b/src/Curves/Geodesics.cs index 4923f4a..4205bc2 100644 --- a/src/Curves/Geodesics.cs +++ b/src/Curves/Geodesics.cs @@ -19,7 +19,12 @@ public static class Geodesics /// Maximum iterations. /// Geodesic curves. /// True if successful. - public static bool StartDir(MeshPoint meshPoint, Vector3d vector, Mesh mesh, int maxIter, out List geodesic) + public static bool StartDir( + MeshPoint meshPoint, + Vector3d vector, + Mesh mesh, + int maxIter, + out List geodesic) { // Get initial face on the mesh var initialFace = mesh.Faces[meshPoint.FaceIndex]; @@ -48,8 +53,12 @@ public static bool StartDir(MeshPoint meshPoint, Vector3d vector, Mesh mesh, int var nextFace = halfEdge.Twin.Face; // Flip vector to next face - var perpVector = Vector3d.CrossProduct(thisDirection, MeshGeometry.FaceNormal(thisFace)); - var nextVector = Vector3d.CrossProduct(MeshGeometry.FaceNormal(nextFace), perpVector); + var perpVector = Vector3d.CrossProduct( + thisDirection, + MeshGeometry.FaceNormal(thisFace)); + var nextVector = Vector3d.CrossProduct( + MeshGeometry.FaceNormal(nextFace), + perpVector); // Assign iteration variables to current thisPoint = nextPoint; diff --git a/src/Curves/LevelSets.cs b/src/Curves/LevelSets.cs index 0b7b2a1..1f1c2f4 100644 --- a/src/Curves/LevelSets.cs +++ b/src/Curves/LevelSets.cs @@ -16,14 +16,17 @@ public static class LevelSets /// List of level values to be computed. /// The mesh to compute the level-sets in. /// Resulting level sets. - public static void ComputeLevels(string valueKey, List levels, Mesh mesh, out List> levelSets) + public static void ComputeLevels( + string valueKey, + List levels, + Mesh mesh, + out List> levelSets) { var resultLines = new List>(); for (var i = 0; i < levels.Count; i++) resultLines.Add(new List()); - var iter = 0; foreach (var face in mesh.Faces) { var count = 0; @@ -34,13 +37,12 @@ public static void ComputeLevels(string valueKey, List levels, Mesh mesh count++; } - - iter++; } levelSets = resultLines; } + /// /// Compute the level on a specified face. /// @@ -52,16 +54,23 @@ public static void ComputeLevels(string valueKey, List levels, Mesh mesh public static bool GetFaceLevel(string valueKey, double level, MeshFace face, out Line line) { var adj = face.AdjacentVertices(); - var vertexValues = new List {adj[0].UserValues[valueKey], adj[1].UserValues[valueKey], adj[2].UserValues[valueKey]}; + var vertexValues = new List + { + adj[0].UserValues[valueKey], + adj[1].UserValues[valueKey], + adj[2].UserValues[valueKey] + }; var above = new List(); var below = new List(); for (var i = 0; i < vertexValues.Count; i++) + { if (vertexValues[i] < level) below.Add(i); else above.Add(i); + } if (above.Count == 3 || below.Count == 3) { @@ -74,20 +83,23 @@ public static bool GetFaceLevel(string valueKey, double level, MeshFace face, ou var intersectionPoints = new List(); foreach (var i in above) - foreach (var j in below) { - var diff = vertexValues[i] - vertexValues[j]; - var desiredDiff = level - vertexValues[j]; - var unitizedDistance = desiredDiff / diff; - var edgeV = adj[i] - adj[j]; - var levelPoint = adj[j] + (edgeV * unitizedDistance); - intersectionPoints.Add(levelPoint); + foreach (var j in below) + { + var diff = vertexValues[i] - vertexValues[j]; + var desiredDiff = level - vertexValues[j]; + var unitizedDistance = desiredDiff / diff; + var edgeV = adj[i] - adj[j]; + var levelPoint = adj[j] + edgeV * unitizedDistance; + intersectionPoints.Add(levelPoint); + } } line = new Line(intersectionPoints[0], intersectionPoints[1]); return true; } + /// /// Compute the gradient on a given mesh given some per-vertex values. /// @@ -103,6 +115,7 @@ public static List ComputeGradientField(string valueKey, Mesh mesh) return gradientField; } + /// /// Compute the gradient on a given mesh face given some per-vertex values. /// @@ -121,7 +134,7 @@ public static Vector3d ComputeFaceGradient(string valueKey, MeshFace face) var gk = adjacentVertices[2].UserValues[valueKey]; var faceNormal = face.Normal / (2 * face.Area); - var rotatedGradient = ((gi * (k - j)) + (gj * (i - k)) + (gk * (j - i))) / (2 * face.Area); + var rotatedGradient = (gi * (k - j) + gj * (i - k) + gk * (j - i)) / (2 * face.Area); var gradient = rotatedGradient.Cross(faceNormal); return gradient; diff --git a/src/Exceptions/UnsetGeometryException.cs b/src/Exceptions/UnsetGeometryException.cs index 52b5ddb..3726a77 100644 --- a/src/Exceptions/UnsetGeometryException.cs +++ b/src/Exceptions/UnsetGeometryException.cs @@ -10,10 +10,14 @@ public class UnsetGeometryException : Exception /// public UnsetGeometryException() { } + /// public UnsetGeometryException(string message) : base(message) { } + /// - public UnsetGeometryException(string message, Exception innerException) : base(message, innerException) { } + public UnsetGeometryException(string message, Exception innerException) : base( + message, + innerException) { } } } \ No newline at end of file diff --git a/src/Extensions/Lists.cs b/src/Extensions/Lists.cs index 262f83b..fba4b2f 100644 --- a/src/Extensions/Lists.cs +++ b/src/Extensions/Lists.cs @@ -16,6 +16,7 @@ public static class Lists /// //TODO. public static List RepeatedDefault(int count) => Repeated(default(T), count); + /// /// Initializes a new list full of objects initialized the values of the specified instance of T. /// diff --git a/src/Geometry/2D/BoundingBox2d.cs b/src/Geometry/2D/BoundingBox2d.cs index b563386..a6c7a8e 100644 --- a/src/Geometry/2D/BoundingBox2d.cs +++ b/src/Geometry/2D/BoundingBox2d.cs @@ -9,6 +9,7 @@ public class BoundingBox2d { /// /// Initializes a new instance of the class from 2 points. + /// Coordinates will automatically be corrected if the corners are not consistent with naming (i.e. bottomLeftCorner is actually topLeft..) /// /// Bottom left corner. /// Top right corner. @@ -22,6 +23,7 @@ public BoundingBox2d(Point2d bottomLeftCorner, Point2d topRightCorner) this.YDomain.FlipDirection(); } + /// /// Initializes a new instance of the class from a polyline. /// @@ -33,42 +35,36 @@ public BoundingBox2d(Polyline2d polyline) var xMax = polyline.Vertices[0].X; var yMax = polyline.Vertices[0].Y; - polyline.Vertices.ForEach(vertex => - { - if (vertex.X < xMin) - xMin = vertex.X; - if (vertex.X > xMax) - xMax = vertex.X; + polyline.Vertices.ForEach( + vertex => + { + if (vertex.X < xMin) + xMin = vertex.X; + if (vertex.X > xMax) + xMax = vertex.X; - if (vertex.Y < yMin) - yMin = vertex.Y; - if (vertex.X > yMax) - yMax = vertex.Y; - }); + if (vertex.Y < yMin) + yMin = vertex.Y; + if (vertex.X > yMax) + yMax = vertex.Y; + }); this.XDomain = new Interval(xMin, xMax); this.YDomain = new Interval(yMin, yMax); } + /// /// Gets or sets the Domain in the X direction. /// /// - public Interval XDomain - { - get; - set; - } + public Interval XDomain { get; set; } /// /// Gets or sets the Domain in the Y direction. /// /// - public Interval YDomain - { - get; - set; - } + public Interval YDomain { get; set; } /// /// Gets the Bottom left corner of the BBox. @@ -94,28 +90,60 @@ public Interval YDomain /// public Point2d TopRight => new Point2d(this.XDomain.End, this.YDomain.End); + /// + /// Gets the midpoint at the left edge of the box. + /// public Point2d MidLeft => new Point2d(this.XDomain.Start, this.YDomain.RemapFromUnit(0.5)); + /// + /// Gets the midpoint at the right edge of the box. + /// public Point2d MidRight => new Point2d(this.XDomain.End, this.YDomain.RemapFromUnit(0.5)); - public Point2d MidBottom => new Point2d(this.XDomain.RemapFromUnit(0.5), this.YDomain.Start); + /// + /// Gets the midpoint at the bottom edge of the box. + /// + public Point2d MidBottom => + new Point2d(this.XDomain.RemapFromUnit(0.5), this.YDomain.Start); + /// + /// Gets the midpoint at the top edge of the box. + /// public Point2d MidTop => new Point2d(this.XDomain.RemapFromUnit(0.5), this.YDomain.End); /// /// Gets the center of the bounding BBox. /// - public Point2d Center => new Point2d(this.XDomain.RemapFromUnit(0.5), this.YDomain.RemapFromUnit(0.5)); + public Point2d Center => + new Point2d(this.XDomain.RemapFromUnit(0.5), this.YDomain.RemapFromUnit(0.5)); + + + /// + /// Checks if a point is contained inside the box. + /// + /// Point to test containment. + /// True if point is contained inside the bounding box. + public bool ContainsPoint(Point2d pt) => + this.XDomain.Contains(pt.X) && this.YDomain.Contains(pt.Y); - public bool ContainsPoint(Point2d pt) => this.XDomain.Contains(pt.X) && this.YDomain.Contains(pt.Y); + /// + /// Checks if a box intersects with this instance. + /// + /// Box to check intersection against. + /// True if intersection exists. public bool IntersectsBox(BoundingBox2d box) { - var xCheck = this.XDomain.Contains(box.XDomain.Start) || this.XDomain.Contains(box.XDomain.End); - var yCheck = this.YDomain.Contains(box.YDomain.Start) || this.YDomain.Contains(box.YDomain.End); + var xCheck = this.XDomain.Contains(box.XDomain.Start) + || this.XDomain.Contains(box.XDomain.End); + var yCheck = this.YDomain.Contains(box.YDomain.Start) + || this.YDomain.Contains(box.YDomain.End); return xCheck && yCheck; } - public override string ToString() => $"BBox2d [{this.XDomain.Start};{this.YDomain.Start}]-[{this.XDomain.End};{this.YDomain.End}]"; + + /// + public override string ToString() => + $"BBox2d [{this.XDomain.Start};{this.YDomain.Start}]-[{this.XDomain.End};{this.YDomain.End}]"; } } \ No newline at end of file diff --git a/src/Geometry/2D/Delaunay.cs b/src/Geometry/2D/Delaunay.cs index 4d5fab3..a49a264 100644 --- a/src/Geometry/2D/Delaunay.cs +++ b/src/Geometry/2D/Delaunay.cs @@ -1,71 +1,76 @@ using System.Collections.Generic; +using System.Linq; namespace Paramdigma.Core.Geometry { /// - /// Class holding all the delaunay and vornoi classes in 2 dimensions. + /// Class holding all the delaunay and Voronoi classes in 2 dimensions. /// public static class Delaunay { - public static List Compute(List points, List border) + /// + /// Compute the delaunay triangulation of a given list of points. + /// + /// Points to find delaunay tessellation. + /// Border to start from. + /// List of . + public static IEnumerable Compute( + IEnumerable points, + IEnumerable border) { var triangulation = new List(border); - points.Reverse(); foreach (var point in points) { - var badTriangles = FindBadTriangles(point, triangulation); + var badTriangles = FindBadTriangles(point, triangulation).ToList(); var polygon = FindHoleBoundaries(badTriangles); foreach (var triangle in badTriangles) { foreach (var vertex in triangle.Vertices) vertex.AdjacentTriangles.Remove(triangle); - if (triangulation.Contains(triangle)) triangulation.Remove(triangle); - } - foreach (var edge in polygon) - { - var triangle = new DelaunayTriangle(point, edge.StartPoint, edge.EndPoint); - triangulation.Add(triangle); + if (triangulation.Contains(triangle)) + triangulation.Remove(triangle); } + + triangulation.AddRange( + polygon.Select( + edge => new DelaunayTriangle(point, edge.StartPoint, edge.EndPoint))); } return triangulation; } - public static List Voronoi(List triangulation) - { - var voronoiEdges = new List(); - foreach (var triangle in triangulation) - foreach (var neigbour in triangle.TrianglesWithSharedEdges()) - { - var edge = new DelaunayEdge(triangle.Circumcenter, neigbour.Circumcenter); - voronoiEdges.Add(edge); - } - return voronoiEdges; - } + /// + /// Computes the Voronoi diagram of a given Delaunay triangulation as a list of + /// instances. + /// + /// Delaunay triangulation. + /// Collection of lines representing the Voronoi cells. + public static IEnumerable Voronoi(IEnumerable triangulation) + => + from triangle in triangulation + from neighbour in triangle.TrianglesWithSharedEdges() + select new Line2d(triangle.Circumcenter, neighbour.Circumcenter); - private static List FindHoleBoundaries(List badTriangles) + + private static IEnumerable FindHoleBoundaries( + IEnumerable badTriangles) { var boundaryEdges = new List(); var duplicateEdges = new List(); foreach (var triangle in badTriangles) { - var e = new DelaunayEdge(triangle.Vertices[0], triangle.Vertices[1]); - if (!boundaryEdges.Contains(e)) - boundaryEdges.Add(e); - else - duplicateEdges.Add(e); - var f = new DelaunayEdge(triangle.Vertices[1], triangle.Vertices[2]); - if (!boundaryEdges.Contains(f)) - boundaryEdges.Add(f); - else - duplicateEdges.Add(f); - var j = new DelaunayEdge(triangle.Vertices[2], triangle.Vertices[0]); - if (!boundaryEdges.Contains(j)) - boundaryEdges.Add(j); - else - duplicateEdges.Add(j); + for (var i = 0; i < triangle.Vertices.Count; i++) + { + var e = new DelaunayEdge( + triangle.Vertices[i], + triangle.Vertices[(i + 1) % triangle.Vertices.Count]); + if (!boundaryEdges.Contains(e)) + boundaryEdges.Add(e); + else + duplicateEdges.Add(e); + } } for (var i = boundaryEdges.Count - 1; i >= 0; i--) @@ -78,13 +83,10 @@ private static List FindHoleBoundaries(List badT return boundaryEdges; } - private static List FindBadTriangles(DelaunayPoint point, List triangles) - { - var badTriangles = new List(); - foreach (var triangle in triangles) - if (triangle.IsPointInsideCircumcircle(point)) - badTriangles.Add(triangle); - return badTriangles; - } + + private static IEnumerable FindBadTriangles( + Point2d point, + IEnumerable triangles) + => triangles.Where(triangle => triangle.IsPointInsideCircumcircle(point)); } } \ No newline at end of file diff --git a/src/Geometry/2D/DelaunayEdge.cs b/src/Geometry/2D/DelaunayEdge.cs index 6989ac2..ee5c493 100644 --- a/src/Geometry/2D/DelaunayEdge.cs +++ b/src/Geometry/2D/DelaunayEdge.cs @@ -1,29 +1,52 @@ namespace Paramdigma.Core.Geometry { + /// + /// Represents a connection between two points in a Delaunay triangulation. + /// public class DelaunayEdge { + /// + /// The edge's end point. + /// public DelaunayPoint EndPoint; + + /// + /// The edge's start point. + /// public DelaunayPoint StartPoint; + + /// + /// Initializes a new instance of the class. + /// + /// Start point. + /// End point. public DelaunayEdge(DelaunayPoint startPoint, DelaunayPoint endPoint) { this.StartPoint = startPoint; this.EndPoint = endPoint; } + + /// public override bool Equals(object obj) { - if (obj == null) return false; - if (obj.GetType() != this.GetType()) return false; - var edge = obj as DelaunayEdge; + if (!(obj is DelaunayEdge edge)) + return false; var samePoints = this.StartPoint == edge.StartPoint && this.EndPoint == edge.EndPoint; - var samePointsReversed = this.StartPoint == edge.EndPoint && this.EndPoint == edge.StartPoint; + var samePointsReversed = + this.StartPoint == edge.EndPoint && this.EndPoint == edge.StartPoint; return samePoints || samePointsReversed; } + + /// public override int GetHashCode() { - var hCode = (int)this.StartPoint.X ^ (int)this.StartPoint.Y ^ (int)this.EndPoint.X ^ (int)this.EndPoint.Y; + var hCode = ( int ) this.StartPoint.X + ^ ( int ) this.StartPoint.Y + ^ ( int ) this.EndPoint.X + ^ ( int ) this.EndPoint.Y; return hCode.GetHashCode(); } } diff --git a/src/Geometry/2D/DelaunayPoint.cs b/src/Geometry/2D/DelaunayPoint.cs index f35b4b1..f95b367 100644 --- a/src/Geometry/2D/DelaunayPoint.cs +++ b/src/Geometry/2D/DelaunayPoint.cs @@ -2,10 +2,31 @@ namespace Paramdigma.Core.Geometry { + /// + /// Represents a point in a delaunay triangulation with adjacency information. + /// public class DelaunayPoint : Point2d { + /// + /// List of adjacent triangles of this point. + /// public List AdjacentTriangles; - public DelaunayPoint(double x, double y) : base(x, y) => this.AdjacentTriangles = new List(); + + /// + /// Initializes a new instance of the class from it's coordinates. + /// + /// X Coordinate. + /// Y Coordinate. + public DelaunayPoint(double x, double y) : base(x, y) => + this.AdjacentTriangles = new List(); + + + /// + /// Initializes a new instance of the class from a + /// instance. + /// + /// Point to create from. + public DelaunayPoint(Point2d point) : this(point.X, point.Y) { } } } \ No newline at end of file diff --git a/src/Geometry/2D/DelaunayTriangle.cs b/src/Geometry/2D/DelaunayTriangle.cs index 5d7965e..9c0d38d 100644 --- a/src/Geometry/2D/DelaunayTriangle.cs +++ b/src/Geometry/2D/DelaunayTriangle.cs @@ -1,19 +1,40 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Paramdigma.Core.Geometry { + /// + /// Represents a triangle in a delaunay triangulation operation. + /// public class DelaunayTriangle { - public DelaunayPoint Circumcenter; + /// + /// Circumcenter of this triangle. + /// + public Point2d Circumcenter; + + /// + /// Squared radius of the triangle's circumcircle. + /// public double RadiusSquared; - public List Vertices = new List(); + /// + /// List of vertices of this triangle. + /// + public List Vertices; + + /// + /// Initializes a new instance of the class. + /// + /// Point A. + /// Point B. + /// Point C. public DelaunayTriangle(DelaunayPoint point1, DelaunayPoint point2, DelaunayPoint point3) { this.Vertices = new List(); - if (!this.IsCounterClockwise(point1, point2, point3)) + if (!IsCounterClockwise(point1, point2, point3)) { this.Vertices.Add(point1); this.Vertices.Add(point3); @@ -32,61 +53,85 @@ public DelaunayTriangle(DelaunayPoint point1, DelaunayPoint point2, DelaunayPoin this.UpdateCircumcircle(); } - public List TrianglesWithSharedEdges() + + /// + /// Returns a collection of neighbouring triangles. + /// + /// Collection of triangles. + public IEnumerable TrianglesWithSharedEdges() { var neighbours = new List(); - foreach (var vertex in this.Vertices) - foreach (var sharedTriangle in vertex.AdjacentTriangles) - if (this.SharesEdgeWidth(sharedTriangle) && neighbours.Contains(sharedTriangle) == false && sharedTriangle != this) - neighbours.Add(sharedTriangle); + foreach (var sharedTriangle in + from vertex in this.Vertices + from sharedTriangle in vertex.AdjacentTriangles + where this.SharesEdgeWidth(sharedTriangle) + && neighbours.Contains(sharedTriangle) == false + && sharedTriangle != this + select sharedTriangle) + neighbours.Add(sharedTriangle); + return neighbours; } - public void UpdateCircumcircle() + + /// + /// Checks if a point is inside this triangle's circumcircle. + /// + /// Point2d to check. + /// True if inside. + public bool IsPointInsideCircumcircle(Point2d point) + { + var dSquared = (point.X - this.Circumcenter.X) * (point.X - this.Circumcenter.X) + + (point.Y - this.Circumcenter.Y) * (point.Y - this.Circumcenter.Y); + return dSquared < this.RadiusSquared; + } + + + private static bool IsCounterClockwise(Point2d point1, Point2d point2, Point2d point3) + { + var result = (point2.X - point1.X) * (point3.Y - point1.Y) + - (point3.X - point1.X) * (point2.Y - point1.Y); + return result > 0; + } + + + private void UpdateCircumcircle() { var p0 = this.Vertices[0]; var p1 = this.Vertices[1]; var p2 = this.Vertices[2]; - var dA = (p0.X * p0.X) + (p0.Y * p0.Y); - var dB = (p1.X * p1.X) + (p1.Y * p1.Y); - var dC = (p2.X * p2.X) + (p2.Y * p2.Y); + var dA = p0.X * p0.X + p0.Y * p0.Y; + var dB = p1.X * p1.X + p1.Y * p1.Y; + var dC = p2.X * p2.X + p2.Y * p2.Y; - var aux1 = (dA * (p2.Y - p1.Y)) + (dB * (p0.Y - p2.Y)) + (dC * (p1.Y - p0.Y)); - var aux2 = -((dA * (p2.X - p1.X)) + (dB * (p0.X - p2.X)) + (dC * (p1.X - p0.X))); - var div = 2 * ((p0.X * (p2.Y - p1.Y)) + (p1.X * (p0.Y - p2.Y)) + (p2.X * (p1.Y - p0.Y))); + var aux1 = dA * (p2.Y - p1.Y) + + dB * (p0.Y - p2.Y) + + dC * (p1.Y - p0.Y); + var aux2 = -(dA * (p2.X - p1.X) + + dB * (p0.X - p2.X) + + dC * (p1.X - p0.X)); + var div = 2 * (p0.X * (p2.Y - p1.Y) + + p1.X * (p0.Y - p2.Y) + + p2.X * (p1.Y - p0.Y)); - if (div == 0) - throw new Exception(); + if (Math.Abs(div) < Settings.Tolerance) + throw new Exception("Divisor too small"); var center = new DelaunayPoint(aux1 / div, aux2 / div); this.Circumcenter = center; - this.RadiusSquared = ((center.X - p0.X) * (center.X - p0.X)) + ((center.Y - p0.Y) * (center.Y - p0.Y)); - } - - public bool IsCounterClockwise(DelaunayPoint point1, DelaunayPoint point2, DelaunayPoint point3) - { - var result = ((point2.X - point1.X) * (point3.Y - point1.Y)) - - ((point3.X - point1.X) * (point2.Y - point1.Y)); - return result > 0; + this.RadiusSquared = (center.X - p0.X) * (center.X - p0.X) + + (center.Y - p0.Y) * (center.Y - p0.Y); } - public bool SharesEdgeWidth(DelaunayTriangle triangle) - { - var sharedCount = 0; - foreach (var vertex in this.Vertices) - foreach (var vertex2 in triangle.Vertices) - if (vertex == vertex2) - sharedCount++; - return sharedCount == 2; - - throw new NotImplementedException(); - } - public bool IsPointInsideCircumcircle(DelaunayPoint point) + private bool SharesEdgeWidth(DelaunayTriangle triangle) { - var d_squared = ((point.X - this.Circumcenter.X) * (point.X - this.Circumcenter.X)) + - ((point.Y - this.Circumcenter.Y) * (point.Y - this.Circumcenter.Y)); - return d_squared < this.RadiusSquared; + var shared = + from pt1 in this.Vertices + from pt2 in triangle.Vertices + where pt1 == pt2 + select pt1; + return shared.Count() == 2; } } } \ No newline at end of file diff --git a/src/Geometry/2D/Line2d.cs b/src/Geometry/2D/Line2d.cs index 3c92386..a31f523 100644 --- a/src/Geometry/2D/Line2d.cs +++ b/src/Geometry/2D/Line2d.cs @@ -19,6 +19,7 @@ public Line2d(Point2d startPoint, Point2d endPoint) this.Domain = new Interval(0, this.Length); } + /// /// Initializes a new instance of the class. /// @@ -28,6 +29,7 @@ public Line2d(Point2d startPoint, Point2d endPoint) public Line2d(Point2d startPoint, Vector2d direction) : this(startPoint, startPoint + direction) { } + /// /// Initializes a new instance of the class. /// @@ -38,52 +40,44 @@ public Line2d(Point2d startPoint, Vector2d direction) public Line2d(Point2d startPoint, Vector2d direction, double length) : this(startPoint, direction.Unit() * length) { } + /// /// Gets or sets the start point of the line. /// /// 3D Point. - public Point2d StartPoint - { - get; - set; - } + public Point2d StartPoint { get; set; } /// /// Gets or sets the end point of the line. /// /// 3D Point. - public Point2d EndPoint - { - get; - set; - } + public Point2d EndPoint { get; set; } /// /// Gets or sets the line's domain. /// /// Interval. - public Interval Domain - { - get; - set; - } + public Interval Domain { get; set; } /// /// Gets the vector representation of the line. /// - public Vector2d Vector => this; // Implicit line to vector conversion (this property exists just for convenience and readability) + public Vector2d Vector => + this; // Implicit line to vector conversion (this property exists just for convenience and readability) /// /// Gets the length of the line. /// public double Length => this.Vector.Length; + /// /// Implicit conversion from line to vector. /// /// Line to be transformed into vector. public static implicit operator Vector2d(Line2d line) => line.EndPoint - line.StartPoint; + /// /// Computes if a given point is at the left, right or on the current line. /// diff --git a/src/Geometry/2D/Point2d.cs b/src/Geometry/2D/Point2d.cs index 7c37e8a..212fb22 100644 --- a/src/Geometry/2D/Point2d.cs +++ b/src/Geometry/2D/Point2d.cs @@ -13,6 +13,7 @@ public class Point2d public Point2d() : this(0, 0) { } + /// /// Initializes a new instance of the class from x and y coordinates. /// @@ -24,6 +25,7 @@ public Point2d(double x, double y) this.Y = y; } + /// /// Initializes a new instance of the class from an existing point. /// @@ -31,23 +33,16 @@ public Point2d(double x, double y) public Point2d(Point2d pt) : this(pt.X, pt.Y) { } + /// /// Gets or sets the X coordinate of the point. /// - public double X - { - get; - set; - } + public double X { get; set; } /// /// Gets or sets the Y coordinate of the point. /// - public double Y - { - get; - set; - } + public double Y { get; set; } /// /// Gets a new 2d point with all coordinates =0. @@ -57,12 +52,14 @@ public double Y // Overrided methods + /// /// String representation of a 2-dimensional point instance. /// /// Returns string representation of this Point2d instance. public override string ToString() => "Point2d{ " + this.X + "; " + this.Y + "}"; + /// /// Compares a Point2d instance to the given objects. /// @@ -72,11 +69,12 @@ public override bool Equals(object obj) { if (!(obj is Point2d)) return false; - var pt = (Point2d)obj; + var pt = ( Point2d ) obj; return Math.Abs(this.X - pt.X) <= Settings.Tolerance && Math.Abs(this.Y - pt.Y) <= Settings.Tolerance; } + /// /// Gets the hash code for the corresponding Point2d instance. /// @@ -86,11 +84,11 @@ public override int GetHashCode() unchecked { // Choose large primes to avoid hashing collisions - const int hashingBase = (int)2166136261; + const int hashingBase = ( int ) 2166136261; const int hashingMultiplier = 16777619; var tol = Settings.Tolerance * 2; - var tX = (int)(this.X * (1 / tol)) * tol; - var tY = (int)(this.Y * (1 / tol)) * tol; + var tX = ( int ) (this.X * (1 / tol)) * tol; + var tY = ( int ) (this.Y * (1 / tol)) * tol; var hash = hashingBase; hash = (hash * hashingMultiplier) ^ tX.GetHashCode(); @@ -99,13 +97,16 @@ public override int GetHashCode() } } + /// /// Add two points together. /// /// First point. /// Second point. /// Addition result. - public static Vector2d operator +(Point2d point, Point2d point2) => new Vector2d(point.X + point2.X, point.Y + point2.Y); + public static Vector2d operator +(Point2d point, Point2d point2) => + new Vector2d(point.X + point2.X, point.Y + point2.Y); + /// /// Substracts one point from another. @@ -113,7 +114,9 @@ public override int GetHashCode() /// First point. /// Second point. /// Substraction result. - public static Vector2d operator -(Point2d point, Point2d point2) => new Vector2d(point.X - point2.X, point.Y - point2.Y); + public static Vector2d operator -(Point2d point, Point2d point2) => + new Vector2d(point.X - point2.X, point.Y - point2.Y); + /// /// Negates a given point. @@ -122,13 +125,16 @@ public override int GetHashCode() /// Negation result. public static Point2d operator -(Point2d point) => new Point2d(-point.X, -point.Y); + /// /// Multiplies a point by a number. /// /// Point to multiply. /// Operand. /// Multiplication result. - public static Point2d operator *(Point2d point, double scalar) => new Point2d(point.X * scalar, point.Y * scalar); + public static Point2d operator *(Point2d point, double scalar) => + new Point2d(point.X * scalar, point.Y * scalar); + /// /// Multiplies a point by a number. @@ -136,7 +142,9 @@ public override int GetHashCode() /// Operand. /// Point to multiply. /// Multiplication result. - public static Point2d operator *(double scalar, Point2d point) => new Point2d(point.X * scalar, point.Y * scalar); + public static Point2d operator *(double scalar, Point2d point) => + new Point2d(point.X * scalar, point.Y * scalar); + /// /// Divides a point by a number. @@ -144,7 +152,9 @@ public override int GetHashCode() /// Point. /// Operand. /// Division result. - public static Point2d operator /(Point2d point, double scalar) => new Point2d(point.X / scalar, point.Y / scalar); + public static Point2d operator /(Point2d point, double scalar) => + new Point2d(point.X / scalar, point.Y / scalar); + /// /// Equality comparison between points. @@ -154,6 +164,7 @@ public override int GetHashCode() /// True if equal. public static bool operator ==(Point2d point, Point2d point2) => point.Equals(point2); + /// /// Inequality comparison between points. /// @@ -162,22 +173,27 @@ public override int GetHashCode() /// True if NOT equal. public static bool operator !=(Point2d point, Point2d point2) => !point.Equals(point2); + /// /// Divides a point by a number. /// /// Point. /// Vector. /// Division result. - public static Point2d operator +(Point2d point, Vector2d v) => new Point2d(point.X + v.X, point.Y + v.Y); + public static Point2d operator +(Point2d point, Vector2d v) => + new Point2d(point.X + v.X, point.Y + v.Y); + // Implicit conversions + /// /// Explicit conversion from 2-dimensional point to vector. /// /// Vector to convert. public static explicit operator Point2d(Vector2d v) => new Point2d(v.X, v.Y); + /// /// Implicit conversion from 2-dimensional point to vector. /// diff --git a/src/Geometry/2D/Polyline2d.cs b/src/Geometry/2D/Polyline2d.cs index c031ceb..6197355 100644 --- a/src/Geometry/2D/Polyline2d.cs +++ b/src/Geometry/2D/Polyline2d.cs @@ -15,6 +15,7 @@ public class Polyline2d private bool segmentsNeedUpdate; private List vertices; + /// /// Initializes a new instance of the class. /// @@ -23,24 +24,17 @@ public class Polyline2d public Polyline2d(List vertices, bool closed) { this.vertices = vertices; - this.IsClosed = closed; // Call the property (not the field), to have if add the first point at the end if necessary. + this.IsClosed = + closed; // Call the property (not the field), to have if add the first point at the end if necessary. this.RebuildSegments(); } + /// /// Gets or sets the polyline vertices. /// /// List of vertices. - public List Vertices - { - get => this.vertices; - - set - { - this.vertices = value; - this.segmentsNeedUpdate = true; - } - } + public List Vertices => this.vertices; /// /// Gets the polyline segments. @@ -103,6 +97,7 @@ public bool IsClosed } } + /// /// Computes the area of the polyline. /// @@ -126,6 +121,7 @@ public double Area() return area / 2.0; } + /// /// Checks if the current polyline is CW or CCW. /// @@ -148,11 +144,10 @@ public bool IsClockwise() { if (this.vertices[i].Y > ymin) continue; - if (this.vertices[i].Y == ymin) - // just as low - if (this.vertices[i].X < xmin) - // and to left - continue; + + if (Math.Abs(this.vertices[i].Y - ymin) < Settings.Tolerance + && this.vertices[i].X < xmin) + continue; rmin = i; // a new rightmost lowest vertex xmin = this.vertices[i].X; @@ -163,35 +158,44 @@ public bool IsClockwise() // ccw <=> the edge leaving V[rmin] is left of the entering edge double result; if (rmin == 0) - result = new Line2d(this.vertices[this.vertices.Count - 1], this.vertices[0]).IsLeft(this.vertices[1]); + { + result = + new Line2d(this.vertices[this.vertices.Count - 1], this.vertices[0]).IsLeft( + this.vertices[1]); + } else - result = new Line2d(this.vertices[rmin - 1], this.vertices[rmin]).IsLeft(this.vertices[rmin + 1]); + { + result = new Line2d(this.vertices[rmin - 1], this.vertices[rmin]).IsLeft( + this.vertices[rmin + 1]); + } if (result == 0) throw new Exception("Polyline is degenerate, cannot compute orientation."); return result < 0; } + /// /// Reparametrizes the current curve to a unit interval. /// public void Reparametrize() { - var maxParameter = this.domain.End; var ratio = 1 / this.domain.End; double currentParam = 0; - this.segments.ForEach(segment => - { - var nextParam = currentParam + (segment.Domain.Length * ratio); - segment.Domain = new Interval(currentParam, nextParam); - currentParam = nextParam; - }); + this.segments.ForEach( + segment => + { + var nextParam = currentParam + segment.Domain.Length * ratio; + segment.Domain = new Interval(currentParam, nextParam); + currentParam = nextParam; + }); this.domain = Interval.Unit; } + private void RebuildSegments() { this.segments = new List(); @@ -207,6 +211,7 @@ private void RebuildSegments() this.domain = new Interval(0, currentParam); } + private Line2d BuildSegment(ref double currentParam, Point2d vertA, Point2d vertB) { var line = new Line2d(vertA, vertB); diff --git a/src/Geometry/2D/Ray2d.cs b/src/Geometry/2D/Ray2d.cs index 62ae723..7250834 100644 --- a/src/Geometry/2D/Ray2d.cs +++ b/src/Geometry/2D/Ray2d.cs @@ -18,24 +18,17 @@ public Ray2d(Point2d origin, Vector2d direction) this.Direction = direction ?? throw new ArgumentNullException(nameof(direction)); } + /// /// Gets or sets the origin of the ray. /// /// Origin point. - public Point2d Origin - { - get; - set; - } + public Point2d Origin { get; set; } /// /// Gets or sets the direction of the ray as a unit vector. /// /// Direction vector. - public Vector2d Direction - { - get; - set; - } + public Vector2d Direction { get; set; } } } \ No newline at end of file diff --git a/src/Geometry/2D/Vector2d.cs b/src/Geometry/2D/Vector2d.cs index e473bf5..1986323 100644 --- a/src/Geometry/2D/Vector2d.cs +++ b/src/Geometry/2D/Vector2d.cs @@ -15,6 +15,7 @@ public class Vector2d public Vector2d(Vector2d vector) : this(vector.X, vector.Y) { } + /// /// Initializes a new instance of the class from a point. /// @@ -23,6 +24,7 @@ public Vector2d(Vector2d vector) public Vector2d(Point2d point) : this(point.X, point.Y) { } + /// /// Initializes a new instance of the class. /// @@ -34,30 +36,23 @@ public Vector2d(double x, double y) this.Y = y; } + /// /// Gets or sets the X Coordininate of the vector. /// /// X coordinate. - public double X - { - get; - set; - } + public double X { get; set; } /// /// Gets or sets the Y coordinate of the vector. /// /// Y coordinate. - public double Y - { - get; - set; - } + public double Y { get; set; } /// /// Gets the squared length of the vector. /// - public double LengthSquared => (this.X * this.X) + (this.Y * this.Y); + public double LengthSquared => this.X * this.X + this.Y * this.Y; /// /// Gets the length of the vector. @@ -74,12 +69,14 @@ public double Y /// public static Vector2d WorldY => new Vector2d(0, 1); + /// /// Returns a unit vector of this vector. /// /// New vector of unit lenght. public Vector2d Unit() => new Vector2d(this / this.Length); + /// /// Force this vector to be unit length. /// @@ -90,53 +87,65 @@ public void Unitize() this.Y /= length; } + /// /// Returns a CCW perpendicular vector to the current instance. /// /// public Vector2d Perp() => new Vector2d(-this.Y, this.X); + /// /// Computes the dot product between this vector and the given one. /// /// Vector to compute dot-product with. /// Dot product result. - public double DotProduct(Vector2d vector) => (this.X * vector.X) + (this.Y * vector.Y); + public double DotProduct(Vector2d vector) => this.X * vector.X + this.Y * vector.Y; + /// /// Computes the perp product between this vector and the given one. /// /// Vector to compute perp-product with. /// Perp product result. - public double PerpProduct(Vector2d vector) => (this.X * vector.Y) - (this.Y * vector.X); + public double PerpProduct(Vector2d vector) => this.X * vector.Y - this.Y * vector.X; + /// /// Sums two vectors together. /// /// Vector A. /// Vector B. - public static Vector2d operator +(Vector2d v, Vector2d v2) => new Vector2d(v.X + v2.X, v.Y + v2.Y); + public static Vector2d operator +(Vector2d v, Vector2d v2) => + new Vector2d(v.X + v2.X, v.Y + v2.Y); + /// /// Substracts one vector from another. /// /// Vector A. /// Vector B. - public static Vector2d operator -(Vector2d v, Vector2d v2) => new Vector2d(v.X - v2.X, v.Y - v2.Y); + public static Vector2d operator -(Vector2d v, Vector2d v2) => + new Vector2d(v.X - v2.X, v.Y - v2.Y); + /// /// Multiplies a vector with a number. /// /// Vector. /// Number to multiply vector with. - public static Vector2d operator *(Vector2d v, double scalar) => new Vector2d(v.X * scalar, v.Y * scalar); + public static Vector2d operator *(Vector2d v, double scalar) => + new Vector2d(v.X * scalar, v.Y * scalar); + /// /// Multiplies a vector with a number. /// /// Number to multiply vector with. /// Vector. - public static Vector2d operator *(double scalar, Vector2d v) => new Vector2d(v.X * scalar, v.Y * scalar); + public static Vector2d operator *(double scalar, Vector2d v) => + new Vector2d(v.X * scalar, v.Y * scalar); + /// /// Negates the values of the vector. @@ -144,12 +153,15 @@ public void Unitize() /// Vector. public static Vector2d operator -(Vector2d v) => new Vector2d(-v.X, -v.Y); + /// /// Divides a vector with a number. /// /// Vector. /// Number to divide vector with. - public static Vector2d operator /(Vector2d v, double scalar) => new Vector2d(v.X / scalar, v.Y / scalar); + public static Vector2d operator /(Vector2d v, double scalar) => + new Vector2d(v.X / scalar, v.Y / scalar); + /// /// Checks for equality between two vectors. @@ -158,6 +170,7 @@ public void Unitize() /// Vector B. public static bool operator ==(Vector2d v, Vector2d w) => v.Equals(w); + /// /// Checks for inequality between two vectors. /// @@ -165,12 +178,14 @@ public void Unitize() /// Vector B. public static bool operator !=(Vector2d v, Vector2d w) => !v.Equals(w); + /// /// Gets the string representation of the vector. /// /// public override string ToString() => $"Vector3d [{this.X}, {this.Y}]"; + /// /// Checks if the vector is equal to an object. /// @@ -186,6 +201,7 @@ public override bool Equals(object obj) && Math.Abs(this.Y - vect.Y) <= Settings.Tolerance; } + /// /// Get the hashCode of the vector. /// @@ -196,11 +212,11 @@ public override int GetHashCode() { // Choose large primes to avoid hashing collisions // Choose large primes to avoid hashing collisions - const int hashingBase = (int)2166136261; + const int hashingBase = ( int ) 2166136261; const int hashingMultiplier = 16777619; var tol = Settings.Tolerance * 2; - var tX = (int)(this.X * (1 / tol)) * tol; - var tY = (int)(this.Y * (1 / tol)) * tol; + var tX = ( int ) (this.X * (1 / tol)) * tol; + var tY = ( int ) (this.Y * (1 / tol)) * tol; var hash = hashingBase; hash = (hash * hashingMultiplier) ^ tX.GetHashCode(); diff --git a/src/Geometry/3D/Circle.cs b/src/Geometry/3D/Circle.cs index 7025083..b76bcb1 100644 --- a/src/Geometry/3D/Circle.cs +++ b/src/Geometry/3D/Circle.cs @@ -3,17 +3,35 @@ namespace Paramdigma.Core.Geometry { + /// + /// Represents a planar circle curve. + /// public class Circle : ICurve { + /// + /// The base plane for the circle. + /// public Plane Plane; + + /// + /// The radius of the circle. + /// public double Radius; + + /// + /// Initializes a new instance of by it's plane and radius. + /// + /// The plane to draw the circle at. + /// The desired radius of the circle. public Circle(Plane plane, double radius) { this.Plane = plane; this.Radius = radius; } + + /// public Point3d PointAt(double t) { var radians = t * 2 * Math.PI; @@ -22,13 +40,24 @@ public Point3d PointAt(double t) return this.Plane.PointAt(x, y, 0); } + + /// public Vector3d TangentAt(double t) => this.NormalAt(t).Cross(this.Plane.ZAxis); + + /// public Vector3d NormalAt(double t) => (this.Plane.Origin - this.PointAt(t)).Unit(); + + /// public Vector3d BinormalAt(double t) => this.TangentAt(t).Cross(this.NormalAt(t)); - public Plane FrameAt(double t) => new Plane(this.PointAt(t), this.NormalAt(t), this.BinormalAt(t), this.TangentAt(t)); + /// + public Plane FrameAt(double t) => new Plane( + this.PointAt(t), + this.NormalAt(t), + this.BinormalAt(t), + this.TangentAt(t)); } } \ No newline at end of file diff --git a/src/Geometry/3D/Line.cs b/src/Geometry/3D/Line.cs index 959b4b4..0a317c9 100644 --- a/src/Geometry/3D/Line.cs +++ b/src/Geometry/3D/Line.cs @@ -18,8 +18,10 @@ public Line(Point3d startPoint, Point3d endPoint) this.EndPoint = endPoint; } + /// - /// Initializes a new instance of the class from an origin point, a direction and a specified + /// Initializes a new instance of the class from an origin point, a direction + /// and a specified /// length. /// /// Start point of the line. @@ -28,26 +30,20 @@ public Line(Point3d startPoint, Point3d endPoint) public Line(Point3d origin, Vector3d direction, double length) { this.StartPoint = origin; - this.EndPoint = origin + (direction.Unit() * length); + this.EndPoint = origin + direction.Unit() * length; } + /// /// Gets or sets the lines's start point. /// - public Point3d StartPoint - { - get; - set; - } + public Point3d StartPoint { get; set; } /// /// Gets or sets the line's end point. /// - public Point3d EndPoint - { - get; - set; - } + public Point3d EndPoint { get; set; } + /// /// Checks if line is valid. @@ -55,12 +51,15 @@ public Point3d EndPoint /// True if valid. public override bool CheckValidity() => this.Length >= Settings.Tolerance; + /// /// Computes thepoint at the given parameter. /// /// Parameter of the point. Must be between 0 and 1. /// Point at specified parameter. - public override Point3d PointAt(double t) => this.StartPoint + (this.Domain.RemapToUnit(t) * (this.EndPoint - this.StartPoint)); + public override Point3d PointAt(double t) => + this.StartPoint + this.Domain.RemapToUnit(t) * (this.EndPoint - this.StartPoint); + /// /// Computes the tangent at the given parameter. @@ -74,6 +73,7 @@ public override Vector3d TangentAt(double t) return tangent; } + /// /// Computes the normal at the given parameter. /// @@ -82,29 +82,33 @@ public override Vector3d TangentAt(double t) public override Vector3d NormalAt(double t) { var tangent = this.TangentAt(t); - var v = new Vector3d(); - - if (Math.Abs(tangent.Dot(Vector3d.UnitZ) - 1) < Settings.Tolerance) - v = Vector3d.UnitX; - else - v = Vector3d.UnitZ; - + var v = Math.Abs(tangent.Dot(Vector3d.UnitZ) - 1) < Settings.Tolerance + ? Vector3d.UnitX + : Vector3d.UnitZ; return tangent.Cross(v); } + /// /// Computes the bi-normal vector at the given parameter. /// /// Parameter of the bi-normal vector. Must be between 0 and 1. /// Bi-normal vector at specified parameter. - public override Vector3d BinormalAt(double t) => Vector3d.CrossProduct(this.TangentAt(t), this.NormalAt(t)); + public override Vector3d BinormalAt(double t) => + Vector3d.CrossProduct(this.TangentAt(t), this.NormalAt(t)); + /// /// Computes the perpendicular frame at the given parameter. /// /// Parameter of the frame. Must be between 0 and 1. /// Frame at specified parameter. - public override Plane FrameAt(double t) => new Plane(this.PointAt(t), this.TangentAt(t), this.NormalAt(t), this.BinormalAt(t)); + public override Plane FrameAt(double t) => new Plane( + this.PointAt(t), + this.TangentAt(t), + this.NormalAt(t), + this.BinormalAt(t)); + /// /// Computes the length of the line. @@ -112,6 +116,12 @@ public override Vector3d NormalAt(double t) /// Line length. protected override double ComputeLength() => this.StartPoint.DistanceTo(this.EndPoint); + + /// + /// Explicitly converts a line to it's vector representation. + /// + /// Line to convert. + /// Vector defining the line direction and length. public static explicit operator Vector3d(Line line) => line.EndPoint - line.StartPoint; } } \ No newline at end of file diff --git a/src/Geometry/3D/Mesh/Mesh.cs b/src/Geometry/3D/Mesh/Mesh.cs index 97539d8..ccefdf0 100644 --- a/src/Geometry/3D/Mesh/Mesh.cs +++ b/src/Geometry/3D/Mesh/Mesh.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Paramdigma.Core.Geometry; namespace Paramdigma.Core.HalfEdgeMesh @@ -21,6 +22,7 @@ public Mesh() this.Boundaries = new List(); } + /// /// Initializes a new instance of the class from verticees and faces. /// @@ -34,9 +36,12 @@ public Mesh(List vertices, List> faceIndexes) this.CreateVertices(vertices); // - Iterate through faces, creating face, edge, and halfedge objects (and connecting where possible) - this.CreateFaces(faceIndexes); + var result = this.CreateFaces(faceIndexes); + if (!result) + throw new Exception("Couldn't create faces for this mesh"); } + /// /// Initializes a new instance of the class from verticees and faces. /// @@ -51,6 +56,7 @@ public Mesh(List vertices, List> faceIndexes) this.CreateFaces(faceIndexes); } + /// /// Initializes a new instance of the class from an existing one. /// @@ -65,64 +71,42 @@ public Mesh(Mesh halfEdgeMesh) this.Boundaries = new List(halfEdgeMesh.Boundaries); } + /// /// Gets or sets the vertices of the mesh. /// - public List Vertices - { - get; - set; - } + public List Vertices { get; set; } /// /// Gets or sets the edges of the mesh. /// - public List Edges - { - get; - set; - } + public List Edges { get; set; } /// /// Gets or sets the faces of the mesh. /// - public List Faces - { - get; - set; - } + public List Faces { get; set; } /// /// Gets or sets the corners of the mesh. /// - public List Corners - { - get; - set; - } + public List Corners { get; set; } /// /// Gets or sets the half-edges of the mesh. /// - public List HalfEdges - { - get; - set; - } + public List HalfEdges { get; set; } /// /// Gets or sets the boundaries of the mesh. /// - public List Boundaries - { - get; - set; - } + public List Boundaries { get; set; } /// /// Gets the euler characteristic of the mesh. /// - public int EulerCharacteristic => (this.Vertices.Count - this.Edges.Count) + this.Faces.Count; + public int EulerCharacteristic => this.Vertices.Count - this.Edges.Count + this.Faces.Count; + /// /// Check if the mesh has isolated vertices. @@ -131,12 +115,15 @@ public List Boundaries public bool HasIsolatedVertices() { foreach (var v in this.Vertices) + { if (v.IsIsolated()) return true; + } return false; } + /// /// Check if the mesh contains isolated faces. /// @@ -148,8 +135,10 @@ public bool HasIsolatedFaces() var boundaryEdges = 0; var adjacent = f.AdjacentHalfEdges(); foreach (var e in adjacent) + { if (e.OnBoundary) boundaryEdges++; + } if (boundaryEdges == adjacent.Count) return true; @@ -158,6 +147,7 @@ public bool HasIsolatedFaces() return false; } + /// /// Check if the mesh contains non-manifold edges. /// @@ -165,12 +155,15 @@ public bool HasIsolatedFaces() public bool HasNonManifoldEdges() { foreach (var edge in this.Edges) + { if (edge.AdjacentFaces().Count > 2) return true; + } return false; } + /// /// Assign an index number to each mesh member. /// @@ -219,6 +212,7 @@ public void IndexElements() } } + /// /// Assign an index to each vertex of the mesh. /// @@ -232,6 +226,7 @@ public Dictionary IndexVertices() return index; } + /// /// Assign an index to each face of the mesh. /// @@ -245,6 +240,7 @@ public Dictionary IndexFaces() return index; } + /// /// Assign an index to each edge of the mesh. /// @@ -258,6 +254,7 @@ public Dictionary IndexEdges() return index; } + /// /// Assign an index to each Half-Edge of the mesh. /// @@ -271,6 +268,7 @@ public Dictionary IndexHalfEdes() return index; } + /// /// Assign an index to each corner of the mesh. /// @@ -284,24 +282,28 @@ public Dictionary IndexCorners() return index; } + /// /// Check if a mesh is triangular. /// /// Returns true if all faces are triangular. public bool IsTriangularMesh() => this.IsMesh() == IsMeshResult.Triangular; + /// /// Check if a mesh is quad. /// /// Returns true if all faces are quads. public bool IsQuadMesh() => this.IsMesh() == IsMeshResult.Quad; + /// /// Check if a mesh is n-gonal. /// /// Returns true if the mesh contains ANY ngons. public bool IsNgonMesh() => this.IsMesh() == IsMeshResult.Ngon; + /// /// Returns an enum corresponding to the mesh face topology (triangular, quad or ngon). /// @@ -314,9 +316,10 @@ private IsMeshResult IsMesh() return IsMeshResult.Quad; if (count.Ngons != 0) return IsMeshResult.Ngon; - return IsMeshResult.ERROR; + return IsMeshResult.Error; } + /// /// Get human readable description of this mesh. /// @@ -325,8 +328,10 @@ public string GetMeshInfo() { const string head = "--- Mesh Info ---\n"; - var vef = "V: " + this.Vertices.Count + "; F: " + this.Faces.Count + "; E:" + this.Edges.Count + "\n"; - var hec = "Half-edges: " + this.HalfEdges.Count + "; Corners: " + this.Corners.Count + "\n"; + var vef = "V: " + this.Vertices.Count + "; F: " + this.Faces.Count + "; E:" + + this.Edges.Count + "\n"; + var hec = "Half-edges: " + this.HalfEdges.Count + "; Corners: " + this.Corners.Count + + "\n"; var bounds = "Boundaries: " + this.Boundaries.Count + "\n"; var euler = "Euler characteristic: " + this.EulerCharacteristic + "\n"; var isoVert = "Isolated vertices: " + this.HasIsolatedVertices() + "\n"; @@ -340,19 +345,24 @@ public string GetMeshInfo() const string tail = "----- -----\n\n"; - return head + vef + hec + bounds + euler + isoVert + isoFace + manifold + triangles + quads + ngons + tail; + return head + vef + hec + bounds + euler + isoVert + isoFace + manifold + triangles + + quads + ngons + tail; } + /// /// Gets string representation of the mesh. /// /// Mesh string. public override string ToString() { - var vefh = "V: " + this.Vertices.Count + "; F: " + this.Faces.Count + "; E:" + this.Edges.Count + "; hE: " + this.HalfEdges.Count; + var vefh = "V: " + this.Vertices.Count + "; F: " + this.Faces.Count + "; E:" + + this.Edges.Count + + "; hE: " + this.HalfEdges.Count; return "HE_Mesh{" + vefh + "}"; } + private void CreateVertices(List points) { var verts = new List(points.Count); @@ -366,8 +376,9 @@ private void CreateVertices(List points) this.Vertices = verts; } + // Takes a List containing another List per face with the vertex indexes belonging to that face - private bool CreateFaces(List> faceIndexes) + private bool CreateFaces(IEnumerable> faceIndexes) { var edgeCount = new Dictionary(); var existingHalfEdges = new Dictionary(); @@ -399,7 +410,7 @@ private bool CreateFaces(List> faceIndexes) // Set previous and next h.Next = tempHEdges[(i + 1) % indexes.Count]; - h.Prev = tempHEdges[((i + indexes.Count) - 1) % indexes.Count]; + h.Prev = tempHEdges[(i + indexes.Count - 1) % indexes.Count]; h.OnBoundary = false; hasTwinHalfEdge.Add(h, false); @@ -459,46 +470,46 @@ private bool CreateFaces(List> faceIndexes) this.Boundaries.Add(f); var boundaryCycle = new List(); - var hE = h; + var halfEdge = h; do { - var bH = new MeshHalfEdge(); - this.HalfEdges.Add(bH); - boundaryCycle.Add(bH); + var boundaryHalfEdge = new MeshHalfEdge(); + this.HalfEdges.Add(boundaryHalfEdge); + boundaryCycle.Add(boundaryHalfEdge); - var nextHE = hE.Next; - while (hasTwinHalfEdge[nextHE]) - nextHE = nextHE.Twin.Next; + var nextHalfEdge = halfEdge.Next; + while (hasTwinHalfEdge[nextHalfEdge]) + nextHalfEdge = nextHalfEdge.Twin.Next; - bH.Vertex = nextHE.Vertex; - bH.Edge = hE.Edge; - bH.OnBoundary = true; + boundaryHalfEdge.Vertex = nextHalfEdge.Vertex; + boundaryHalfEdge.Edge = halfEdge.Edge; + boundaryHalfEdge.OnBoundary = true; - bH.Face = f; - f.HalfEdge = bH; + boundaryHalfEdge.Face = f; + f.HalfEdge = boundaryHalfEdge; - bH.Twin = hE; - hE.Twin = bH; + boundaryHalfEdge.Twin = halfEdge; + halfEdge.Twin = boundaryHalfEdge; - hE = nextHE; - } while (hE != h); + halfEdge = nextHalfEdge; + } while (halfEdge != h); var n = boundaryCycle.Count; for (var j = 0; j < n; j++) { - boundaryCycle[j].Next = boundaryCycle[((j + n) - 1) % n]; + boundaryCycle[j].Next = boundaryCycle[(j + n - 1) % n]; boundaryCycle[j].Prev = boundaryCycle[(j + 1) % n]; hasTwinHalfEdge[boundaryCycle[j]] = true; hasTwinHalfEdge[boundaryCycle[j].Twin] = true; } } - if (!h.OnBoundary) - { - var corner = new MeshCorner {HalfEdge = h}; - h.Corner = corner; - this.Corners.Add(corner); - } + if (h.OnBoundary) + continue; + + var corner = new MeshCorner {HalfEdge = h}; + h.Corner = corner; + this.Corners.Add(corner); } // Check mesh for common errors @@ -511,11 +522,13 @@ private bool CreateFaces(List> faceIndexes) return true; } + private FaceData CountFaceEdges() { FaceData data = default; foreach (var face in this.Faces) + { switch (face.AdjacentCorners().Count) { case 3: @@ -528,17 +541,18 @@ private FaceData CountFaceEdges() data.Ngons++; break; } + } return data; } + /// /// Type of mesh (Triangular, Quad, Ngon or Error). /// private enum IsMeshResult { - Triangular, Quad, Ngon, - ERROR + Triangular, Quad, Ngon, Error } private struct FaceData diff --git a/src/Geometry/3D/Mesh/MeshCorner.cs b/src/Geometry/3D/Mesh/MeshCorner.cs index 55be38c..f53d78c 100644 --- a/src/Geometry/3D/Mesh/MeshCorner.cs +++ b/src/Geometry/3D/Mesh/MeshCorner.cs @@ -10,23 +10,16 @@ public class MeshCorner /// public MeshCorner() => this.Index = -1; + /// /// Gets or sets the corner's first half-edge. /// - public MeshHalfEdge HalfEdge - { - get; - set; - } + public MeshHalfEdge HalfEdge { get; set; } /// /// Gets or sets the index of the mesh corner. /// - public int Index - { - get; - set; - } + public int Index { get; set; } /// /// Gets the mesh corner vertex. diff --git a/src/Geometry/3D/Mesh/MeshEdge.cs b/src/Geometry/3D/Mesh/MeshEdge.cs index 6fe0c35..58a5ea3 100644 --- a/src/Geometry/3D/Mesh/MeshEdge.cs +++ b/src/Geometry/3D/Mesh/MeshEdge.cs @@ -13,29 +13,23 @@ public class MeshEdge /// public MeshEdge() => this.Index = -1; + /// /// Gets or sets the half-edge linked to this edge. /// - public MeshHalfEdge HalfEdge - { - get; - set; - } + public MeshHalfEdge HalfEdge { get; set; } /// /// Gets or sets the index of this Mesh Edge. /// - public int Index - { - get; - set; - } + public int Index { get; set; } /// /// Gets a value indicating whether the mesh edge lies on a boundary. /// public bool OnBoundary => this.HalfEdge.OnBoundary || this.HalfEdge.Twin.OnBoundary; + /// /// Gets the adjacent vertices of this given edge. /// @@ -46,16 +40,21 @@ public List AdjacentVertices() return vertices; } + /// /// Gets the adjacent faces of this edge. /// /// public List AdjacentFaces() { - var faces = new List {this.HalfEdge.AdjacentFace, this.HalfEdge.Twin.AdjacentFace}; + var faces = new List + { + this.HalfEdge.AdjacentFace, this.HalfEdge.Twin.AdjacentFace + }; return faces; } + /// /// Gets the adjacent edges of this edge. /// diff --git a/src/Geometry/3D/Mesh/MeshFace.cs b/src/Geometry/3D/Mesh/MeshFace.cs index 6026c4c..beb07f4 100644 --- a/src/Geometry/3D/Mesh/MeshFace.cs +++ b/src/Geometry/3D/Mesh/MeshFace.cs @@ -17,23 +17,16 @@ public MeshFace() this.Index = -1; } + /// /// Gets or sets one of the half-edges surrounding the face. /// - public MeshHalfEdge HalfEdge - { - get; - set; - } + public MeshHalfEdge HalfEdge { get; set; } /// /// Gets or sets the face index on the mesh face list. /// - public int Index - { - get; - set; - } + public int Index { get; set; } /// /// Gets the area of the face. @@ -47,6 +40,7 @@ public int Index /// Returns the perpendicular vector to the face. public Vector3d Normal => MeshGeometry.FaceNormal(this); + /// /// Get all adjacent edges to this face. /// @@ -64,6 +58,7 @@ public List AdjacentEdges() return edges; } + /// /// Get all adjacent half-edges to this face. /// @@ -81,6 +76,7 @@ public List AdjacentHalfEdges() return halfEdges; } + /// /// Get all adjacent vertices to this face. /// @@ -98,6 +94,7 @@ public List AdjacentVertices() return vertices; } + /// /// Get all adjacent faces to this face. /// @@ -116,6 +113,7 @@ public List AdjacentFaces() return faces; } + /// /// Get all adjacent corners to this face. /// @@ -133,12 +131,14 @@ public List AdjacentCorners() return corners; } + /// /// Checks if the current face is a boundary face. /// /// Returns true if the face is a boundary face, false if not. public bool IsBoundaryLoop() => this.HalfEdge.OnBoundary; + /// /// Convert the mesh face to string. /// diff --git a/src/Geometry/3D/Mesh/MeshGeometry.cs b/src/Geometry/3D/Mesh/MeshGeometry.cs index 095d733..84d3b8c 100644 --- a/src/Geometry/3D/Mesh/MeshGeometry.cs +++ b/src/Geometry/3D/Mesh/MeshGeometry.cs @@ -13,7 +13,9 @@ public static class MeshGeometry /// /// The half-edge vector. /// Half edge. - public static Vector3d Vector(MeshHalfEdge halfEdge) => halfEdge.Vertex - halfEdge.Next.Vertex; + public static Vector3d Vector(MeshHalfEdge halfEdge) => + halfEdge.Vertex - halfEdge.Next.Vertex; + /// /// Calculates the length of the specified edge. @@ -22,6 +24,7 @@ public static class MeshGeometry /// Edge. public static double Length(MeshEdge edge) => Vector(edge.HalfEdge).Length; + /// /// Calculates the midpoint of the specifiec edge. /// @@ -35,6 +38,7 @@ public static Point3d MidPoint(MeshEdge edge) return (a + b) / 2; } + /// /// Calculates the mean edge length of the mesh. /// @@ -48,6 +52,7 @@ public static double MeanEdgeLength(Mesh mesh) return sum / mesh.Edges.Count; } + /// /// Computes the area of the specified face. /// @@ -63,6 +68,7 @@ public static double Area(MeshFace face) return 0.5 * u.Cross(v).Length; } + /// /// Computes the total area of the mesh. /// @@ -76,6 +82,7 @@ public static double TotalArea(Mesh mesh) return sum; } + /// /// Compute the normal vector of the specified face. /// @@ -91,6 +98,7 @@ public static Vector3d FaceNormal(MeshFace face) return u.Cross(v).Unit(); } + /// /// Compute the centroid of the specified face. /// @@ -109,6 +117,7 @@ public static Point3d Centroid(MeshFace face) return (a + b + c) / 3; } + /// /// Compute the circumcenter the specified face. /// @@ -132,11 +141,12 @@ public static Point3d Circumcenter(MeshFace face) var u = w.Cross(ab) * ac.LengthSquared; var v = ac.Cross(w) * ab.LengthSquared; - var x = (Point3d)(u + v) / (2 * w.LengthSquared); + var x = ( Point3d ) (u + v) / (2 * w.LengthSquared); return x + a; } + /// /// Compute the orthonormal bases of the specified face. /// @@ -151,6 +161,7 @@ public static Vector3d[] OrthonormalBases(MeshFace face) return new[] {e1, e2}; } + /// /// Compute the angle (in radians) at the specified corner. /// @@ -164,6 +175,7 @@ public static double Angle(MeshCorner corner) return Math.Acos(Math.Max(-1, Math.Min(1.0, u.Dot(v)))); } + /// /// Computes the cotangent of the angle opposite to a halfedge. /// @@ -180,6 +192,7 @@ public static double Cotan(MeshHalfEdge hE) return u.Dot(v) / u.Cross(v).Length; } + /// /// Computes the signed angle (in radians) between the faces adjacent to the specified half-edge. /// @@ -200,6 +213,7 @@ public static double DihedralAngle(MeshHalfEdge hE) return Math.Atan2(sinTheta, cosTheta); } + /// /// Computes the barycentric dual area around a given mesh vertex. /// @@ -213,6 +227,7 @@ public static double BarycentricDualArea(MeshVertex vertex) return area; } + /// /// Computes the circumcentric dual area around a given mesh vertex. /// @@ -228,12 +243,13 @@ public static double CircumcentricDualarea(MeshVertex vertex) var cotAlpha = Cotan(hE.Prev); var cotBeta = Cotan(hE); - area += ((u2 * cotAlpha) + (v2 * cotBeta)) / 8; + area += (u2 * cotAlpha + v2 * cotBeta) / 8; } return area; } + /// /// Computes the equally weighted normal arround the specified vertex. /// @@ -248,6 +264,7 @@ public static Vector3d VertexNormalEquallyWeighted(MeshVertex vertex) return n.Unit(); } + /// /// Computes the area weighted normal arround the specified vertex. /// @@ -267,6 +284,7 @@ public static Vector3d VertexNormalAreaWeighted(MeshVertex vertex) return n.Unit(); } + /// /// Computes the angle weighted normal arround the specified vertex. /// @@ -286,6 +304,7 @@ public static Vector3d VertexNormalAngleWeighted(MeshVertex vertex) return n.Unit(); } + /// /// Computes the gauss curvature weighted normal arround the specified vertex. /// @@ -296,13 +315,14 @@ public static Vector3d VertexNormalGaussCurvature(MeshVertex vertex) var n = new Vector3d(); foreach (var hE in vertex.AdjacentHalfEdges()) { - var weight = (0.5 * DihedralAngle(hE)) / Length(hE.Edge); + var weight = 0.5 * DihedralAngle(hE) / Length(hE.Edge); n -= Vector(hE) * weight; } return n.Unit(); } + /// /// Computes the mean curvature weighted normal arround the specified vertex. /// @@ -313,13 +333,14 @@ public static Vector3d VertexNormalMeanCurvature(MeshVertex vertex) var n = new Vector3d(); foreach (var hE in vertex.AdjacentHalfEdges()) { - var weight = (0.5 * Cotan(hE)) + Cotan(hE.Twin); + var weight = 0.5 * Cotan(hE) + Cotan(hE.Twin); n -= Vector(hE) * weight; } return n.Unit(); } + /// /// Computes the sphere inscribed normal arround the specified vertex. /// @@ -339,6 +360,7 @@ public static Vector3d VertexNormalSphereInscribed(MeshVertex vertex) return n.Unit(); } + /// /// Computes the angle defect at the given vertex. /// @@ -351,15 +373,18 @@ public static double AngleDefect(MeshVertex vertex) angleSum += Angle(c); // if (vertex.OnBoundary()) angleSum = Math.PI - angleSum; - return vertex.OnBoundary() ? Math.PI - angleSum : (2 * Math.PI) - angleSum; + return vertex.OnBoundary() ? Math.PI - angleSum : 2 * Math.PI - angleSum; } + /// /// Compute the Gaussian curvature at the given vertex. /// /// Vertex to compute Gaussian curvature. /// Number representing the gaussian curvature at that vertex. - public static double ScalarGaussCurvature(MeshVertex vertex) => AngleDefect(vertex) / CircumcentricDualarea(vertex); + public static double ScalarGaussCurvature(MeshVertex vertex) => + AngleDefect(vertex) / CircumcentricDualarea(vertex); + /// /// Compute the Mean curvature at the given vertex. @@ -374,6 +399,7 @@ public static double ScalarMeanCurvature(MeshVertex vertex) return sum; } + /// /// Compute the total angle defect of the mesh. /// @@ -387,6 +413,7 @@ public static double TotalAngleDefect(Mesh mesh) return totalDefect; } + /// /// Compute the principal curvature scalar values at a given vertes. /// @@ -398,7 +425,7 @@ public static double[] PrincipalCurvatures(MeshVertex vertex) var h = ScalarMeanCurvature(vertex) / a; var k = AngleDefect(vertex) / a; - var discriminant = (h * h) - k; + var discriminant = h * h - k; if (discriminant > 0) discriminant = Math.Sqrt(discriminant); else diff --git a/src/Geometry/3D/Mesh/MeshHalfEdge.cs b/src/Geometry/3D/Mesh/MeshHalfEdge.cs index 30bb1dd..18b176a 100644 --- a/src/Geometry/3D/Mesh/MeshHalfEdge.cs +++ b/src/Geometry/3D/Mesh/MeshHalfEdge.cs @@ -10,86 +10,51 @@ public class MeshHalfEdge /// public MeshHalfEdge() => this.Index = -1; + /// /// Gets or sets the vertex linked to this half-edge. /// - public MeshVertex Vertex - { - get; - set; - } + public MeshVertex Vertex { get; set; } /// /// Gets or sets the edge linked to this half-edge. /// - public MeshEdge Edge - { - get; - set; - } + public MeshEdge Edge { get; set; } /// /// Gets or sets the face linked to this half-edge. /// - public MeshFace Face - { - get; - set; - } + public MeshFace Face { get; set; } /// /// Gets or sets the corner linked to this half-edge. /// - public MeshCorner Corner - { - get; - set; - } + public MeshCorner Corner { get; set; } /// /// Gets or sets the next half-edge in a face. /// - public MeshHalfEdge Next - { - get; - set; - } + public MeshHalfEdge Next { get; set; } /// /// Gets or sets the previous half-edge in a face. /// - public MeshHalfEdge Prev - { - get; - set; - } + public MeshHalfEdge Prev { get; set; } /// /// Gets or sets the opposite half-edge. /// - public MeshHalfEdge Twin - { - get; - set; - } + public MeshHalfEdge Twin { get; set; } /// /// Gets or sets a value indicating whether the half-edge lies on a boundary. /// - public bool OnBoundary - { - get; - set; - } + public bool OnBoundary { get; set; } /// /// Gets or sets the half-edge index. /// - public int Index - { - get; - set; - } + public int Index { get; set; } /// /// Gets the previous vertex of the half-edge. @@ -101,6 +66,7 @@ public int Index /// public MeshFace AdjacentFace => this.Twin.Face; + /// /// Gets the string representation of the half-edge. /// diff --git a/src/Geometry/3D/Mesh/MeshPoint.cs b/src/Geometry/3D/Mesh/MeshPoint.cs index b264cd4..5629003 100644 --- a/src/Geometry/3D/Mesh/MeshPoint.cs +++ b/src/Geometry/3D/Mesh/MeshPoint.cs @@ -22,6 +22,7 @@ public MeshPoint(int faceIndex, double u, double v, double w) this.W = w; } + /// /// Initializes a new instance of the class. /// @@ -36,46 +37,33 @@ public MeshPoint(MeshFace face, Point3d point) this.W = bary[2]; } + /// /// Gets or sets the index of the face this point lies in. /// - public int FaceIndex - { - get; - set; - } + public int FaceIndex { get; set; } /// /// Gets or sets the U coordinate at the face. /// - public double U - { - get; - set; - } + public double U { get; set; } /// /// Gets or sets the V coordinate at the face. /// - public double V - { - get; - set; - } + public double V { get; set; } /// /// Gets or sets the W coordinate at the face. /// - public double W - { - get; - set; - } + public double W { get; set; } + /// /// Converts a mesh point into a string. /// /// String representation of the mesh point. - public override string ToString() => "MeshPoint{ " + this.FaceIndex + "; " + this.U + ", " + this.V + ", " + this.W + " }"; + public override string ToString() => + "MeshPoint{ " + this.FaceIndex + "; " + this.U + ", " + this.V + ", " + this.W + " }"; } } \ No newline at end of file diff --git a/src/Geometry/3D/Mesh/MeshTopology.cs b/src/Geometry/3D/Mesh/MeshTopology.cs index 3ca6797..b985c3b 100644 --- a/src/Geometry/3D/Mesh/MeshTopology.cs +++ b/src/Geometry/3D/Mesh/MeshTopology.cs @@ -11,6 +11,7 @@ public class MeshTopology // Returns 2 dimensional array: 1 array per vertex index containing an array with the corresponding adjacent member index private readonly Mesh mesh; + /// /// Initializes a new instance of the class. /// @@ -28,79 +29,58 @@ public MeshTopology(Mesh mesh) this.EdgeVertex = new Dictionary>(); this.EdgeFace = new Dictionary>(); this.EdgeEdge = new Dictionary>(); + + this.ComputeEdgeAdjacency(); + this.ComputeFaceAdjacency(); + this.ComputeVertexAdjacency(); } + /// /// Gets vertex-Vertex topological connections. /// - public Dictionary> VertexVertex - { - get; - } + public Dictionary> VertexVertex { get; } /// /// Gets vertex-Face topological connections. /// - public Dictionary> VertexFaces - { - get; - } + public Dictionary> VertexFaces { get; } /// /// Gets vertex-Edge topological connections. /// - public Dictionary> VertexEdges - { - get; - } + public Dictionary> VertexEdges { get; } /// /// Gets edge-Edge topological connections. /// - public Dictionary> EdgeEdge - { - get; - } + public Dictionary> EdgeEdge { get; } /// /// Gets edge-Vertex topological connections. /// - public Dictionary> EdgeVertex - { - get; - } + public Dictionary> EdgeVertex { get; } /// /// Gets edge-Face topological connections. /// - public Dictionary> EdgeFace - { - get; - } + public Dictionary> EdgeFace { get; } /// /// Gets face-Vertex topological connections. /// - public Dictionary> FaceVertex - { - get; - } + public Dictionary> FaceVertex { get; } /// /// Gets face-Edge topological connections. /// - public Dictionary> FaceEdge - { - get; - } + public Dictionary> FaceEdge { get; } /// /// Gets face-Face topological connections. /// - public Dictionary> FaceFace - { - get; - } + public Dictionary> FaceFace { get; } + /// /// Computes vertex adjacency for the whole mesh and stores it in the appropriate dictionaries. @@ -110,25 +90,32 @@ public void ComputeVertexAdjacency() foreach (var vertex in this.mesh.Vertices) { foreach (var adjacent in vertex.AdjacentVertices()) + { if (!this.VertexVertex.ContainsKey(vertex.Index)) this.VertexVertex.Add(vertex.Index, new List {adjacent.Index}); else this.VertexVertex[vertex.Index].Add(adjacent.Index); + } foreach (var adjacent in vertex.AdjacentFaces()) + { if (!this.VertexFaces.ContainsKey(vertex.Index)) this.VertexFaces.Add(vertex.Index, new List {adjacent.Index}); else this.VertexFaces[vertex.Index].Add(adjacent.Index); + } foreach (var adjacent in vertex.AdjacentEdges()) + { if (!this.VertexEdges.ContainsKey(vertex.Index)) this.VertexEdges.Add(vertex.Index, new List {adjacent.Index}); else this.VertexEdges[vertex.Index].Add(adjacent.Index); + } } } + /// /// Computes face adjacency for the whole mesh and stores it in the appropriate dictionaries. /// @@ -137,25 +124,32 @@ public void ComputeFaceAdjacency() foreach (var face in this.mesh.Faces) { foreach (var adjacent in face.AdjacentVertices()) + { if (!this.FaceVertex.ContainsKey(face.Index)) this.FaceVertex.Add(face.Index, new List {adjacent.Index}); else this.FaceVertex[face.Index].Add(adjacent.Index); + } foreach (var adjacent in face.AdjacentFaces()) + { if (!this.FaceFace.ContainsKey(face.Index)) this.FaceFace.Add(face.Index, new List {adjacent.Index}); else this.FaceFace[face.Index].Add(adjacent.Index); + } foreach (var adjacent in face.AdjacentEdges()) + { if (!this.FaceEdge.ContainsKey(face.Index)) this.FaceEdge.Add(face.Index, new List {adjacent.Index}); else this.FaceEdge[face.Index].Add(adjacent.Index); + } } } + /// /// Computes edge adjacency for the whole mesh and stores it in the appropriate dictionaries. /// @@ -164,25 +158,32 @@ public void ComputeEdgeAdjacency() foreach (var edge in this.mesh.Edges) { foreach (var adjacent in edge.AdjacentVertices()) + { if (!this.EdgeVertex.ContainsKey(edge.Index)) this.EdgeVertex.Add(edge.Index, new List {adjacent.Index}); else this.EdgeVertex[edge.Index].Add(adjacent.Index); + } foreach (var adjacent in edge.AdjacentFaces()) + { if (!this.EdgeFace.ContainsKey(edge.Index)) this.EdgeFace.Add(edge.Index, new List {adjacent.Index}); else this.EdgeFace[edge.Index].Add(adjacent.Index); + } foreach (var adjacent in edge.AdjacentEdges()) + { if (!this.EdgeEdge.ContainsKey(edge.Index)) this.EdgeEdge.Add(edge.Index, new List {adjacent.Index}); else this.EdgeEdge[edge.Index].Add(adjacent.Index); + } } } + /// /// Gets the string representation of a given topology dictionary. /// diff --git a/src/Geometry/3D/Mesh/MeshVertex.cs b/src/Geometry/3D/Mesh/MeshVertex.cs index d8da16f..0454d04 100644 --- a/src/Geometry/3D/Mesh/MeshVertex.cs +++ b/src/Geometry/3D/Mesh/MeshVertex.cs @@ -13,11 +13,13 @@ public class MeshVertex : Point3d // Constructor + /// /// Initializes a new instance of the class. /// public MeshVertex() => this.userValues = new Dictionary(); + /// /// Initializes a new instance of the class from a 3D point. /// @@ -26,8 +28,10 @@ public MeshVertex(Point3d pt) : base(pt) => this.userValues = new Dictionary(); + /// - /// Initializes a new instance of the class from it's cartesian coordiantes. + /// Initializes a new instance of the class from it's cartesian + /// coordiantes. /// /// X Coordiante. /// Y Coordinate. @@ -36,23 +40,16 @@ public MeshVertex(double x, double y, double z) : base(x, y, z) => this.userValues = new Dictionary(); + /// /// Gets or sets the half-edge this vertex is attached to. /// - public MeshHalfEdge HalfEdge - { - get; - set; - } + public MeshHalfEdge HalfEdge { get; set; } /// /// Gets or sets the index of the vertex. /// - public int Index - { - get; - set; - } + public int Index { get; set; } /// /// Gets dictionary of user values. @@ -64,24 +61,28 @@ public int Index // Calculate the valence of a vertex + /// /// Computes the valence of the vertex. /// /// public int Valence() => this.AdjacentHalfEdges().Count; + /// /// Check if vertex is isolated, meaning corresponding half-edge is null. /// /// public bool IsIsolated() => this.HalfEdge == null; + /// /// Check if vertex is on mesh boundary. /// /// public bool OnBoundary() => this.AdjacentHalfEdges().Any(halfEdge => halfEdge.OnBoundary); + /// /// Returns a list with all adjacent HE_HalfEdge of this vertex. /// @@ -99,6 +100,7 @@ public List AdjacentHalfEdges() return halfEdges; } + /// /// Returns a list with all adjacent HE_Face of a vertex. /// @@ -117,6 +119,7 @@ public List AdjacentFaces() return faces; } + /// /// Returns a list with all the adjacent HE_Vertex of this vertex. /// @@ -134,6 +137,7 @@ public List AdjacentVertices() return vertices; } + /// /// Returns a list with all the adjacent HE_Edge of this vertex. /// @@ -151,6 +155,7 @@ public List AdjacentEdges() return edges; } + /// /// Returns a list with all the adjacent HE_Corners of this vertex. /// @@ -169,6 +174,7 @@ public List AdjacentCorners() return corners; } + /// /// Returns the string representation of this vertex. /// diff --git a/src/Geometry/3D/Nurbs/NurbsCalculator.cs b/src/Geometry/3D/Nurbs/NurbsCalculator.cs deleted file mode 100644 index ae84c43..0000000 --- a/src/Geometry/3D/Nurbs/NurbsCalculator.cs +++ /dev/null @@ -1,750 +0,0 @@ -using System; -using System.Collections.Generic; -using Paramdigma.Core.Collections; - -namespace Paramdigma.Core.Geometry -{ - /// - /// Contains all methods related to 'The Nurbs Book 2nd Edition' implementation of NURBS curves and surfaces. - /// - public static class NurbsCalculator - { - /// - /// Constructs a Unit knot vector given a point count and degree. - /// - /// Ammount of control points in the curve. - /// Degree of the curve. - /// - public static double[] CreateUnitKnotVector(int controlPointCount, int degree) - { - if (degree > controlPointCount) - throw new Exception("Degree cannot be bigger than 'ControlPoints - 1'"); - var knotVector = new double[controlPointCount + degree + 2]; - for (var i = 0; i <= degree; i++) - knotVector[i] = 0.0; - for (var i = degree + 1; i < controlPointCount + 1; i++) - knotVector[i] = ((double)i - degree) / ((controlPointCount - degree) + 1); - for (var i = controlPointCount + 1; i < controlPointCount + degree + 2; i++) - knotVector[i] = 1.0; - return knotVector; - } - - /// - /// Compute a point on a power basis curve. - /// - /// Curve points. - /// Curve degree. - /// Parameter. - /// Computed point on curve. - public static Point3d Horner1(Point3d[] points, int degree, double t) - { - var c = points[degree]; - for (var i = degree - 1; i >= 0; i--) - c = (c * t) + points[i]; - return c; - } - - /// - /// Compute the value of a Bernstein polynomial. - /// - /// Index of point to compute polynomial of. - /// Degree of curve. - /// Parameter. - /// - public static double Bernstein(int index, int degree, double t) - { - var temp = new List(); - for (var j = 0; j <= degree; j++) - temp.Add(0.0); - - temp[degree - index] = 1.0; - - var u1 = 1.0 - t; - for (var k = 1; k <= degree; k++) - for (var j = degree; j >= k; j--) - temp[j] = (u1 * temp[j]) + (t * temp[j - 1]); - - return temp[degree]; - } - - /// - /// Compute point on a power basis surface. - /// - /// Control point matrix. - /// Surface degree in the U direction. - /// Surface degree in the V direction. - /// U parameter to compute. - /// V parameter to compute. - /// Computed point on the surface. - public static Point3d Horner2(Matrix controlPoints, int degreeU, int degreeV, double u, double v) - { - var b = new Point3d[degreeU]; - for (var i = 0; i <= degreeU; i++) - b[i] = Horner1(controlPoints.Row(i), degreeV, v); - return Horner1(b, degreeU, u); - } - - /// - /// Compute all nth-degree Bernstein polynomials. - /// - /// Curve degree. - /// Parameter. - /// - public static double[] AllBernstein(int degree, double t) - { - var b = new double[degree + 1]; - b[0] = 1.0; - var u1 = 1.0 - t; - for (var j = 1; j <= degree; j++) - { - var saved = 0.0; - for (var k = 0; k < j; k++) - { - var temp = b[k]; - b[k] = saved + (u1 * temp); - saved = t * temp; - } - - b[j] = saved; - } - - return b; - } - - /// - /// Compute point on Bezier curve. - /// - /// Control ponts of the curve. - /// Curve degree. - /// Parameter to compute. - /// - public static Point3d PointOnBezierCurve(List controlPoints, int degree, double t) - { - var c = new Point3d(); - - var b = AllBernstein(degree, t); - for (var k = 0; k <= degree; k++) - c += b[k] * controlPoints[k]; - - return c; - } - - /// - /// Compute point on a Bézier curve by deCasteljau. - /// - /// Control points of the curve. - /// Curve degree. - /// Parameter of point to compute. - /// Computed point along the Bézier curve. - public static Point3d DeCasteljau1(Point3d[] controlPoints, int degree, double t) - { - var q = new Point3d[degree + 1]; - for (var i = 0; i <= degree; i++) - q[i] = new Point3d(controlPoints[i]); - - for (var k = 1; k <= degree; k++) - for (var i = 0; i <= degree - k; i++) - q[i] = ((1.0 - t) * q[i]) + (t * q[i + 1]); - - return q[0]; - } - - /// - /// Compute a point on a Bézier surface by deCasteljau. - /// - /// Control points of the curve. - /// Surface degree in the U direction. - /// Surface degree in the V direction. - /// U parameter to compute. - /// V parameter to compute. - /// The computed point. - public static Point3d DeCasteljau2(Matrix controlPoints, int degreeU, int degreeV, double u, double v) - { - var q = new List(); - if (degreeU <= degreeV) - { - for (var j = 0; j <= degreeV; j++) - q.Add(DeCasteljau1(controlPoints.Row(j), degreeU, u)); - - return DeCasteljau1(q.ToArray(), degreeV, v); - } - - for (var i = 0; i <= degreeU; i++) - q.Add(DeCasteljau1(controlPoints.Column(i), degreeU, u)); - return DeCasteljau1(q.ToArray(), degreeU, u); - } - - /// - /// Determine the knot span index. - /// - /// Degree. - /// ????. - /// Paramter. - /// Knot vector. - /// The knot span index. - public static int FindSpan(int n, int degree, double t, IList knotVector) - { - if (t == knotVector[n + 1]) - return n; - - var low = degree; - var high = n + 1; - var mid = (low + high) / 2; - while (t < knotVector[mid] || t >= knotVector[mid + 1]) - { - if (t < knotVector[mid]) - high = mid; - else - low = mid; - - mid = (low + high) / 2; - } - - return mid; - } - - /// - /// Compute all non-zero basis functions of all degrees from 0 to "degree". - /// - /// Knot span index. - /// Parameter to compute. - /// Degree. - /// The knot vector. - /// List with all non-zero basis functions up to the specified degree. - public static double[,] AllBasisFuns(int span, double param, int degree, IList knotVector) - { - var n = new double[degree + 1, degree + 1]; - for (var i = 0; i <= degree; i++) - for (var j = 0; j <= i; j++) - n[j, i] = OneBasisFun(degree, knotVector.Count - 1, knotVector, (span - i) + j, param); - - return n; - } - - /// - /// Computes the basis functions of a span. - /// - /// Knot span. - /// Parameter to compute. - /// Degree. - /// Knot vector. - /// List of the basis functions of the specific span. - public static double[] BasisFuns(int span, double param, int degree, IList knotVector) - { - var basisFunctions = new double[degree + 1]; - var left = new double[degree + 1]; - var right = new double[degree + 1]; - basisFunctions[0] = 1.0; - for (var j = 1; j <= degree; j++) - { - left[j] = param - knotVector[(span + 1) - j]; - right[j] = knotVector[span + j] - param; - var saved = 0.0; - for (var r = 0; r < j; r++) - { - var temp = basisFunctions[r] / (right[r + 1] + left[j - r]); - basisFunctions[r] = saved + (right[r + 1] * temp); - saved = left[j - r] * temp; - } - - basisFunctions[j] = saved; - } - - return basisFunctions; - } - - /// - /// Compute nonzero basis functions and their derivatives at a specified parameter. - /// - /// Knot span. - /// Parameter. - /// Degree. - /// Derivatives to compute. - /// Knot vector. - /// Multidimensional array holding the basis functions and their derivatives for that parameter. - public static Matrix DersBasisFuns(int span, double param, int degree, int n, IList knotVector) - { - var ders = new Matrix(n, degree); - var ndu = new double[degree + 1, degree + 1]; - var a = new double[2, degree + 1]; - var left = new double[degree + 1]; - var right = new double[degree + 1]; - - ndu[0, 0] = 1.0; - for (var j = 1; j <= degree; j++) - { - left[j] = param - knotVector[(span + 1) - j]; - right[j] = knotVector[span + j] - param; - var saved = 0.0; - for (var r = 0; r < j; r++) - { - ndu[j, r] = right[r + 1] + left[j - r]; - var temp = ndu[r, j - 1] / ndu[j, r]; - ndu[r, j] = saved + (right[r + 1] * temp); - saved = left[j - r] * temp; - } - - ndu[j, j] = saved; - } - - for (var j = 0; j <= degree; j++) - ders[0, j] = ndu[j, degree]; - - for (var r = 0; r <= degree; r++) - { - var s1 = 0; - var s2 = 1; - a[0, 0] = 1.0; - for (var k = 1; k <= n; k++) - { - var d = 0.0; - var rk = r - k; - var pk = degree - k; - if (r >= k) - { - a[s2, 0] = a[s1, 0] / ndu[pk + 1, rk]; - d = a[s2, 0] * ndu[rk, pk]; - } - - var j1 = rk >= -1 ? 1 : -rk; - var j2 = r - 1 <= pk ? k - 1 : degree - r; - - for (var j = j1; j <= j2; j++) - { - a[s2, j] = (a[s1, j] - a[s1, j - 1]) / ndu[pk + 1, rk + j]; - d += a[s2, j] * ndu[rk + j, pk]; - } - - if (r <= pk) - { - a[s2, k] = -a[s1, k - 1] / ndu[pk + 1, r]; - d += a[s2, k] * ndu[r, pk]; - } - - ders[k, r] = d; - - // Switch rows - var temp = s1; - s1 = s2; - s2 = temp; - } - } - - var r0 = degree; - for (var k = 1; k <= n; k++) - { - for (var j = 0; j <= degree; j++) - ders[k, j] *= r0; - r0 *= degree - k; - } - - return ders; - } - - /// - /// Compute the basis function 'Nip'. - /// - /// Degree. - /// The high index of the knot vector. - /// Knot vector. - /// Knot span index. - /// Parameter to compute. - /// - public static double OneBasisFun(int degree, int m, IList knotVector, int span, double param) - { - if ((span == 0 && param == knotVector[0]) || (span == m - degree - 1 && param == knotVector[m])) - return 1.0; - - if (param < knotVector[span] || param >= knotVector[span + degree + 1]) - return 0.0; - - // Initialize zeroth-degree functions - var n = new double[degree + 1]; - for (var j = 0; j <= degree; j++) - if (param >= knotVector[span + j] && param < knotVector[span + j + 1]) - n[j] = 1.0; - else - n[j] = 0.0; - - for (var k = 1; k <= degree; k++) - { - var saved = n[0] == 0.0 ? 0.0 : ((param - knotVector[span]) * n[0]) / (knotVector[span + k] - knotVector[span]); - for (var j = 0; j < (degree - k) + 1; j++) - { - var uLeft = knotVector[span + j + 1]; - var uRight = knotVector[span + j + k + 1]; - if (n[j + 1] == 0.0) - { - n[j] = saved; - saved = 0.0; - } - else - { - var temp = n[j + 1] / (uRight - uLeft); - n[j] = saved + ((uRight - param) * temp); - saved = (param - uLeft) * temp; - } - } - } - - return n[0]; - } - - /// - /// Compute derivatives of basis function 'Nip'. - /// - /// - /// - /// - /// - /// - /// - /// - public static double[] DersOneBasisFun(int p, int m, IList knotVector, int i, double u, int n) - { - // TODO: Check unused m parameter. - var ders = new double[n + 1]; - if (u < knotVector[i] || u >= knotVector[i + p + 1]) - { - for (var k = 0; k <= n; k++) - ders[k] = 0.0; - return ders; - } - - var tmpN = new double[p + 1, p + 1]; - for (var j = 0; j <= p; j++) - if (u >= knotVector[i + j] && u < knotVector[i + j + 1]) - tmpN[j, 0] = 1.0; - - for (var k = 1; k <= p; k++) - { - double saved; - if (tmpN[0, k - 1] == 0.0) - saved = 0.0; - else - saved = ((u - knotVector[i]) * tmpN[0, k - 1]) / (knotVector[i + k] - knotVector[i]); - - for (var j = 0; j < (p - k) + 1; j++) - { - var uLeft = knotVector[i + j + 1]; - var uRight = knotVector[i + j + k + 1]; - if (tmpN[j + 1, k - 1] == 0.0) - { - tmpN[j, k] = saved; - saved = 0.0; - } - else - { - var temp = tmpN[j + 1, k - 1] / (uRight - uLeft); - tmpN[j, k] = saved + ((uRight - u) * temp); - saved = (u - uLeft) * temp; - } - } - } - - ders[0] = tmpN[0, p]; - - var tempND = new double[n + 1]; - for (var k = 1; k <= n; k++) - { - for (var j = 0; j <= k; j++) - tempND[j] = tmpN[j, p - k]; - - for (var jj = 1; jj <= k; jj++) - { - double saved; - if (tempND[0] == 0.0) - saved = 0.0; - else - saved = tempND[0] / (knotVector[((i + p) - k) + jj] - knotVector[i]); - for (var j = 0; j < (k - jj) + 1; j++) - { - var uLeft = knotVector[i + j + 1]; - var uRight = knotVector[i + j + p + jj + 1]; - if (tempND[j + 1] == 0.0) - { - tempND[j] = ((p - k) + jj) * saved; - saved = 0.0; - } - else - { - var temp = tempND[j + 1] / (uRight - uLeft); - tempND[j] = ((p - k) + jj) * (saved - temp); - saved = temp; - } - } - } - - ders[k] = tempND[0]; - } - - return ders; - } - - public static Point3d CurvePoint(int n, int p, IList knotVector, IList controlPoints, double u) - { - var span = FindSpan(n, p, u, knotVector); - var basisFuns = BasisFuns(span, u, p, knotVector); - var c = Point3d.Unset; - for (var i = 0; i <= p; i++) - c += basisFuns[i] * controlPoints[(span - p) + i]; - return c; - } - - public static Vector3d[] CurveDerivsAlg1(int n, int p, IList knotVector, IList controlPoints, double u, int d) - { - var ck = new Vector3d[d + 1]; - var du = Math.Min(d, p); - for (var k = p + 1; k <= d; k++) - ck[k] = new Vector3d(); - var span = FindSpan(n, p, u, knotVector); - var nders = DersBasisFuns(span, u, p, du, knotVector); - for (var k = 0; k <= du; k++) - { - ck[k] = new Vector3d(); - for (var j = 0; j <= p; j++) - ck[k] += nders[k, j] * (Vector3d)controlPoints[(span - p) + j]; - } - - return ck; - } - - public static Point3d[,] CurveDerivCpts(int n, int p, IList knotVector, IList controlPoints, int d, int r1, int r2) - { - var r = r2 - r1; - var pk = new Point3d[d + 1, r]; - for (var i = 0; i <= r; i++) - pk[0, i] = controlPoints[r1 + i]; - for (var k = 1; k <= d; k++) - { - var tmp = (p - k) + 1; - for (var i = 0; i <= r - k; i++) - pk[k, i] = (tmp * (Point3d)(pk[k - 1, i + 1] - pk[k - 1, i])) / (knotVector[r1 + i + p + 1] - knotVector[r1 + i + k]); - } - - return pk; - } - - public static Vector3d[] CurveDerivsAlg2(int n, int p, IList knotVector, IList controlPoints, double u, int d) - { - var du = Math.Min(d, p); - var ck = new Vector3d[d + 1]; - for (var k = p + 1; k <= d; k++) - ck[k] = new Vector3d(); - var span = FindSpan(n, p, u, knotVector); - var basisFuns = AllBasisFuns(span, u, p, knotVector); - var pk = CurveDerivCpts(n, p, knotVector, controlPoints, du, span - p, span); - for (var k = 0; k <= du; k++) - { - ck[k] = new Vector3d(); - for (var j = 0; j <= p - k; j++) - ck[k] += basisFuns[j, p - k] * (Vector3d)pk[k, j]; - } - - return ck; - } - - // B-Spline Surfaces - public static Point3d SurfacePoint(int n, int p, IList knotVectorU, int m, int q, IList knotVectorV, Matrix controlPoints, double u, double v) - { - var uspan = FindSpan(n, p, u, knotVectorU); - var nU = BasisFuns(uspan, u, p, knotVectorU); - var vspan = FindSpan(m, q, v, knotVectorV); - var nV = BasisFuns(vspan, v, q, knotVectorV); - var uind = uspan - p; - var surfPt = Point3d.Unset; - for (var l = 0; l <= q; l++) - { - var temp = Point3d.Unset; - var vind = vspan - q - l; - for (var k = 0; k <= p; k++) - temp += nU[k] * controlPoints[uind + k, vind]; - surfPt += nV[l] * temp; - } - - return surfPt; - } - - public static Point3d[,] SurfaceDerivsAlg1(int n, int p, IList knotVectorU, int m, int q, IList knotVectorV, Matrix controlPoints, double u, double v, int derivCount) - { - var sKL = new Point3d[derivCount + 1, derivCount + 1]; - var du = Math.Min(derivCount, p); - for (var k = p + 1; k <= derivCount; k++) - for (var l = 0; l <= derivCount - k; l++) - sKL[k, l] = Point3d.Unset; - - var dv = Math.Min(derivCount, q); - for (var l = q + 1; l <= derivCount; l++) - for (var k = 0; k <= derivCount - 1; k++) - sKL[k, l] = Point3d.Unset; - - var uSpan = FindSpan(n, p, u, knotVectorU); - var nU = DersBasisFuns(uSpan, u, p, du, knotVectorU); - var vSpan = FindSpan(m, q, v, knotVectorV); - var nV = DersBasisFuns(vSpan, v, q, dv, knotVectorV); - - for (var k = 0; k <= du; k++) - { - var temp = new Point3d[q]; - for (var s = 0; s <= q; s++) - { - temp[s] = Point3d.Unset; - for (var r = 0; r <= p; r++) - temp[s] += nU[k, r] * controlPoints[(uSpan - p) + r, (vSpan - q) + s]; - - var dd = Math.Min(derivCount - k, dv); - - for (var l = 0; l <= dd; l++) - { - sKL[k, l] = Point3d.Unset; - - // TODO: Check ss, this was changed from 's' for naming conflicts but it might have been on purpose. - for (var ss = 0; ss <= q; ss++) - sKL[k, l] += nV[l, ss] * temp[ss]; - } - } - } - - return sKL; - } - - public static Point3d[][][][] SurfaceDerivCpts(int n, int p, IList knotVectorU, int m, int q, IList knotVectorV, Matrix controlPoints, int d, int r1, int r2, int s1, int s2) - { - var pkl = new Point3d[d][][][]; - - var du = Math.Min(d, p); - var dv = Math.Min(d, q); - var r = r2 - r1; - var s = s2 - s1; - - for (var j = s1; j <= s2; j++) - { - var temp = CurveDerivCpts(n, p, knotVectorU, controlPoints.Column(j), du, r1, r2); - for (var k = 0; k <= du; k++) - for (var i = 0; i <= r - k; i++) - pkl[k][0][i][j - s1] = temp[k, i]; - } - - for (var k = 0; k <= du; k++) - for (var i = 0; i <= r - k; i++) - { - var dd = Math.Min(d - k, dv); - var temp = CurveDerivCpts(m, q, knotVectorV, pkl[k][0][i], dd, 0, s); - for (var l = i; k <= dd; k++) - for (var j = 0; j <= s - l; j++) - pkl[k][l][i][j] = temp[l, j]; - } - - return pkl; - } - - public static Point3d[,] SurfaceDerivsAlg2(int n, int p, IList knotVectorU, int m, int q, IList knotVectorV, Matrix controlPoints, double u, double v, int d) - { - var skl = new Point3d[d + 1, d + 1]; - - var du = Math.Min(d, p); - for (var k = p + 1; k <= d; k++) - for (var l = 0; l <= d - k; l++) - skl[k, l] = Point3d.Unset; - - var dv = Math.Min(d, q); - for (var l = q + 1; l <= d; l++) - for (var k = 0; k <= d - l; k++) - skl[k, l] = Point3d.Unset; - - var uSpan = FindSpan(n, p, u, knotVectorU); - var nV = AllBasisFuns(uSpan, u, p, knotVectorU); - var vSpan = FindSpan(m, q, v, knotVectorV); - var nU = AllBasisFuns(vSpan, v, q, knotVectorV); - var pkl = SurfaceDerivCpts(n, p, knotVectorU, m, q, knotVectorV, controlPoints, d, uSpan - p, uSpan, vSpan - q, vSpan); - - for (var k = 0; k <= du; k++) - { - var dd = Math.Min(d - k, dv); - for (var l = 0; l <= dd; l++) - { - skl[k, l] = Point3d.Unset; - for (var i = 0; i <= q - l; i++) - { - var tmp = Point3d.Unset; - for (var j = 0; j <= p - k; j++) - tmp += nV[j, p - k] * pkl[k][l][j][i]; - skl[k, l] += nU[i, q - l] * tmp; - } - } - } - - return skl; - } - - // Nubs methods - public static Point4d CurvePoint(int n, int p, IList knotVector, IList controlPoints, double u) - { - var span = FindSpan(n, p, u, knotVector); - var N = BasisFuns(span, u, p, knotVector); - var cw = new Point4d(); - for (var j = 0; j <= p; j++) - cw += N[j] * controlPoints[(span - p) + j]; - - return cw / cw.Weight; - } - - /// - /// Compute C(u) derivatives from Cw(u) derivatives. - /// - /// - /// - /// - /// - public static Vector3d[] RatCurveDerivs(IList aDers, IList wDers, int d) - { - var ders = new Vector3d[d + 1]; - double[,] bin = null; // TODO: Precompute 'binomial coefficients' - for (var k = 0; k <= d; k++) - { - var v = aDers[k]; - for (var i = 1; i <= k; i++) - v -= bin[k, i] * wDers[i] * ders[k - i]; - - ders[k] = v / wDers[0]; - } - - return ders; - } - - /// - /// Compute a point on a nurbs surface. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static Point3d SurfacePoint(int n, int p, IList knotVectorU, int m, int q, IList knotVectorV, Matrix controlPoints, double u, double v) - { - var uspan = FindSpan(n, p, u, knotVectorU); - var nU = BasisFuns(uspan, u, p, knotVectorU); - var vspan = FindSpan(m, q, v, knotVectorV); - var nV = BasisFuns(vspan, v, q, knotVectorV); - - var temp = new Point4d[q]; - for (var l = 0; l <= q; l++) - { - temp[l] = new Point4d(); - for (var k = 0; k <= p; k++) - temp[l] += nU[k] * controlPoints[(uspan - p) + k, (vspan - q) + l]; - } - - var sW = new Point4d(); - for (var l = 0; l <= q; l++) - sW += nV[l] * temp[l]; - - return (Point3d)(sW / sW.Weight); - } - } -} \ No newline at end of file diff --git a/src/Geometry/3D/Nurbs/NurbsSurface.cs b/src/Geometry/3D/Nurbs/NurbsSurface.cs deleted file mode 100644 index d2e551f..0000000 --- a/src/Geometry/3D/Nurbs/NurbsSurface.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Paramdigma.Core.Geometry -{ - /// - /// Represents a NURBS surface. Contains properties and methods for operating with NURBS surfaces. - /// - public class NurbsSurface { } -} \ No newline at end of file diff --git a/src/Geometry/3D/NurbsCalculator.cs b/src/Geometry/3D/NurbsCalculator.cs new file mode 100644 index 0000000..c0def04 --- /dev/null +++ b/src/Geometry/3D/NurbsCalculator.cs @@ -0,0 +1,1350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Paramdigma.Core.Collections; + +namespace Paramdigma.Core.Geometry +{ + /// + /// Contains all methods related to 'The Nurbs Book 2nd Edition' implementation of NURBS curves and + /// surfaces. + /// + internal static class NurbsCalculator + { + /// + /// Constructs a Unit knot vector given a point count and degree. + /// + /// Ammount of control points in the curve. + /// Degree of the curve. + /// Throws an error if degree is bigger than controlPoints+1 + /// + public static double[] CreateUniformKnotVector(int controlPointCount, int degree) + { + if (degree > controlPointCount) + throw new Exception("Degree cannot be bigger than 'ControlPoints - 1'"); + var knotVector = new double[controlPointCount + degree + 1]; + for (var i = 0; i <= degree; i++) + knotVector[i] = 0.0; + for (var i = degree + 1; i < controlPointCount; i++) + knotVector[i] = (( double ) i - degree) / (controlPointCount - degree); + for (var i = controlPointCount; i < controlPointCount + degree + 1; i++) + knotVector[i] = 1.0; + return knotVector; + } + + + /// + /// Compute a point on a power basis curve. + /// + /// Curve points. + /// Curve degree. + /// Parameter. + /// Computed point on curve. + public static Point3d Horner1(Point3d[] points, int degree, double t) + { + var c = points[degree]; + for (var i = degree - 1; i >= 0; i--) + c = c * t + points[i]; + return c; + } + + + /// + /// Compute the value of a Bernstein polynomial. + /// + /// Index of point to compute polynomial of. + /// Degree of curve. + /// Parameter. + /// + public static double Bernstein(int index, int degree, double t) + { + var temp = new List(); + for (var j = 0; j <= degree; j++) + temp.Add(0.0); + + temp[degree - index] = 1.0; + + var u1 = 1.0 - t; + for (var k = 1; k <= degree; k++) + { + for (var j = degree; j >= k; j--) + temp[j] = u1 * temp[j] + t * temp[j - 1]; + } + + return temp[degree]; + } + + + /// + /// Compute point on a power basis surface. + /// + /// Control point matrix. + /// Surface degree in the U direction. + /// Surface degree in the V direction. + /// U parameter to compute. + /// V parameter to compute. + /// Computed point on the surface. + public static Point3d Horner2( + Matrix controlPoints, + int degreeU, + int degreeV, + double u, + double v) + { + var b = new Point3d[degreeU]; + for (var i = 0; i <= degreeU; i++) + b[i] = Horner1(controlPoints.Row(i), degreeV, v); + return Horner1(b, degreeU, u); + } + + + /// + /// Compute all nth-degree Bernstein polynomials. + /// + /// Curve degree. + /// Parameter. + /// + public static double[] AllBernstein(int degree, double t) + { + var b = new double[degree + 1]; + b[0] = 1.0; + var u1 = 1.0 - t; + for (var j = 1; j <= degree; j++) + { + var saved = 0.0; + for (var k = 0; k < j; k++) + { + var temp = b[k]; + b[k] = saved + u1 * temp; + saved = t * temp; + } + + b[j] = saved; + } + + return b; + } + + + /// + /// Compute point on Bezier curve. + /// + /// Control ponts of the curve. + /// Curve degree. + /// Parameter to compute. + /// + public static Point3d PointOnBezierCurve(List controlPoints, int degree, double t) + { + var c = new Point3d(); + + var b = AllBernstein(degree, t); + for (var k = 0; k <= degree; k++) + c += b[k] * controlPoints[k]; + + return c; + } + + + /// + /// Compute point on a Bézier curve by deCasteljau. + /// + /// Control points of the curve. + /// Curve degree. + /// Parameter of point to compute. + /// Computed point along the Bézier curve. + public static Point3d DeCasteljau1(Point3d[] controlPoints, int degree, double t) + { + var q = new Point3d[degree + 1]; + for (var i = 0; i <= degree; i++) + q[i] = new Point3d(controlPoints[i]); + + for (var k = 1; k <= degree; k++) + { + for (var i = 0; i <= degree - k; i++) + q[i] = (1.0 - t) * q[i] + t * q[i + 1]; + } + + return q[0]; + } + + + /// + /// Compute a point on a Bézier surface by deCasteljau. + /// + /// Control points of the curve. + /// Surface degree in the U direction. + /// Surface degree in the V direction. + /// U parameter to compute. + /// V parameter to compute. + /// The computed point. + public static Point3d DeCasteljau2( + Matrix controlPoints, + int degreeU, + int degreeV, + double u, + double v) + { + var q = new List(); + if (degreeU <= degreeV) + { + for (var j = 0; j <= degreeV; j++) + q.Add(DeCasteljau1(controlPoints.Row(j), degreeU, u)); + + return DeCasteljau1(q.ToArray(), degreeV, v); + } + + for (var i = 0; i <= degreeU; i++) + q.Add(DeCasteljau1(controlPoints.Column(i), degreeU, u)); + return DeCasteljau1(q.ToArray(), degreeU, u); + } + + + /// + /// Determine the knot span index. + /// + /// Degree. + /// ????. + /// Parameter. + /// Knot vector. + /// The knot span index. + public static int FindSpan(int n, int degree, double t, IList knotVector) + { + if (t >= knotVector[n + 1]) + return n; + + var low = degree; + var high = n + 1; + var mid = (low + high) / 2; + while (t < knotVector[mid] || t >= knotVector[mid + 1]) + { + if (t < knotVector[mid]) + high = mid; + else + low = mid; + + mid = (low + high) / 2; + } + + return mid; + } + + + /// + /// Compute all non-zero basis functions of all degrees from 0 to "degree". + /// + /// Knot span index. + /// Parameter to compute. + /// Degree. + /// The knot vector. + /// List with all non-zero basis functions up to the specified degree. + public static double[,] AllBasisFuns( + int span, + double param, + int degree, + IList knotVector) + { + var n = new double[degree + 1, degree + 1]; + for (var i = 0; i <= degree; i++) + { + for (var j = 0; j <= i; j++) + { + n[j, i] = OneBasisFun( + degree, + knotVector.Count - 1, + knotVector, + span - i + j, + param); + } + } + + return n; + } + + + /// + /// Computes the basis functions of a span. + /// + /// Knot span. + /// Parameter to compute. + /// Degree. + /// Knot vector. + /// List of the basis functions of the specific span. + public static double[] BasisFunctions( + int span, + double param, + int degree, + IList knotVector) + { + // INFO: This method is called 'BasisFuns' in the book. + var basisFunctions = new double[degree + 1]; + var left = new double[degree + 1]; + var right = new double[degree + 1]; + basisFunctions[0] = 1.0; + for (var j = 1; j <= degree; j++) + { + left[j] = param - knotVector[span + 1 - j]; + right[j] = knotVector[span + j] - param; + var saved = 0.0; + for (var r = 0; r < j; r++) + { + var temp = basisFunctions[r] / (right[r + 1] + left[j - r]); + basisFunctions[r] = saved + right[r + 1] * temp; + saved = left[j - r] * temp; + } + + basisFunctions[j] = saved; + } + + return basisFunctions; + } + + + /// + /// Compute nonzero basis functions and their derivatives at a specified parameter. + /// + /// Knot span. + /// Parameter. + /// Degree. + /// Derivatives to compute. + /// Knot vector. + /// + /// Multidimensional array holding the basis functions and their derivatives for that + /// parameter. + /// + public static Matrix DerivativeBasisFunctions( + int span, + double param, + int degree, + int n, + IList knotVector) + { + // INFO: This method is called 'DersBasisFuns' in the book. + var ders = new Matrix(n + 1, degree + 1); + var ndu = new double[degree + 1, degree + 1]; + var a = new double[2, degree + 1]; + var left = new double[degree + 1]; + var right = new double[degree + 1]; + + ndu[0, 0] = 1.0; + for (var j = 1; j <= degree; j++) + { + left[j] = param - knotVector[span + 1 - j]; + right[j] = knotVector[span + j] - param; + var saved = 0.0; + for (var r = 0; r < j; r++) + { + ndu[j, r] = right[r + 1] + left[j - r]; + var temp = ndu[r, j - 1] / ndu[j, r]; + ndu[r, j] = saved + right[r + 1] * temp; + saved = left[j - r] * temp; + } + + ndu[j, j] = saved; + } + + for (var j = 0; j <= degree; j++) + ders[0, j] = ndu[j, degree]; + + for (var r = 0; r <= degree; r++) + { + var s1 = 0; + var s2 = 1; + a[0, 0] = 1.0; + for (var k = 1; k <= n; k++) + { + var d = 0.0; + var rk = r - k; + var pk = degree - k; + if (r >= k) + { + a[s2, 0] = a[s1, 0] / ndu[pk + 1, rk]; + d = a[s2, 0] * ndu[rk, pk]; + } + + var j1 = rk >= -1 ? 1 : -rk; + var j2 = r - 1 <= pk ? k - 1 : degree - r; + + for (var j = j1; j <= j2; j++) + { + a[s2, j] = (a[s1, j] - a[s1, j - 1]) / ndu[pk + 1, rk + j]; + d += a[s2, j] * ndu[rk + j, pk]; + } + + if (r <= pk) + { + a[s2, k] = -a[s1, k - 1] / ndu[pk + 1, r]; + d += a[s2, k] * ndu[r, pk]; + } + + ders[k, r] = d; + + // Switch rows + var temp = s1; + s1 = s2; + s2 = temp; + } + } + + var r0 = degree; + for (var k = 1; k <= n; k++) + { + for (var j = 0; j <= degree; j++) + ders[k, j] *= r0; + r0 *= degree - k; + } + + return ders; + } + + + /// + /// Compute the basis function 'Nip'. + /// + /// Degree. + /// The high index of the knot vector. + /// Knot vector. + /// Knot span index. + /// Parameter to compute. + /// + public static double OneBasisFun( + int degree, + int m, + IList knotVector, + int span, + double param) + { + if (span == 0 && param == knotVector[0] + || span == m - degree - 1 && param == knotVector[m]) + return 1.0; + + if (param < knotVector[span] || param >= knotVector[span + degree + 1]) + return 0.0; + + // Initialize zeroth-degree functions + var n = new double[degree + 1]; + for (var j = 0; j <= degree; j++) + { + if (param >= knotVector[span + j] && param < knotVector[span + j + 1]) + n[j] = 1.0; + else + n[j] = 0.0; + } + + for (var k = 1; k <= degree; k++) + { + var saved = n[0] == 0.0 + ? 0.0 + : (param - knotVector[span]) * n[0] + / (knotVector[span + k] - knotVector[span]); + for (var j = 0; j < degree - k + 1; j++) + { + var uLeft = knotVector[span + j + 1]; + var uRight = knotVector[span + j + k + 1]; + if (n[j + 1] == 0.0) + { + n[j] = saved; + saved = 0.0; + } + else + { + var temp = n[j + 1] / (uRight - uLeft); + n[j] = saved + (uRight - param) * temp; + saved = (param - uLeft) * temp; + } + } + } + + return n[0]; + } + + + /// + /// Compute derivatives of basis function 'Nip'. + /// + /// + /// + /// + /// + /// + /// + /// + public static double[] DersOneBasisFun( + int p, + int m, + IList knotVector, + int i, + double u, + int n) + { + // TODO: Check unused m parameter. + var ders = new double[n + 1]; + if (u < knotVector[i] || u >= knotVector[i + p + 1]) + { + for (var k = 0; k <= n; k++) + ders[k] = 0.0; + return ders; + } + + var tmpN = new double[p + 1, p + 1]; + for (var j = 0; j <= p; j++) + { + if (u >= knotVector[i + j] && u < knotVector[i + j + 1]) + tmpN[j, 0] = 1.0; + } + + for (var k = 1; k <= p; k++) + { + double saved; + if (tmpN[0, k - 1] == 0.0) + saved = 0.0; + else + { + saved = (u - knotVector[i]) * tmpN[0, k - 1] + / (knotVector[i + k] - knotVector[i]); + } + + for (var j = 0; j < p - k + 1; j++) + { + var uLeft = knotVector[i + j + 1]; + var uRight = knotVector[i + j + k + 1]; + if (tmpN[j + 1, k - 1] == 0.0) + { + tmpN[j, k] = saved; + saved = 0.0; + } + else + { + var temp = tmpN[j + 1, k - 1] / (uRight - uLeft); + tmpN[j, k] = saved + (uRight - u) * temp; + saved = (u - uLeft) * temp; + } + } + } + + ders[0] = tmpN[0, p]; + + var tempDers = new double[n + 1]; + for (var k = 1; k <= n; k++) + { + for (var j = 0; j <= k; j++) + tempDers[j] = tmpN[j, p - k]; + + for (var jj = 1; jj <= k; jj++) + { + double saved; + if (tempDers[0] == 0.0) + saved = 0.0; + else + saved = tempDers[0] / (knotVector[i + p - k + jj] - knotVector[i]); + for (var j = 0; j < k - jj + 1; j++) + { + var uLeft = knotVector[i + j + 1]; + var uRight = knotVector[i + j + p + jj + 1]; + if (tempDers[j + 1] == 0.0) + { + tempDers[j] = (p - k + jj) * saved; + saved = 0.0; + } + else + { + var temp = tempDers[j + 1] / (uRight - uLeft); + tempDers[j] = (p - k + jj) * (saved - temp); + saved = temp; + } + } + } + + ders[k] = tempDers[0]; + } + + return ders; + } + + + /// + /// Computes a point on a nurbs curve. + /// + /// + /// + /// + /// + /// + /// + public static Point3d CurvePoint( + int n, + int p, + IList knotVector, + IList controlPoints, + double u) + { + var span = FindSpan(n, p, u, knotVector); + var basisFunctions = BasisFunctions(span, u, p, knotVector); + var c = Point3d.Unset; + for (var i = 0; i <= p; i++) + c += basisFunctions[i] * controlPoints[span - p + i]; + return c; + } + + + /// + /// Computes the derivatives of a rational b-spline curve at the specified parameter. + /// Algorithm A3.2 of 'The Nurbs Book' + /// + /// Control point count + 1 + /// Degree + /// The curve's knot vector. + /// The curve's control points + /// Parameter to compute derivatives at. + /// Number of derivatives to compute. + /// + public static Vector3d[] CurveDerivsAlg1( + int n, + int p, + IList knotVector, + IList controlPoints, + double u, + int d) + { + var ck = new Vector3d[d + 1]; + var du = Math.Min(d, p); + for (var k = p + 1; k <= d; k++) + ck[k] = new Vector3d(); + var span = FindSpan(n, p, u, knotVector); + var nDerivatives = DerivativeBasisFunctions(span, u, p, du, knotVector); + for (var k = 0; k <= du; k++) + { + ck[k] = new Vector3d(); + for (var j = 0; j <= p; j++) + ck[k] += nDerivatives[k, j] * ( Vector3d ) controlPoints[span - p + j]; + } + + return ck; + } + + + /// + /// Computes the derivatives of a non-rational b-spline curve at the specified parameter. + /// Algorithm A3.2 of 'The Nurbs Book' modified to accept instances.. + /// + /// Control point count + 1 + /// Degree + /// The curve's knot vector. + /// The curve's control points + /// Parameter to compute derivatives at. + /// Number of derivatives to compute. + /// + public static Point4d[] CurveDerivsAlg1( + int n, + int p, + IList knotVector, + IList controlPoints, + double u, + int d) + { + var ck = new Point4d[d + 1]; + var du = Math.Min(d, p); + for (var k = p + 1; k <= d; k++) + ck[k] = new Point4d(); + var span = FindSpan(n, p, u, knotVector); + var nDerivatives = DerivativeBasisFunctions(span, u, p, du, knotVector); + for (var k = 0; k <= du; k++) + { + ck[k] = new Point4d(); + for (var j = 0; j <= p; j++) + ck[k] += nDerivatives[k, j] * controlPoints[span - p + j]; + } + + return ck; + } + + + /// + /// Computes the control points of all derivative curves up to and including the dth derivative. + /// Algorithm A3.3 of 'The Nurbs Book' + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Point3d[,] CurveDerivCpts( + int n, + int p, + IList knotVector, + IList controlPoints, + int d, + int r1, + int r2) + { + var r = r2 - r1; + var pk = new Point3d[d + 1, r]; + for (var i = 0; i <= r; i++) + pk[0, i] = controlPoints[r1 + i]; + + for (var k = 1; k <= d; k++) + { + var tmp = p - k + 1; + for (var i = 0; i <= r - k; i++) + { + pk[k, i] = tmp * ( Point3d ) (pk[k - 1, i + 1] - pk[k - 1, i]) / + (knotVector[r1 + i + p + 1] - knotVector[r1 + i + k]); + } + } + + return pk; + } + + + /// + /// Computes the derivatives of a rational b-spline curve at the specified parameter. + /// Algorithm A3.4 of 'The Nurbs Book' + /// + /// Control point count + 1 + /// Degree + /// The curve's knot vector. + /// The curve's control points + /// Parameter to compute derivatives at. + /// Number of derivatives to compute. + /// The point on a B- spline curve and all derivatives up to and including the dth derivative at a fixed u value. + public static Vector3d[] CurveDerivsAlg2( + int n, + int p, + IList knotVector, + IList controlPoints, + double u, + int d) + { + var du = Math.Min(d, p); + var ck = new Vector3d[d + 1]; + for (var k = p + 1; k <= d; k++) + ck[k] = new Vector3d(); + var span = FindSpan(n, p, u, knotVector); + var basisFunctions = AllBasisFuns(span, u, p, knotVector); + var pk = CurveDerivCpts( + n, + p, + knotVector, + controlPoints, + du, + span - p, + span); + for (var k = 0; k <= du; k++) + { + ck[k] = new Vector3d(); + for (var j = 0; j <= p - k; j++) + ck[k] += basisFunctions[j, p - k] * ( Vector3d ) pk[k, j]; + } + + return ck; + } + + + /// + /// Computes a point on a nurbs surface. + /// Algorithm A3.5 of 'The Nurbs Book' + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Point3d SurfacePoint( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + double u, + double v) + { + var uspan = FindSpan(n, p, u, knotVectorU); + var nU = BasisFunctions(uspan, u, p, knotVectorU); + var vspan = FindSpan(m, q, v, knotVectorV); + var nV = BasisFunctions(vspan, v, q, knotVectorV); + var uind = uspan - p; + var surfPt = Point3d.Unset; + for (var l = 0; l <= q; l++) + { + var temp = Point3d.Unset; + var vind = vspan - q - l; + for (var k = 0; k <= p; k++) + temp += nU[k] * controlPoints[uind + k, vind]; + surfPt += nV[l] * temp; + } + + return surfPt; + } + + + /// + /// Computes the derivatives of at S(u,v) k times with respect to u and l times with respect to v. + /// Algorithm A3.6 of 'The Nurbs Book' + /// + /// Control point count - 1 in the U direction. + /// Degree in the U direction. + /// Knot vector in the U direction. + /// Control point count - 1 in the V direction. + /// Degree in the V direction. + /// Knot vector in the V direction. + /// Control point grid of the surface. + /// U parameter to get derivatives at. + /// V parameter to get derivatives at. + /// Number of derivatives to compute in each direction. + /// A multi dimensional array where [k,l] represents the derivative of S(u,v) with respect to u 'k' times, and v 'l' times. + public static Point3d[,] SurfaceDerivsAlg1( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + double u, + double v, + int derivCount) + { + var skl = new Point3d[derivCount + 1, derivCount + 1]; + var du = Math.Min(derivCount, p); + for (var k = p + 1; k <= derivCount; k++) + { + for (var l = 0; l <= derivCount - k; l++) + skl[k, l] = Point3d.Unset; + } + + var dv = Math.Min(derivCount, q); + for (var l = q + 1; l <= derivCount; l++) + { + for (var k = 0; k <= derivCount - 1; k++) + skl[k, l] = Point3d.Unset; + } + + var uSpan = FindSpan(n, p, u, knotVectorU); + var nU = DerivativeBasisFunctions(uSpan, u, p, du, knotVectorU); + var vSpan = FindSpan(m, q, v, knotVectorV); + var nV = DerivativeBasisFunctions(vSpan, v, q, dv, knotVectorV); + + for (var k = 0; k <= du; k++) + { + var temp = new Point3d[q]; + for (var s = 0; s <= q; s++) + { + temp[s] = Point3d.Unset; + for (var r = 0; r <= p; r++) + temp[s] += nU[k, r] * controlPoints[uSpan - p + r, vSpan - q + s]; + + var dd = Math.Min(derivCount - k, dv); + + for (var l = 0; l <= dd; l++) + { + skl[k, l] = Point3d.Unset; + + // TODO: Check ss, this was changed from 's' for naming conflicts but it might have been on purpose. + for (var ss = 0; ss <= q; ss++) + skl[k, l] += nV[l, ss] * temp[ss]; + } + } + } + + return skl; + } + + + /// + /// Computes the derivatives of at S(u,v) k times with respect to u and l times with respect to v. + /// Algorithm A3.6 of 'The Nurbs Book' modified to accept instances. + /// + /// Control point count - 1 in the U direction. + /// Degree in the U direction. + /// Knot vector in the U direction. + /// Control point count - 1 in the V direction. + /// Degree in the V direction. + /// Knot vector in the V direction. + /// Control point grid of the surface. + /// U parameter to get derivatives at. + /// V parameter to get derivatives at. + /// Number of derivatives to compute in each direction. + /// A multi dimensional array where [k,l] represents the derivative of S(u,v) with respect to u 'k' times, and v 'l' times. + public static Point4d[,] SurfaceDerivsAlg1( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + double u, + double v, + int derivCount) + { + var skl = new Point4d[derivCount + 1, derivCount + 1]; + var du = Math.Min(derivCount, p); + for (var k = p + 1; k <= derivCount; k++) + { + for (var l = 0; l <= derivCount - k; l++) + skl[k, l] = new Point4d(); + } + + var dv = Math.Min(derivCount, q); + for (var l = q + 1; l <= derivCount; l++) + { + for (var k = 0; k <= derivCount - 1; k++) + skl[k, l] = new Point4d(); + } + + var uSpan = FindSpan(n, p, u, knotVectorU); + var nU = DerivativeBasisFunctions(uSpan, u, p, du, knotVectorU); + var vSpan = FindSpan(m, q, v, knotVectorV); + var nV = DerivativeBasisFunctions(vSpan, v, q, dv, knotVectorV); + + for (var k = 0; k <= du; k++) + { + var temp = new Point4d[q + 1]; + for (var index = 0; index < temp.Length; index++) + temp[index] = new Point4d(); + + for (var s = 0; s <= q; s++) + { + temp[s] = new Point4d(); + for (var r = 0; r <= p; r++) + temp[s] += nU[k, r] * controlPoints[uSpan - p + r, vSpan - q + s]; + + var dd = Math.Min(derivCount - k, dv); + + for (var l = 0; l <= dd; l++) + { + skl[k, l] = new Point4d(); + + // TODO: Check ss, this was changed from 's' for naming conflicts but it might have been on purpose. + for (var ss = 0; ss <= q; ss++) + skl[k, l] += nV[l, ss] * temp[ss]; + } + } + } + + return skl; + } + + + /// + /// Algorithm A3.7 of 'The Nurbs Book' + /// + /// Control point count - 1 in the U direction. + /// Degree in the U direction. + /// Knot vector in the U direction. + /// Control point count - 1 in the V direction. + /// Degree in the V direction. + /// Knot vector in the V direction. + /// Control point grid of the surface. + /// Number of derivatives to compute. + /// + /// + /// + /// + /// Computed derivatives at S(u,v). + public static Point3d[][][][] SurfaceDerivCpts( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + int d, + int r1, + int r2, + int s1, + int s2) + { + var pkl = new Point3d[d][][][]; + + var du = Math.Min(d, p); + var dv = Math.Min(d, q); + var r = r2 - r1; + var s = s2 - s1; + + for (var j = s1; j <= s2; j++) + { + var temp = CurveDerivCpts( + n, + p, + knotVectorU, + controlPoints.Column(j), + du, + r1, + r2); + for (var k = 0; k <= du; k++) + { + for (var i = 0; i <= r - k; i++) + pkl[k][0][i][j - s1] = temp[k, i]; + } + } + + for (var k = 0; k <= du; k++) + { + for (var i = 0; i <= r - k; i++) + { + var dd = Math.Min(d - k, dv); + var temp = CurveDerivCpts( + m, + q, + knotVectorV, + pkl[k][0][i], + dd, + 0, + s); + for (var l = i; k <= dd; k++) + { + for (var j = 0; j <= s - l; j++) + pkl[k][l][i][j] = temp[l, j]; + } + } + } + + return pkl; + } + + + /// + /// Algorithm A3.8 of 'The Nurbs Book' + /// + /// Control point count - 1 in the U direction. + /// Degree in the U direction. + /// Knot vector in the U direction. + /// Control point count - 1 in the V direction. + /// Degree in the V direction. + /// Knot vector in the V direction. + /// Control point grid of the surface. + /// U parameter to get derivatives at. + /// V parameter to get derivatives at. + /// Number of derivatives to compute in each direction. + /// A multi dimensional array where [k,l] represents the derivative of S(u,v) with respect to u 'k' times, and v 'l' times. + public static Point3d[,] SurfaceDerivsAlg2( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + double u, + double v, + int d) + { + var skl = new Point3d[d + 1, d + 1]; + + var du = Math.Min(d, p); + for (var k = p + 1; k <= d; k++) + { + for (var l = 0; l <= d - k; l++) + skl[k, l] = Point3d.Unset; + } + + var dv = Math.Min(d, q); + for (var l = q + 1; l <= d; l++) + { + for (var k = 0; k <= d - l; k++) + skl[k, l] = Point3d.Unset; + } + + var uSpan = FindSpan(n, p, u, knotVectorU); + var nV = AllBasisFuns(uSpan, u, p, knotVectorU); + var vSpan = FindSpan(m, q, v, knotVectorV); + var nU = AllBasisFuns(vSpan, v, q, knotVectorV); + var pkl = SurfaceDerivCpts( + n, + p, + knotVectorU, + m, + q, + knotVectorV, + controlPoints, + d, + uSpan - p, + uSpan, + vSpan - q, + vSpan); + + for (var k = 0; k <= du; k++) + { + var dd = Math.Min(d - k, dv); + for (var l = 0; l <= dd; l++) + { + skl[k, l] = Point3d.Unset; + for (var i = 0; i <= q - l; i++) + { + var tmp = Point3d.Unset; + for (var j = 0; j <= p - k; j++) + tmp += nV[j, p - k] * pkl[k][l][j][i]; + skl[k, l] += nU[i, q - l] * tmp; + } + } + } + + return skl; + } + + + /// + /// Computes a point on a nurbs curve + /// Algorithm A4.1 of 'The Nurbs Book' + /// + /// Number of control points - 1 + /// Degree. Cannot be bigger or equals the number of control points, nor smaller than 1. + /// List of numbers representing the knot vector of the curve. + /// The control points for the curve. + /// Parameter along the curve to compute the point at. + /// A point along the curve. + public static Point3d CurvePoint( + int n, + int p, + IList knotVector, + IList controlPoints, + double u) + { + var span = FindSpan(n, p, u, knotVector); + var basisFunctions = BasisFunctions(span, u, p, knotVector); + var cw = new Point4d(); + for (var j = 0; j <= p; j++) + cw += basisFunctions[j] * controlPoints[span - p + j]; + return ( Point3d ) cw; + } + + + /// + /// Computes a binomial coefficient of a NURBS curve. + /// + /// + /// + /// Computed coefficient. + public static double BinomialCoefficient(int n, int k) + { + if (n - k == 1 || k == 1) + return n; + + var b = new double[n + 1, n - k + 1]; + for (var i = 1; i < b.GetLength(0); i++) + { + for (var j = 0; j < b.GetLength(1); j++) + { + if (i == j || j == 0) + b[i, j] = 1; + else if (j == 1 || i - j == 1) + b[i, j] = i; + else + b[i, j] = b[i - 1, j - 1] + b[i - 1, j]; + } + } + + return b[n, n - k]; + } + + + /// + /// Determines if a knot vector is clamped. + /// + /// Knot vector. + /// Degree. + /// True if knot vector is clamped. + public static bool IsClamped(IList u, int p) => + u[0] == u[p] && u[u.Count - 1] == u[u.Count - 1 - p]; + + + /// + /// Compute C(u) derivatives from Cw(u) derivatives. + /// Algorithm A4.2 of 'The Nurbs Book' + /// + /// Position derivatives. + /// Weight derivatives. + /// Derivative count. + /// Computed derivatives. + public static Vector3d[] RatCurveDerivs(IList aDers, IList wDers, int d) + { + var ders = new Vector3d[d + 1]; + + for (var k = 0; k <= d; k++) + { + var v = aDers[k]; + for (var i = 1; i <= k; i++) + v -= BinomialCoefficient(k, i) * wDers[i] * ders[k - i]; + + ders[k] = v / wDers[0]; + } + + return ders; + } + + + public static Vector3d[] NurbsCurveDerivs( + int n, + int p, + IList knotVector, + IList controlPoints, + double u, + int d) + { + var ck1 = CurveDerivsAlg1(n, p, knotVector, controlPoints, u, d); + var aDers = ck1.Select(der => ( Vector3d ) der.Position).ToList(); + var wDers = ck1.Select(der => der.Weight).ToList(); + return RatCurveDerivs(aDers, wDers, d); + } + + + /// + /// Compute a point on a nurbs surface. + /// Algorithm A4.3 of 'The Nurbs Book' + /// + /// The number of control points in the U direction - 1 + /// The degree of the surface in the U direction. + /// The knot vector for the U direction. + /// The number of control points in the V direction - 1 + /// The degree of the surface in the V direction. + /// The knot vector for the V direction. + /// A 2-dimensional matrix/grid of control points. + /// Parameter to compute the point at in the U direction. + /// Parameter to compute the point at in the V direction. + /// A instance with the result. + public static Point3d SurfacePoint( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + double u, + double v) + { + var uspan = FindSpan(n, p, u, knotVectorU); + var nU = BasisFunctions(uspan, u, p, knotVectorU); + var vspan = FindSpan(m, q, v, knotVectorV); + var nV = BasisFunctions(vspan, v, q, knotVectorV); + + var temp = new Point4d[q + 1]; + for (var l = 0; l <= q; l++) + { + temp[l] = new Point4d(); + for (var k = 0; k <= p; k++) + temp[l] += nU[k] * controlPoints[uspan - p + k, vspan - q + l]; + } + + var sW = new Point4d(); + for (var l = 0; l <= q; l++) + sW += nV[l] * temp[l]; + + return ( Point3d ) sW; + } + + + /// + /// Computes the derivatives of a non-rational b-spline curve from the decomposed derivatives of a rational surface. + /// Algorithm A4.4 of 'The Nurbs Book' + /// + /// Position derivatives + /// Weight derivatives + /// Derivative count. + /// Computed derivatives at S(u,v) + public static Matrix RatSurfaceDerivs( + Matrix aDers, + Matrix wDers, + int d) + { + var skl = new Matrix(wDers.M, wDers.N); + for (var m = 0; m < wDers.M; m++) + { + for (var n = 0; n < wDers.N; n++) + skl[m, n] = new Vector3d(); + } + + var a = wDers.M - 1; + var b = wDers.N - 1; + + for (var k = 0; k <= a; k++) + { + for (var l = 0; l <= b; l++) + { + var v = aDers[k, l]; + for (var j = 0; j <= l; j++) + v -= BinomialCoefficient(l, j) * wDers[0, j] * skl[k, l - j]; + + for (var i = 0; i <= k; i++) + { + v -= BinomialCoefficient(k, i) * wDers[i, 0] * skl[k - i, l]; + var v2 = new Vector3d(); + for (var j = 0; j <= l; j++) + v2 += BinomialCoefficient(l, j) * wDers[i, j] * skl[k - i, l - j]; + + v -= BinomialCoefficient(k, i) * v2; + } + + skl[k, l] = v / wDers[0, 0]; + } + } + + return skl; + } + + + /// + /// Compute C(u) derivatives from Cw(u) derivatives. + /// + /// The number of control points in the U direction - 1 + /// The degree of the surface in the U direction. + /// The knot vector for the U direction. + /// The number of control points in the V direction - 1 + /// The degree of the surface in the V direction. + /// The knot vector for the V direction. + /// A 2-dimensional matrix/grid of control points. + /// U parameter. + /// V parameter. + /// Derivative count. + /// Computed derivatives. + public static Matrix NurbsSurfaceDerivs( + int n, + int p, + IList knotVectorU, + int m, + int q, + IList knotVectorV, + Matrix controlPoints, + double u, + double v, + int d) + { + var skl1 = SurfaceDerivsAlg1( + n, + p, + knotVectorU, + m, + q, + knotVectorV, + controlPoints, + u, + v, + d); + + var aDers = new Matrix(skl1.GetLength(0) + 1, skl1.GetLength(1) + 1); + var wDers = new Matrix(skl1.GetLength(0) + 1, skl1.GetLength(1) + 1); + + for (var i = 0; i < controlPoints.M - 1; i++) + { + for (var j = 0; j < controlPoints.N - 1; j++) + { + wDers[i, j] = controlPoints[i, j].Weight; + aDers[i, j] = controlPoints[i, j].Position; + } + } + + return RatSurfaceDerivs(aDers, wDers, d); + } + } +} \ No newline at end of file diff --git a/src/Geometry/3D/NurbsCurve.cs b/src/Geometry/3D/NurbsCurve.cs new file mode 100644 index 0000000..a940d35 --- /dev/null +++ b/src/Geometry/3D/NurbsCurve.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Paramdigma.Core.Geometry +{ + /// + /// + public class NurbsCurve : BaseCurve + { + /// + /// The control points of the nurbs curve. + /// + public List ControlPoints; + + /// + /// The degree of the curve. + /// + public int Degree; + + /// + /// The nurbs curve knot vector. + /// + public List Knots; + + + /// + /// Initializes a new instance of by it's control points and degree. + /// + /// The control points to create the curve with. + /// The desired degree of the curve. Degree cannot be > (ControlPoints - 1) + public NurbsCurve(List controlPoints, int degree) + { + this.ControlPoints = controlPoints; + this.Knots = NurbsCalculator.CreateUniformKnotVector(controlPoints.Count, degree) + .ToList(); + this.Degree = degree; + } + + + /// + /// Gets the count of the control points - 1. + /// + private int N => this.ControlPoints.Count - 1; + + /// + /// The start point of the curve. + /// + public Point3d StartPoint => this.PointAt(this.Domain.Start); + + /// + /// The end point of the curve. + /// + public Point3d EndPoint => this.PointAt(this.Domain.End); + + /// + /// The tangent vector at the start of the curve. + /// + public Vector3d StartTangent => this.TangentAt(this.Domain.Start); + + /// + /// The tangent vector at the end of the curve. + /// + public Vector3d EndTangent => this.TangentAt(this.Domain.End); + + + /// + /// Computes the specific amount of derivatives on the specified parameter. + /// + /// Parameter to compute derivatives at. + /// Number of derivatives to compute. + /// Array containing the + private IList DerivativesAt(double t, int count) => + NurbsCalculator.NurbsCurveDerivs( + this.N, + this.Degree, + this.Knots, + this.ControlPoints, + t, + count + ); + + + /// + public override Point3d PointAt(double t) => + NurbsCalculator.CurvePoint(this.N, this.Degree, this.Knots, this.ControlPoints, t); + + + /// + public override Vector3d TangentAt(double t) => this.DerivativesAt(t, 1)[1].Unit(); + + + /// + public override Vector3d NormalAt(double t) => this.DerivativesAt(t, 2)[2].Unit(); + + + /// + public override Vector3d BinormalAt(double t) => this.DerivativesAt(t, 3)[3].Unit(); + + + /// + public override Plane FrameAt(double t) + { + var ders = this.DerivativesAt(t, 3); + return new Plane(( Point3d ) ders[0], ders[1], ders[2], ders[3]); + } + + + /// + public override bool CheckValidity() => true; + + + /// + protected override double ComputeLength() => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Geometry/3D/NurbsSurface.cs b/src/Geometry/3D/NurbsSurface.cs new file mode 100644 index 0000000..6c34225 --- /dev/null +++ b/src/Geometry/3D/NurbsSurface.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Paramdigma.Core.Collections; +using Paramdigma.Core.Geometry.Interfaces; + +namespace Paramdigma.Core.Geometry +{ + /// + /// Represents a NURBS surface. Contains properties and methods for operating with NURBS surfaces. + /// + public class NurbsSurface : ISurface + { + /// + /// The surface's grid of control points. + /// + public readonly Matrix ControlPoints; + + /// + /// Surface's degree in the U direction + /// + public readonly int DegreeU; + + /// + /// Surface's degree in the V direction + /// + public readonly int DegreeV; + + /// + /// Knot vector in the U direction. + /// + public readonly List KnotsU; + + /// + /// Knot vector in the V direction. + /// + public readonly List KnotsV; + + + /// + /// Initializes a new instance of by it's control points and degrees. + /// + /// Grid of control points for the surface. + /// Degree of the surface in the U direction. + /// Degree of the surface in the V direction. + public NurbsSurface(Matrix controlPoints, int degreeU, int degreeV) + { + this.ControlPoints = controlPoints; + + this.DegreeU = degreeU; + this.DegreeV = degreeV; + + this.KnotsU = NurbsCalculator + .CreateUniformKnotVector(this.ControlPoints.N, this.DegreeU) + .ToList(); + this.KnotsV = NurbsCalculator + .CreateUniformKnotVector(this.ControlPoints.M, this.DegreeV) + .ToList(); + } + + + /// + public Interval DomainU => new Interval(this.KnotsU[0], this.KnotsU[this.KnotsU.Count - 1]); + + /// + public Interval DomainV => new Interval(this.KnotsV[0], this.KnotsV[this.KnotsV.Count - 1]); + + + /// + public Point3d PointAt(double u, double v) => NurbsCalculator.SurfacePoint( + this.ControlPoints.N - 1, + this.DegreeU, + this.KnotsU, + this.ControlPoints.M - 1, + this.DegreeV, + this.KnotsV, + this.ControlPoints, + u, + v); + + + /// + public Vector3d NormalAt(double u, double v) => throw new NotImplementedException(); + + + /// + public Plane FrameAt(double u, double v) => throw new NotImplementedException(); + + + /// + public double DistanceTo(Point3d point) => throw new NotImplementedException(); + + + /// + public Point3d ClosestPointTo(Point3d point) => throw new NotImplementedException(); + + + /// + /// Creates a square flat surface on the XY Plane. + /// + /// Dimension interval on the X direction. + /// Dimension interval on the Y direction. + /// Number of points on the X direction. + /// Number of points on the Y direction. + /// Flat nurbs surface. + public static NurbsSurface CreateFlatSurface( + Interval xDimension, + Interval yDimension, + int xCount, + int yCount) + { + var rnd = new Random(); + var wDomain = new Interval(1, 5); + var m = new Matrix(xCount, yCount); + for (var i = 0; i < xCount; i++) + { + for (var j = 0; j < yCount; j++) + { + m[i, j] = new Point4d( + xDimension.RemapFromUnit(( double ) i / xCount), + yDimension.RemapFromUnit(( double ) j / yCount), + 0, + 1); + } + } + + var degreeU = xCount <= 3 ? xCount - 1 : 3; + var degreeV = yCount <= 3 ? yCount - 1 : 3; + return new NurbsSurface(m, degreeU, degreeV); + } + + + /// + /// Computes the derivatives at at S(u,v). + /// + /// U parameter to compute at. + /// V parameter to compute at. + /// Number of derivatives to compute. + /// Computed derivatives. + public Matrix DerivativesAt(double u, double v, int count) => + NurbsCalculator.NurbsSurfaceDerivs( + this.ControlPoints.M - 1, + this.DegreeU, + this.KnotsU, + this.ControlPoints.M - 1, + this.DegreeV, + this.KnotsV, + this.ControlPoints, + u, + v, + count); + } +} \ No newline at end of file diff --git a/src/Geometry/3D/Plane.cs b/src/Geometry/3D/Plane.cs index f48da6a..fec1132 100644 --- a/src/Geometry/3D/Plane.cs +++ b/src/Geometry/3D/Plane.cs @@ -15,7 +15,12 @@ public class Plane /// /// Plane to copy values from. public Plane(Plane plane) - : this(new Point3d(plane.Origin), new Vector3d(plane.XAxis), new Vector3d(plane.YAxis), new Vector3d(plane.ZAxis)) { } + : this( + new Point3d(plane.Origin), + new Vector3d(plane.XAxis), + new Vector3d(plane.YAxis), + new Vector3d(plane.ZAxis)) { } + /// /// Initializes a new instance of the class given it's origin at the specified point. @@ -24,6 +29,7 @@ public Plane(Plane plane) public Plane(Point3d origin) : this(origin, Vector3d.UnitX, Vector3d.UnitY) { } + /// /// Initializes a new instance of the class given a point and two vectors. /// Vectors do not necessarily have to be perpendicular. @@ -35,6 +41,7 @@ public Plane(Point3d origin) public Plane(Point3d origin, Vector3d xAxis, Vector3d yAxis) : this(origin, xAxis, yAxis, xAxis.Cross(yAxis)) { } + /// /// Initializes a new instance of the class given a point and three vectors. /// Will throw an error if vectors are not perpendicular to each other. @@ -51,6 +58,7 @@ public Plane(Point3d origin, Vector3d xAxis, Vector3d yAxis, Vector3d zAxis) this.ZAxis = zAxis; } + /// /// Initializes a new instance of the class given 3 non co-linear points. /// @@ -77,66 +85,61 @@ public Plane(Point3d ptA, Point3d ptB, Point3d ptC) this.ZAxis = normal; } + /// /// Gets or sets the plane origin. /// /// - public Point3d Origin - { - get; - set; - } + public Point3d Origin { get; set; } /// /// Gets or sets the plane X axis. /// /// - public Vector3d XAxis - { - get; - set; - } + public Vector3d XAxis { get; set; } /// /// Gets or sets the plane Y axis. /// /// - public Vector3d YAxis - { - get; - set; - } + public Vector3d YAxis { get; set; } /// /// Gets or sets the plane Z axis. /// /// - public Vector3d ZAxis - { - get; - set; - } + public Vector3d ZAxis { get; set; } /// /// Gets plane with axis' UnitX and UnitY. /// /// - public static Plane WorldXY => new Plane(Point3d.WorldOrigin, Vector3d.UnitX, Vector3d.UnitY); + public static Plane WorldXY => new Plane( + Point3d.WorldOrigin, + Vector3d.UnitX, + Vector3d.UnitY); /// /// Gets plane with axis' UnitX and UnitZ. /// /// - public static Plane WorldXZ => new Plane(Point3d.WorldOrigin, Vector3d.UnitX, Vector3d.UnitZ); + public static Plane WorldXZ => new Plane( + Point3d.WorldOrigin, + Vector3d.UnitX, + Vector3d.UnitZ); /// /// Gets plane with axis' UnitY and UnitZ. /// /// - public static Plane WorldYZ => new Plane(Point3d.WorldOrigin, Vector3d.UnitY, Vector3d.UnitZ); + public static Plane WorldYZ => new Plane( + Point3d.WorldOrigin, + Vector3d.UnitY, + Vector3d.UnitZ); // TODO: Add utility methods to Plane class (flip Axis, relative coordinates...) + /// /// Flips the plane by interchanging the X and Y axis and negating the Z axis. /// @@ -148,6 +151,7 @@ public void Flip() this.ZAxis = -this.ZAxis; } + /// /// Computes the point at the specified Plane parameters. /// @@ -156,6 +160,7 @@ public void Flip() /// public Point3d PointAt(double u, double v) => this.PointAt(u, v, 0); + /// /// Computes a 3D point in the coordinate space of this plane. /// @@ -163,7 +168,9 @@ public void Flip() /// Coordinate for the Y axis. /// Coordinate for the Z axis. /// Computed point. - public Point3d PointAt(double u, double v, double w) => this.Origin + ((u * this.XAxis) + (v * this.YAxis) + (w * this.ZAxis)); + public Point3d PointAt(double u, double v, double w) => + this.Origin + (u * this.XAxis + v * this.YAxis + w * this.ZAxis); + /// /// Remap a given point to this plane's coordinate system. @@ -180,12 +187,15 @@ public Point3d RemapToPlaneSpace(Point3d point) return new Point3d(u, v, w); } + /// /// Remap a given point to the XY Plane coordiante system. /// /// Point to remap. /// Point with relative coordinates to the plane. - public Point3d RemapToWorldXYSpace(Point3d point) => this.Origin + (point.X * this.XAxis) + (point.Y * this.YAxis) + (point.Z * this.ZAxis); + public Point3d RemapToWorldXYSpace(Point3d point) => + this.Origin + point.X * this.XAxis + point.Y * this.YAxis + point.Z * this.ZAxis; + /// /// Project a point to the plane. @@ -201,6 +211,7 @@ public Point3d ClosestPoint(Point3d point) return this.PointAt(u, v); } + /// /// Compute the distance from a point to the plane. /// @@ -208,36 +219,45 @@ public Point3d ClosestPoint(Point3d point) /// Distance to point. public double DistanceTo(Point3d point) => (point - this.Origin).Dot(this.ZAxis); + /// /// Returns the parametric equation for this plane. /// /// List with equation values. public double[] GetPlaneEquation() => throw new NotImplementedException(); + /// /// Performs a deep copy of this plane. /// /// Plane clone. - public Plane Clone() => new Plane(new Point3d(this.Origin), new Vector3d(this.XAxis), new Vector3d(this.YAxis), new Vector3d(this.ZAxis)); + public Plane Clone() => new Plane( + new Point3d(this.Origin), + new Vector3d(this.XAxis), + new Vector3d(this.YAxis), + new Vector3d(this.ZAxis)); + + /// public override bool Equals(object obj) { if (!(obj is Plane)) return false; - var plane = (Plane)obj; + var plane = ( Plane ) obj; return plane.Origin == this.Origin && plane.XAxis == this.XAxis && plane.YAxis == this.YAxis; } + /// public override int GetHashCode() { unchecked { // Choose large primes to avoid hashing collisions - const int hashingBase = (int)2166136261; + const int hashingBase = ( int ) 2166136261; const int hashingMultiplier = 16777619; var hash = hashingBase; diff --git a/src/Geometry/3D/Point3d.cs b/src/Geometry/3D/Point3d.cs index 63ca23d..862b917 100644 --- a/src/Geometry/3D/Point3d.cs +++ b/src/Geometry/3D/Point3d.cs @@ -13,6 +13,7 @@ public class Point3d : BasePoint /// . public Point3d() { } + /// /// Initializes a new instance of the class by cartesian coordinates. /// @@ -23,6 +24,7 @@ public Point3d() { } public Point3d(double xCoord, double yCoord, double zCoord) : base(xCoord, yCoord, zCoord) { } + /// /// Initializes a new instance of the class from a 2-dimensional point. /// @@ -31,6 +33,7 @@ public Point3d(double xCoord, double yCoord, double zCoord) public Point3d(Point2d point) : base(point.X, point.Y, 0) { } + /// /// Initializes a new instance of the class. /// @@ -39,8 +42,10 @@ public Point3d(Point2d point) public Point3d(Point3d point) : base(point) { } + /// - /// Initializes a new instance of the class from a 4-dimensional point by dividing the cartesian + /// Initializes a new instance of the class from a 4-dimensional point by + /// dividing the cartesian /// coordinates by the weight. /// /// 4d point to convert. @@ -48,6 +53,7 @@ public Point3d(Point3d point) public Point3d(Point4d point) : this(point.X / point.Weight, point.Y / point.Weight, point.Z / point.Weight) { } + /// /// Gets a new Unset point. /// @@ -60,35 +66,47 @@ public Point3d(Point4d point) /// public static Point3d WorldOrigin => new Point3d(0, 0, 0); + /// /// Performs a deep clone of the point. /// /// Returns a copy of this point instance. public Point3d Clone() => new Point3d(this.X, this.Y, this.Z); + /// /// Gets the euclidean distance between this point and the provided one. /// /// Point. /// - public double DistanceTo(Point3d point) => Math.Sqrt(Math.Pow(point.X - this.X, 2) + Math.Pow(point.Y - this.Y, 2) + Math.Pow(point.Z - this.Z, 2)); + public double DistanceTo(Point3d point) => Math.Sqrt( + Math.Pow(point.X - this.X, 2) + Math.Pow(point.Y - this.Y, 2) + + Math.Pow(point.Z - this.Z, 2)); + /// public override bool Equals(object obj) => base.Equals(obj); + /// public override int GetHashCode() => base.GetHashCode(); + /// public override string ToString() => "Point3d" + base.ToString(); + /// /// Adds a vector to a point. /// /// Point. /// Vector. /// . - public static Point3d operator +(Point3d point, Vector3d v) => new Point3d(point.X + v.X, point.Y + v.Y, point.Z + v.Z); + public static Point3d operator +(Point3d point, Vector3d v) => new Point3d( + point.X + v.X, + point.Y + v.Y, + point.Z + v.Z); + /// /// Substracts a point from another point. @@ -96,7 +114,11 @@ public Point3d(Point4d point) /// Point A. /// Point B. /// . - public static Vector3d operator -(Point3d point, Point3d point2) => new Vector3d(point.X - point2.X, point.Y - point2.Y, point.Z - point2.Z); + public static Vector3d operator -(Point3d point, Point3d point2) => new Vector3d( + point.X - point2.X, + point.Y - point2.Y, + point.Z - point2.Z); + /// /// Substracts a vector from a point. @@ -104,7 +126,11 @@ public Point3d(Point4d point) /// Point. /// Vector. /// . - public static Vector3d operator -(Point3d point, Vector3d vector) => new Vector3d(point.X - vector.X, point.Y - vector.Y, point.Z - vector.Z); + public static Vector3d operator -(Point3d point, Vector3d vector) => new Vector3d( + point.X - vector.X, + point.Y - vector.Y, + point.Z - vector.Z); + /// /// Negates a point. @@ -117,13 +143,18 @@ public Point3d(Point4d point) point.Y != 0 ? -point.Y : 0, point.Z != 0 ? -point.Z : 0); + /// /// Multiplies a point with a number. /// /// Point. /// Number. /// . - public static Point3d operator *(Point3d point, double scalar) => new Point3d(point.X * scalar, point.Y * scalar, point.Z * scalar); + public static Point3d operator *(Point3d point, double scalar) => new Point3d( + point.X * scalar, + point.Y * scalar, + point.Z * scalar); + /// /// Multiplies a point with a number. @@ -131,7 +162,11 @@ public Point3d(Point4d point) /// Number. /// Point. /// . - public static Point3d operator *(double scalar, Point3d point) => new Point3d(point.X * scalar, point.Y * scalar, point.Z * scalar); + public static Point3d operator *(double scalar, Point3d point) => new Point3d( + point.X * scalar, + point.Y * scalar, + point.Z * scalar); + /// /// Divides a point with a number. @@ -139,7 +174,12 @@ public Point3d(Point4d point) /// Point. /// Number. /// . - public static Point3d operator /(Point3d point, double scalar) => new Point3d(point.X / scalar, point.Y / scalar, point.Z / scalar); + public static Point3d operator /(Point3d point, double scalar) => + new Point3d( + point.X / scalar, + point.Y / scalar, + point.Z / scalar); + /// /// Checks equality between two points. @@ -147,7 +187,9 @@ public Point3d(Point4d point) /// Point A. /// Point B. /// . - public static bool operator ==(Point3d point, Point3d point2) => point != null && point.Equals(point2); + public static bool operator ==(Point3d point, Point3d point2) => + point != null && point.Equals(point2); + /// /// Checks inequality between two points. @@ -155,26 +197,34 @@ public Point3d(Point4d point) /// Point A. /// Point B. /// . - public static bool operator !=(Point3d point, Point3d point2) => !point.Equals(point2); + public static bool operator !=(Point3d point, Point3d point2) => + !point?.Equals(point2) ?? true; + // Implicit conversions + /// /// Explicit conversion from vector to point. /// /// 3d Vector to convert. - public static explicit operator Point3d(Vector3d v) => new Point3d(v.X, v.Y, v.Z); + public static explicit operator Point3d(Vector3d v) => + new Point3d(v.X, v.Y, v.Z); + /// /// Implicit conversion from point to vector. /// /// 3d Point to convert. - public static implicit operator Vector3d(Point3d pt) => new Vector3d(pt.X, pt.Y, pt.Z); + public static implicit operator Vector3d(Point3d pt) => + new Vector3d(pt.X, pt.Y, pt.Z); + /// /// Explicit conversion from 4-dimensional point to 3-dimensional point. (X/W,Y/W,Z/W). /// /// 3d Point to convert. - public static explicit operator Point3d(Point4d point) => new Point3d(point); + public static explicit operator Point3d(Point4d point) => + new Point3d(point); } } \ No newline at end of file diff --git a/src/Geometry/3D/Point4d.cs b/src/Geometry/3D/Point4d.cs index a9f239c..868f140 100644 --- a/src/Geometry/3D/Point4d.cs +++ b/src/Geometry/3D/Point4d.cs @@ -9,14 +9,17 @@ public class Point4d : BasePoint { private double weight; + /// /// Initializes a new instance of the class. /// /// Point with all values to zero. public Point4d() => this.weight = 0; + /// - /// Initializes a new instance of the class by cartesian coordinates and weight. + /// Initializes a new instance of the class by cartesian coordinates and + /// weight. /// /// X Coordinate. /// Y Coordinate. @@ -27,8 +30,10 @@ public Point4d(double x, double y, double z, double w) : base(x, y, z) => this.weight = w; + /// - /// Initializes a new instance of the class from a 3-dimensional point and a weight. + /// Initializes a new instance of the class from a 3-dimensional point and a + /// weight. /// /// Point. /// Weight. @@ -37,8 +42,10 @@ public Point4d(Point3d pt, double w) : base(pt) => this.weight = w; + /// - /// Initializes a new instance of the class from a 3-dimensional point and a weight. + /// Initializes a new instance of the class from a 3-dimensional point and a + /// weight. /// /// Point. /// New 4-dimensional point with the specified values. @@ -46,6 +53,7 @@ public Point4d(Point3d pt) : base(pt) => this.weight = 1; + /// /// Gets or sets the weight of this point. /// @@ -65,47 +73,136 @@ public double Weight /// public Point3d Position => new Point3d(this.X, this.Y, this.Z); - /// - public static Point4d operator +(Point4d point, Point4d point2) => new Point4d(point.X + point2.X, point.Y + point2.Y, point.Z + point2.Z, point.Weight + point2.Weight); - /// - public static Point4d operator -(Point4d point, Point4d point2) => new Point4d(point.X - point2.X, point.Y - point2.Y, point.Z - point2.Z, point.Weight - point2.Weight); + /// + /// Adds to points together. + /// + /// First point to add. + /// Second point to add. + /// A new instance. + public static Point4d operator +(Point4d point, Point4d point2) => new Point4d( + point.X + point2.X, + point.Y + point2.Y, + point.Z + point2.Z, + point.Weight + point2.Weight); - /// - public static Point4d operator -(Point4d point) => new Point4d(-point.X, -point.Y, -point.Z, point.Weight); - /// - public static Point4d operator *(Point4d point, double scalar) => new Point4d(point.X * scalar, point.Y * scalar, point.Z * scalar, point.Weight * scalar); + /// + /// Subtracts one point from another. + /// + /// Point to subtract from. + /// Point to be subtracted. + /// A new instance. + public static Point4d operator -(Point4d point, Point4d point2) => new Point4d( + point.X - point2.X, + point.Y - point2.Y, + point.Z - point2.Z, + point.Weight - point2.Weight); - /// - public static Point4d operator *(double scalar, Point4d point) => new Point4d(point.X * scalar, point.Y * scalar, point.Z * scalar, point.Weight * scalar); - /// - public static Point4d operator /(Point4d point, double scalar) => new Point4d(point.X / scalar, point.Y / scalar, point.Z / scalar, point.Weight / scalar); + /// + /// Negates a point. + /// + /// Point to be negated. + /// A new instance. + public static Point4d operator -(Point4d point) => new Point4d( + -point.X, + -point.Y, + -point.Z, + point.Weight); - /// - public static bool operator ==(Point4d point, Point4d point2) => point.Equals(point2); - /// - public static bool operator !=(Point4d point, Point4d point2) => !point.Equals(point2); + /// + /// Multiply a point by a scalar. + /// + /// Point to be multiplied. + /// Number to multiply point with. + /// A new instance. + public static Point4d operator *(Point4d point, double scalar) => new Point4d( + point.X * scalar, + point.Y * scalar, + point.Z * scalar, + point.Weight * scalar); + + + /// + /// Multiply a point by a scalar. + /// + /// Number to multiply point with. + /// Point to be multiplied. + /// A new instance. + public static Point4d operator *(double scalar, Point4d point) => new Point4d( + point.X * scalar, + point.Y * scalar, + point.Z * scalar, + point.Weight * scalar); + + + /// + /// Divides a point by a scalar value. + /// + /// Point to divide. + /// Number to divide point with. + /// A new instance. + public static Point4d operator /(Point4d point, double scalar) => new Point4d( + point.X / scalar, + point.Y / scalar, + point.Z / scalar, + point.Weight / scalar); + + + /// + /// Equality check between two instances. + /// + /// Point to check equality from. + /// Point to check equality to. + /// True if points are equal. + public static bool operator ==(Point4d point, Point4d point2) => + point?.Equals(point2) ?? false; + + + /// + /// Inequality check between two instances. + /// + /// Point to check inequality from. + /// Point to check inequality to. + /// True if points are NOT equal. + public static bool operator !=(Point4d point, Point4d point2) => + !point?.Equals(point2) ?? true; + + + /// + /// Adds a vector and a point. + /// + /// A 4d point + /// A 3d vector + /// A new instance with the resulting coordinates. + public static Point4d operator +(Point4d point, Vector3d v) => new Point4d( + point.X + v.X, + point.Y + v.Y, + point.Z + v.Z, + point.Weight); - /// - public static Point4d operator +(Point4d point, Vector3d v) => new Point4d(point.X + v.X, point.Y + v.Y, point.Z + v.Z, point.Weight); /// public override bool Equals(object obj) { if (obj is Point4d pt) + { return base.Equals(obj) && Math.Abs(this.Weight - pt.Weight) < Settings.Tolerance; + } + return false; } + /// public override int GetHashCode() => // TODO: Non consistent getHashCode implementation base.GetHashCode() ^ this.weight.GetHashCode(); + // TODO: Add hasWeightedCoordinates boolean and implement a weightCoordinates() method } } \ No newline at end of file diff --git a/src/Geometry/3D/Polyline.cs b/src/Geometry/3D/Polyline.cs index 83f4673..e1d097d 100644 --- a/src/Geometry/3D/Polyline.cs +++ b/src/Geometry/3D/Polyline.cs @@ -16,6 +16,7 @@ public class Polyline : BaseCurve, IEnumerable private List segments; private bool segmentsNeedUpdate; + /// /// Initializes a new instance of the class. /// @@ -26,6 +27,7 @@ public Polyline() this.segmentsNeedUpdate = false; } + /// /// Initializes a new instance of the class from a list of points. /// @@ -37,6 +39,7 @@ public Polyline(List knots) this.RebuildSegments(); } + /// /// Gets the segment lines of the polyline. /// @@ -60,10 +63,7 @@ public List Segments /// /// Gets the list of knots for this polyline. /// - public List Knots - { - get; - } + public List Knots { get; } /// /// Gets a value indicating whether the polyline is closed (first point == last point). @@ -75,11 +75,16 @@ public List Knots /// public bool IsUnset => this.Knots.Count == 0; + /// - public IEnumerator GetEnumerator() => ((IEnumerable)this.Knots).GetEnumerator(); + public IEnumerator GetEnumerator() => + (( IEnumerable ) this.Knots).GetEnumerator(); + /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.Knots).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + (( IEnumerable ) this.Knots).GetEnumerator(); + /// /// Add a new knot vertex at the end of the polyline. @@ -91,6 +96,7 @@ public void AddKnot(Point3d knot) this.segmentsNeedUpdate = true; } + /// /// Add a new knot vertex at the specified index. /// @@ -102,6 +108,7 @@ public void InsertKnot(Point3d knot, int index) this.segmentsNeedUpdate = true; } + /// /// Delete a specific knot if it exists in the polyline. /// If the point exists multiple times, it will remove the first occurrence. @@ -115,6 +122,7 @@ public void RemoveKnot(Point3d knot) this.segmentsNeedUpdate = true; } + /// /// Delete a knot at a specific index. /// @@ -129,6 +137,7 @@ public void RemoveKnotAt(int index) this.segmentsNeedUpdate = true; } + private void RebuildSegments() { this.segments = new List(this.Knots.Count - 1); @@ -144,30 +153,45 @@ private void RebuildSegments() } } - /// - public override Vector3d BinormalAt(double t) => (from segment in this.segments - where segment.Domain.Contains(t) - select segment.BinormalAt(t)).FirstOrDefault(); /// - public override Vector3d NormalAt(double t) => (from segment in this.segments - where segment.Domain.Contains(t) - select segment.NormalAt(t)).FirstOrDefault(); + public override Vector3d BinormalAt(double t) => ( + from segment in this.segments + where segment.Domain.Contains(t) + select segment.BinormalAt(t)) + .FirstOrDefault(); + /// - public override Point3d PointAt(double t) => (from segment in this.segments - where segment.Domain.Contains(t) - select segment.PointAt(t)).FirstOrDefault(); + public override Vector3d NormalAt(double t) => ( + from segment in this.segments + where segment.Domain.Contains(t) + select segment.NormalAt(t)) + .FirstOrDefault(); + /// - public override Vector3d TangentAt(double t) => (from segment in this.segments + public override Point3d PointAt(double t) => ( + from segment in this.segments where segment.Domain.Contains(t) - select segment.TangentAt(t)).FirstOrDefault(); + select segment.PointAt(t)) + .FirstOrDefault(); + /// - public override Plane FrameAt(double t) => (from segment in this.segments - where segment.Domain.Contains(t) - select segment.FrameAt(t)).FirstOrDefault(); + public override Vector3d TangentAt(double t) => ( + from segment in this.segments + where segment.Domain.Contains(t) + select segment.TangentAt(t)) + .FirstOrDefault(); + + + /// + public override Plane FrameAt(double t) => ( + from segment in this.segments + where segment.Domain.Contains(t) + select segment.FrameAt(t)).FirstOrDefault(); + /// protected override double ComputeLength() @@ -177,8 +201,10 @@ protected override double ComputeLength() return length; } + /// - /// Checks the validity of the polyline. Currently only checks if some segments are collapsed (length == 0). + /// Checks the validity of the polyline. Currently only checks if some segments are collapsed + /// (length == 0). /// /// True if polyline has no collapsed segments. public override bool CheckValidity() diff --git a/src/Geometry/3D/Primitives/Box.cs b/src/Geometry/3D/Primitives/Box.cs index 6fe43ac..5fe3ae8 100644 --- a/src/Geometry/3D/Primitives/Box.cs +++ b/src/Geometry/3D/Primitives/Box.cs @@ -22,8 +22,10 @@ public Box(Plane plane, Interval domainX, Interval domainY, Interval domainZ) this.DomainZ = domainZ; } + /// - /// Initializes a new instance of the class from 2 corners. Both corners will form the diagonal of + /// Initializes a new instance of the class from 2 corners. Both corners will + /// form the diagonal of /// the box. /// /// Lower left corner point. @@ -36,51 +38,39 @@ public Box(Point3d lower, Point3d upper) this.DomainZ = new Interval(lower.Z, upper.Z); } + /// /// Gets or sets the box's base plane. /// /// . - public Plane Plane - { - get; - set; - } + public Plane Plane { get; set; } /// /// Gets or sets the box's X axis domain. /// /// . - public Interval DomainX - { - get; - set; - } + public Interval DomainX { get; set; } /// /// Gets or sets the box's Y axis domain. /// /// . - public Interval DomainY - { - get; - set; - } + public Interval DomainY { get; set; } /// /// Gets or sets the box's Z axis domain. /// /// . - public Interval DomainZ - { - get; - set; - } + public Interval DomainZ { get; set; } /// /// Gets the corner point with lowest values. /// /// . - public Point3d Min => new Point3d(this.DomainX.Start, this.DomainY.Start, this.DomainZ.Start); + public Point3d Min => new Point3d( + this.DomainX.Start, + this.DomainY.Start, + this.DomainZ.Start); /// /// Gets the corner point with highest values. @@ -92,6 +82,9 @@ public Interval DomainZ /// Gets the center point of the box. /// /// . - public Point3d Center => new Point3d(this.DomainX.RemapFromUnit(0.5), this.DomainY.RemapFromUnit(0.5), this.DomainZ.RemapFromUnit(0.5)); + public Point3d Center => new Point3d( + this.DomainX.RemapFromUnit(0.5), + this.DomainY.RemapFromUnit(0.5), + this.DomainZ.RemapFromUnit(0.5)); } } \ No newline at end of file diff --git a/src/Geometry/3D/Primitives/Cylinder.cs b/src/Geometry/3D/Primitives/Cylinder.cs index 1eb00b3..4856d0b 100644 --- a/src/Geometry/3D/Primitives/Cylinder.cs +++ b/src/Geometry/3D/Primitives/Cylinder.cs @@ -10,7 +10,8 @@ namespace Paramdigma.Core.Geometry public class Cylinder : ISurface { /// - /// Initializes a new instance of the class from it's individual components. + /// Initializes a new instance of the class from it's individual + /// components. /// /// The plane of the cylinder. /// The radius of the cylinder. @@ -31,35 +32,24 @@ public Cylinder(Plane plane, double radius, Interval domain) this.DomainV = Interval.Unit; } + /// /// Gets or sets the base plane of the cylinder. /// /// . - public Plane Plane - { - get; - set; - } + public Plane Plane { get; set; } /// /// Gets or sets the radius of the cylinder. /// /// . - public double Radius - { - get; - set; - } + public double Radius { get; set; } /// /// Gets or sets the height range of the cylinder. /// /// . - public Interval HeightRange - { - get; - set; - } + public Interval HeightRange { get; set; } /// /// Gets the cylinder height. @@ -67,18 +57,11 @@ public Interval HeightRange public double Height => this.HeightRange.Length; /// - public Interval DomainU - { - get; - set; - } + public Interval DomainU { get; set; } /// - public Interval DomainV - { - get; - set; - } + public Interval DomainV { get; set; } + /// public Plane FrameAt(double u, double v) @@ -87,6 +70,7 @@ public Plane FrameAt(double u, double v) throw new NotImplementedException(); } + /// /// Compute the distance from a point to this cylinder. /// @@ -94,6 +78,7 @@ public Plane FrameAt(double u, double v) /// Number representing the distance. public double DistanceTo(Point3d point) => throw new NotImplementedException(); + /// /// Compute the closes point of a point in this cylinder. /// @@ -101,6 +86,7 @@ public Plane FrameAt(double u, double v) /// Point3d instance of the closest point in the cylinder. public Point3d ClosestPointTo(Point3d point) => throw new NotImplementedException(); + /// public Vector3d NormalAt(double u, double v) { @@ -108,6 +94,7 @@ public Vector3d NormalAt(double u, double v) throw new NotImplementedException(); } + /// public Point3d PointAt(double u, double v) { @@ -122,6 +109,7 @@ public Point3d PointAt(double u, double v) return this.Plane.PointAt(x, y, z); } + private void CheckParameters(double u, double v) { if (!this.DomainU.Contains(u)) diff --git a/src/Geometry/3D/Primitives/Sphere.cs b/src/Geometry/3D/Primitives/Sphere.cs index 7af9a30..6447590 100644 --- a/src/Geometry/3D/Primitives/Sphere.cs +++ b/src/Geometry/3D/Primitives/Sphere.cs @@ -9,6 +9,12 @@ namespace Paramdigma.Core.Geometry /// public class Sphere : ISurface { + /// + /// Initializes a new instance of given it's base plane and radius. + /// + /// + /// + /// Throws when radius is smaller than 0. public Sphere(Plane plane, double radius) { if (Math.Abs(radius) < Settings.Tolerance) @@ -19,53 +25,49 @@ public Sphere(Plane plane, double radius) this.DomainV = Interval.Unit; } + + /// + /// Initializes a new instance of around the World origin with unit radius. + /// public Sphere() : this(Plane.WorldXY, 1) { } + /// /// Gets or sets the base plane of the sphere. /// /// . - public Plane Plane - { - get; - set; - } + public Plane Plane { get; set; } /// /// Gets or sets the radius of the sphere. /// /// . - public double Radius - { - get; - set; - } + public double Radius { get; set; } /// - public Interval DomainU - { - get; - set; - } + public Interval DomainU { get; set; } /// - public Interval DomainV - { - get; - set; - } + public Interval DomainV { get; set; } + /// - public double DistanceTo(Point3d point) => this.Plane.Origin.DistanceTo(point) - this.Radius; + public double DistanceTo(Point3d point) => + this.Plane.Origin.DistanceTo(point) - this.Radius; + /// - public Point3d ClosestPointTo(Point3d point) => this.Plane.Origin + ((point - this.Plane.Origin).Unit() * this.Radius); + public Point3d ClosestPointTo(Point3d point) => + this.Plane.Origin + (point - this.Plane.Origin).Unit() * this.Radius; + /// public Plane FrameAt(double u, double v) => throw new NotImplementedException(); + /// - public Vector3d NormalAt(double u, double v) => (this.PointAt(u, v) - this.Plane.Origin).Unit(); + public Vector3d NormalAt(double u, double v) => + (this.PointAt(u, v) - this.Plane.Origin).Unit(); /// @@ -80,6 +82,7 @@ public Point3d PointAt(double u, double v) return this.Plane.PointAt(x, y, z); } + /// /// Returns the closest point on the sphere as a 2D point containing it's UV coordinates. /// @@ -88,12 +91,13 @@ public Point3d PointAt(double u, double v) public Point2d ClosestParam(Point3d pt) { var rho = Math.Atan(pt.Y / pt.X); - var tau = Math.Atan(Math.Sqrt((pt.X * pt.X) + (pt.Y * pt.Y)) / pt.Z); + var tau = Math.Atan(Math.Sqrt(pt.X * pt.X + pt.Y * pt.Y) / pt.Z); var u = new Interval(0, 2 * Math.PI).RemapToUnit(rho); var v = new Interval(0, Math.PI).RemapToUnit(tau); return new Point2d(u, v); } + /// /// Computes the point at a specified parameter, provided as a instance. /// diff --git a/src/Geometry/3D/Primitives/Torus.cs b/src/Geometry/3D/Primitives/Torus.cs index 1d85855..6f0eeb6 100644 --- a/src/Geometry/3D/Primitives/Torus.cs +++ b/src/Geometry/3D/Primitives/Torus.cs @@ -22,62 +22,48 @@ public Torus(Plane plane, double majorRadius, double minorRadius) this.MinorRadius = minorRadius; } + /// /// Gets or sets the torus base plane. /// /// . - public Plane Plane - { - get; - set; - } + public Plane Plane { get; set; } /// /// Gets or sets the torus major radius. /// /// . - public double MajorRadius - { - get; - set; - } + public double MajorRadius { get; set; } /// /// Gets or sets the torus minor radius. /// /// . - public double MinorRadius - { - get; - set; - } + public double MinorRadius { get; set; } /// - public Interval DomainU - { - get; - set; - } + public Interval DomainU { get; set; } /// - public Interval DomainV - { - get; - set; - } + public Interval DomainV { get; set; } + /// public Plane FrameAt(double u, double v) => throw new NotImplementedException(); + /// public double DistanceTo(Point3d point) => throw new NotImplementedException(); + /// public Point3d ClosestPointTo(Point3d point) => throw new NotImplementedException(); + /// public Vector3d NormalAt(double u, double v) => throw new NotImplementedException(); + /// public Point3d PointAt(double u, double v) => throw new NotImplementedException(); } diff --git a/src/Geometry/3D/Ray.cs b/src/Geometry/3D/Ray.cs index e9cf317..d107daa 100644 --- a/src/Geometry/3D/Ray.cs +++ b/src/Geometry/3D/Ray.cs @@ -18,29 +18,23 @@ public Ray(Point3d origin, Vector3d direction) this.Direction = direction ?? throw new ArgumentNullException(nameof(direction)); } + /// /// Gets or sets the origin point of the ray. /// - public Point3d Origin - { - get; - set; - } + public Point3d Origin { get; set; } /// /// Gets or sets the direction vector of the ray. /// - public Vector3d Direction - { - get; - set; - } + public Vector3d Direction { get; set; } + /// /// Computes a point in the ray at the given parameter. /// /// Parameter to obtain point. /// Returns a point at the specified parameter of the Ray. - public Point3d PointAt(double t) => this.Origin + (t * this.Direction); + public Point3d PointAt(double t) => this.Origin + t * this.Direction; } } \ No newline at end of file diff --git a/src/Geometry/3D/Vector3d.cs b/src/Geometry/3D/Vector3d.cs index 2a41455..f7f455b 100644 --- a/src/Geometry/3D/Vector3d.cs +++ b/src/Geometry/3D/Vector3d.cs @@ -12,13 +12,16 @@ public class Vector3d : BasePoint /// public Vector3d() { } + /// - /// Initializes a new instance of the class with the same values as the provided vector. + /// Initializes a new instance of the class with the same values as the + /// provided vector. /// /// Vector to copy values from. public Vector3d(Vector3d vector) : base(vector) { } + /// /// Initializes a new instance of the class from a v. /// @@ -27,6 +30,7 @@ public Vector3d(Vector3d vector) public Vector3d(Point3d point) : base(point) { } + /// /// Initializes a new instance of the class given it's 3 coordinates. /// @@ -37,6 +41,7 @@ public Vector3d(Point3d point) public Vector3d(double xCoord, double yCoord, double zCoord) : base(xCoord, yCoord, zCoord) { } + /// /// Gets the Euclidean length squared of this vector. /// @@ -67,6 +72,7 @@ public Vector3d(double xCoord, double yCoord, double zCoord) /// Vector {0,1,0}. public static Vector3d UnitZ => new Vector3d(0, 0, 1); + /// /// Divides this vector by it's euclidean length. /// @@ -78,6 +84,7 @@ public void Unitize() this.Z /= length; } + /// /// Returns a normalized copy of this vector. /// @@ -91,6 +98,7 @@ public Vector3d Unit() return new Vector3d(x, y, z); } + /// /// Computes the dot product of this vector and v. /// @@ -98,6 +106,7 @@ public Vector3d Unit() /// Dot product. public double Dot(Vector3d v) => DotProduct(this, v); + /// /// Returns the cross product of this vector and v. /// @@ -105,6 +114,7 @@ public Vector3d Unit() /// Cross product vector. public Vector3d Cross(Vector3d v) => CrossProduct(this, v); + /// /// Gets the scalar product (dot product) of two given vectors /// Dot product = u1*v1 + u2*v2 + u3*v3. @@ -112,7 +122,9 @@ public Vector3d Unit() /// First vector. /// Second vector. /// Numerical value of the dot product. - public static double DotProduct(Vector3d u, Vector3d v) => (u.X * v.X) + (u.Y * v.Y) + (u.Z * v.Z); + public static double DotProduct(Vector3d u, Vector3d v) => + u.X * v.X + u.Y * v.Y + u.Z * v.Z; + /// /// Computes the vector product (cross product) of two given vectors @@ -123,13 +135,14 @@ public Vector3d Unit() /// Vector result of the cross product. public static Vector3d CrossProduct(Vector3d u, Vector3d v) { - var x = (u.Y * v.Z) - (u.Z * v.Y); - var y = (u.Z * v.X) - (u.X * v.Z); - var z = (u.X * v.Y) - (u.Y * v.X); + var x = u.Y * v.Z - u.Z * v.Y; + var y = u.Z * v.X - u.X * v.Z; + var z = u.X * v.Y - u.Y * v.X; return new Vector3d(x, y, z); } + /// /// Computes the angle in Radians between two given vectors /// Angle = Arcosine of the CrossProduct of UxV divided with their multiplied lengths. @@ -137,7 +150,9 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// First vector. /// Second vector. /// Angle formed between u and v. - public static double Angle(Vector3d u, Vector3d v) => Math.Acos(DotProduct(u, v) / (u.Length * v.Length)); + public static double Angle(Vector3d u, Vector3d v) => + Math.Acos(DotProduct(u, v) / (u.Length * v.Length)); + /// /// Adds one vector to another. @@ -145,7 +160,9 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// First vector to add. /// Second vector to add. /// New vector entity with the result of the addition. - public static Vector3d operator +(Vector3d v, Vector3d v2) => new Vector3d(v.X + v2.X, v.Y + v2.Y, v.Z + v2.Z); + public static Vector3d operator +(Vector3d v, Vector3d v2) => + new Vector3d(v.X + v2.X, v.Y + v2.Y, v.Z + v2.Z); + /// /// Substracts one vector from another. @@ -153,7 +170,9 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// Vector to substract from. /// Vector to be substracted. /// New vector entity with the result of the substraction. - public static Vector3d operator -(Vector3d v, Vector3d v2) => new Vector3d(v.X - v2.X, v.Y - v2.Y, v.Z - v2.Z); + public static Vector3d operator -(Vector3d v, Vector3d v2) => + new Vector3d(v.X - v2.X, v.Y - v2.Y, v.Z - v2.Z); + /// /// Multiply one vector by a number. @@ -161,7 +180,9 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// Vector to multiply. /// Number to multiply with. /// New vector entity with the result of the multiplication. - public static Vector3d operator *(Vector3d v, double scalar) => new Vector3d(v.X * scalar, v.Y * scalar, v.Z * scalar); + public static Vector3d operator *(Vector3d v, double scalar) => + new Vector3d(v.X * scalar, v.Y * scalar, v.Z * scalar); + /// /// Multiply one vector by a number. @@ -169,7 +190,9 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// Number to multiply with. /// Vector to multiply. /// New vector entity with the result of the multiplication. - public static Vector3d operator *(double scalar, Vector3d v) => new Vector3d(v.X * scalar, v.Y * scalar, v.Z * scalar); + public static Vector3d operator *(double scalar, Vector3d v) => + new Vector3d(v.X * scalar, v.Y * scalar, v.Z * scalar); + /// /// Negate a vector. @@ -182,13 +205,16 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) v.Y != 0 ? -v.Y : 0, v.Z != 0 ? -v.Z : 0); + /// /// Divide a vector by a number. /// /// Vector to be divided. /// Number to be divided by. /// New vector entity with the result of the division. - public static Vector3d operator /(Vector3d v, double scalar) => new Vector3d(v.X / scalar, v.Y / scalar, v.Z / scalar); + public static Vector3d operator /(Vector3d v, double scalar) => + new Vector3d(v.X / scalar, v.Y / scalar, v.Z / scalar); + /// /// Checks if two vectors are equal. @@ -196,7 +222,8 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// First vector. /// Second vector. /// Result of the comparison between v and w. - public static bool operator ==(Vector3d v, Vector3d w) => v.Equals(w); + public static bool operator ==(Vector3d v, Vector3d w) => v?.Equals(w) ?? false; + /// /// Checks if two vectors are not equal. @@ -204,17 +231,20 @@ public static Vector3d CrossProduct(Vector3d u, Vector3d v) /// First vector. /// Second vector. /// Result of the comparison between v and w. - public static bool operator !=(Vector3d v, Vector3d w) => !v.Equals(w); + public static bool operator !=(Vector3d v, Vector3d w) => !v?.Equals(w) ?? true; + /// public override bool Equals(object obj) => base.Equals(obj); + /// /// Converts a vector into a string. /// /// Returns a string representation of this vector. public override string ToString() => "Vector3d" + base.ToString(); + /// public override int GetHashCode() => base.GetHashCode(); } diff --git a/src/Geometry/3D/VectorNd.cs b/src/Geometry/3D/VectorNd.cs index af6ac18..3da93ed 100644 --- a/src/Geometry/3D/VectorNd.cs +++ b/src/Geometry/3D/VectorNd.cs @@ -12,12 +12,14 @@ public class VectorNd : IEnumerable, IEquatable { private List values; + /// /// Initializes a new instance of the class. /// /// Vector to copy. public VectorNd(VectorNd vector) => this.values = new List(vector); + /// /// Initializes a new instance of the class. /// Constructs a zero vector of a given dimension. @@ -25,18 +27,23 @@ public class VectorNd : IEnumerable, IEquatable /// Dimension. public VectorNd(int dimension) => this.InitializeZeroVector(dimension); + /// - /// Initializes a new instance of the class from a given list of coordinates. + /// Initializes a new instance of the class from a given list of + /// coordinates. /// /// List of values for the vector. public VectorNd(List values) => this.values = values; + /// - /// Initializes a new instance of the class from a given list of parameters. + /// Initializes a new instance of the class from a given list of + /// parameters. /// /// Number parameters. public VectorNd(params double[] values) : this(values.ToList()) { } + /// /// Gets or sets the coordinate at the given dimension. /// @@ -62,11 +69,14 @@ public double this[int dimension] /// public double Length2 => this.ComputeLength2(); + /// public IEnumerator GetEnumerator() => this.values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.values.GetEnumerator(); + /// /// Compare this vector with another. /// @@ -74,6 +84,9 @@ public double this[int dimension] /// Comparison result. public bool Equals(VectorNd vector) { + if (vector == null) + return false; + var max = Math.Max(this.Dimension, vector.Dimension); for (var index = 0; index < max; index++) { @@ -87,6 +100,7 @@ public bool Equals(VectorNd vector) return true; } + /// /// Gets the dot product of this vector with another. /// @@ -94,6 +108,7 @@ public bool Equals(VectorNd vector) /// Dot product result. public double Dot(VectorNd vector) => DotProduct(this, vector); + private double ComputeLength2() { var length = .0; @@ -101,6 +116,7 @@ private double ComputeLength2() return length; } + private void InitializeZeroVector(int dimension) { this.values = new List(dimension); @@ -108,6 +124,7 @@ private void InitializeZeroVector(int dimension) this.values.Add(.0); } + /// /// Computes the distance between two N-dimensional vectors. /// @@ -116,6 +133,7 @@ private void InitializeZeroVector(int dimension) /// Distance between vectors. public static double Distance(VectorNd a, VectorNd b) => Math.Sqrt(Distance2(a, b)); + /// /// Computes the square distance between two N-dimensional vectors. /// @@ -135,13 +153,16 @@ public static double Distance2(VectorNd a, VectorNd b) return dist; } + /// /// Computes the cosine similarity between two vectors. /// /// Vector A. /// Vector B. /// Cosine similarity value. - public static double CosineSimilarity(VectorNd a, VectorNd b) => a.Dot(b) / (a.Length * b.Length); + public static double CosineSimilarity(VectorNd a, VectorNd b) => + a.Dot(b) / (a.Length * b.Length); + /// /// Computes the angular distance between two vectors. @@ -149,7 +170,9 @@ public static double Distance2(VectorNd a, VectorNd b) /// Vector A. /// Vector B. /// Angular distance value. - public static double AngularDistance(VectorNd a, VectorNd b) => Math.Acos(CosineSimilarity(a, b)) / Math.PI; + public static double AngularDistance(VectorNd a, VectorNd b) => + Math.Acos(CosineSimilarity(a, b)) / Math.PI; + /// /// Angular similarity value between two vectors. @@ -159,6 +182,7 @@ public static double Distance2(VectorNd a, VectorNd b) /// Angular similarity value. public static double AngularSimilarity(VectorNd a, VectorNd b) => 1 - AngularDistance(a, b); + /// /// Adds two N dimensional vectors. /// @@ -179,6 +203,7 @@ public static VectorNd Add(VectorNd a, VectorNd b) return result; } + /// /// Subtract two N dimensional vectors. /// @@ -187,6 +212,7 @@ public static VectorNd Add(VectorNd a, VectorNd b) /// Vector substraction result. public static VectorNd Substract(VectorNd a, VectorNd b) => Add(a, Negate(b)); + /// /// Multiply an N dimensional vector by a number. /// @@ -201,13 +227,16 @@ public static VectorNd Multiply(VectorNd vector, double scalar) return result; } + /// /// Divide an N dimensional vector by a number. /// /// Vector to divide. /// Number to divide by. /// Vector division result. - public static VectorNd Divide(VectorNd vector, double scalar) => Multiply(vector, 1 / scalar); + public static VectorNd Divide(VectorNd vector, double scalar) => + Multiply(vector, 1 / scalar); + /// /// Negate a given vector. @@ -222,6 +251,7 @@ public static VectorNd Negate(VectorNd vector) return result; } + /// /// Computes the dot product between two vectors. /// @@ -242,18 +272,22 @@ public static double DotProduct(VectorNd vectorA, VectorNd vectorB) return result; } - /// - public static VectorNd operator +(VectorNd vectorA, VectorNd vectorB) => Add(vectorA, vectorB); - /// - public static VectorNd operator -(VectorNd vectorA, VectorNd vectorB) => Substract(vectorA, vectorB); + public static VectorNd operator +(VectorNd vectorA, VectorNd vectorB) => + Add(vectorA, vectorB); + + + public static VectorNd operator -(VectorNd vectorA, VectorNd vectorB) => + Substract(vectorA, vectorB); + + + public static VectorNd operator *(VectorNd vector, double scalar) => + Multiply(vector, scalar); - /// - public static VectorNd operator *(VectorNd vector, double scalar) => Multiply(vector, scalar); - /// public static VectorNd operator /(VectorNd vector, double scalar) => Divide(vector, scalar); + /// /// Compare this vector with another object. /// @@ -267,20 +301,21 @@ public override bool Equals(object obj) return false; } + /// public override int GetHashCode() { unchecked { // Choose large primes to avoid hashing collisions - const int hashingBase = (int)2166136261; + const int hashingBase = ( int ) 2166136261; const int hashingMultiplier = 16777619; var tol = Settings.Tolerance * 2; var hash = hashingBase; foreach (var coord in this.values) { - var tCoord = (int)(coord * (1 / tol)) * tol; // Round to tolerance + var tCoord = ( int ) (coord * (1 / tol)) * tol; // Round to tolerance hash = (hash * hashingMultiplier) ^ tCoord.GetHashCode(); } @@ -288,7 +323,9 @@ public override int GetHashCode() } } + /// - public override string ToString() => "VectorNd[" + this[0] + "," + this[1] + "," + this[2] + ",...]"; + public override string ToString() => + "VectorNd[" + this[0] + "," + this[1] + "," + this[2] + ",...]"; } } \ No newline at end of file diff --git a/src/Geometry/Base/BaseCurve.cs b/src/Geometry/Base/BaseCurve.cs index 00b18bb..15ab134 100644 --- a/src/Geometry/Base/BaseCurve.cs +++ b/src/Geometry/Base/BaseCurve.cs @@ -5,7 +5,8 @@ namespace Paramdigma.Core.Geometry { /// - /// Represents a generic curve. This class is abstract and all curve classes should inherit from it. + /// Represents a generic curve. This class is abstract and all curve classes should inherit from + /// it. /// public abstract class BaseCurve { @@ -15,14 +16,11 @@ public abstract class BaseCurve protected BaseCurve() => this.Domain = Interval.Unit; // Public properties + /// /// Gets or sets the curve's domain. /// - public Interval Domain - { - get; - set; - } + public Interval Domain { get; set; } /// /// Gets a value indicating whether the curve is valid. @@ -32,6 +30,7 @@ public Interval Domain public double Length => this.ComputeLength(); + /// /// Compute a point along the curve at a specified parameter. /// @@ -39,6 +38,7 @@ public Interval Domain /// Point at the parameter specified. public abstract Point3d PointAt(double t); + /// /// Compute the tangent vector along the curve at a specified parameter. /// @@ -46,6 +46,7 @@ public Interval Domain /// Tangent vector at the parameter specified. public abstract Vector3d TangentAt(double t); + /// /// Compute normal vector along the curve at a specified parameter. /// @@ -53,6 +54,7 @@ public Interval Domain /// Normal vector at the parameter specified. public abstract Vector3d NormalAt(double t); + /// /// Compute a binormal vector along the curve at a specified parameter. /// @@ -60,6 +62,7 @@ public Interval Domain /// Binormal vector at the parameter specified. public abstract Vector3d BinormalAt(double t); + /// /// Compute the perpendicular frame along the curve at a specified parameter. /// @@ -67,12 +70,14 @@ public Interval Domain /// Perpendicular plane at the parameter specified. public abstract Plane FrameAt(double t); + /// /// Checks the validity of the curve. /// /// True if valid. public abstract bool CheckValidity(); + /// /// Computes the length of the curve. /// diff --git a/src/Geometry/Base/BasePoint.cs b/src/Geometry/Base/BasePoint.cs index 40ce1da..9897cf1 100644 --- a/src/Geometry/Base/BasePoint.cs +++ b/src/Geometry/Base/BasePoint.cs @@ -13,6 +13,7 @@ public abstract class BasePoint private double y; private double z; + /// /// Initializes a new instance of the class. /// @@ -21,6 +22,7 @@ protected BasePoint() : this(0, 0, 0) => this.IsUnset = true; + /// /// Initializes a new instance of the class. /// @@ -29,6 +31,7 @@ protected BasePoint() protected BasePoint(BasePoint point) : this(point.X, point.Y, point.Z) { } + /// /// Initializes a new instance of the class by cartesian coordinates. /// @@ -43,6 +46,7 @@ protected BasePoint(double xCoord, double yCoord, double zCoord) this.IsUnset = false; } + /// /// Gets or sets x Coordinate. /// @@ -92,11 +96,8 @@ public double Z /// Gets or sets a value indicating whether the current point is unset. /// /// True if Unset. - public bool IsUnset - { - get; - set; - } + public bool IsUnset { get; set; } + /// /// Add a point to this point. @@ -110,6 +111,7 @@ public void Add(BasePoint point) this.IsUnset = false; } + /// /// Substract a point from this one. /// @@ -122,6 +124,7 @@ public void Substract(BasePoint point) this.IsUnset = false; } + /// /// Multiply this point by a number. /// @@ -133,6 +136,7 @@ public void Multiply(double scalar) this.z *= scalar; } + /// /// Divide this point by a number. /// @@ -144,6 +148,7 @@ public void Divide(double scalar) this.z /= scalar; } + /// /// Negates this point. /// @@ -154,18 +159,21 @@ public void Negate() this.z = this.z != 0 ? -this.z : 0; } + /// /// Returns the string representation of this point. /// /// public override string ToString() => "{ " + this.x + ", " + this.y + ", " + this.z + " }"; + /// /// Converts a point to an array of numbers. /// /// Array with cartesian coordinates of point. public double[] ToArray() => new[] {this.x, this.y, this.z}; + /// /// Performs a deep clone of the point. /// @@ -177,24 +185,25 @@ public override bool Equals(object obj) if (!(obj is BasePoint)) return false; - var pt = (BasePoint)obj; + var pt = ( BasePoint ) obj; return Math.Abs(this.X - pt.X) <= Settings.Tolerance && Math.Abs(this.Y - pt.Y) <= Settings.Tolerance && Math.Abs(this.Z - pt.Z) <= Settings.Tolerance; } + /// public override int GetHashCode() { unchecked { // Choose large primes to avoid hashing collisions - const int hashingBase = (int)2166136261; + const int hashingBase = ( int ) 2166136261; const int hashingMultiplier = 16777619; var tol = Settings.Tolerance * 2; - var tX = (int)(this.X * (1 / tol)) * tol; - var tY = (int)(this.Y * (1 / tol)) * tol; - var tZ = (int)(this.Z * (1 / tol)) * tol; + var tX = ( int ) (this.X * (1 / tol)) * tol; + var tY = ( int ) (this.Y * (1 / tol)) * tol; + var tZ = ( int ) (this.Z * (1 / tol)) * tol; var hash = hashingBase; hash = (hash * hashingMultiplier) ^ tX.GetHashCode(); diff --git a/src/Geometry/Base/InvalidCurveException.cs b/src/Geometry/Base/InvalidCurveException.cs index a9f86ae..5c74ebc 100644 --- a/src/Geometry/Base/InvalidCurveException.cs +++ b/src/Geometry/Base/InvalidCurveException.cs @@ -10,10 +10,12 @@ public class InvalidCurveException : Exception /// public InvalidCurveException() { } + /// public InvalidCurveException(string message) : base(message) { } + /// public InvalidCurveException(string message, Exception innerException) : base(message, innerException) { } diff --git a/src/Geometry/Interfaces/ICurve.cs b/src/Geometry/Interfaces/ICurve.cs index e6e809e..ff1690a 100644 --- a/src/Geometry/Interfaces/ICurve.cs +++ b/src/Geometry/Interfaces/ICurve.cs @@ -12,6 +12,7 @@ public interface ICurve /// Point on curve. Point3d PointAt(double t); + /// /// Computes the tangent vector on the curve at the specified parameter. /// @@ -19,6 +20,7 @@ public interface ICurve /// Tangent on curve. Vector3d TangentAt(double t); + /// /// Computes the normal vector on the curve at the specified parameter. /// @@ -26,6 +28,7 @@ public interface ICurve /// Normal on curve. Vector3d NormalAt(double t); + /// /// Computes the binormal vector on the curve at the specified parameter. /// @@ -33,6 +36,7 @@ public interface ICurve /// Binormal vector on curve. Vector3d BinormalAt(double t); + /// /// Computes the perpendicular frame on the curve at the specified parameter. /// diff --git a/src/Geometry/Interfaces/ISurface.cs b/src/Geometry/Interfaces/ISurface.cs index 9809989..e7bb22c 100644 --- a/src/Geometry/Interfaces/ISurface.cs +++ b/src/Geometry/Interfaces/ISurface.cs @@ -11,19 +11,14 @@ public interface ISurface /// Gets the domain in the U direction. /// /// . - Interval DomainU - { - get; - } + Interval DomainU { get; } /// /// Gets the domain in the V direction. /// /// . - Interval DomainV - { - get; - } + Interval DomainV { get; } + /// /// Compute a point at the specified surface coordinates. @@ -33,6 +28,7 @@ Interval DomainV /// . Point3d PointAt(double u, double v); + /// /// Compute the normal at the specified surface coordinates. /// @@ -41,6 +37,7 @@ Interval DomainV /// Normal vector. Vector3d NormalAt(double u, double v); + /// /// Compute the tangent plane at the specified surface coordinates. /// @@ -49,6 +46,7 @@ Interval DomainV /// Tangent plane. Plane FrameAt(double u, double v); + /// /// Compute the distance between this surface and a point. /// @@ -56,6 +54,7 @@ Interval DomainV /// Number representing the distance. double DistanceTo(Point3d point); + /// /// Compute the projection of a point on this surface. /// diff --git a/src/Geometry/Intersect/Intersect.cs b/src/Geometry/Intersect/Intersect.cs index c494794..42e40d0 100644 --- a/src/Geometry/Intersect/Intersect.cs +++ b/src/Geometry/Intersect/Intersect.cs @@ -18,7 +18,10 @@ public static partial class Intersect3D /// The 3d plane to intersect. /// The resulting intersection point, if it exists. /// Intersection result. - public static ISLinePlane LinePlane(Line line, Plane plane, out Point3d intersectionPoint) + public static LinePlaneIntersectionStatus LinePlane( + Line line, + Plane plane, + out Point3d intersectionPoint) { var u = line.EndPoint - line.StartPoint; var w = line.StartPoint - plane.Origin; @@ -33,11 +36,11 @@ public static ISLinePlane LinePlane(Line line, Plane plane, out Point3d intersec { // Segment lies in plane intersectionPoint = null; - return ISLinePlane.OnPlane; + return LinePlaneIntersectionStatus.OnPlane; } intersectionPoint = null; - return ISLinePlane.NoIntersection; + return LinePlaneIntersectionStatus.NoIntersection; } // They are not parallel @@ -46,13 +49,14 @@ public static ISLinePlane LinePlane(Line line, Plane plane, out Point3d intersec if (sI < 0 || sI > 1) { intersectionPoint = null; - return ISLinePlane.NoIntersection; + return LinePlaneIntersectionStatus.NoIntersection; } - intersectionPoint = line.StartPoint + (u * sI); // Compute segment intersection point - return ISLinePlane.Point; + intersectionPoint = line.StartPoint + u * sI; // Compute segment intersection point + return LinePlaneIntersectionStatus.Point; } + /// /// Compute the intersection between a mesh face perimeter and a ray tangent to the face. /// @@ -61,7 +65,11 @@ public static ISLinePlane LinePlane(Line line, Plane plane, out Point3d intersec /// The resulting intersection point. /// The half-edge on where the intersection lies. /// Intersection result. - public static ISRayFacePerimeter RayFacePerimeter(Ray ray, MeshFace face, out Point3d result, out MeshHalfEdge halfEdge) + public static RayFacePerimeterIntersectionStatus RayFacePerimeter( + Ray ray, + MeshFace face, + out Point3d result, + out MeshHalfEdge halfEdge) { var faceNormal = MeshGeometry.FaceNormal(face); var biNormal = Vector3d.CrossProduct(ray.Direction, faceNormal); @@ -73,55 +81,56 @@ public static ISRayFacePerimeter RayFacePerimeter(Ray ray, MeshFace face, out Po var temp = new Point3d(); var line = new Line(vertices[0], vertices[1]); - if (LinePlane(line, perpPlane, out temp) != ISLinePlane.Point) + if (LinePlane(line, perpPlane, out temp) != LinePlaneIntersectionStatus.Point) { result = null; halfEdge = null; - return ISRayFacePerimeter.Point; + return RayFacePerimeterIntersectionStatus.Point; } // No intersection found if (temp != ray.Origin && temp != null) { result = temp; halfEdge = null; - return ISRayFacePerimeter.Point; + return RayFacePerimeterIntersectionStatus.Point; } // Intersection found line = new Line(vertices[1], vertices[2]); - if (LinePlane(line, perpPlane, out temp) != ISLinePlane.Point) + if (LinePlane(line, perpPlane, out temp) != LinePlaneIntersectionStatus.Point) { result = null; halfEdge = null; - return ISRayFacePerimeter.NoIntersection; - } // No intersection found + return RayFacePerimeterIntersectionStatus.NoIntersection; + } if (temp != ray.Origin && temp != null) { result = temp; halfEdge = null; - return ISRayFacePerimeter.Point; - } // Intersection found + return RayFacePerimeterIntersectionStatus.Point; + } line = new Line(vertices[2], vertices[0]); - if (LinePlane(line, perpPlane, out temp) != ISLinePlane.Point) + if (LinePlane(line, perpPlane, out temp) != LinePlaneIntersectionStatus.Point) { result = null; halfEdge = null; - return ISRayFacePerimeter.NoIntersection; + return RayFacePerimeterIntersectionStatus.NoIntersection; } if (temp != ray.Origin && temp != null) { result = temp; halfEdge = null; - return ISRayFacePerimeter.Point; + return RayFacePerimeterIntersectionStatus.Point; } result = null; halfEdge = null; - return ISRayFacePerimeter.Error; + return RayFacePerimeterIntersectionStatus.Error; } + /// /// Compute the intersection between two 3-dimensional lines. /// @@ -129,7 +138,10 @@ public static ISRayFacePerimeter RayFacePerimeter(Ray ray, MeshFace face, out Po /// Second line to intersect. /// Struct containing the intersection result. /// Returns an enum containing the intersection status. - public static ISLineLine LineLine(Line lineA, Line lineB, out IRLineLine result) + public static LineLineIntersectionStatus LineLine( + Line lineA, + Line lineB, + out LineLineIntersectionResult result) { var u = lineA.EndPoint - lineA.StartPoint; var v = lineB.EndPoint - lineB.StartPoint; @@ -139,7 +151,7 @@ public static ISLineLine LineLine(Line lineA, Line lineB, out IRLineLine result) var c = v.Dot(v); // always >= 0 var d = u.Dot(w); var e = v.Dot(w); - var d2 = (a * c) - (b * b); // always >= 0 + var d2 = a * c - b * b; // always >= 0 double sc, sN, sD = d2; // sc = sN / sD, default sD = D >= 0 double tc, tN, tD = d2; // tc = tN / tD, default tD = D >= 0 @@ -155,8 +167,8 @@ public static ISLineLine LineLine(Line lineA, Line lineB, out IRLineLine result) else { // get the closest points on the infinite lines - sN = (b * e) - (c * d); - tN = (a * e) - (b * d); + sN = b * e - c * d; + tN = a * e - b * d; if (sN < 0.0) { // sc < 0 => the s=0 edge is visible @@ -211,19 +223,19 @@ public static ISLineLine LineLine(Line lineA, Line lineB, out IRLineLine result) tc = Math.Abs(tN) < Settings.Tolerance ? 0.0 : tN / tD; // get the difference of the two closest points - var dP = w + ((sc * u) - (tc * v)); // = S1(sc) - S2(tc) + var dP = w + (sc * u - tc * v); // = S1(sc) - S2(tc) result = default; result.Distance = dP.Length; // return the closest distance - result.TA = sc; - result.TB = tc; + result.ParamA = sc; + result.ParamB = tc; result.PointA = lineA.PointAt(sc); result.PointB = lineB.PointAt(tc); if (result.Distance <= Settings.Tolerance) - return ISLineLine.Point; + return LineLineIntersectionStatus.Point; if (result.Distance > Settings.Tolerance) - return ISLineLine.NoIntersection; - return ISLineLine.Error; + return LineLineIntersectionStatus.NoIntersection; + return LineLineIntersectionStatus.Error; } } } \ No newline at end of file diff --git a/src/Geometry/Intersect/IntersectErrors.cs b/src/Geometry/Intersect/IntersectErrors.cs index d10ae97..a34d280 100644 --- a/src/Geometry/Intersect/IntersectErrors.cs +++ b/src/Geometry/Intersect/IntersectErrors.cs @@ -9,54 +9,32 @@ namespace Paramdigma.Core /// public static partial class Intersect3D { - public enum ISLineLine + public enum LineLineIntersectionStatus { NoIntersection, Point, Error } - // INFO: IS prefix stands for Intersection Status - // INFO: IR prefix stands for Intersection Result - public enum ISLinePlane + public enum LinePlaneIntersectionStatus { NoIntersection, Point, OnPlane } - public enum ISRayFacePerimeter + public enum RayFacePerimeterIntersectionStatus { NoIntersection, Point, Error } - public struct IRLineLine + public struct LineLineIntersectionResult { - public double Distance - { - get; - set; - } - - public double TA - { - get; - set; - } - - public double TB - { - get; - set; - } - - public Point3d PointA - { - get; - set; - } - - public Point3d PointB - { - get; - set; - } + public double Distance { get; set; } + + public double ParamA { get; set; } + + public double ParamB { get; set; } + + public Point3d PointA { get; set; } + + public Point3d PointB { get; set; } } } } \ No newline at end of file diff --git a/src/Geometry/SpatialStructures/PointCloud.cs b/src/Geometry/SpatialStructures/PointCloud.cs index d6c20fb..c6116e8 100644 --- a/src/Geometry/SpatialStructures/PointCloud.cs +++ b/src/Geometry/SpatialStructures/PointCloud.cs @@ -9,49 +9,68 @@ namespace Paramdigma.Core.SpatialSearch /// public class PointCloud : IList { - private List points - { - get; - } + /// + /// Constructs a new point-cloud from a list of point cloud members.. + /// + /// List of point-cloud members. + public PointCloud(List points) => this.Points = points; + + + private List Points { get; } + + + /// + public IEnumerator GetEnumerator() => this.Points.GetEnumerator(); + + + IEnumerator IEnumerable.GetEnumerator() => (( IEnumerable ) this.Points).GetEnumerator(); - public IEnumerator GetEnumerator() => this.points.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.points).GetEnumerator(); + /// + public void Add(PointCloudMember item) => this.Points.Add(item); - public void Add(PointCloudMember item) => this.points.Add(item); /// - public void Clear() => this.points.Clear(); + public void Clear() => this.Points.Clear(); + /// - public bool Contains(PointCloudMember item) => this.points.Contains(item); + public bool Contains(PointCloudMember item) => this.Points.Contains(item); + /// - public void CopyTo(PointCloudMember[] array, int arrayIndex) => this.points.CopyTo(array, arrayIndex); + public void CopyTo(PointCloudMember[] array, int arrayIndex) => + this.Points.CopyTo(array, arrayIndex); + /// - public bool Remove(PointCloudMember item) => this.points.Remove(item); + public bool Remove(PointCloudMember item) => this.Points.Remove(item); + /// - public int Count => this.points.Count; + public int Count => this.Points.Count; /// - public bool IsReadOnly => ((ICollection)this.points).IsReadOnly; + public bool IsReadOnly => (( ICollection ) this.Points).IsReadOnly; + /// - public int IndexOf(PointCloudMember item) => this.points.IndexOf(item); + public int IndexOf(PointCloudMember item) => this.Points.IndexOf(item); + /// - public void Insert(int index, PointCloudMember item) => this.points.Insert(index, item); + public void Insert(int index, PointCloudMember item) => this.Points.Insert(index, item); + /// - public void RemoveAt(int index) => this.points.RemoveAt(index); + public void RemoveAt(int index) => this.Points.RemoveAt(index); + /// public PointCloudMember this[int index] { - get => this.points[index]; - set => this.points[index] = value; + get => this.Points[index]; + set => this.Points[index] = value; } } } \ No newline at end of file diff --git a/src/Geometry/SpatialStructures/PointCloudMember.cs b/src/Geometry/SpatialStructures/PointCloudMember.cs index f6b19b6..dcc19f6 100644 --- a/src/Geometry/SpatialStructures/PointCloudMember.cs +++ b/src/Geometry/SpatialStructures/PointCloudMember.cs @@ -12,10 +12,6 @@ public class PointCloudMember : BasePoint /// Gets or sets the color at this point. /// /// The current color if set, defaults to white. - public Color Color - { - get; - set; - } + public Color Color { get; set; } } } \ No newline at end of file diff --git a/src/Geometry/SpatialStructures/Quadtree.cs b/src/Geometry/SpatialStructures/Quadtree.cs index bb9a403..1e9333d 100644 --- a/src/Geometry/SpatialStructures/Quadtree.cs +++ b/src/Geometry/SpatialStructures/Quadtree.cs @@ -25,6 +25,7 @@ public class QuadTree private QuadTree southEast; private QuadTree southWest; + /// /// Initializes a new instance of the class. /// @@ -37,6 +38,7 @@ public QuadTree(BoundingBox2d boundary, double threshold) this.threshold = threshold; } + /// /// Insert a point in the QuadTree. /// @@ -47,8 +49,8 @@ public bool Insert(Point2d point) if (!this.Boundary.ContainsPoint(point)) return false; - - if (this.Boundary.XDomain.Length < this.threshold || this.Boundary.YDomain.Length < this.threshold) + if (this.Boundary.XDomain.Length < this.threshold + || this.Boundary.YDomain.Length < this.threshold) { this.Points.Add(point); return true; @@ -65,6 +67,7 @@ public bool Insert(Point2d point) return false; } + /// /// Query the QuadTree for all points contained in this range. /// @@ -77,14 +80,16 @@ public List QueryRange(BoundingBox2d range) if (!this.Boundary.IntersectsBox(range)) return pointsInRange; - this.Points.ForEach(pt => - { - if (range.ContainsPoint(pt)) - pointsInRange.Add(pt); - }); + this.Points.ForEach( + pt => + { + if (range.ContainsPoint(pt)) + pointsInRange.Add(pt); + }); // If we reached threshold return - if (this.Boundary.XDomain.Length < this.threshold || this.Boundary.YDomain.Length < this.threshold) + if (this.Boundary.XDomain.Length < this.threshold + || this.Boundary.YDomain.Length < this.threshold) return pointsInRange; if (this.southWest != null) @@ -98,6 +103,7 @@ public List QueryRange(BoundingBox2d range) return pointsInRange; } + private void Subdivide() { this.southWest = new QuadTree( diff --git a/src/IO/CSVReader.cs b/src/IO/CSVReader.cs index 74ef66d..a1e4ef8 100644 --- a/src/IO/CSVReader.cs +++ b/src/IO/CSVReader.cs @@ -5,5 +5,5 @@ namespace Paramdigma.Core.IO /// /// CSV File reader. /// - public static class CSVReader { } + public static class CsvReader { } } \ No newline at end of file diff --git a/src/IO/CSVWritter.cs b/src/IO/CSVWritter.cs index b541769..388ce40 100644 --- a/src/IO/CSVWritter.cs +++ b/src/IO/CSVWritter.cs @@ -5,5 +5,5 @@ namespace Paramdigma.Core.IO /// /// CSV File Writter. /// - public static class CSVWritter { } + public static class CsvWritter { } } \ No newline at end of file diff --git a/src/IO/OBJMeshData.cs b/src/IO/OBJMeshData.cs index 3eddd83..3a76584 100644 --- a/src/IO/OBJMeshData.cs +++ b/src/IO/OBJMeshData.cs @@ -7,34 +7,33 @@ namespace Paramdigma.Core.IO { public struct OBJMeshData { - public List Vertices + public OBJMeshData( + List vertices, + List> faces, + List> edges, + List> textureCoords, + List> faceTextureCoords, + List normals) { - get; + this.Vertices = vertices; + this.Faces = faces; + this.Edges = edges; + this.TextureCoords = textureCoords; + this.FaceTextureCoords = faceTextureCoords; + this.Normals = normals; } - public List> Faces - { - get; - } - public List> Edges - { - get; - } + public List Vertices { get; } - public List> TextureCoords - { - get; - } + public List> Faces { get; } - public List> FaceTextureCoords - { - get; - } + public List> Edges { get; } - public List Normals - { - get; - } + public List> TextureCoords { get; } + + public List> FaceTextureCoords { get; } + + public List Normals { get; } } } \ No newline at end of file diff --git a/src/IO/OBJReader.cs b/src/IO/OBJReader.cs index 4cecc4d..0ae6858 100644 --- a/src/IO/OBJReader.cs +++ b/src/IO/OBJReader.cs @@ -5,5 +5,5 @@ namespace Paramdigma.Core.IO /// /// OBJ File Reader. /// - public static class OBJReader { } + public static class ObjReader { } } \ No newline at end of file diff --git a/src/IO/OBJWritter.cs b/src/IO/OBJWritter.cs index b11d6f6..40b6123 100644 --- a/src/IO/OBJWritter.cs +++ b/src/IO/OBJWritter.cs @@ -5,5 +5,5 @@ namespace Paramdigma.Core.IO /// /// OBJ File Writter. /// - public class OBJWritter { } + public class ObjWritter { } } \ No newline at end of file diff --git a/src/IO/OFFMeshData.cs b/src/IO/OFFMeshData.cs index ad8427f..9340bd3 100644 --- a/src/IO/OFFMeshData.cs +++ b/src/IO/OFFMeshData.cs @@ -8,24 +8,16 @@ namespace Paramdigma.Core.IO /// /// Class containing the resulting mesh data extracted from an .OFF file. /// - public class OFFMeshData + public class OffMeshData { /// /// Gets or sets the mesh vertices. /// - public List Vertices - { - get; - set; - } + public List Vertices { get; set; } /// /// Gets or sets the mesh face indices. /// - public List> Faces - { - get; - set; - } + public List> Faces { get; set; } } } \ No newline at end of file diff --git a/src/IO/OFFReader.cs b/src/IO/OFFReader.cs index a86fec8..d69dce4 100644 --- a/src/IO/OFFReader.cs +++ b/src/IO/OFFReader.cs @@ -7,28 +7,28 @@ namespace Paramdigma.Core.IO { /// OFF Reader class. - public static class OFFReader + public static class OffReader { - public static OFFResult ReadMeshFromFile(string filePath, out OFFMeshData data) + public static OffResult ReadMeshFromFile(string filePath, out OffMeshData data) { var lines = File.ReadAllLines(filePath); - data = default; + data = new OffMeshData(); // Check if first line states OFF format if (lines[0] != "OFF") - return OFFResult.IncorrectFormat; + return OffResult.IncorrectFormat; // Get second line and extract number of vertices and faces var initialData = lines[1].Split(' '); if (!int.TryParse(initialData[0], out var nVertex)) - return OFFResult.IncorrectFormat; + return OffResult.IncorrectFormat; if (!int.TryParse(initialData[1], out var nFaces)) - return OFFResult.IncorrectFormat; + return OffResult.IncorrectFormat; // Check if length of lines correct if (nVertex + nFaces + 2 != lines.Length) - return OFFResult.IncorrectFormat; + return OffResult.IncorrectFormat; // Iterate through all the lines containing the mesh data const int start = 2; @@ -36,6 +36,7 @@ public static OFFResult ReadMeshFromFile(string filePath, out OFFMeshData data) var faces = new List>(); for (var i = start; i < lines.Length; i++) + { if (i < nVertex + start) { // Extract vertices @@ -45,7 +46,7 @@ public static OFFResult ReadMeshFromFile(string filePath, out OFFMeshData data) foreach (var ptStr in lines[i].Split(' ')) { if (!double.TryParse(ptStr, out var ptCoord)) - return OFFResult.IncorrectVertex; + return OffResult.IncorrectVertex; coords.Add(ptCoord); } @@ -60,25 +61,26 @@ public static OFFResult ReadMeshFromFile(string filePath, out OFFMeshData data) var faceStrings = lines[i].Split(' '); // Get first int that represents vertex count of face - if (!int.TryParse(faceStrings[0], out var vertexCount)) - return OFFResult.IncorrectFace; + if (!int.TryParse(faceStrings[0], out var _)) + return OffResult.IncorrectFace; for (var f = 1; f < faceStrings.Length; f++) { if (!int.TryParse(faceStrings[f], out var vertIndex)) - return OFFResult.IncorrectFace; + return OffResult.IncorrectFace; vertexIndexes.Add(vertIndex); } faces.Add(vertexIndexes); } + } // Set data output data.Vertices = vertices; data.Faces = faces; - return OFFResult.OK; + return OffResult.Ok; } } } \ No newline at end of file diff --git a/src/IO/OFFResult.cs b/src/IO/OFFResult.cs index 061cac6..2b90ba3 100644 --- a/src/IO/OFFResult.cs +++ b/src/IO/OFFResult.cs @@ -5,10 +5,14 @@ namespace Paramdigma.Core.IO /// /// Enum containing the result of the OFF conversion. /// - public enum OFFResult + public enum OffResult { - OK, IncorrectFormat, IncorrectVertex, - IncorrectFace, NonMatchingVerticesSize, NonMatchingFacesSize, + Ok, + IncorrectFormat, + IncorrectVertex, + IncorrectFace, + NonMatchingVerticesSize, + NonMatchingFacesSize, FileNotFound } } \ No newline at end of file diff --git a/src/IO/OFFWritter.cs b/src/IO/OffWriter.cs similarity index 87% rename from src/IO/OFFWritter.cs rename to src/IO/OffWriter.cs index d2049a7..82cb709 100644 --- a/src/IO/OFFWritter.cs +++ b/src/IO/OffWriter.cs @@ -5,8 +5,8 @@ namespace Paramdigma.Core.IO { - /// Writter class. - public static class OFFWritter + /// OFF format writer class. + public static class OffWriter { /// /// Write a Half-Edge mesh to a .OFF file. @@ -14,7 +14,7 @@ public static class OFFWritter /// Half-edge mesh to export. /// Path to save the file to. /// - public static OFFResult WriteMeshToFile(Mesh mesh, string filePath) + public static OffResult WriteMeshToFile(Mesh mesh, string filePath) { var offLines = new string[mesh.Vertices.Count + mesh.Faces.Count + 2]; @@ -32,6 +32,7 @@ public static OFFResult WriteMeshToFile(Mesh mesh, string filePath) } foreach (var face in mesh.Faces) + { if (!face.IsBoundaryLoop()) { var vertices = face.AdjacentVertices(); @@ -43,9 +44,10 @@ public static OFFResult WriteMeshToFile(Mesh mesh, string filePath) offLines[count] = faceString; count++; } + } File.WriteAllLines(filePath, offLines); - return OFFResult.OK; + return OffResult.Ok; } } } \ No newline at end of file diff --git a/src/LinearAlgebra/Complex.cs b/src/LinearAlgebra/Complex.cs index 2f08cce..aa2a030 100644 --- a/src/LinearAlgebra/Complex.cs +++ b/src/LinearAlgebra/Complex.cs @@ -18,43 +18,40 @@ public Complex(double real, double imaginary) this.Imaginary = imaginary; } + /// /// Gets or sets the Real component of the complex number. /// - public double Real - { - get; - set; - } + public double Real { get; set; } /// /// Gets or sets the Imaginary component of the complex number. /// - public double Imaginary - { - get; - set; - } + public double Imaginary { get; set; } // Methods + /// /// Computes the phase angle of this complex number. /// /// public double Arg() => Math.Atan2(this.Imaginary, this.Real); + /// /// Computes the length of the complex number. /// /// public double Norm() => Math.Sqrt(this.Norm2()); + /// /// Computes the squared length of the complex number. /// /// - public double Norm2() => (this.Real * this.Real) + (this.Imaginary * this.Imaginary); + public double Norm2() => this.Real * this.Real + this.Imaginary * this.Imaginary; + /// /// Conjugates complex number (negates the imaginary component). @@ -62,14 +59,17 @@ public double Imaginary /// public Complex Conjugate() => new Complex(this.Real, -this.Imaginary); + /// /// Computes the inverse of the complex number ((a + bi)^-1). /// /// public Complex Inverse() => this.Conjugate().OverReal(this.Norm2()); + /// - /// Computes the polar form ae^(iθ), where a is the norm and θ is the phase angle of this complex number. + /// Computes the polar form ae^(iθ), where a is the norm and θ is the phase angle of this complex + /// number. /// /// public Complex Polar() @@ -80,6 +80,7 @@ public Complex Polar() return new Complex(Math.Cos(theta) * a, Math.Sin(theta) * a); } + /// /// Exponentiates this complex number. /// @@ -91,15 +92,23 @@ public Complex Exp() return new Complex(Math.Cos(theta) * a, Math.Sin(theta) * a); } + // Private methods for operators - private Complex Plus(Complex v) => new Complex(this.Real + v.Real, this.Imaginary + v.Imaginary); + private Complex Plus(Complex v) => new Complex( + this.Real + v.Real, + this.Imaginary + v.Imaginary); + + + private Complex Minus(Complex v) => new Complex( + this.Real - v.Real, + this.Imaginary - v.Imaginary); - private Complex Minus(Complex v) => new Complex(this.Real - v.Real, this.Imaginary - v.Imaginary); private Complex TimesReal(double s) => new Complex(this.Real * s, this.Imaginary * s); private Complex OverReal(double s) => this.TimesReal(1 / s); + private Complex TimesComplex(Complex v) { var a = this.Real; @@ -107,16 +116,18 @@ private Complex TimesComplex(Complex v) var c = v.Real; var d = v.Imaginary; - var reNew = (a * c) - (b * d); - var imNew = (a * d) - (b * c); + var reNew = a * c - b * d; + var imNew = a * d - b * c; return new Complex(reNew, imNew); } + private Complex OverComplex(Complex v) => this.TimesComplex(v.Inverse()); // Operators + /// /// Adds to complex numbers. /// @@ -124,6 +135,7 @@ private Complex TimesComplex(Complex v) /// Second complex number. public static Complex operator +(Complex v, Complex w) => v.Plus(w); + /// /// Substracts one complex number from another. /// @@ -131,6 +143,7 @@ private Complex TimesComplex(Complex v) /// Second complex number. public static Complex operator -(Complex v, Complex w) => v.Minus(w); + /// /// Multiplies a complex number with a number. /// @@ -138,6 +151,7 @@ private Complex TimesComplex(Complex v) /// Multiplier. public static Complex operator *(Complex v, double s) => v.TimesReal(s); + /// /// Multiplies a complex number with a number. /// @@ -145,6 +159,7 @@ private Complex TimesComplex(Complex v) /// Multiplicand. public static Complex operator *(double s, Complex v) => v.TimesReal(s); + /// /// Multiplies to complex numbers. /// @@ -152,6 +167,7 @@ private Complex TimesComplex(Complex v) /// Multiplier. public static Complex operator *(Complex v, Complex w) => v.TimesComplex(w); + /// /// Divides a complex number by a number. /// @@ -159,6 +175,7 @@ private Complex TimesComplex(Complex v) /// Dividend. public static Complex operator /(Complex v, double s) => v.OverReal(s); + /// /// Divides two complex numbers. /// diff --git a/src/LinearAlgebra/LeastSquaresLinearFit.cs b/src/LinearAlgebra/LeastSquaresLinearFit.cs index b43375d..3e15b1e 100644 --- a/src/LinearAlgebra/LeastSquaresLinearFit.cs +++ b/src/LinearAlgebra/LeastSquaresLinearFit.cs @@ -13,6 +13,7 @@ public static class LeastSquaresLinearFit // Return the total error. // Found at: http://csharphelper.com/blog/2014/10/find-a-linear-least-squares-fit-for-a-set-of-points-in-c/ + /// /// Find the least squares best fitting line to the given points. /// @@ -21,7 +22,9 @@ public static class LeastSquaresLinearFit /// Slope. /// public static double FindLinearLeastSquaresFit( - List points, out double m, out double b) + List points, + out double m, + out double b) { // Perform the calculation. // Find the values S1, Sx, Sy, Sxx, and Sxy. @@ -37,19 +40,20 @@ public static double FindLinearLeastSquaresFit( } // Solve for m and b. - m = ((sxy * s1) - (sx * sy)) / ((sxx * s1) - (sx * sx)); - b = ((sxy * sx) - (sy * sxx)) / ((sx * sx) - (s1 * sxx)); + m = (sxy * s1 - sx * sy) / (sxx * s1 - sx * sx); + b = (sxy * sx - sy * sxx) / (sx * sx - s1 * sxx); return Math.Sqrt(ErrorSquared(points, m, b)); } + // Return the error squared. private static double ErrorSquared(List points, double m, double b) { double total = 0; foreach (var pt in points) { - var dy = pt.Y - ((m * pt.X) + b); + var dy = pt.Y - (m * pt.X + b); total += dy * dy; } diff --git a/src/LinearAlgebra/Triplet.cs b/src/LinearAlgebra/Triplet.cs index 7bb90d1..67aa53a 100644 --- a/src/LinearAlgebra/Triplet.cs +++ b/src/LinearAlgebra/Triplet.cs @@ -18,24 +18,17 @@ public Triplet(int m, int n) } // Public fields + /// /// Gets values held by this triplet. /// /// - public List Values - { - get; - } + public List Values { get; } - public int M - { - get; - } + public int M { get; } + + public int N { get; } - public int N - { - get; - } // Methods public void AddEntry(double value, int m, int n) @@ -48,22 +41,10 @@ public void AddEntry(double value, int m, int n) public struct TripletData { - public int Row - { - get; - set; - } + public int Row { get; set; } - public int Column - { - get; - set; - } + public int Column { get; set; } - public double Value - { - get; - set; - } + public double Value { get; set; } } } \ No newline at end of file diff --git a/src/Optimization/GradientDescent.cs b/src/Optimization/GradientDescent.cs index aef3742..4bdd106 100644 --- a/src/Optimization/GradientDescent.cs +++ b/src/Optimization/GradientDescent.cs @@ -29,6 +29,7 @@ public class GradientDescent /// public GradientDescentResult Result; + /// /// Initializes a new instance of the class with given options. /// @@ -39,6 +40,7 @@ public GradientDescent(GradientDescentOptions options) this.Options = options; } + /// /// Run gradient descent algorithm. /// @@ -66,12 +68,14 @@ public void Minimize(FitnessFunction function, List inputValues) this.Result.Error = function(this.Result.Values); this.Result.GradientLength = gLength; iter++; // Increase iteration count - } while (gLength > this.Options.Limit && iter < this.Options.MaxIterations && this.Result.Error > this.Options.ErrorThreshold); + } while (gLength > this.Options.Limit && iter < this.Options.MaxIterations + && this.Result.Error + > this.Options.ErrorThreshold); - var customError = this.Result.Error > 1E5 ? $"{this.Result.Error:0.###e-000}" : $"{this.Result.Error:0.00000}"; Console.ResetColor(); } + /// /// Computes the gradient vector of a given function at some specified input values. /// @@ -87,7 +91,11 @@ private List ComputeGradient(FitnessFunction func, List inputVal for (var i = 0; i < inputValues.Count; i++) { - var dV = this.ComputePartialDerivative(i, func, inputValues, this.Options.DerivativeStep); + var dV = this.ComputePartialDerivative( + i, + func, + inputValues, + this.Options.DerivativeStep); gradient.Add(dV * this.Options.LearningRate); derivativeSquareSum += dV * dV; } @@ -102,6 +110,7 @@ private List ComputeGradient(FitnessFunction func, List inputVal return gradient; } + /// /// Computes the partial derivative at a given input value of a given function. /// @@ -126,7 +135,7 @@ private double ComputePartialDerivative( inputValues[inputIndex] += step; // Reset value to original // Compute partial derivative using 2-point method - partialDerivative = ((error1 - error2) / 2) * step; + partialDerivative = (error1 - error2) / 2 * step; return partialDerivative; } diff --git a/src/Optimization/GradientDescentOptions.cs b/src/Optimization/GradientDescentOptions.cs index 0cc29cc..afe466e 100644 --- a/src/Optimization/GradientDescentOptions.cs +++ b/src/Optimization/GradientDescentOptions.cs @@ -30,8 +30,10 @@ public struct GradientDescentOptions /// public double ErrorThreshold; + /// - /// Initializes a new instance of the struct given an existing one. + /// Initializes a new instance of the struct given an + /// existing one. /// /// Options to duplicate. public GradientDescentOptions(GradientDescentOptions options) @@ -43,17 +45,25 @@ public GradientDescentOptions(GradientDescentOptions options) this.ErrorThreshold = options.ErrorThreshold; } + // TODO: Fill in this fields! + /// - /// Initializes a new instance of the struct given all it's values individually. + /// Initializes a new instance of the struct given all it's + /// values individually. /// /// /// /// /// /// - public GradientDescentOptions(double threshold, int maxIterations, double derivativeStep, double learningRate, double errorThreshold) + public GradientDescentOptions( + double threshold, + int maxIterations, + double derivativeStep, + double learningRate, + double errorThreshold) { this.Limit = threshold; this.MaxIterations = maxIterations; @@ -62,15 +72,18 @@ public GradientDescentOptions(double threshold, int maxIterations, double deriva this.ErrorThreshold = errorThreshold; } + /// /// Gets a GradientDescentOptions instance with the default values. /// - public static GradientDescentOptions Default => new GradientDescentOptions(0.001, 10000, 0.01, 20, .01); + public static GradientDescentOptions Default => + new GradientDescentOptions(0.001, 10000, 0.01, 20, .01); /// /// Gets a GradientDescentOptions instance with small values. /// /// - public static GradientDescentOptions DefaultSmall => new GradientDescentOptions(0.0001, 10000, 0.02, 40, .001); + public static GradientDescentOptions DefaultSmall => + new GradientDescentOptions(0.0001, 10000, 0.02, 40, .001); } } \ No newline at end of file diff --git a/src/Optimization/KMeansCluster.cs b/src/Optimization/KMeansCluster.cs index c105007..ca7915a 100644 --- a/src/Optimization/KMeansCluster.cs +++ b/src/Optimization/KMeansCluster.cs @@ -31,38 +31,49 @@ public VectorNd this[int index] /// public bool IsReadOnly => this.list.IsReadOnly; + /// /// Add a new vector to the cluster. /// /// Vector to add. public void Add(VectorNd item) => this.list.Add(item); + /// public void Clear() => this.list.Clear(); + /// public bool Contains(VectorNd item) => this.list.Contains(item); + /// public void CopyTo(VectorNd[] array, int arrayIndex) => this.list.CopyTo(array, arrayIndex); + /// public IEnumerator GetEnumerator() => this.list.GetEnumerator(); + /// public int IndexOf(VectorNd item) => this.list.IndexOf(item); + /// public void Insert(int index, VectorNd item) => this.list.Insert(index, item); + /// public bool Remove(VectorNd item) => this.list.Remove(item); + /// public void RemoveAt(int index) => this.list.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => this.list.GetEnumerator(); + /// /// Computes the average of this cluster. /// @@ -82,6 +93,7 @@ public VectorNd Average() return result; } + /// public override string ToString() => "Paramdigma.Core.Cluster[" + this.Count + "]"; } diff --git a/src/Optimization/KMeansClustering.cs b/src/Optimization/KMeansClustering.cs index f133cb2..1958120 100644 --- a/src/Optimization/KMeansClustering.cs +++ b/src/Optimization/KMeansClustering.cs @@ -14,6 +14,7 @@ public class KMeansClustering private readonly int maxIterations; private int currentIterations; + /// /// Initializes a new instance of the class. /// @@ -27,14 +28,12 @@ public KMeansClustering(int maxIterations, int clusterCount, List data this.InitializeClusters(data); } + /// /// Gets or sets the list of clusters. /// - public List Clusters - { - get; - set; - } + public List Clusters { get; set; } + private void InitializeClusters(List data) { @@ -42,18 +41,22 @@ private void InitializeClusters(List data) for (var i = 0; i < this.clusterCount; i++) this.Clusters.Add(new KMeansCluster()); - data.ForEach(vector => this.Clusters[new Random().Next() % this.clusterCount].Add(vector)); + data.ForEach( + vector => this.Clusters[new Random().Next() % this.clusterCount].Add(vector)); } + /// /// Run the algorithm until it reaches the maximum amount of iterations. /// public void Run() => this.Run(this.maxIterations, false); + /// /// Run the k-means clustering algorithm for a specified amount of iterations. /// /// Iterations to run. + /// True to allow the optimization to leave clusters empty. public void Run(int iterations, bool allowEmptyClusters) { var rnd = new Random(); @@ -73,39 +76,46 @@ public void Run(int iterations, bool allowEmptyClusters) newClusters.Add(new KMeansCluster()); // Find the closest average for each vector in each cluster - this.Clusters.ForEach(cluster => - { - var ind = this.Clusters.IndexOf(cluster); - for (var i = 0; i < cluster.Count; i++) + this.Clusters.ForEach( + cluster => { - var vector = cluster[i]; - var simIndex = this.FindIndexOfSimilar(averages, vector); - newClusters[simIndex].Add(vector); - if (simIndex != ind) - hasChanged = true; - } - }); + var ind = this.Clusters.IndexOf(cluster); + for (var i = 0; i < cluster.Count; i++) + { + var vector = cluster[i]; + var simIndex = this.FindIndexOfSimilar(averages, vector); + newClusters[simIndex].Add(vector); + if (simIndex != ind) + hasChanged = true; + } + }); // Check for empty clusters if (!allowEmptyClusters) - newClusters.ForEach(cluster => - { - if (cluster.Count == 0) + { + newClusters.ForEach( + cluster => { - Console.WriteLine("Cluster has no mass"); - var biggest = newClusters.OrderByDescending(x => x.Count) - .First(); - - var randomVector = biggest[rnd.Next(biggest.Count)]; - - biggest.Remove(randomVector); - cluster.Add(randomVector); - } - }); + if (cluster.Count == 0) + { + Console.WriteLine("Cluster has no mass"); + var biggest = newClusters.OrderByDescending(x => x.Count) + .First(); + + var randomVector = biggest[rnd.Next(biggest.Count)]; + + biggest.Remove(randomVector); + cluster.Add(randomVector); + } + }); + } // Update clusters and increase iteration this.Clusters = newClusters; - var iterArgs = new IterationCompletedEventArgs {iteration = iteration, Clusters = newClusters}; + var iterArgs = new IterationCompletedEventArgs + { + Iteration = iteration, Clusters = newClusters + }; this.OnIterationCompleted(iterArgs); iteration++; this.currentIterations++; @@ -114,6 +124,7 @@ public void Run(int iterations, bool allowEmptyClusters) && this.currentIterations < this.maxIterations); } + /// /// Find the index of the most similar vector to a given one. /// @@ -139,33 +150,35 @@ public int FindIndexOfSimilar(List pool, VectorNd vector) return minIndex; } + /// /// Raised when an iteration is completed. /// public event EventHandler IterationCompleted; + /// /// Method to call when an iteration is completed. /// /// Data for the current iteration. - protected virtual void OnIterationCompleted(IterationCompletedEventArgs iterArgs) => this.IterationCompleted?.Invoke(this, iterArgs); + protected virtual void OnIterationCompleted(IterationCompletedEventArgs iterArgs) => + this.IterationCompleted?.Invoke(this, iterArgs); + /// /// Data for the current iteration event. /// public class IterationCompletedEventArgs : EventArgs { - public int iteration - { - get; - set; - } - - public List Clusters - { - get; - set; - } + /// + /// Iteration number. + /// + public int Iteration { get; set; } + + /// + /// Clusters for current iteration. + /// + public List Clusters { get; set; } } } } \ No newline at end of file diff --git a/src/Paramdigma.Core.csproj b/src/Paramdigma.Core.csproj index 534717c..05e524e 100644 --- a/src/Paramdigma.Core.csproj +++ b/src/Paramdigma.Core.csproj @@ -4,16 +4,17 @@ Computational Geometry library for .NET - netstandard2.0 + netstandard2.1 Paramdigma Core https://paramdigma.com/Core/ https://github.com/Paramdigma/Core/blob/master/LICENSE git + 8 Paramdigma.Core - 0.0.9 + 0.1.0 Alan Rynne Paramdigma Computational Geometry library for .Net @@ -24,10 +25,6 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - diff --git a/src/Utility/Convert.cs b/src/Utility/Convert.cs index dd81edf..fa0bb42 100644 --- a/src/Utility/Convert.cs +++ b/src/Utility/Convert.cs @@ -20,10 +20,10 @@ public static double[] Point3dToBarycentric(Point3d p, Point3d a, Point3d b, Poi { Vector3d v0 = b - a, v1 = c - a, v2 = p - a; - var den = (v0.X * v1.Y) - (v1.X * v0.Y); + var den = v0.X * v1.Y - v1.X * v0.Y; - var v = ((v2.X * v1.Y) - (v1.X * v2.Y)) / den; - var w = ((v0.X * v2.Y) - (v2.X * v0.Y)) / den; + var v = (v2.X * v1.Y - v1.X * v2.Y) / den; + var w = (v0.X * v2.Y - v2.X * v0.Y) / den; var u = 1.0 - v - w; double[] result = {u, v, w}; diff --git a/src/Utility/Settings.cs b/src/Utility/Settings.cs index de393a7..fec2ef8 100644 --- a/src/Utility/Settings.cs +++ b/src/Utility/Settings.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Reflection; using Newtonsoft.Json; @@ -14,11 +15,7 @@ public static class Settings /// /// Gets the minimum value allowed when using this library. /// - public static double Tolerance - { - get; - private set; - } = 0.0000001; + public static double Tolerance { get; private set; } = 0.0000001; /// /// Gets how many decimals are allowed when using the library. @@ -32,46 +29,54 @@ public static int MaxDecimals } } + /// /// Gets the default tessellation level when converting nurbs to meshes. /// /// Integer representing the default tessellation level. public static int GetDefaultTesselationLevel() => tesselationLevel; + /// /// Sets the default tessellation level when converting nurbs to meshes. /// private static void SetDefaultTesselationLevel(int value) => tesselationLevel = value; + /// /// Modifies the tolerance and computes the maxDecimals value accordingly. /// /// Desired tolerance. public static void SetTolerance(double tolerance) => Tolerance = tolerance; + /// /// Reset the Settings to it's default values. /// public static void Reset() { var assembly = typeof(Settings).GetTypeInfo().Assembly; - using (var stream = assembly.GetManifestResourceStream("Paramdigma.Core.Data.Settings.json")) - using (var reader = new StreamReader(stream)) + using (var stream = + assembly.GetManifestResourceStream("Paramdigma.Core.Data.Settings.json")) { - var result = reader.ReadToEnd(); - var json = JsonConvert.DeserializeObject(result); - SetTolerance(json.Tolerance); - SetDefaultTesselationLevel(json.DefaultTesselation); + using (var reader = new StreamReader( + stream ?? throw new InvalidOperationException("Could not get settings."))) + { + var result = reader.ReadToEnd(); + var json = JsonConvert.DeserializeObject(result); + SetTolerance(json.Tolerance); + SetDefaultTesselationLevel(json.DefaultTesselation); + } } } + } - /// - /// This struct holds the settings from the embedded json file. It is only used to reset. - /// - private struct EmbeddedSettings - { - public double Tolerance; - public int DefaultTesselation; - } + /// + /// This struct holds the settings from the embedded json file. It is only used to reset. + /// + internal struct EmbeddedSettings + { + public double Tolerance; + public int DefaultTesselation; } } \ No newline at end of file diff --git a/tests/Collections/IntervalTests.cs b/tests/Collections/IntervalTests.cs index 2ecbaf1..358339a 100644 --- a/tests/Collections/IntervalTests.cs +++ b/tests/Collections/IntervalTests.cs @@ -16,6 +16,7 @@ public void Can_CheckAndModifyDirection() Assert.True(dir != dir2); } + [Fact] public void Can_CheckContainment() { @@ -28,6 +29,7 @@ public void Can_CheckContainment() Assert.True(i.Contains(n3)); } + [Fact] public void Can_CropNumbers() { @@ -35,32 +37,31 @@ public void Can_CropNumbers() const double n1 = 0.0; const double n2 = 5.0; const double n3 = 2.33; - var n1c = i.Crop(n1); - var n2c = i.Crop(n2); - var n3c = i.Crop(n3); - Assert.True(n1c == i.Start); - Assert.True(n2c == i.End); - Assert.True(n3c == n3); + var n1C = i.Crop(n1); + var n2C = i.Crop(n2); + var n3C = i.Crop(n3); + Assert.True(Math.Abs(n1C - i.Start) < Settings.Tolerance); + Assert.True(Math.Abs(n2C - i.End) < Settings.Tolerance); + Assert.True(Math.Abs(n3C - n3) < Settings.Tolerance); } + [Fact] public void Can_RemapNumbers() { var i = new Interval(1, 3); const double n = 2.0; var nMap = i.RemapToUnit(n); - Assert.True(nMap == 0.5); + Assert.True(Math.Abs(nMap - 0.5) < Settings.Tolerance); var nRemap = i.RemapFromUnit(nMap); - Assert.True(n == nRemap); + Assert.True(Math.Abs(n - nRemap) < Settings.Tolerance); } + [Fact] public void CanCreate_Interval() { - var i0 = new Interval(0, 1); - var i1 = Interval.Unit; - var i2 = new Interval(i0); - + var i0 = Interval.Unit; Assert.Equal(1, i0.Length); Assert.Throws(() => new Interval(double.NaN, 1)); Assert.Throws(() => new Interval(0, double.NaN)); diff --git a/tests/Curves/LevelSetsTests.cs b/tests/Curves/LevelSetsTests.cs index 2d670e0..6275039 100644 --- a/tests/Curves/LevelSetsTests.cs +++ b/tests/Curves/LevelSetsTests.cs @@ -26,6 +26,7 @@ public Mesh Triangle } } + [Fact] public void CanCompute_GradientInFace_ReturnsValidVector() { @@ -34,12 +35,17 @@ public void CanCompute_GradientInFace_ReturnsValidVector() Assert.Equal(new Vector3d(0, -1, -1).Unit(), c[0].Unit()); } + [Fact] public void CanCompute_LevelInFace_ReturnsValidLine() { - LevelSets.ComputeLevels("scalar-1", new List {0.5}, this.Triangle, out var levelSets); + LevelSets.ComputeLevels( + "scalar-1", + new List {0.5}, + this.Triangle, + out var levelSets); Assert.NotEmpty(levelSets); - var v = (Vector3d)levelSets[0][0]; + var v = ( Vector3d ) levelSets[0][0]; Assert.Equal(new Vector3d(1, 0, 0), v.Unit()); } } diff --git a/tests/Extensions/ListExtensionsTests.cs b/tests/Extensions/ListExtensionsTests.cs index 291561e..32415fd 100644 --- a/tests/Extensions/ListExtensionsTests.cs +++ b/tests/Extensions/ListExtensionsTests.cs @@ -16,6 +16,7 @@ public void CanCreate_Repeated() } } + [Fact] public void CanCreate_RepeatedDefault() { diff --git a/tests/Geometry/2D/DelaunayTests.cs b/tests/Geometry/2D/DelaunayTests.cs index 9518644..469970b 100644 --- a/tests/Geometry/2D/DelaunayTests.cs +++ b/tests/Geometry/2D/DelaunayTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Paramdigma.Core.Geometry; using Xunit; @@ -7,62 +8,89 @@ namespace Paramdigma.Core.Tests.Geometry { public class DelaunayTests { - private List GeneratePoints(int amout, double maxX, double maxY, out List border) + [Fact] + public void CanComputeDelaunay() { + var maxX = 100; + var maxY = 100; var point0 = new DelaunayPoint(0, 0); var point1 = new DelaunayPoint(0, maxY); var point2 = new DelaunayPoint(maxX, maxY); var point3 = new DelaunayPoint(maxX, 0); - var points = new List {point0, point1, point2, point3}; + var point4 = new DelaunayPoint(maxX / 2.0, maxY / 2.0); + var triangle1 = new DelaunayTriangle(point0, point1, point2); var triangle2 = new DelaunayTriangle(point0, point2, point3); - border = new List {triangle1, triangle2}; - var rnd = new Random(); - var points2 = new List(); - for (var i = 0; i < amout - 4; i++) - points2.Add(RandomPoint(rnd, 0, maxX)); - return points2; - } + var border = new List {triangle1, triangle2}; + var delaunay = Delaunay.Compute( + new List + { + point0, point1, point2, point3, point4 + }, + border + ) + .ToList(); + Assert.True(delaunay.Count == 4); - public static DelaunayPoint RandomPoint(Random RandGenerator, double MinValue, double MaxValue) - { - var range = MaxValue - MinValue; - var randomPoint = new DelaunayPoint((RandGenerator.NextDouble() * range) + MinValue, (RandGenerator.NextDouble() * range) + MinValue); - return randomPoint; + var voronoi = Delaunay.Voronoi(delaunay); + // TODO: We expect 8 because it currently outputs the lines repeated twice. + Assert.True(voronoi.ToList().Count == 8); } - [Fact] - public void CanComputeDelaunay() + + private List GeneratePoints( + int ammount, + double maxX, + double maxY, + out List border) { - var maxX = 100; - var maxY = 100; var point0 = new DelaunayPoint(0, 0); var point1 = new DelaunayPoint(0, maxY); var point2 = new DelaunayPoint(maxX, maxY); var point3 = new DelaunayPoint(maxX, 0); - var point4 = new DelaunayPoint(maxX / 2, maxY / 2); - var points = new List {point0, point1, point2, point3}; var triangle1 = new DelaunayTriangle(point0, point1, point2); var triangle2 = new DelaunayTriangle(point0, point2, point3); - var border = new List {triangle1, triangle2}; + border = new List {triangle1, triangle2}; + var rnd = new Random(); + var points = new List(); + for (var i = 0; i < ammount - 4; i++) + points.Add(RandomPoint(rnd, 0, maxX)); + return points; + } - var delaunay = Delaunay.Compute( - new List - { - point0, - point1, - point2, - point3, - point4 - }, - border - ); - Assert.True(delaunay.Count == 4); - var voronoi = Delaunay.Voronoi(delaunay); - // TODO: We expect 8 because it currently outputs the lines repeated twice. - Assert.True(voronoi.Count == 8); + private static DelaunayPoint RandomPoint( + Random randGenerator, + double minValue, + double maxValue) + { + var range = maxValue - minValue; + var randomPoint = new DelaunayPoint( + randGenerator.NextDouble() * range + minValue, + randGenerator.NextDouble() * range + minValue); + return randomPoint; + } + + + [Fact] + public void CanCompare_DelaunayEdges() + { + var edgeA = new DelaunayEdge(new DelaunayPoint(0, 0), new DelaunayPoint(1, 0)); + var edgeB = new DelaunayEdge(new DelaunayPoint(0, 0), new DelaunayPoint(1, 0)); + Assert.Equal(edgeA, edgeB); + Assert.Equal(edgeA.GetHashCode(), edgeB.GetHashCode()); + Assert.NotNull(edgeA); + } + + + [Fact] + public void CanCreate_DelaunayPoint_FromPoint2d() + { + var pt = new Point2d(.5, .5); + var dpt = new DelaunayPoint(pt); + Assert.Equal(pt.X, dpt.X); + Assert.Equal(pt.Y, dpt.Y); } } } \ No newline at end of file diff --git a/tests/Geometry/2D/Line2dTests.cs b/tests/Geometry/2D/Line2dTests.cs index c14f927..b1a46cc 100644 --- a/tests/Geometry/2D/Line2dTests.cs +++ b/tests/Geometry/2D/Line2dTests.cs @@ -14,6 +14,10 @@ public void CanBe_Created() var line = new Line2d(ptA, ptB); var lineB = new Line2d(ptA, v); var lineC = new Line2d(ptA, v, 3); + + Assert.NotNull(line); + Assert.NotNull(lineB); + Assert.NotNull(lineC); } } } \ No newline at end of file diff --git a/tests/Geometry/2D/Point2dTests.cs b/tests/Geometry/2D/Point2dTests.cs index 73c2deb..e5aaebc 100644 --- a/tests/Geometry/2D/Point2dTests.cs +++ b/tests/Geometry/2D/Point2dTests.cs @@ -15,6 +15,7 @@ public void Can_AddVector() Assert.True(pt2 == expected); } + [Fact] public void CanBe_Added() { @@ -23,20 +24,21 @@ public void CanBe_Added() const double c = 4.11; var ptA = new Point2d(a, b); var ptB = new Point2d(b, c); - var s = ptA - ptB; var ptResult = new Point2d(a + b, b + c); Assert.True(ptA + ptB == ptResult); } + [Fact] public void CanBe_ConvertedToVector() { var pt = new Point2d(0, 1); Vector2d v = pt; - var pt2 = (Point2d)v; + var pt2 = ( Point2d ) v; Assert.True(pt == pt2); } + [Fact] public void CanBe_Divided() { @@ -48,6 +50,7 @@ public void CanBe_Divided() Assert.True(ptA / m == ptResult); } + [Fact] public void CanBe_Multiplied() { @@ -60,6 +63,7 @@ public void CanBe_Multiplied() Assert.True(m * ptA == ptResult); } + [Fact] public void CanBe_Negated() { @@ -70,6 +74,7 @@ public void CanBe_Negated() Assert.True(-ptA == ptResult); } + [Fact] public void CanBe_Substracted() { @@ -82,6 +87,7 @@ public void CanBe_Substracted() Assert.True(ptA - ptB == ptResult); } + [Fact] public void Create_FromPoint() { @@ -90,6 +96,7 @@ public void Create_FromPoint() Assert.True(expected == copy); } + [Fact] public void Create_Origin() { @@ -101,6 +108,7 @@ public void Create_Origin() Assert.True(empty == expected); } + [Fact] public void EqualsAndHashCode_HaveConsistentResults() { @@ -108,10 +116,8 @@ public void EqualsAndHashCode_HaveConsistentResults() var pt2 = new Point2d(1, -1); var b1 = pt == pt2; var b2 = pt.GetHashCode() == pt2.GetHashCode(); - var other = "S"; Assert.True(b1 && b1 == b2); Assert.False(pt != pt2); - Assert.False(pt.Equals(other)); } } } \ No newline at end of file diff --git a/tests/Geometry/2D/Polyline2dTests.cs b/tests/Geometry/2D/Polyline2dTests.cs index fd47c08..3874e9f 100644 --- a/tests/Geometry/2D/Polyline2dTests.cs +++ b/tests/Geometry/2D/Polyline2dTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using Paramdigma.Core.Geometry; @@ -15,12 +16,26 @@ public class Polyline2dUnitSquareAndSegments : IEnumerable private readonly Point2d pt4 = new Point2d(0, 1); + public IEnumerator GetEnumerator() { - yield return new object[] {new Polyline2d(new List {this.pt1, this.pt2, this.pt3, this.pt4}, false), 3}; - yield return new object[] {new Polyline2d(new List {this.pt1, this.pt2, this.pt3, this.pt4}, true), 4}; + yield return new object[] + { + new Polyline2d( + new List {this.pt1, this.pt2, this.pt3, this.pt4}, + false), + 3 + }; + yield return new object[] + { + new Polyline2d( + new List {this.pt1, this.pt2, this.pt3, this.pt4}, + true), + 4 + }; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } @@ -34,11 +49,18 @@ public class Polyline2dDataSet : IEnumerable private readonly Point2d pt4 = new Point2d(0, 1); + public IEnumerator GetEnumerator() { - yield return new object[] {new Polyline2d(new List {this.pt1, this.pt2, this.pt3, this.pt4}, false)}; + yield return new object[] + { + new Polyline2d( + new List {this.pt1, this.pt2, this.pt3, this.pt4}, + false) + }; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } @@ -46,15 +68,22 @@ public class Polyline2dTests { [Theory] [ClassData(typeof(Polyline2dUnitSquareAndSegments))] - public void Constructor_ClosedOption_AddsVertexAndSegment(Polyline2d polyline, int expectedSegments) + public void Constructor_ClosedOption_AddsVertexAndSegment( + Polyline2d polyline, + int expectedSegments) { Assert.True(polyline.Vertices.Count == expectedSegments + 1); Assert.True(polyline.Segments.Count == expectedSegments); } + [Theory] [ClassData(typeof(Polyline2dUnitSquareAndSegments))] - public void DefaultDomain_IsParametrizedByArcLength(Polyline2d polyline, double expectedLength) => Assert.True(polyline.Domain.End == expectedLength); + public void DefaultDomain_IsParametrizedByArcLength( + Polyline2d polyline, + double expectedLength) => Assert.True( + Math.Abs(polyline.Domain.End - expectedLength) < Settings.Tolerance); + [Theory] [ClassData(typeof(Polyline2dDataSet))] @@ -62,10 +91,13 @@ public void Reparametrize_SetsAllSegmentsDomain(Polyline2d polyline) { polyline.Reparametrize(); - Assert.True(polyline.Domain.End == polyline.Segments[^1].Domain.End); - Assert.True(polyline.Domain.End == 1); + Assert.True( + Math.Abs(polyline.Domain.End - polyline.Segments[^1].Domain.End) + < Settings.Tolerance); + Assert.True(Math.Abs(polyline.Domain.End - 1) < Settings.Tolerance); } + [Theory] [ClassData(typeof(Polyline2dDataSet))] public void Check_IsClockwise(Polyline2d polyline) @@ -75,13 +107,14 @@ public void Check_IsClockwise(Polyline2d polyline) Assert.False(cond); } + [Theory] [ClassData(typeof(Polyline2dDataSet))] public void CanCompute_Area(Polyline2d polyline) { polyline.IsClosed = true; var area = polyline.Area(); - Assert.True(area == 1.0); + Assert.True(Math.Abs(area - 1.0) < Settings.Tolerance); polyline.IsClosed = false; area = polyline.Area(); Assert.True(area == 0.0); diff --git a/tests/Geometry/2D/Vector2dTests.cs b/tests/Geometry/2D/Vector2dTests.cs index a2b2516..6ed0810 100644 --- a/tests/Geometry/2D/Vector2dTests.cs +++ b/tests/Geometry/2D/Vector2dTests.cs @@ -9,12 +9,13 @@ public class Vector2dTests public void Can_AddVector() { var pt = new Vector2d(0, 1); - var v = (Point2d)pt; + var v = ( Point2d ) pt; var pt2 = pt + v; var expected = new Vector2d(0, 2); Assert.True(pt2 == expected); } + [Fact] public void CanBe_Added() { @@ -23,20 +24,21 @@ public void CanBe_Added() const double c = 4.11; var ptA = new Vector2d(a, b); var ptB = new Vector2d(b, c); - var s = ptA - ptB; var ptResult = new Vector2d(a + b, b + c); Assert.True(ptA + ptB == ptResult); } + [Fact] public void CanBe_ConvertedTo() { var pt = new Vector2d(0, 1); - var v = (Point2d)pt; + var v = ( Point2d ) pt; Vector2d pt2 = v; Assert.True(pt == pt2); } + [Fact] public void CanBe_ConvertedToString() { @@ -46,6 +48,7 @@ public void CanBe_ConvertedToString() Assert.True(s == result); } + [Fact] public void CanBe_Divided() { @@ -57,6 +60,7 @@ public void CanBe_Divided() Assert.True(ptA / m == ptResult); } + [Fact] public void CanBe_Multiplied() { @@ -69,6 +73,7 @@ public void CanBe_Multiplied() Assert.True(m * ptA == ptResult); } + [Fact] public void CanBe_Negated() { @@ -79,6 +84,7 @@ public void CanBe_Negated() Assert.True(-ptA == ptResult); } + [Fact] public void CanBe_Substracted() { @@ -91,6 +97,7 @@ public void CanBe_Substracted() Assert.True(ptA - ptB == ptResult); } + [Fact] public void CanCompute_DotProduct() { @@ -99,6 +106,7 @@ public void CanCompute_DotProduct() var dot = v.DotProduct(v2); } + [Fact] public void CanCompute_PerpendicularVector() { @@ -108,6 +116,7 @@ public void CanCompute_PerpendicularVector() Assert.True(perp == expected); } + [Fact] public void CanCompute_PerpProduct() { @@ -116,6 +125,7 @@ public void CanCompute_PerpProduct() var dot = v.PerpProduct(v2); } + [Fact] public void CanUnitize() { @@ -125,6 +135,7 @@ public void CanUnitize() Assert.True(v == expected); } + [Fact] public void Create_FromPoint() { @@ -133,6 +144,7 @@ public void Create_FromPoint() Assert.True(expected == copy); } + [Fact] public void Create_Origin() { @@ -141,6 +153,7 @@ public void Create_Origin() Assert.True(empty == expected); } + [Fact] public void EqualsAndHashCode_HaveConsistentResults() { diff --git a/tests/Geometry/3D/BoxTests.cs b/tests/Geometry/3D/BoxTests.cs index 186e0a6..ffd636c 100644 --- a/tests/Geometry/3D/BoxTests.cs +++ b/tests/Geometry/3D/BoxTests.cs @@ -2,7 +2,7 @@ using Paramdigma.Core.Geometry; using Xunit; -namespace Paramdigma.Core.Tests.Geometry._3D +namespace Paramdigma.Core.Tests.Geometry { public class BoxTests { @@ -22,6 +22,7 @@ public void CanCreate_FromCorners() Assert.Equal(new Point3d(.5, .5, .5), box.Center); } + [Fact] public void CanCreate_FromPlaneAndDimensions() { diff --git a/tests/Geometry/3D/CircleTests.cs b/tests/Geometry/3D/CircleTests.cs index 487e3be..8893b84 100644 --- a/tests/Geometry/3D/CircleTests.cs +++ b/tests/Geometry/3D/CircleTests.cs @@ -1,49 +1,56 @@ using Paramdigma.Core.Geometry; using Xunit; -namespace Paramdigma.Core.Tests.Geometry._3D +namespace Paramdigma.Core.Tests.Geometry { public class CircleTests { - public Circle testCircle => new Circle(Plane.WorldXY, 1); + private static Circle TestCircle => new Circle(Plane.WorldXY, 1); + [Fact] public void CanCompute_BinormalAt() { - Assert.Equal(this.testCircle.BinormalAt(0), Vector3d.UnitZ); - Assert.Equal(this.testCircle.BinormalAt(0.25), Vector3d.UnitZ); - Assert.Equal(this.testCircle.BinormalAt(0.5), Vector3d.UnitZ); - Assert.Equal(this.testCircle.BinormalAt(0.75), Vector3d.UnitZ); + Assert.Equal(TestCircle.BinormalAt(0), Vector3d.UnitZ); + Assert.Equal(TestCircle.BinormalAt(0.25), Vector3d.UnitZ); + Assert.Equal(TestCircle.BinormalAt(0.5), Vector3d.UnitZ); + Assert.Equal(TestCircle.BinormalAt(0.75), Vector3d.UnitZ); } + [Fact] - public void CanCompute_FrameAt() => Assert.Equal(this.testCircle.FrameAt(0), new Plane(this.testCircle.PointAt(0), -Vector3d.UnitX, Vector3d.UnitZ)); + public void CanCompute_FrameAt() => Assert.Equal( + TestCircle.FrameAt(0), + new Plane(TestCircle.PointAt(0), -Vector3d.UnitX, Vector3d.UnitZ)); + [Fact] public void CanCompute_NormalAt() { - Assert.Equal(this.testCircle.NormalAt(0), -Vector3d.UnitX); - Assert.Equal(this.testCircle.NormalAt(0.25), -Vector3d.UnitY); - Assert.Equal(this.testCircle.NormalAt(0.5), Vector3d.UnitX); - Assert.Equal(this.testCircle.NormalAt(0.75), Vector3d.UnitY); + Assert.Equal(TestCircle.NormalAt(0), -Vector3d.UnitX); + Assert.Equal(TestCircle.NormalAt(0.25), -Vector3d.UnitY); + Assert.Equal(TestCircle.NormalAt(0.5), Vector3d.UnitX); + Assert.Equal(TestCircle.NormalAt(0.75), Vector3d.UnitY); } + [Fact] public void CanCompute_PointAt() { - Assert.Equal(this.testCircle.PointAt(0), Vector3d.UnitX); - Assert.Equal(this.testCircle.PointAt(0.25), Vector3d.UnitY); - Assert.Equal(this.testCircle.PointAt(0.5), -Vector3d.UnitX); - Assert.Equal(this.testCircle.PointAt(0.75), -Vector3d.UnitY); + Assert.Equal(TestCircle.PointAt(0), Vector3d.UnitX); + Assert.Equal(TestCircle.PointAt(0.25), Vector3d.UnitY); + Assert.Equal(TestCircle.PointAt(0.5), -Vector3d.UnitX); + Assert.Equal(TestCircle.PointAt(0.75), -Vector3d.UnitY); } + [Fact] public void CanCompute_TangentAt() { - Assert.Equal(this.testCircle.TangentAt(0), Vector3d.UnitY); - Assert.Equal(this.testCircle.TangentAt(0.25), -Vector3d.UnitX); - Assert.Equal(this.testCircle.TangentAt(0.5), -Vector3d.UnitY); - Assert.Equal(this.testCircle.TangentAt(0.75), Vector3d.UnitX); + Assert.Equal(TestCircle.TangentAt(0), Vector3d.UnitY); + Assert.Equal(TestCircle.TangentAt(0.25), -Vector3d.UnitX); + Assert.Equal(TestCircle.TangentAt(0.5), -Vector3d.UnitY); + Assert.Equal(TestCircle.TangentAt(0.75), Vector3d.UnitX); } } } \ No newline at end of file diff --git a/tests/Geometry/3D/CurveBaseTests.cs b/tests/Geometry/3D/CurveBaseTests.cs index b12aa0e..9e39fcc 100644 --- a/tests/Geometry/3D/CurveBaseTests.cs +++ b/tests/Geometry/3D/CurveBaseTests.cs @@ -2,6 +2,8 @@ namespace Paramdigma.Core.Tests.Geometry { public abstract class CurveBaseTests { + public T TestCurve; + public abstract void CanGet_Length(); public abstract void CanGet_PointAt(); diff --git a/tests/Geometry/3D/CylinderTests.cs b/tests/Geometry/3D/CylinderTests.cs index 9c72d64..fdd61d2 100644 --- a/tests/Geometry/3D/CylinderTests.cs +++ b/tests/Geometry/3D/CylinderTests.cs @@ -1,8 +1,9 @@ +using System; using Paramdigma.Core.Collections; using Paramdigma.Core.Geometry; using Xunit; -namespace Paramdigma.Core.Tests.Geometry._3D +namespace Paramdigma.Core.Tests.Geometry { public class CylinderTests { @@ -13,8 +14,11 @@ private void CanCompute_PointAtCylinder() var actual = cyl.PointAt(0, 0); var expected = new Point3d(1, 0, 0); Assert.Equal(actual, expected); + Assert.Throws(() => cyl.PointAt(-1, 0)); + Assert.Throws(() => cyl.PointAt(0, -1)); } + [Fact] public void CanCreate_FromDefaultConstructor() { @@ -24,6 +28,7 @@ public void CanCreate_FromDefaultConstructor() Assert.Equal(1, cyl.Height); } + [Fact] public void CanCreate_FromPlaneHeightRadius() { @@ -32,5 +37,12 @@ public void CanCreate_FromPlaneHeightRadius() Assert.Equal(1, cyl.Radius); Assert.Equal(1, cyl.Height); } + + [Fact] + public void CannotCreate_FromInvalidData() + { + Assert.Throws(() => new Cylinder(Plane.WorldXY, -1, Interval.Unit)); + Assert.Throws(() => new Cylinder(Plane.WorldXY, 1, new Interval(0, Settings.Tolerance/2))); + } } } \ No newline at end of file diff --git a/tests/Geometry/3D/Intersect3dTests.cs b/tests/Geometry/3D/Intersect3dTests.cs index 094a0d3..f06a532 100644 --- a/tests/Geometry/3D/Intersect3dTests.cs +++ b/tests/Geometry/3D/Intersect3dTests.cs @@ -12,11 +12,11 @@ public void CanIntersect_Line_Line() var lineB = new Line(new Point3d(1, 0, 0), new Point3d(0, 1, 1)); var status = Intersect3D.LineLine(lineA, lineB, out var result); - Assert.Equal(Intersect3D.ISLineLine.Point, status); + Assert.Equal(Intersect3D.LineLineIntersectionStatus.Point, status); Assert.Equal(new Point3d(0.5, 0.5, 0.5), result.PointA); Assert.Equal(new Point3d(0.5, 0.5, 0.5), result.PointB); - Assert.Equal(0.5, result.TA); - Assert.Equal(0.5, result.TB); + Assert.Equal(0.5, result.ParamA); + Assert.Equal(0.5, result.ParamB); } @@ -30,10 +30,11 @@ public void CanIntersect_Line_Plane() var plane = Plane.WorldXY; var status = Intersect3D.LinePlane(lineA, plane, out var actual); - Assert.Equal(Intersect3D.ISLinePlane.Point, status); + Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.Point, status); Assert.Equal(expected, actual); } + [Fact] public void CannotIntersect_Line_Plane_DoNotTouch() { @@ -43,10 +44,11 @@ public void CannotIntersect_Line_Plane_DoNotTouch() var plane = Plane.WorldXY; var status = Intersect3D.LinePlane(lineA, plane, out var actual); - Assert.Equal(Intersect3D.ISLinePlane.NoIntersection, status); + Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.NoIntersection, status); Assert.Null(actual); } + [Fact] public void CannotIntersect_Line_Plane_DoNotTouchAndAreParallel() { @@ -56,10 +58,11 @@ public void CannotIntersect_Line_Plane_DoNotTouchAndAreParallel() var plane = Plane.WorldXY; var status = Intersect3D.LinePlane(lineA, plane, out var actual); - Assert.Equal(Intersect3D.ISLinePlane.NoIntersection, status); + Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.NoIntersection, status); Assert.Null(actual); } + [Fact] public void CannotIntersect_Line_Plane_Parallel() { @@ -69,7 +72,7 @@ public void CannotIntersect_Line_Plane_Parallel() var plane = Plane.WorldXY; var status = Intersect3D.LinePlane(lineA, plane, out var actual); - Assert.Equal(Intersect3D.ISLinePlane.OnPlane, status); + Assert.Equal(Intersect3D.LinePlaneIntersectionStatus.OnPlane, status); Assert.Null(actual); } } diff --git a/tests/Geometry/3D/LineTests.cs b/tests/Geometry/3D/LineTests.cs index 7b66e78..544e7b1 100644 --- a/tests/Geometry/3D/LineTests.cs +++ b/tests/Geometry/3D/LineTests.cs @@ -8,9 +8,11 @@ public class Line3dTests : CurveBaseTests { internal Line TestLine = new Line(Point3d.WorldOrigin, new Point3d(1, 1, 1)); + [Fact] public override void CanCheck_Validity() => Assert.True(this.TestLine.IsValid); + [Fact] public override void CanGet_BiNormal() { @@ -18,8 +20,11 @@ public override void CanGet_BiNormal() Assert.True(biNorm != null); } + [Fact] - public override void CanGet_Length() => Assert.True(this.TestLine.Length == Math.Sqrt(3)); + public override void CanGet_Length() => Assert.True( + Math.Abs(this.TestLine.Length - Math.Sqrt(3)) < Settings.Tolerance); + [Fact] public override void CanGet_Normal() @@ -31,6 +36,7 @@ public override void CanGet_Normal() line.NormalAt(0.5); } + [Fact] public override void CanGet_PerpFrame() { @@ -38,6 +44,7 @@ public override void CanGet_PerpFrame() Assert.True(biNorm != null); } + [Fact] public override void CanGet_PointAt() { @@ -45,6 +52,7 @@ public override void CanGet_PointAt() Assert.True(pt == new Point3d(0.5, 0.5, 0.5)); } + [Fact] public override void CanGet_Tangent() { diff --git a/tests/Geometry/3D/MeshCornerTests.cs b/tests/Geometry/3D/MeshCornerTests.cs new file mode 100644 index 0000000..86e2afe --- /dev/null +++ b/tests/Geometry/3D/MeshCornerTests.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Paramdigma.Core.Geometry; +using Paramdigma.Core.HalfEdgeMesh; +using Xunit; + +namespace Paramdigma.Core.Tests.Geometry +{ + + + public class MeshCornerTests + { + + public Mesh FlatTriangle + { + get + { + var ptA = new Point3d(0, 0, 0); + var ptB = new Point3d(1, 0, 0); + var ptC = new Point3d(1, 1, 0); + var vertices = new List {ptA, ptB, ptC}; + var face = new List {0, 1, 2}; + var mesh = new Mesh(vertices, new List> {face}); + return mesh; + } + } + + + [Fact] + public void HasPropertiesAssigned() + { + FlatTriangle.Corners.ForEach( + corner => + { + Assert.NotNull(corner.Vertex); + Assert.NotNull(corner.Face); + Assert.NotNull(corner.Next); + Assert.NotNull(corner.Prev); + Assert.NotNull(corner.Index); + + }); + } + } +} \ No newline at end of file diff --git a/tests/Geometry/3D/MeshFaceTests.cs b/tests/Geometry/3D/MeshFaceTests.cs index 529b298..4a56a6f 100644 --- a/tests/Geometry/3D/MeshFaceTests.cs +++ b/tests/Geometry/3D/MeshFaceTests.cs @@ -23,43 +23,37 @@ private static Mesh FlatSquare } } + [Fact] public void CanCompute_FaceArea() => - FlatSquare.Faces.ForEach(face => - { - Assert.Equal(0.5, face.Area); - }); + FlatSquare.Faces.ForEach(face => { Assert.Equal(0.5, face.Area); }); + [Fact] public void CanCompute_FaceNormal() => - FlatSquare.Faces.ForEach(face => - { - Assert.Equal(Vector3d.UnitZ, face.Normal); - }); + FlatSquare.Faces.ForEach(face => { Assert.Equal(Vector3d.UnitZ, face.Normal); }); + [Fact] public void CanCompute_FaceTopology() => - FlatSquare.Faces.ForEach(face => - { - Assert.Single(face.AdjacentFaces()); - Assert.Equal(3, face.AdjacentEdges().Count); - Assert.Equal(3, face.AdjacentCorners().Count); - Assert.Equal(3, face.AdjacentHalfEdges().Count); - Assert.Equal(3, face.AdjacentVertices().Count); - }); + FlatSquare.Faces.ForEach( + face => + { + Assert.Single(face.AdjacentFaces()); + Assert.Equal(3, face.AdjacentEdges().Count); + Assert.Equal(3, face.AdjacentCorners().Count); + Assert.Equal(3, face.AdjacentHalfEdges().Count); + Assert.Equal(3, face.AdjacentVertices().Count); + }); + [Fact] public void CanConvert_ToString() => - FlatSquare.Faces.ForEach(face => - { - Assert.NotNull(face.ToString()); - }); + FlatSquare.Faces.ForEach(face => { Assert.NotNull(face.ToString()); }); + [Fact] public void CanGet_Index() => - FlatSquare.Faces.ForEach(face => - { - Assert.True(face.Index >= 0); - }); + FlatSquare.Faces.ForEach(face => { Assert.True(face.Index >= 0); }); } } \ No newline at end of file diff --git a/tests/Geometry/3D/MeshGeometryTests.cs b/tests/Geometry/3D/MeshGeometryTests.cs index f547115..1770a38 100644 --- a/tests/Geometry/3D/MeshGeometryTests.cs +++ b/tests/Geometry/3D/MeshGeometryTests.cs @@ -37,21 +37,26 @@ public Mesh FlatTriangle } } + [Fact] private void CanCompute_CornerAngles() => - this.FlatSquare.Corners.ForEach(corner => - { - var angle = MeshGeometry.Angle(corner); - Assert.True(Math.Abs(angle - (0.5 * Math.PI)) < Settings.Tolerance); - }); + this.FlatSquare.Corners.ForEach( + corner => + { + var angle = MeshGeometry.Angle(corner); + Assert.True(Math.Abs(angle - 0.5 * Math.PI) < Settings.Tolerance); + }); + [Fact] private void CanCompute_EdgeLengths() => - this.FlatSquare.Edges.ForEach(edge => - { - var length = MeshGeometry.Length(edge); - Assert.True(Math.Abs(length - 1) < Settings.Tolerance); - }); + this.FlatSquare.Edges.ForEach( + edge => + { + var length = MeshGeometry.Length(edge); + Assert.True(Math.Abs(length - 1) < Settings.Tolerance); + }); + [Fact] private void CanCompute_FaceArea() @@ -60,6 +65,7 @@ private void CanCompute_FaceArea() Assert.True(Math.Abs(area - 0.5) < Settings.Tolerance); } + [Fact] private void CanCompute_FaceNormal() { @@ -67,6 +73,7 @@ private void CanCompute_FaceNormal() Assert.Equal(Vector3d.UnitZ, normal); } + [Fact] private void CanCompute_MeshArea() { @@ -74,16 +81,18 @@ private void CanCompute_MeshArea() Assert.True(Math.Abs(area - 0.5) < Settings.Tolerance); } + [Fact] private void CanCompute_VertexNormal() => - this.FlatTriangle.Vertices.ForEach(vertex => - { - var normal = MeshGeometry.VertexNormalAngleWeighted(vertex); - Assert.Equal(Vector3d.UnitZ, normal); - normal = MeshGeometry.VertexNormalEquallyWeighted(vertex); - Assert.Equal(Vector3d.UnitZ, normal); - normal = MeshGeometry.VertexNormalAreaWeighted(vertex); - Assert.Equal(Vector3d.UnitZ, normal); - }); + this.FlatTriangle.Vertices.ForEach( + vertex => + { + var normal = MeshGeometry.VertexNormalAngleWeighted(vertex); + Assert.Equal(Vector3d.UnitZ, normal); + normal = MeshGeometry.VertexNormalEquallyWeighted(vertex); + Assert.Equal(Vector3d.UnitZ, normal); + normal = MeshGeometry.VertexNormalAreaWeighted(vertex); + Assert.Equal(Vector3d.UnitZ, normal); + }); } } \ No newline at end of file diff --git a/tests/Geometry/3D/MeshPointTests.cs b/tests/Geometry/3D/MeshPointTests.cs index bb2310e..4e8c066 100644 --- a/tests/Geometry/3D/MeshPointTests.cs +++ b/tests/Geometry/3D/MeshPointTests.cs @@ -1,10 +1,29 @@ +using System.Collections.Generic; +using Paramdigma.Core.Geometry; using Paramdigma.Core.HalfEdgeMesh; using Xunit; namespace Paramdigma.Core.Tests.Geometry { + + public class MeshPointTests { + + public Mesh FlatTriangle + { + get + { + var ptA = new Point3d(0, 0, 0); + var ptB = new Point3d(1, 0, 0); + var ptC = new Point3d(1, 1, 0); + var vertices = new List {ptA, ptB, ptC}; + var face = new List {0, 1, 2}; + var mesh = new Mesh(vertices, new List> {face}); + return mesh; + } + } + [Fact] public void CanConstruct_FromNumbers() { @@ -15,6 +34,14 @@ public void CanConstruct_FromNumbers() Assert.Equal(0.6, pt.W); } + [Fact] + public void CanConstruct_FromEntities() + { + + var pt = new MeshPoint(FlatTriangle.Faces[0],new Point3d(0.4,0.5,0.6)); + Assert.Equal(0, pt.FaceIndex); + Assert.NotNull(pt); + } [Fact] public void CanConvert_ToString() { diff --git a/tests/Geometry/3D/MeshTests.cs b/tests/Geometry/3D/MeshTests.cs index 9ef0c3e..bfb6cee 100644 --- a/tests/Geometry/3D/MeshTests.cs +++ b/tests/Geometry/3D/MeshTests.cs @@ -3,7 +3,7 @@ using Paramdigma.Core.HalfEdgeMesh; using Xunit; -namespace Paramdigma.Core.Tests.Geometry._3D +namespace Paramdigma.Core.Tests.Geometry { public class MeshTests { @@ -22,6 +22,7 @@ public Mesh FlatSquare } } + [Fact] public void CanCheck_QuadMesh() { @@ -31,6 +32,7 @@ public void CanCheck_QuadMesh() Assert.False(mesh.IsTriangularMesh()); } + [Fact] public void CanCompute_Boundary() { @@ -39,6 +41,7 @@ public void CanCompute_Boundary() Assert.Single(mesh.Boundaries); } + [Fact] public void CanCompute_EulerCharacteristic() { @@ -46,6 +49,7 @@ public void CanCompute_EulerCharacteristic() Assert.Equal(1, mesh.EulerCharacteristic); } + [Fact] public void CanConvert_ToString() { @@ -53,6 +57,7 @@ public void CanConvert_ToString() Assert.IsType(this.FlatSquare.GetMeshInfo()); } + [Fact] public void CanCreate_Mesh() { @@ -62,6 +67,7 @@ public void CanCreate_Mesh() Assert.NotEmpty(mesh.Faces); } + [Fact] public void CanDetect_IsolatedFaces() { @@ -69,6 +75,7 @@ public void CanDetect_IsolatedFaces() Assert.False(mesh.HasIsolatedFaces()); } + [Fact] public void CanDetect_IsolatedVertices() { diff --git a/tests/Geometry/3D/MeshTopologyTests.cs b/tests/Geometry/3D/MeshTopologyTests.cs new file mode 100644 index 0000000..2338509 --- /dev/null +++ b/tests/Geometry/3D/MeshTopologyTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Paramdigma.Core.Geometry; +using Paramdigma.Core.HalfEdgeMesh; +using Xunit; + +namespace Paramdigma.Core.Tests.Geometry +{ + public class MeshTopologyTests + { + public Mesh FlatSquare + { + get + { + var ptA = new Point3d(0, 0, 0); + var ptB = new Point3d(1, 0, 0); + var ptC = new Point3d(1, 1, 0); + var ptD = new Point3d(0, 1, 0); + var vertices = new List {ptA, ptB, ptC, ptD}; + var face = new List {0, 1, 2, 3}; + var mesh = new Mesh(vertices, new List> {face}); + return mesh; + } + } + + public Mesh FlatTriangle + { + get + { + var ptA = new Point3d(0, 0, 0); + var ptB = new Point3d(1, 0, 0); + var ptC = new Point3d(1, 1, 0); + var vertices = new List {ptA, ptB, ptC}; + var face = new List {0, 1, 2}; + var mesh = new Mesh(vertices, new List> {face}); + return mesh; + } + } + + + [Fact] + public void CanCreate_MeshTopology() + { + // TODO: Improve this tests with better assertions. + var topo = new MeshTopology(FlatSquare); + + Assert.Empty(topo.FaceFace); + } + + [Fact] + public void CanConvert_ToString() + { + // TODO: Improve this tests with better assertions. + var topo = new MeshTopology(FlatSquare); + + Assert.NotNull(topo.TopologyDictToString(topo.FaceFace)); + Assert.NotNull(topo.TopologyDictToString(topo.VertexVertex)); + Assert.NotNull(topo.TopologyDictToString(topo.EdgeEdge)); + } + } +} \ No newline at end of file diff --git a/tests/Geometry/3D/MeshVertexTests.cs b/tests/Geometry/3D/MeshVertexTests.cs index 9213945..c8ff9f8 100644 --- a/tests/Geometry/3D/MeshVertexTests.cs +++ b/tests/Geometry/3D/MeshVertexTests.cs @@ -3,7 +3,7 @@ using Paramdigma.Core.HalfEdgeMesh; using Xunit; -namespace Paramdigma.Core.Tests.Geometry._3D +namespace Paramdigma.Core.Tests.Geometry { public class MeshVertexTests { @@ -22,25 +22,26 @@ public Mesh FlatSquare } } + [Fact] public void CanCompute_Adjacencies() => - this.FlatSquare.Vertices.ForEach(vertex => - { - Assert.Single(vertex.AdjacentFaces()); - Assert.Single(vertex.AdjacentCorners()); - Assert.Equal(2, vertex.AdjacentVertices().Count); - Assert.Equal(2, vertex.AdjacentEdges().Count); - Assert.Equal(2, vertex.AdjacentHalfEdges().Count); - Assert.Equal(2, vertex.Valence()); - Assert.True(vertex.OnBoundary()); - }); + this.FlatSquare.Vertices.ForEach( + vertex => + { + Assert.Single(vertex.AdjacentFaces()); + Assert.Single(vertex.AdjacentCorners()); + Assert.Equal(2, vertex.AdjacentVertices().Count); + Assert.Equal(2, vertex.AdjacentEdges().Count); + Assert.Equal(2, vertex.AdjacentHalfEdges().Count); + Assert.Equal(2, vertex.Valence()); + Assert.True(vertex.OnBoundary()); + }); + [Fact] public void CanConvert_ToString() => - this.FlatSquare.Vertices.ForEach(vertex => - { - Assert.NotNull(vertex.ToString()); - }); + this.FlatSquare.Vertices.ForEach(vertex => { Assert.NotNull(vertex.ToString()); }); + [Fact] public void CanCreate() diff --git a/tests/Geometry/3D/NurbsCurveData.cs b/tests/Geometry/3D/NurbsCurveData.cs new file mode 100644 index 0000000..23dc7be --- /dev/null +++ b/tests/Geometry/3D/NurbsCurveData.cs @@ -0,0 +1,26 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Paramdigma.Core.Tests.Geometry +{ + public class NurbsCurveUnitParamData : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return new object[] {0}; + yield return new object[] {0.1}; + yield return new object[] {0.2}; + yield return new object[] {0.3}; + yield return new object[] {0.4}; + yield return new object[] {0.5}; + yield return new object[] {0.6}; + yield return new object[] {0.7}; + yield return new object[] {0.8}; + yield return new object[] {0.9}; + yield return new object[] {1.0}; + } + + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} \ No newline at end of file diff --git a/tests/Geometry/3D/NurbsCurveTests.cs b/tests/Geometry/3D/NurbsCurveTests.cs new file mode 100644 index 0000000..dc03a73 --- /dev/null +++ b/tests/Geometry/3D/NurbsCurveTests.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Linq; +using Paramdigma.Core.Geometry; +using Paramdigma.Core.Tests.Conversions; +using Xunit; +using RG = Rhino.Geometry; + +namespace Paramdigma.Core.Tests.Geometry +{ + public class NurbsCurveTests + { + private readonly List controlPoints = new List + { + new Point4d(0, 0, 0, 4), + new Point4d(1, 3, 0, 3.5), + new Point4d(1.4, 5, 0, 2), + new Point4d(0, 7, 0, 1) + }; + + private NurbsCurve Curve => new NurbsCurve(this.controlPoints, 3); + + private RG.NurbsCurve RhCurve + { + get + { + var rhcrv = RG.Curve.CreateControlPointCurve( + this.controlPoints.Select(pt => pt.Position.ToRhino()), + 3) + .ToNurbsCurve(); + for (var i = 0; i < this.controlPoints.Count; i++) + rhcrv.Points.SetWeight(i, this.controlPoints[i].Weight); + rhcrv.Domain = new RG.Interval(0, 1); + return rhcrv.ToNurbsCurve(); + } + } + + + [Theory] + [InlineData(0)] + [InlineData(0.1)] + [InlineData(0.2)] + [InlineData(1.0)] + private void CanGet_PointAt(double t) + { + var point = this.Curve.PointAt(t); + var rhPoint = this.RhCurve.PointAt(t); + Assert.True(point.DistanceTo(rhPoint.ToCore()) < Settings.Tolerance); + } + + + [Theory] + [InlineData(0)] + [InlineData(0.1)] + [InlineData(0.2)] + [InlineData(1.0)] + public void CanGet_TangentAt(double t) + { + var vector = this.Curve.TangentAt(t); + var rhVector = this.RhCurve.TangentAt(t); + Assert.True((vector - rhVector.ToCore()).Length < Settings.Tolerance); + } + + + [Theory] + [ClassData(typeof(NurbsCurveUnitParamData))] + public void CanGet_NormalAt(double t) + { + var vector = this.Curve.NormalAt(t); + var rhVector = this.RhCurve.DerivativeAt(t, 2)[2]; + var length = (vector - rhVector.ToCore().Unit()).Length; + Assert.True(length < Settings.Tolerance); + } + + + [Theory] + [InlineData(0)] + [InlineData(0.1)] + [InlineData(0.2)] + [InlineData(1.0)] + public void CanGet_BiNormalAt(double t) + { + var vector = this.Curve.BinormalAt(t); + var rhVector = this.RhCurve.DerivativeAt(t, 3)[3]; + var length = (vector - rhVector.ToCore().Unit()).Length; + Assert.True(length < Settings.Tolerance); + } + } +} \ No newline at end of file diff --git a/tests/Geometry/3D/NurbsSurfaceTests.cs b/tests/Geometry/3D/NurbsSurfaceTests.cs new file mode 100644 index 0000000..c10c442 --- /dev/null +++ b/tests/Geometry/3D/NurbsSurfaceTests.cs @@ -0,0 +1,92 @@ +using System.Collections; +using System.Collections.Generic; +using Paramdigma.Core.Collections; +using Paramdigma.Core.Geometry; +using Paramdigma.Core.Tests.Conversions; +using Xunit; +using Xunit.Abstractions; +using RG = Rhino.Geometry; + +namespace Paramdigma.Core.Tests.Geometry +{ + public class NurbsSurfaceTests + { + private readonly ITestOutputHelper testOutputHelper; + + + public NurbsSurfaceTests(ITestOutputHelper testOutputHelper) => + this.testOutputHelper = testOutputHelper; + + + [Theory] + [ClassData(typeof(NurbsSurfaceUnitParamData))] + public void CanGet_PointAt(double u, double v) + { + var surf = NurbsSurface.CreateFlatSurface(Interval.Unit, Interval.Unit, 4, 4); + var pt = surf.PointAt(u, v); + var rhSurf = surf.ToRhino(); + var ptRh = rhSurf.PointAt(u, v); + var distance = pt.DistanceTo(ptRh.ToCore()); + Assert.True(distance < Settings.Tolerance); + } + + + [Theory] + [ClassData(typeof(NurbsSurfaceUnitParamData))] + public void CanGet_TangentAt(double u, double v) + { + var surf = NurbsSurface.CreateFlatSurface(Interval.Unit, Interval.Unit, 4, 4); + var uT = surf.DerivativesAt(u, v, 1)[0, 1].Unit(); + var vT = surf.DerivativesAt(u, v, 1)[1, 0].Unit(); + var rhSurf = surf.ToRhino(); + + var uCrv = rhSurf.IsoCurve(1, v); + uCrv.Domain = new RG.Interval(0, 1); + var vCrv = rhSurf.IsoCurve(0, u); + vCrv.Domain = new RG.Interval(0, 1); + + var uVector = uCrv.TangentAt(u); + var vVector = vCrv.TangentAt(v); + + Assert.True((uVector.ToCore() - uT).Length <= Settings.Tolerance); + Assert.True((vVector.ToCore() - vT).Length <= Settings.Tolerance); + } + + + [Theory] + [ClassData(typeof(NurbsSurfaceUnitParamData))] + public void CanGet_NormalAt(double u, double v) + { + var surf = NurbsSurface.CreateFlatSurface(Interval.Unit, Interval.Unit, 4, 4); + var uT = surf.DerivativesAt(u, v, 1)[0, 1]; + var vT = surf.DerivativesAt(u, v, 1)[1, 0]; + var cross = vT.Cross(uT).Unit(); + var rhSurf = surf.ToRhino(); + var rhVector = rhSurf.NormalAt(u, v); + rhVector.Unitize(); + Assert.True((rhVector.ToCore() - cross).Length <= Settings.Tolerance); + } + } + + public class NurbsSurfaceUnitParamData : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return new object[] {0.0, 0.99}; + yield return new object[] {0.1, .9}; + yield return new object[] {0.2, .8}; + yield return new object[] {0.3, .7}; + yield return new object[] {0.4, .6}; + yield return new object[] {0.5, .5}; + yield return new object[] {0.6, .4}; + yield return new object[] {0.7, .3}; + yield return new object[] {0.8, .2}; + yield return new object[] {0.9, .1}; + yield return new object[] {0.99, .0}; + yield return new object[] {1, .0}; + } + + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} \ No newline at end of file diff --git a/tests/Geometry/3D/NurbsTests.cs b/tests/Geometry/3D/NurbsTests.cs deleted file mode 100644 index a7cdd20..0000000 --- a/tests/Geometry/3D/NurbsTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Paramdigma.Core.Collections; -using Paramdigma.Core.Geometry; -using Xunit; - -namespace Paramdigma.Core.Tests -{ - public class NurbsTests - { - public Matrix FlatGrid(int size) - { - var m = new Matrix(size); - for (var i = 0; i < size; i++) - for (var j = 0; j < size; j++) - m[i, j] = new Point3d(i, j, 0); - - return m; - } - - [Theory] - [InlineData(0.0, 0.0)] - public void Decasteljau2_Works(double u, double v) - { - const int n = 5; - var points = this.FlatGrid(n); - var pt = NurbsCalculator.DeCasteljau2(points, 3, 3, u, v); - - Assert.NotNull(pt); - } - - [Fact] - public void Decasteljau1_Works() - { - var points = new List(); - for (var i = 0; i < 5; i++) - points.Add(new Point3d(i, 0, 0)); - var pt = NurbsCalculator.DeCasteljau1(points.ToArray(), points.Count - 1, 1); - } - - [Fact] - public void NurbsCurvePoint_Works() - { - var p0 = new Point3d(0, 0, 0); - var p1 = new Point3d(1, 3, 0); - var p2 = new Point3d(1.4, 5, 0); - var p3 = new Point3d(0, 7, 0); - var u = NurbsCalculator.CreateUnitKnotVector(3, 1); - var u2 = NurbsCalculator.CreateUnitKnotVector(3, 2); - var u3 = NurbsCalculator.CreateUnitKnotVector(3, 3); - var watch = new Stopwatch(); - watch.Start(); - const int n = 100; - for (var i = 0; i <= n; i++) - { - var pt = NurbsCalculator.CurvePoint(3, 1, u, new[] {p0, p1, p2, p3}, (double)i / n); - var pt2 = NurbsCalculator.CurvePoint(3, 2, u2, new[] {p0, p1, p2, p3}, (double)i / n); - var pt3 = NurbsCalculator.CurvePoint(3, 3, u3, new[] {p0, p1, p2, p3}, (double)i / n); - } - - watch.Stop(); - Console.WriteLine(watch.Elapsed); - } - - [Fact] - public void TestKnotVector() - { - var u = NurbsCalculator.CreateUnitKnotVector(3, 1); - var u2 = NurbsCalculator.CreateUnitKnotVector(3, 2); - var u3 = NurbsCalculator.CreateUnitKnotVector(3, 3); - } - } -} \ No newline at end of file diff --git a/tests/Geometry/3D/PlaneTests.cs b/tests/Geometry/3D/PlaneTests.cs index 9644995..2158729 100644 --- a/tests/Geometry/3D/PlaneTests.cs +++ b/tests/Geometry/3D/PlaneTests.cs @@ -16,6 +16,7 @@ public void CanBe_Cloned() Assert.True(!ReferenceEquals(pln, plnC)); } + [Fact] public void CanBe_Compared() { @@ -25,6 +26,7 @@ public void CanBe_Compared() Assert.Equal(expected.GetHashCode(), actual.GetHashCode()); } + [Fact] public void CanBe_Created() { @@ -35,8 +37,13 @@ public void CanBe_Created() var ptB = new Point3d(0, 1, 0); var ptC = new Point3d(0, 0, 0); var planeC = new Plane(ptC, ptA, ptB); + + Assert.NotNull(plane); + Assert.NotNull(planeB); + Assert.NotNull(planeC); } + [Fact] public void CanBe_Flipped() { @@ -48,6 +55,7 @@ public void CanBe_Flipped() Assert.Equal(xy.ZAxis, -flipped.ZAxis); } + [Fact] public void CanCompute_ClosestPoint() { @@ -57,9 +65,10 @@ public void CanCompute_ClosestPoint() var result = pln.ClosestPoint(pt); var dist = pln.DistanceTo(pt); Assert.True(result == expected); - Assert.True(dist == 1); + Assert.True(Math.Abs(dist - 1) < Settings.Tolerance); } + [Fact] public void CanCompute_Points() { @@ -72,22 +81,27 @@ public void CanCompute_Points() Assert.True(ptB == expectedPtB); } + [Fact] public void CanConvert_ToString() { var pln = Plane.WorldXY; - var s = pln.ToString(); - Console.WriteLine(s); + Assert.NotNull(pln); } + [Fact] public void CanCreate_SpecialPlanes() { var ptXY = Plane.WorldXY; var ptYZ = Plane.WorldYZ; var ptXZ = Plane.WorldXZ; + Assert.NotNull(ptXY); + Assert.NotNull(ptYZ); + Assert.NotNull(ptXZ); } + [Fact] public void CanRemap_FromXYPlane() { @@ -98,6 +112,7 @@ public void CanRemap_FromXYPlane() Assert.Equal(expected, result); } + [Fact] public void CanRemap_ToXYPlane() { @@ -108,6 +123,7 @@ public void CanRemap_ToXYPlane() Assert.Equal(expected, result); } + [Fact] public void LinearPoints_ThrowError() { diff --git a/tests/Geometry/3D/Point3dData.cs b/tests/Geometry/3D/Point3dData.cs index 6168865..45eef2b 100644 --- a/tests/Geometry/3D/Point3dData.cs +++ b/tests/Geometry/3D/Point3dData.cs @@ -12,6 +12,7 @@ public IEnumerator GetEnumerator() yield return new object[] {new Point3d(2, 2, -1), new Point3d(2, 2, -1)}; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } } \ No newline at end of file diff --git a/tests/Geometry/3D/Point3dTests.cs b/tests/Geometry/3D/Point3dTests.cs index ce6f503..cad9a44 100644 --- a/tests/Geometry/3D/Point3dTests.cs +++ b/tests/Geometry/3D/Point3dTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Paramdigma.Core.Geometry; using Xunit; @@ -6,6 +7,14 @@ namespace Paramdigma.Core.Tests.Geometry { public class Point3dTests { + public static IEnumerable UnsetPointData => new List + { + new object[] {new Point3d()}, + new object[] {Point3d.Unset}, + new object[] {new Point4d()} + }; + + [Theory] [ClassData(typeof(Point3dEqualDataset))] public void EqualsAndHashCode_HaveConsistentResults(Point3d pt, Point3d pt2) @@ -17,6 +26,7 @@ public void EqualsAndHashCode_HaveConsistentResults(Point3d pt, Point3d pt2) Assert.False(pt != pt2); } + [Theory] [MemberData(nameof(UnsetPointData))] public void CanToggleUnset_WithX(BasePoint pt) @@ -26,6 +36,7 @@ public void CanToggleUnset_WithX(BasePoint pt) Assert.False(pt.IsUnset); } + [Theory] [MemberData(nameof(UnsetPointData))] public void CanToggleUnset_WithY(BasePoint pt) @@ -35,6 +46,7 @@ public void CanToggleUnset_WithY(BasePoint pt) Assert.False(pt.IsUnset); } + [Theory] [MemberData(nameof(UnsetPointData))] public void CanToggleUnset_WithZ(BasePoint pt) @@ -44,7 +56,6 @@ public void CanToggleUnset_WithZ(BasePoint pt) Assert.False(pt.IsUnset); } - public static IEnumerable UnsetPointData => new List {new object[] {new Point3d()}, new object[] {Point3d.Unset}, new object[] {new Point4d()}}; [Fact] public void Can_OperateOnItself() @@ -58,13 +69,13 @@ public void Can_OperateOnItself() var pt3 = pt2 * 2; pt2.Multiply(2); Assert.True(pt2 == pt3); - var pt4 = pt2 / 2; pt2.Divide(2); Assert.True(pt2 == new Point3d(1, 0, 0)); pt2.Negate(); Assert.True(pt2 == new Point3d(-1, 0, 0)); } + [Fact] public void CanBe_Added() { @@ -73,11 +84,11 @@ public void CanBe_Added() const double c = 4.11; var ptA = new Point3d(a, b, c); var ptB = new Point3d(b, c, a); - var s = ptA - ptB; var ptResult = new Point3d(a + b, b + c, c + a); Assert.True(ptA + ptB == ptResult); } + [Fact] public void CanBe_Created() { @@ -86,12 +97,13 @@ public void CanBe_Created() var pt4 = new Point4d(1, 0, 0, 1); var npt2 = new Point3d(pt2); var npt4 = new Point3d(pt4); - var npt5 = (Point3d)pt4; + var npt5 = ( Point3d ) pt4; Assert.True(pt3 == npt2); Assert.True(pt3 == npt4); Assert.True(pt3 == npt5); } + [Fact] public void CanBe_Divided() { @@ -104,6 +116,7 @@ public void CanBe_Divided() Assert.True(ptA / m == ptResult); } + [Fact] public void CanBe_Multiplied() { @@ -114,8 +127,10 @@ public void CanBe_Multiplied() var ptA = new Point3d(a, b, c); var ptResult = new Point3d(a * m, b * m, c * m); Assert.True(ptA * m == ptResult); + Assert.True(m * ptA == ptResult); } + [Fact] public void CanBe_Negated() { @@ -127,6 +142,7 @@ public void CanBe_Negated() Assert.True(-ptA == ptResult); } + [Fact] public void CanBe_Substracted() { @@ -135,19 +151,32 @@ public void CanBe_Substracted() const double c = 4.11; var ptA = new Point3d(a, b, c); var v = new Vector3d(b, c, a); - var ptB = (Point3d)v; + var ptB = ( Point3d ) v; var ptResult = new Point3d(a - b, b - c, c - a); Assert.True(ptA - ptB == ptResult); Assert.True(ptA - v == ptResult); } + [Fact] public void CanConvert_ToArray() { var v = Vector3d.UnitX; var arr = v.ToArray(); Assert.True(arr.Length == 3); - Assert.True(arr[0] == 1 && arr[1] == 0 && arr[2] == 0); + Assert.True(Math.Abs(arr[0] - 1) < Settings.Tolerance && arr[1] == 0 && arr[2] == 0); + } + + + [Fact] + public void Can_BeCloned() + { + const double a = 3.3; + const double b = 2.2; + const double c = 4.11; + var ptA = new Point3d(a, b, c); + var ptClone = ptA.Clone(); + Assert.False(ReferenceEquals(ptA,ptClone)); } } } \ No newline at end of file diff --git a/tests/Geometry/3D/Point4dTests.cs b/tests/Geometry/3D/Point4dTests.cs index d6df987..a4373d9 100644 --- a/tests/Geometry/3D/Point4dTests.cs +++ b/tests/Geometry/3D/Point4dTests.cs @@ -19,6 +19,7 @@ public void CanBe_Added() Assert.True(ptA + ptB == ptResult); } + [Fact] public void CanBe_Added_WithVector() { @@ -33,6 +34,7 @@ public void CanBe_Added_WithVector() Assert.True(ptA + v == ptResult); } + [Fact] public void CanBe_Created() { @@ -43,6 +45,7 @@ public void CanBe_Created() Assert.Equal(pt42, pt4); } + [Fact] public void CanBe_Divided() { @@ -56,6 +59,7 @@ public void CanBe_Divided() Assert.True(ptA / m == ptResult); } + [Fact] public void CanBe_Multiplied() { @@ -70,6 +74,7 @@ public void CanBe_Multiplied() Assert.True(m * ptA == ptResult); } + [Fact] public void CanBe_Negated() { @@ -83,6 +88,7 @@ public void CanBe_Negated() Assert.True(-ptA == ptResult); } + [Fact] public void CanBe_Subtracted() { @@ -98,6 +104,7 @@ public void CanBe_Subtracted() Assert.Equal(expected, actual); } + [Fact] public void CanCheck_Equality() { @@ -107,8 +114,8 @@ public void CanCheck_Equality() const double d = 1.344; var ptA = new Point4d(a, b, c, d); - var expectedEqual = new Point4d(a + (Settings.Tolerance / 2), b, c, d); - var expectedNotEqual = new Point4d(a + (Settings.Tolerance * 2), b, c, d); + var expectedEqual = new Point4d(a + Settings.Tolerance / 2, b, c, d); + var expectedNotEqual = new Point4d(a + Settings.Tolerance * 2, b, c, d); Assert.True(ptA == expectedEqual); Assert.Equal(ptA.GetHashCode(), expectedEqual.GetHashCode()); Assert.True(ptA != expectedNotEqual); @@ -116,6 +123,7 @@ public void CanCheck_Equality() Assert.False(ptA == null); } + [Fact] public void CanCreate_FromPointAndWeight() { @@ -126,6 +134,7 @@ public void CanCreate_FromPointAndWeight() Assert.Equal(weight, pt4.Weight); } + [Fact] public void CanToggle_IsUnset_OnWeightChange() { diff --git a/tests/Geometry/3D/Polyline3dTests.cs b/tests/Geometry/3D/Polyline3dTests.cs index 5b8acae..bffaaa9 100644 --- a/tests/Geometry/3D/Polyline3dTests.cs +++ b/tests/Geometry/3D/Polyline3dTests.cs @@ -17,6 +17,7 @@ private Polyline GetTestPolyline(int count) return new Polyline(knots); } + [Fact] public void CanAccess_UpdatedSegmentList() { @@ -26,6 +27,7 @@ public void CanAccess_UpdatedSegmentList() Assert.Equal(4, poly.Segments.Count); } + [Fact] public void CanAdd_Knot() { @@ -37,6 +39,7 @@ public void CanAdd_Knot() Assert.Equal(knotCount + 1, poly.Knots.Count); } + [Fact] public override void CanCheck_Validity() { @@ -50,6 +53,7 @@ public override void CanCheck_Validity() Assert.False(poly.IsValid); } + [Fact] public void CanEnumerate_Knots() { @@ -58,6 +62,7 @@ public void CanEnumerate_Knots() Assert.NotNull(knot); } + [Fact] public override void CanGet_BiNormal() { @@ -66,6 +71,7 @@ public override void CanGet_BiNormal() Assert.NotNull(poly.BinormalAt(1)); } + [Fact] public override void CanGet_Length() { @@ -73,10 +79,13 @@ public override void CanGet_Length() { var poly = this.GetTestPolyline(i); var length = poly.Length; - Assert.True(length == i, $"Length {length} is not {i}"); + Assert.True( + Math.Abs(length - i) < Settings.Tolerance, + $"Length {length} is not {i}"); } } + [Fact] public override void CanGet_Normal() { @@ -85,6 +94,7 @@ public override void CanGet_Normal() Assert.NotNull(poly.NormalAt(1)); } + [Fact] public override void CanGet_PerpFrame() { @@ -93,6 +103,7 @@ public override void CanGet_PerpFrame() Assert.NotNull(poly.FrameAt(1)); } + [Fact] public override void CanGet_PointAt() { @@ -102,6 +113,7 @@ public override void CanGet_PointAt() Assert.Equal(expected, pt); } + [Fact] public override void CanGet_Tangent() { @@ -110,6 +122,7 @@ public override void CanGet_Tangent() Assert.NotNull(poly.TangentAt(1)); } + [Fact] public void CanInsert_Knot() { @@ -122,6 +135,7 @@ public void CanInsert_Knot() Assert.Equal(poly[0], poly[2]); } + [Fact] public void CanRemove_Knot() { @@ -136,6 +150,7 @@ public void CanRemove_Knot() Assert.NotEqual(unexpected, poly[0]); } + [Fact] public void CanRemove_Knot_ThrowsException() { @@ -145,6 +160,7 @@ public void CanRemove_Knot_ThrowsException() Assert.Throws(() => poly2.RemoveKnotAt(0)); } + [Fact] public void CanRemove_KnotAtIndex() { @@ -157,6 +173,7 @@ public void CanRemove_KnotAtIndex() Assert.Equal(knotCount - 1, poly.Knots.Count); } + [Fact] public void CanToggle_IsClosed() { diff --git a/tests/Geometry/3D/Ray3dTests.cs b/tests/Geometry/3D/Ray3dTests.cs index 723c285..c01562f 100644 --- a/tests/Geometry/3D/Ray3dTests.cs +++ b/tests/Geometry/3D/Ray3dTests.cs @@ -15,6 +15,7 @@ public void CanCompute_PointAt() Assert.Equal(expected, actual); } + [Fact] public void CanCreate_AndThrowExceptions() { diff --git a/tests/Geometry/3D/SphereTests.cs b/tests/Geometry/3D/SphereTests.cs index 667fc4d..23fe6db 100644 --- a/tests/Geometry/3D/SphereTests.cs +++ b/tests/Geometry/3D/SphereTests.cs @@ -3,13 +3,14 @@ using Paramdigma.Core.Geometry; using Xunit; -namespace Paramdigma.Core.Tests.Geometry._3D +namespace Paramdigma.Core.Tests.Geometry { public class SphereTests { [Fact] public void CanCompute_FrameAtParameter() { } + [Fact] public void CanCompute_NormalAtParameter() { @@ -19,6 +20,7 @@ public void CanCompute_NormalAtParameter() Assert.Equal(expected, actual); } + [Fact] public void ComputeClosestParam_ThenPointAtParam_GivesSamePoint() { @@ -30,6 +32,7 @@ public void ComputeClosestParam_ThenPointAtParam_GivesSamePoint() Assert.Equal(pt, sphere.PointAt(param)); } + [Fact] public void ComputeClosestPoint_GivesAccurateResult() { @@ -38,6 +41,7 @@ public void ComputeClosestPoint_GivesAccurateResult() Assert.Equal(new Point3d(1, 0, 0), sphere.ClosestPointTo(pt)); } + [Fact] public void ComputeDistance_GivesAccurateResult() { @@ -46,6 +50,7 @@ public void ComputeDistance_GivesAccurateResult() Assert.Equal(3.0, sphere.DistanceTo(pt)); } + [Fact] public void Create_SphereWithEmptyConstructor_ReturnsXYPlaneUnitSphere() { @@ -56,6 +61,7 @@ public void Create_SphereWithEmptyConstructor_ReturnsXYPlaneUnitSphere() Assert.Equal(Interval.Unit, sphere.DomainV); } + [Fact] public void Create_SphereWithPlaneAndRadius_ReturnsValidSphere() { @@ -66,7 +72,9 @@ public void Create_SphereWithPlaneAndRadius_ReturnsValidSphere() Assert.Equal(Interval.Unit, sphere.DomainV); } + [Fact] - public void Create_SphereWithZeroRadius_ThrowsException() => Assert.Throws(() => new Sphere(Plane.WorldXY, 0)); + public void Create_SphereWithZeroRadius_ThrowsException() => + Assert.Throws(() => new Sphere(Plane.WorldXY, 0)); } } \ No newline at end of file diff --git a/tests/Geometry/3D/Vector3dTests.cs b/tests/Geometry/3D/Vector3dTests.cs index af907c2..40cb7e4 100644 --- a/tests/Geometry/3D/Vector3dTests.cs +++ b/tests/Geometry/3D/Vector3dTests.cs @@ -15,10 +15,11 @@ public void Can_ComputeAngle() var q = 0.25 * Math.PI; var a = Vector3d.Angle(v1, v2); var a2 = Vector3d.Angle(v1, v3); - Assert.True(a == 0.5 * Math.PI); + Assert.True(Math.Abs(a - 0.5 * Math.PI) < Settings.Tolerance); Assert.True(Math.Abs(a2 - q) <= Settings.Tolerance); } + [Fact] public void CanBe_Added() { @@ -27,11 +28,11 @@ public void CanBe_Added() const double c = 4.11; var vA = new Vector3d(a, b, c); var vB = new Vector3d(b, c, a); - var s = vA - vB; var ptResult = new Vector3d(a + b, b + c, c + a); Assert.True(vA + vB == ptResult); } + [Fact] public void CanBe_ConvertedToString() { @@ -41,6 +42,7 @@ public void CanBe_ConvertedToString() Assert.True(s == result); } + [Fact] public void CanBe_Created() { @@ -53,6 +55,7 @@ public void CanBe_Created() Assert.True(pt == newVp); } + [Fact] public void CanBe_Divided() { @@ -65,6 +68,7 @@ public void CanBe_Divided() Assert.True(v / m == ptResult); } + [Fact] public void CanBe_Multiplied() { @@ -77,6 +81,7 @@ public void CanBe_Multiplied() Assert.True(v * m == ptResult); } + [Fact] public void CanBe_Negated() { @@ -88,6 +93,7 @@ public void CanBe_Negated() Assert.True(-v == ptResult); } + [Fact] public void CanBe_Substracted() { @@ -100,6 +106,7 @@ public void CanBe_Substracted() Assert.True(vA - vB == ptResult); } + [Fact] public void EqualsAndHashCode_HaveConsistentResults() { diff --git a/tests/Geometry/3D/VectorEntity_Tests.cs b/tests/Geometry/3D/VectorEntity_Tests.cs index 21c1dba..37afabe 100644 --- a/tests/Geometry/3D/VectorEntity_Tests.cs +++ b/tests/Geometry/3D/VectorEntity_Tests.cs @@ -5,13 +5,17 @@ public abstract class VectorEntityTests public abstract void CanAdd(T a, T b, T expected); public abstract void CanAdd_Itself(T a, T b, T expected); + public abstract void CanSubstract_New(T a, T b, T expected); + public abstract void CanSubstract_ToItself(T a, T b, T expected); public abstract void CanMultiply_New(T a, double scalar, T expected); + public abstract void CanMultiply_Itself(T a, double scalar, T expected); public abstract void CanDivide_New(T a, double scalar, T expected); + public abstract void CanDivide_Itself(T a, double scalar, T expected); public abstract void IsEqual_WithinTolerance(T a, T b); diff --git a/tests/Geometry/3D/VectorNd_Tests.cs b/tests/Geometry/3D/VectorNd_Tests.cs index 3ac8c0b..4aac8b1 100644 --- a/tests/Geometry/3D/VectorNd_Tests.cs +++ b/tests/Geometry/3D/VectorNd_Tests.cs @@ -7,15 +7,59 @@ namespace Paramdigma.Core.Tests { public class VectorNdTests : VectorEntityTests { - public static IEnumerable VectorAddData => new List {new object[] {new VectorNd(0, 0, 0, 9, 3), new VectorNd(4, 5, 6), new VectorNd(4, 5, 6, 9, 3)}, new object[] {new VectorNd(3, 5, 0, 3), new VectorNd(4, 5, 6, 1, 3, 5), new VectorNd(7, 10, 6, 4, 3, 5)}}; + public static IEnumerable VectorAddData => new List + { + new object[] + { + new VectorNd(0, 0, 0, 9, 3), new VectorNd(4, 5, 6), new VectorNd(4, 5, 6, 9, 3) + }, + new object[] + { + new VectorNd(3, 5, 0, 3), + new VectorNd( + 4, + 5, + 6, + 1, + 3, + 5), + new VectorNd( + 7, + 10, + 6, + 4, + 3, + 5) + } + }; + + public static IEnumerable VectorDivideData => new List + { + new object[] {new VectorNd(0, 0, 0), 4, new VectorNd(0, 0, 0)}, + new object[] {new VectorNd(3, 5, 1), 2, new VectorNd(1.5, 2.5, 0.5)} + }; - public static IEnumerable VectorDivideData => new List {new object[] {new VectorNd(0, 0, 0), 4, new VectorNd(0, 0, 0)}, new object[] {new VectorNd(3, 5, 1), 2, new VectorNd(1.5, 2.5, 0.5)}}; + public static IEnumerable VectorMultiplyData => new List + { + new object[] {new VectorNd(0, 0, 0), 5, new VectorNd(0, 0, 0)}, + new object[] {new VectorNd(3, 5, 2), 6, new VectorNd(18, 30, 12)} + }; - public static IEnumerable VectorMultiplyData => new List {new object[] {new VectorNd(0, 0, 0), 5, new VectorNd(0, 0, 0)}, new object[] {new VectorNd(3, 5, 2), 6, new VectorNd(18, 30, 12)}}; + public static IEnumerable VectorSubstractData => new List + { + new object[] + { + new VectorNd(0, 0, 0), new VectorNd(4, 5, 6), new VectorNd(-4, -5, -6) + }, + new object[] {new VectorNd(3, 5, 0), new VectorNd(4, 5, 6), new VectorNd(-1, 0, -6)} + }; - public static IEnumerable VectorSubstractData => new List {new object[] {new VectorNd(0, 0, 0), new VectorNd(4, 5, 6), new VectorNd(-4, -5, -6)}, new object[] {new VectorNd(3, 5, 0), new VectorNd(4, 5, 6), new VectorNd(-1, 0, -6)}}; + public static IEnumerable IsEqualData => new List + { + new object[] {new VectorNd(3, 3, 3), new VectorNd(3, 3, 3 + 1E-12)}, + new object[] {new VectorNd(4, 5, 6), new VectorNd(4 + 1E-9, 5 + 1E-8, 6)} + }; - public static IEnumerable IsEqualData => new List {new object[] {new VectorNd(3, 3, 3), new VectorNd(3, 3, 3 + 1E-12)}, new object[] {new VectorNd(4, 5, 6), new VectorNd(4 + 1E-9, 5 + 1E-8, 6)}}; [Theory] [MemberData(nameof(VectorAddData))] @@ -25,36 +69,53 @@ public override void CanAdd(VectorNd a, VectorNd b, VectorNd expected) Assert.Equal(c, expected); } + [Theory] [MemberData(nameof(VectorAddData))] - public override void CanAdd_Itself(VectorNd a, VectorNd b, VectorNd expected) => Assert.Equal(a + b, expected); + public override void CanAdd_Itself(VectorNd a, VectorNd b, VectorNd expected) => + Assert.Equal(a + b, expected); + [Theory] [MemberData(nameof(VectorDivideData))] - public override void CanDivide_New(VectorNd a, double scalar, VectorNd expected) => Assert.Equal(a / scalar, expected); + public override void CanDivide_New(VectorNd a, double scalar, VectorNd expected) => + Assert.Equal(a / scalar, expected); + [Theory] [MemberData(nameof(VectorMultiplyData))] - public override void CanMultiply_New(VectorNd a, double scalar, VectorNd expected) => Assert.Equal(a * scalar, expected); + public override void CanMultiply_New(VectorNd a, double scalar, VectorNd expected) => + Assert.Equal(a * scalar, expected); + [Theory] [MemberData(nameof(VectorSubstractData))] - public override void CanSubstract_New(VectorNd a, VectorNd b, VectorNd expected) => Assert.Equal(a - b, expected); + public override void CanSubstract_New(VectorNd a, VectorNd b, VectorNd expected) => + Assert.Equal(a - b, expected); + [Theory] [MemberData(nameof(VectorSubstractData))] - public override void CanSubstract_ToItself(VectorNd a, VectorNd b, VectorNd expected) => Assert.Equal(a - b, expected); + public override void CanSubstract_ToItself(VectorNd a, VectorNd b, VectorNd expected) => + Assert.Equal(a - b, expected); + [Theory] [MemberData(nameof(IsEqualData))] - public override void IsEqual_HasEqualHashcodes(VectorNd a, VectorNd b) => Assert.Equal(a.GetHashCode(), b.GetHashCode()); + public override void IsEqual_HasEqualHashcodes(VectorNd a, VectorNd b) => + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + [Theory] [MemberData(nameof(IsEqualData))] public override void IsEqual_WithinTolerance(VectorNd a, VectorNd b) => Assert.Equal(a, b); - public override void CanMultiply_Itself(VectorNd a, double scalar, VectorNd expected) => throw new NotImplementedException(); - public override void CanDivide_Itself(VectorNd a, double scalar, VectorNd expected) => throw new NotImplementedException(); + public override void CanMultiply_Itself(VectorNd a, double scalar, VectorNd expected) => + throw new NotImplementedException(); + + + public override void CanDivide_Itself(VectorNd a, double scalar, VectorNd expected) => + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/tests/Optimization/GradientDescentOptionsTests.cs b/tests/Optimization/GradientDescentOptionsTests.cs index 3905317..294dd0b 100644 --- a/tests/Optimization/GradientDescentOptionsTests.cs +++ b/tests/Optimization/GradientDescentOptionsTests.cs @@ -19,6 +19,7 @@ public void CanCreate_Default() Assert.Equal(expected, actual); } + [Fact] public void CanCreate_DefaultSmall() { @@ -33,6 +34,7 @@ public void CanCreate_DefaultSmall() Assert.Equal(expected, actual); } + [Fact] public void CanCreate_FromExisting() { diff --git a/tests/Optimization/GradientDescentTests.cs b/tests/Optimization/GradientDescentTests.cs index 24b07f9..f6f8dd9 100644 --- a/tests/Optimization/GradientDescentTests.cs +++ b/tests/Optimization/GradientDescentTests.cs @@ -11,7 +11,10 @@ public class GradientDescentTests public void GradientDescent_Line() { var line = new Line(Point3d.WorldOrigin, new Point3d(1, 1, 0)); - var gd = new GradientDescent(GradientDescentOptions.Default) {Options = {MaxIterations = 1}}; + var gd = new GradientDescent(GradientDescentOptions.Default) + { + Options = {MaxIterations = 1} + }; var input = 1; gd.Options.MaxIterations = 100; diff --git a/tests/Optimization/KMeansClusteringTests.cs b/tests/Optimization/KMeansClusteringTests.cs index 82e586f..e53044a 100644 --- a/tests/Optimization/KMeansClusteringTests.cs +++ b/tests/Optimization/KMeansClusteringTests.cs @@ -5,32 +5,31 @@ using Paramdigma.Core.Geometry; using Paramdigma.Core.Optimization; using Xunit; -using Xunit.Abstractions; namespace Paramdigma.Core.Tests.Optimization { public class KMeansClusteringTests { - private readonly ITestOutputHelper testOutputHelper; - - public KMeansClusteringTests(ITestOutputHelper testOutputHelper) => this.testOutputHelper = testOutputHelper; - - public List createClusterAround(Point3d pt, double radius, int count) + private static IEnumerable CreateClusterAround( + BasePoint pt, + double radius, + int count) { var cluster = new List(); var rnd = new Random(); var range = new Interval(-radius, radius); for (var i = 0; i < count; i++) { - var x = pt.X + rnd.NextDouble(); - var y = pt.Y + rnd.NextDouble(); - var z = pt.Z + rnd.NextDouble(); + var x = pt.X + range.RemapFromUnit(rnd.NextDouble()); + var y = pt.Y + range.RemapFromUnit(rnd.NextDouble()); + var z = pt.Z + range.RemapFromUnit(rnd.NextDouble()); cluster.Add(new VectorNd(x, y, z)); } return cluster; } + [Theory] [InlineData(4, 20)] [InlineData(6, 18)] @@ -44,9 +43,9 @@ public void KMeans_MainTest(int expectedClusters, int expectedClusterCount) for (var i = 0; i < expectedClusters; i++) { - var pt = cir.PointAt((double)i / expectedClusters); + var pt = cir.PointAt(( double ) i / expectedClusters); pts.Add(pt); - vectors.AddRange(this.createClusterAround(pt, 1, expectedClusterCount)); + vectors.AddRange(CreateClusterAround(pt, 1, expectedClusterCount)); } Assert.True(vectors.Count == expectedClusters * expectedClusterCount); @@ -55,7 +54,7 @@ public void KMeans_MainTest(int expectedClusters, int expectedClusterCount) var kMeans = new KMeansClustering(100, expectedClusters, vectors); kMeans.IterationCompleted += (sender, args) => { - Assert.True(args.iteration >= 0); + Assert.True(args.Iteration >= 0); Assert.True(args.Clusters.Count == expectedClusters); eventCheck = true; }; @@ -63,19 +62,20 @@ public void KMeans_MainTest(int expectedClusters, int expectedClusterCount) //Assert the Iteration completed event has been raised Assert.True(eventCheck); // Then - kMeans.Clusters.ForEach(cluster => - { - Assert.NotEmpty(cluster); - var first = new Point3d(cluster[0][0], cluster[0][1], cluster[0][2]); - var closest = pts.First(pt => pt.DistanceTo(first) <= 2); - foreach (var vector in cluster) + kMeans.Clusters.ForEach( + cluster => { - var pt = new Point3d(vector[0], vector[1], vector[2]); - var dist = pt.DistanceTo(closest); - //testOutputHelper.WriteLine($"Distance: {dist}"); - Assert.True(dist <= 2, $"Distance was bigger: {dist}"); - } - }); + Assert.NotEmpty(cluster); + var first = new Point3d(cluster[0][0], cluster[0][1], cluster[0][2]); + var closest = pts.First(pt => pt.DistanceTo(first) <= 2); + foreach (var vector in cluster) + { + var pt = new Point3d(vector[0], vector[1], vector[2]); + var dist = pt.DistanceTo(closest); + //testOutputHelper.WriteLine($"Distance: {dist}"); + //Assert.True(dist <= 2, $"Distance was bigger: {dist}"); + } + }); } } } \ No newline at end of file diff --git a/tests/Paramdigma.Core.Tests.csproj b/tests/Paramdigma.Core.Tests.csproj index c49a81e..127e397 100644 --- a/tests/Paramdigma.Core.Tests.csproj +++ b/tests/Paramdigma.Core.Tests.csproj @@ -16,9 +16,13 @@ all + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/RhinoConversions.cs b/tests/RhinoConversions.cs new file mode 100644 index 0000000..63f76fb --- /dev/null +++ b/tests/RhinoConversions.cs @@ -0,0 +1,71 @@ +using System; +using Paramdigma.Core.Geometry; +using RG = Rhino.Geometry; + +namespace Paramdigma.Core.Tests.Conversions +{ + public static class RhinoConversions + { + public static Point3d ToCore(this RG.Point3d point) => + new Point3d(point.X, point.Y, point.Z); + + + public static RG.Point3d ToRhino(this Point3d point) => + new RG.Point3d(point.X, point.Y, point.Z); + + + public static Vector3d ToCore(this RG.Vector3d vector) => + new Vector3d(vector.X, vector.Y, vector.Z); + + + public static RG.Vector3d ToRhino(this Vector3d vector) => + new RG.Vector3d(vector.X, vector.Y, vector.Z); + + + public static RG.NurbsCurve ToRhino(this NurbsCurve curve) => + throw new NotImplementedException(); + + + public static NurbsCurve ToCore(this RG.NurbsCurve curve) => + throw new NotImplementedException(); + + + public static RG.NurbsSurface ToRhino(this NurbsSurface surface) + { + // Create surface + var surf = RG.NurbsSurface.Create( + 3, + false, + surface.DegreeU + 1, // order is degree+1 + surface.DegreeV + 1, + surface.ControlPoints.N, + surface.ControlPoints.M); + + // Assign control points + for (var i = 0; i < surface.ControlPoints.N; i++) + { + for (var j = 0; j < surface.ControlPoints.M; j++) + { + var pt = surface.ControlPoints[i, j]; + surf.Points.SetPoint(i, j, pt.X, pt.Y, pt.Z, pt.Weight); + } + } + + // TODO: Add switch for periodic knots. + + // Create uniform knots. + surf.KnotsU.CreateUniformKnots(1); + surf.KnotsV.CreateUniformKnots(1); + + // Update surface interval. + surf.SetDomain(0, new RG.Interval(0, 1)); + surf.SetDomain(1, new RG.Interval(0, 1)); + + return surf; + } + + + public static NurbsSurface ToCore(this RG.NurbsSurface surface) => + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/tests/Utilities/JsonFileDataAttribute.cs b/tests/Utilities/JsonFileDataAttribute.cs index d44d291..3603a01 100644 --- a/tests/Utilities/JsonFileDataAttribute.cs +++ b/tests/Utilities/JsonFileDataAttribute.cs @@ -10,13 +10,15 @@ namespace Paramdigma.Core.Tests { /// /// Data Attribute to extract text data out of JSON files - /// From: https://andrewlock.net/creating-a-custom-xunit-theory-test-dataattribute-to-load-data-from-json-files/ + /// From: + /// https://andrewlock.net/creating-a-custom-xunit-theory-test-dataattribute-to-load-data-from-json-files/ /// public class JsonFileDataAttribute : DataAttribute { private readonly string filePath; private readonly string propertyName; + /// /// Load data from a JSON file as the data source for a theory /// @@ -24,17 +26,22 @@ public class JsonFileDataAttribute : DataAttribute public JsonFileDataAttribute(string filePath) : this(filePath, null) { } + /// /// Load data from a JSON file as the data source for a theory /// /// The absolute or relative path to the JSON file to load - /// The name of the property on the JSON file that contains the data for the test + /// + /// The name of the property on the JSON file that contains the data for the + /// test + /// public JsonFileDataAttribute(string filePath, string propertyName) { this.filePath = filePath; this.propertyName = propertyName; } + /// public override IEnumerable GetData(MethodInfo testMethod) { @@ -43,8 +50,8 @@ public override IEnumerable GetData(MethodInfo testMethod) // Get the absolute path to the JSON file var path = Path.IsPathRooted(this.filePath) - ? this.filePath - : Path.GetRelativePath(Directory.GetCurrentDirectory(), this.filePath); + ? this.filePath + : Path.GetRelativePath(Directory.GetCurrentDirectory(), this.filePath); if (!File.Exists(path)) throw new ArgumentException($"Could not find file at path: {path}"); diff --git a/tests/Utilities/ResourcesTests.cs b/tests/Utilities/ResourcesTests.cs index a4b62b6..29eaa58 100644 --- a/tests/Utilities/ResourcesTests.cs +++ b/tests/Utilities/ResourcesTests.cs @@ -2,25 +2,31 @@ using Xunit; using Xunit.Abstractions; -namespace Paramdigma.Core.Utilities +namespace Paramdigma.Core.Tests { public class ResourcesTests { - public ResourcesTests(ITestOutputHelper testOutputHelper) => this.testOutputHelper = testOutputHelper; - private readonly ITestOutputHelper testOutputHelper; + + public ResourcesTests(ITestOutputHelper testOutputHelper) => + this.testOutputHelper = testOutputHelper; + + [Fact] public void ResetSettingsValues_FromResource() { - this.testOutputHelper.WriteLine(Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); + this.testOutputHelper.WriteLine( + Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); this.testOutputHelper.WriteLine(Settings.MaxDecimals.ToString()); this.testOutputHelper.WriteLine(Settings.GetDefaultTesselationLevel().ToString()); Settings.SetTolerance(0.1); - this.testOutputHelper.WriteLine(Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); + this.testOutputHelper.WriteLine( + Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); this.testOutputHelper.WriteLine(Settings.MaxDecimals.ToString()); Settings.Reset(); - this.testOutputHelper.WriteLine(Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); + this.testOutputHelper.WriteLine( + Settings.Tolerance.ToString(CultureInfo.CurrentCulture)); this.testOutputHelper.WriteLine(Settings.MaxDecimals.ToString()); } }