diff options
author | neothemachine <[email protected]> | 2012-12-05 19:19:31 +0100 |
---|---|---|
committer | neothemachine <[email protected]> | 2012-12-05 19:19:31 +0100 |
commit | 7ca002eb1d1510767cd524a800710cf3898ba699 (patch) | |
tree | 7b3bd1dec9f58e32d083b287f44569526761bb32 /ardor3d-math | |
parent | 105f890b5e750aaa5ec63e768e77eca2af8631a4 (diff) | |
parent | 9dd02f103042cb8a196f8a3ed2278da443e345bf (diff) |
Merge branch 'kill_trunk' into dependencies
Diffstat (limited to 'ardor3d-math')
82 files changed, 24074 insertions, 0 deletions
diff --git a/ardor3d-math/.settings/org.eclipse.jdt.core.prefs b/ardor3d-math/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..637bf01 --- /dev/null +++ b/ardor3d-math/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,291 @@ +eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=1
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=120
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/ardor3d-math/.settings/org.eclipse.jdt.ui.prefs b/ardor3d-math/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..6539a7f --- /dev/null +++ b/ardor3d-math/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,115 @@ +cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=true
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=true
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=true
+cleanup.always_use_this_for_non_static_field_access=false
+cleanup.always_use_this_for_non_static_method_access=false
+cleanup.convert_to_enhanced_for_loop=false
+cleanup.correct_indentation=true
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=false
+cleanup.organize_imports=true
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_parentheses_in_expressions=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+cleanup_profile=_ArdorLabs
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_ArdorLabs
+formatter_settings_version=12
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=\#;java;javax;org;com;
+org.eclipse.jdt.ui.javadoc=false
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=0
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param} the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * @author ${user}\r\n *\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\r\n * ${see_to_overridden}\r\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\r\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=true
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=true
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/ardor3d-math/pom.xml b/ardor3d-math/pom.xml new file mode 100644 index 0000000..64bd20b --- /dev/null +++ b/ardor3d-math/pom.xml @@ -0,0 +1,47 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ardor3d</groupId>
+ <artifactId>ardor3d</artifactId>
+ <version>0.9-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ardor3d-math</artifactId>
+ <packaging>bundle</packaging>
+ <name>Ardor 3D Math</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-savable</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymockclassextension</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>
+
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/ColorRGBA.java b/ardor3d-math/src/main/java/com/ardor3d/math/ColorRGBA.java new file mode 100644 index 0000000..ea11e50 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/ColorRGBA.java @@ -0,0 +1,1040 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * ColorRGBA is a 4 component color value (red, green, blue, alpha). The standard range for each individual component is + * [0f, 1f]. Non-standard use of color (for example HDR rendering) may need to use values outside of this range however, + * so the value is not clipped or enforced. + */ +public class ColorRGBA implements Cloneable, Savable, Externalizable, ReadOnlyColorRGBA, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<ColorRGBA> COLOR_POOL = ObjectPool.create(ColorRGBA.class, + MathConstants.maxMathPoolSize); + + /** + * the color black (0, 0, 0, 1). + */ + public static final ReadOnlyColorRGBA BLACK = new ColorRGBA(0f, 0f, 0f, 1f); + /** + * the color black with a zero alpha value (0, 0, 0, 0). + */ + public static final ReadOnlyColorRGBA BLACK_NO_ALPHA = new ColorRGBA(0f, 0f, 0f, 0f); + /** + * the color white (1, 1, 1, 1). + */ + public static final ReadOnlyColorRGBA WHITE = new ColorRGBA(1f, 1f, 1f, 1f); + /** + * the color gray (.2f, .2f, .2f, 1). + */ + public static final ReadOnlyColorRGBA DARK_GRAY = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f); + /** + * the color gray (.5f, .5f, .5f, 1). + */ + public static final ReadOnlyColorRGBA GRAY = new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f); + /** + * the color gray (.8f, .8f, .8f, 1). + */ + public static final ReadOnlyColorRGBA LIGHT_GRAY = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); + /** + * the color red (1, 0, 0, 1). + */ + public static final ReadOnlyColorRGBA RED = new ColorRGBA(1f, 0f, 0f, 1f); + /** + * the color green (0, 1, 0, 1). + */ + public static final ReadOnlyColorRGBA GREEN = new ColorRGBA(0f, 1f, 0f, 1f); + /** + * the color blue (0, 0, 1, 1). + */ + public static final ReadOnlyColorRGBA BLUE = new ColorRGBA(0f, 0f, 1f, 1f); + /** + * the color yellow (1, 1, 0, 1). + */ + public static final ReadOnlyColorRGBA YELLOW = new ColorRGBA(1f, 1f, 0f, 1f); + /** + * the color magenta (1, 0, 1, 1). + */ + public static final ReadOnlyColorRGBA MAGENTA = new ColorRGBA(1f, 0f, 1f, 1f); + /** + * the color cyan (0, 1, 1, 1). + */ + public static final ReadOnlyColorRGBA CYAN = new ColorRGBA(0f, 1f, 1f, 1f); + /** + * the color orange (251/255f, 130/255f, 0, 1). + */ + public static final ReadOnlyColorRGBA ORANGE = new ColorRGBA(251f / 255f, 130f / 255f, 0f, 1f); + /** + * the color brown (65/255f, 40/255f, 25/255f, 1). + */ + public static final ReadOnlyColorRGBA BROWN = new ColorRGBA(65f / 255f, 40f / 255f, 25f / 255f, 1f); + /** + * the color pink (1, 0.68f, 0.68f, 1). + */ + public static final ReadOnlyColorRGBA PINK = new ColorRGBA(1f, 0.68f, 0.68f, 1f); + + protected float _r = 0; + protected float _g = 0; + protected float _b = 0; + protected float _a = 0; + + /** + * Constructs a new, mutable color set to (1, 1, 1, 1). + */ + public ColorRGBA() { + this(1, 1, 1, 1); + } + + /** + * Constructs a new, mutable color set to the (r, g, b, a) values of the provided source color. + * + * @param src + */ + public ColorRGBA(final ReadOnlyColorRGBA src) { + this(src.getRed(), src.getGreen(), src.getBlue(), src.getAlpha()); + } + + /** + * Constructs a new color set to (r, g, b, a). + * + * @param r + * @param g + * @param b + * @param a + */ + public ColorRGBA(final float r, final float g, final float b, final float a) { + _r = r; + _g = g; + _b = b; + _a = a; + } + + @Override + public float getRed() { + return _r; + } + + @Override + public float getGreen() { + return _g; + } + + @Override + public float getBlue() { + return _b; + } + + @Override + public float getAlpha() { + return _a; + } + + /** + * @param index + * @return r value if index == 0, g value if index == 1, b value if index == 2 or a value if index == 3 + * @throws IllegalArgumentException + * if index is not one of 0, 1, 2, 3. + */ + @Override + public float getValue(final int index) { + switch (index) { + case 0: + return getRed(); + case 1: + return getGreen(); + case 2: + return getBlue(); + case 3: + return getAlpha(); + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + + /** + * @param index + * which field index in this color to set. + * @param value + * to set to one of r, g, b or a. + * @throws IllegalArgumentException + * if index is not one of 0, 1, 2, 3. + */ + public void setValue(final int index, final float value) { + switch (index) { + case 0: + setRed(value); + return; + case 1: + setGreen(value); + return; + case 2: + setBlue(value); + return; + case 3: + setAlpha(value); + return; + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + + /** + * Stores the float values of this color in the given float array. + * + * @param store + * if null, a new float[4] array is created. + * @return the float array + * @throws NullPointerException + * if store is null. + * @throws ArrayIndexOutOfBoundsException + * if store is not at least length 4. + */ + @Override + public float[] toArray(float[] store) { + if (store == null) { + store = new float[4]; + } + // do last first to ensure size is correct before any edits occur. + store[3] = getAlpha(); + store[2] = getBlue(); + store[1] = getGreen(); + store[0] = getRed(); + return store; + } + + /** + * Sets the red component of this color to the given float value. + * + * @param r + * new red value, generally should be in the range [0.0f, 1.0f] + */ + public void setRed(final float r) { + _r = r; + } + + /** + * Sets the green component of this color to the given float value. + * + * @param g + * new green value, generally should be in the range [0.0f, 1.0f] + */ + public void setGreen(final float g) { + _g = g; + } + + /** + * Sets the blue component of this color to the given float value. + * + * @param b + * new blue value, generally should be in the range [0.0f, 1.0f] + */ + public void setBlue(final float b) { + _b = b; + } + + /** + * Sets the alpha component of this color to the given float value. Consider that an alpha of 1.0f means opaque (can + * not see through) and 0.0f means transparent. + * + * @param a + * new alpha value, generally should be in the range [0.0f, 1.0f] + */ + public void setAlpha(final float a) { + _a = a; + } + + /** + * Sets the value of this color to (r, g, b, a) + * + * @param r + * new red value, generally should be in the range [0.0f, 1.0f] + * @param g + * new green value, generally should be in the range [0.0f, 1.0f] + * @param b + * new blue value, generally should be in the range [0.0f, 1.0f] + * @param a + * new alpha value, generally should be in the range [0.0f, 1.0f] + * @return this color for chaining + */ + public ColorRGBA set(final float r, final float g, final float b, final float a) { + setRed(r); + setGreen(g); + setBlue(b); + setAlpha(a); + return this; + } + + /** + * Sets the value of this color to the (r, g, b, a) values of the provided source color. + * + * @param source + * @return this color for chaining + * @throws NullPointerException + * if source is null. + */ + public ColorRGBA set(final ReadOnlyColorRGBA source) { + _r = source.getRed(); + _g = source.getGreen(); + _b = source.getBlue(); + _a = source.getAlpha(); + return this; + } + + /** + * Sets the value of this color to (0, 0, 0, 0) + * + * @return this color for chaining + */ + public ColorRGBA zero() { + return set(0, 0, 0, 0); + } + + /** + * Brings all values (r,g,b,a) into the range [0.0f, 1.0f]. If a value is above or below this range it is replaced + * with the appropriate end of the range. + * + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + */ + @Override + public ColorRGBA clamp(final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(this); + } else if (result != this) { + result.set(this); + } + + if (result._r < 0.0f) { + result._r = 0.0f; + } else if (result._r > 1.0f) { + result._r = 1.0f; + } + + if (result._g < 0.0f) { + result._g = 0.0f; + } else if (result._g > 1.0f) { + result._g = 1.0f; + } + + if (result._b < 0.0f) { + result._b = 0.0f; + } else if (result._b > 1.0f) { + result._b = 1.0f; + } + + if (result._a < 0.0f) { + result._a = 0.0f; + } else if (result._a > 1.0f) { + result._a = 1.0f; + } + + return result; + } + + /** + * Brings all values (r,g,b,a) into the range [0.0f, 1.0f]. If a value is above or below this range it is replaced + * with the appropriate end of the range. + * + * @return this color for chaining + */ + public ColorRGBA clampLocal() { + return clamp(this); + } + + /** + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a random, mutable opaque color. + */ + public static ColorRGBA randomColor(final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + result._r = MathUtils.nextRandomFloat(); + result._g = MathUtils.nextRandomFloat(); + result._b = MathUtils.nextRandomFloat(); + result._a = 1.0f; + return result; + } + + /** + * @return this color, stored as an integer by converting the values to the range [0, 255] and combining them as + * single byte values into a 4 byte int in the order ARGB. Note that this method expects color values in the + * [0.0f, 1.0f] range. + */ + @Override + public int asIntARGB() { + final int argb = ((int) (_a * 255) & 0xFF) << 24 | ((int) (_r * 255) & 0xFF) << 16 + | ((int) (_g * 255) & 0xFF) << 8 | (int) (_b * 255) & 0xFF; + return argb; + } + + /** + * @return this color, stored as an integer by converting the values to the range [0, 255] and combining them as + * single byte values into a 4 byte int in the order RGBA. Note that this method expects color values in the + * [0.0f, 1.0f] range. + */ + @Override + public int asIntRGBA() { + final int rgba = ((int) (_r * 255) & 0xFF) << 24 | ((int) (_g * 255) & 0xFF) << 16 + | ((int) (_b * 255) & 0xFF) << 8 | (int) (_a * 255) & 0xFF; + return rgba; + } + + /** + * Reads a color, packed into a 4 byte int as 1 byte values in the order ARGB. These byte values are normalized to + * the range [0.0f, 1.0f] + * + * @param color + * @return this color for chaining + */ + public ColorRGBA fromIntARGB(final int color) { + _a = ((byte) (color >> 24) & 0xFF) / 255f; + _r = ((byte) (color >> 16) & 0xFF) / 255f; + _g = ((byte) (color >> 8) & 0xFF) / 255f; + _b = ((byte) color & 0xFF) / 255f; + return this; + } + + /** + * Reads a color, packed into a 4 byte int as 1 byte values in the order RGBA. These byte values are normalized to + * the range [0.0f, 1.0f] + * + * @param color + * @return this color for chaining + */ + public ColorRGBA fromIntRGBA(final int color) { + _r = ((byte) (color >> 24) & 0xFF) / 255f; + _g = ((byte) (color >> 16) & 0xFF) / 255f; + _b = ((byte) (color >> 8) & 0xFF) / 255f; + _a = ((byte) color & 0xFF) / 255f; + return this; + } + + /** + * @return this string as a hex value (#RRGGBBAA). e.g. opaque blue is #0000ffff + */ + @Override + public String asHexRRGGBBAA() { + final StringBuilder sb = new StringBuilder("#"); + final String red = Integer.toHexString(Math.round(MathUtils.clamp(getRed(), 0f, 1f) * 255)); + final String green = Integer.toHexString(Math.round(MathUtils.clamp(getGreen(), 0f, 1f) * 255)); + final String blue = Integer.toHexString(Math.round(MathUtils.clamp(getBlue(), 0f, 1f) * 255)); + final String alpha = Integer.toHexString(Math.round(MathUtils.clamp(getAlpha(), 0f, 1f) * 255)); + if (red.length() < 2) { + sb.append("0"); + } + sb.append(red); + if (green.length() < 2) { + sb.append("0"); + } + sb.append(green); + if (blue.length() < 2) { + sb.append("0"); + } + sb.append(blue); + if (alpha.length() < 2) { + sb.append("0"); + } + sb.append(alpha); + return sb.toString(); + } + + /** + * Adds the given values to those of this color and returns them in store. + * + * @param r + * @param g + * @param b + * @param a + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return (this.r + r, this.g + g, this.b + b, this.a + a) + */ + @Override + public ColorRGBA add(final float r, final float g, final float b, final float a, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + return result.set(getRed() + r, getGreen() + g, getBlue() + b, getAlpha() + a); + } + + /** + * Increments the values of this color with the given r, g, b and a values. + * + * @param r + * @param g + * @param b + * @param a + * @return this color for chaining + */ + public ColorRGBA addLocal(final float r, final float g, final float b, final float a) { + return set(getRed() + r, getGreen() + g, getBlue() + b, getAlpha() + a); + } + + /** + * Adds the values of the given source color to those of this color and returns them in store. + * + * @param source + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return (this.r + source.r, this.g + source.g, this.b + source.b, this.a + source.a) + * @throws NullPointerException + * if source is null. + */ + @Override + public ColorRGBA add(final ReadOnlyColorRGBA source, final ColorRGBA store) { + return add(source.getRed(), source.getGreen(), source.getBlue(), source.getAlpha(), store); + } + + /** + * Increments the values of this color with the r, g, b and a values of the given color. + * + * @param source + * @return this color for chaining + * @throws NullPointerException + * if source is null. + */ + public ColorRGBA addLocal(final ReadOnlyColorRGBA source) { + return addLocal(source.getRed(), source.getGreen(), source.getBlue(), source.getAlpha()); + } + + /** + * Subtracts the given values from those of this color and returns them in store. + * + * @param r + * @param g + * @param b + * @param a + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return (this.r - r, this.g - g, this.b - b, this.a - a) + */ + @Override + public ColorRGBA subtract(final float r, final float g, final float b, final float a, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + return result.set(getRed() - r, getGreen() - g, getBlue() - b, getAlpha() - a); + } + + /** + * Decrements the values of this color by the given r, g, b and a values. + * + * @param r + * @param g + * @param b + * @param a + * @return this color for chaining + */ + public ColorRGBA subtractLocal(final float r, final float g, final float b, final float a) { + return set(getRed() - r, getGreen() - g, getBlue() - b, getAlpha() - a); + } + + /** + * Subtracts the values of the given source color from those of this color and returns them in store. + * + * @param source + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return (this.r - source.r, this.g - source.g, this.b - source.b, this.a - source.a) + * @throws NullPointerException + * if source is null. + */ + @Override + public ColorRGBA subtract(final ReadOnlyColorRGBA source, final ColorRGBA store) { + return subtract(source.getRed(), source.getGreen(), source.getBlue(), source.getAlpha(), store); + } + + /** + * Decrements the values of this color by the r, g, b and a values from the given source color. + * + * @param source + * @return this color for chaining + * @throws NullPointerException + * if source is null. + */ + public ColorRGBA subtractLocal(final ReadOnlyColorRGBA source) { + return subtractLocal(source.getRed(), source.getGreen(), source.getBlue(), source.getAlpha()); + } + + /** + * Multiplies the values of this color by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a new color (this.r * scalar, this.g * scalar, this.b * scalar, this.a * scalar) + */ + @Override + public ColorRGBA multiply(final float scalar, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + return result.set(getRed() * scalar, getGreen() * scalar, getBlue() * scalar, getAlpha() * scalar); + } + + /** + * Internally modifies the values of this color by multiplying them each by the given scalar value. + * + * @param scalar + * @return this color for chaining + * + * . + */ + public ColorRGBA multiplyLocal(final float scalar) { + return set(getRed() * scalar, getGreen() * scalar, getBlue() * scalar, getAlpha() * scalar); + } + + /** + * Multiplies the values of this color by the given scalar value and returns the result in store. + * + * @param scale + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a new color (this.r * scale.r, this.g * scale.g, this.b * scale.b, this.a * scale.a) + */ + @Override + public ColorRGBA multiply(final ReadOnlyColorRGBA scale, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + return result.set(getRed() * scale.getRed(), getGreen() * scale.getGreen(), getBlue() * scale.getBlue(), + getAlpha() * scale.getAlpha()); + } + + /** + * Internally modifies the values of this color by multiplying them each by the given scale values. + * + * @param scale + * @return this color for chaining + */ + public ColorRGBA multiplyLocal(final ReadOnlyColorRGBA scale) { + return set(getRed() * scale.getRed(), getGreen() * scale.getGreen(), getBlue() * scale.getBlue(), getAlpha() + * scale.getAlpha()); + } + + /** + * Divides the values of this color by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a new color (this.r / scalar, this.g / scalar, this.b / scalar, this.a / scalar) + */ + @Override + public ColorRGBA divide(final float scalar, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + return result.set(getRed() / scalar, getGreen() / scalar, getBlue() / scalar, getAlpha() / scalar); + } + + /** + * Internally modifies the values of this color by dividing them each by the given scalar value. + * + * @param scalar + * @return this color for chaining + * @throws ArithmeticException + * if scalar is 0 + */ + public ColorRGBA divideLocal(final float scalar) { + final float invScalar = 1.0f / scalar; + + return set(getRed() * invScalar, getGreen() * invScalar, getBlue() * invScalar, getAlpha() * invScalar); + } + + /** + * Divides the values of this color by the given scale values and returns the result in store. + * + * @param scale + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a new color (this.r / scale.r, this.g / scale.g, this.b / scale.b, this.a / scale.a) + */ + @Override + public ColorRGBA divide(final ReadOnlyColorRGBA scale, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + return result.set(getRed() / scale.getRed(), getGreen() / scale.getGreen(), getBlue() / scale.getBlue(), + getAlpha() / scale.getAlpha()); + } + + /** + * Internally modifies the values of this color by dividing them each by the given scale values. + * + * @param scale + * @return this color for chaining + */ + public ColorRGBA divideLocal(final ReadOnlyColorRGBA scale) { + return set(getRed() / scale.getRed(), getGreen() / scale.getGreen(), getBlue() / scale.getBlue(), getAlpha() + / scale.getAlpha()); + } + + /** + * Performs a linear interpolation between this color and the given end color, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the current value of this color and if it is + * closer to 1, the result will be closer to the end value. + * + * @param endColor + * @param scalar + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a new mutable color as described above. + * @throws NullPointerException + * if endVec is null. + */ + @Override + public ColorRGBA lerp(final ReadOnlyColorRGBA endColor, final float scalar, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + final float r = (1.0f - scalar) * getRed() + scalar * endColor.getRed(); + final float g = (1.0f - scalar) * getGreen() + scalar * endColor.getGreen(); + final float b = (1.0f - scalar) * getBlue() + scalar * endColor.getBlue(); + final float a = (1.0f - scalar) * getAlpha() + scalar * endColor.getAlpha(); + return result.set(r, g, b, a); + } + + /** + * Performs a linear interpolation between this color and the given end color, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the current value of this color and if it is + * closer to 1, the result will be closer to the end value. The result is stored back in this color. + * + * @param endColor + * @param scalar + * @return this color for chaining + * @throws NullPointerException + * if endVec is null. + */ + public ColorRGBA lerpLocal(final ReadOnlyColorRGBA endColor, final float scalar) { + setRed((1.0f - scalar) * getRed() + scalar * endColor.getRed()); + setGreen((1.0f - scalar) * getGreen() + scalar * endColor.getGreen()); + setBlue((1.0f - scalar) * getBlue() + scalar * endColor.getBlue()); + setAlpha((1.0f - scalar) * getAlpha() + scalar * endColor.getAlpha()); + return this; + } + + /** + * Performs a linear interpolation between the given begin and end colors, using the given scalar as a percent. iow, + * if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the result + * will be closer to the end value. + * + * @param beginColor + * @param endColor + * @param scalar + * the scalar as a percent. + * @param store + * the color to store the result in for return. If null, a new color object is created and returned. + * @return a new mutable color as described above. + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public static ColorRGBA lerp(final ReadOnlyColorRGBA beginColor, final ReadOnlyColorRGBA endColor, + final float scalar, final ColorRGBA store) { + ColorRGBA result = store; + if (result == null) { + result = new ColorRGBA(); + } + + final float r = (1.0f - scalar) * beginColor.getRed() + scalar * endColor.getRed(); + final float g = (1.0f - scalar) * beginColor.getGreen() + scalar * endColor.getGreen(); + final float b = (1.0f - scalar) * beginColor.getBlue() + scalar * endColor.getBlue(); + final float a = (1.0f - scalar) * beginColor.getAlpha() + scalar * endColor.getAlpha(); + return result.set(r, g, b, a); + } + + /** + * Performs a linear interpolation between the given begin and end colors, using the given scalar as a percent. iow, + * if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the result + * will be closer to the end value. The result is stored back in this color. + * + * @param beginColor + * @param endColor + * @param changeAmnt + * the scalar as a percent. + * @return this color for chaining + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public ColorRGBA lerpLocal(final ReadOnlyColorRGBA beginColor, final ReadOnlyColorRGBA endColor, final float scalar) { + setRed((1.0f - scalar) * beginColor.getRed() + scalar * endColor.getRed()); + setGreen((1.0f - scalar) * beginColor.getGreen() + scalar * endColor.getGreen()); + setBlue((1.0f - scalar) * beginColor.getBlue() + scalar * endColor.getBlue()); + setAlpha((1.0f - scalar) * beginColor.getAlpha() + scalar * endColor.getAlpha()); + return this; + } + + /** + * Check a color... if it is null or its values are NaN or infinite, return false. Else return true. + * + * @param color + * the color to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyColorRGBA color) { + if (color == null) { + return false; + } + if (Float.isNaN(color.getRed()) || Float.isNaN(color.getGreen()) || Float.isNaN(color.getBlue()) + || Float.isNaN(color.getAlpha())) { + return false; + } + if (Float.isInfinite(color.getRed()) || Float.isInfinite(color.getGreen()) || Float.isInfinite(color.getBlue()) + || Float.isInfinite(color.getAlpha())) { + return false; + } + return true; + } + + /** + * @return the string representation of this color. + */ + @Override + public String toString() { + return "com.ardor3d.math.ColorRGBA [R=" + getRed() + ", G=" + getGreen() + ", B=" + getBlue() + ", A=" + + getAlpha() + "]"; + } + + /** + * @return returns a unique code for this color object based on its values. If two colors are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + final int r = Float.floatToIntBits(getRed()); + result += 31 * result + r; + + final int g = Float.floatToIntBits(getGreen()); + result += 31 * result + g; + + final int b = Float.floatToIntBits(getBlue()); + result += 31 * result + b; + + final int a = Float.floatToIntBits(getAlpha()); + result += 31 * result + a; + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this color and the provided color have the same r, g, b and a values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyColorRGBA)) { + return false; + } + final ReadOnlyColorRGBA comp = (ReadOnlyColorRGBA) o; + return getRed() == comp.getRed() && getGreen() == comp.getGreen() && getBlue() == comp.getBlue() + && getAlpha() == comp.getAlpha(); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public ColorRGBA clone() { + return new ColorRGBA(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends ColorRGBA> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(getRed(), "r", 1); + capsule.write(getGreen(), "g", 1); + capsule.write(getBlue(), "b", 1); + capsule.write(getAlpha(), "a", 1); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + setRed(capsule.readFloat("r", 1)); + setGreen(capsule.readFloat("g", 1)); + setBlue(capsule.readFloat("b", 1)); + setAlpha(capsule.readFloat("a", 1)); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setRed(in.readFloat()); + setGreen(in.readFloat()); + setBlue(in.readFloat()); + setAlpha(in.readFloat()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeFloat(getRed()); + out.writeFloat(getGreen()); + out.writeFloat(getBlue()); + out.writeFloat(getAlpha()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of ColorRGBA that is intended for temporary use in calculations and so forth. Multiple calls + * to the method should return instances of this class that are not currently in use. + */ + public final static ColorRGBA fetchTempInstance() { + if (MathConstants.useMathPools) { + return ColorRGBA.COLOR_POOL.fetch(); + } else { + return new ColorRGBA(); + } + } + + /** + * Releases a ColorRGBA back to be used by a future call to fetchTempInstance. TAKE CARE: this ColorRGBA object + * should no longer have other classes referencing it or "Bad Things" will happen. + * + * @param color + * the ColorRGBA to release. + */ + public final static void releaseTempInstance(final ColorRGBA color) { + if (MathConstants.useMathPools) { + ColorRGBA.COLOR_POOL.release(color); + } + } + + /** + * Parses the given string for a color value. Currently we support hex notation - # followed by 1, 2, 3, 4, 6, or 8 + * chars 0-9A-F. + * <ul> + * <li>chars: pattern - notes</li> + * <li>1: V - RGB is parsed as val/15, A=1</li> + * <li>2: VA - RGB is parsed as V/15, A as A/15</li> + * <li>3: RGB - RGB is parsed as R/15, G/15, B/15, A=1</li> + * <li>4: RGB - RGBA are parsed as R/15, G/15, B/15, A/15</li> + * <li>6: RRGGBB - RGB is parsed as RR/255, GG/255, BB/255, A=1</li> + * <li>8: RRGGBBAA - RGBA is parsed as RR/255, GG/255, BB/255, AA/255</li> + * </ul> + * + * @param colorString + * @param store + * @return + */ + public static ColorRGBA parseColor(final String colorString, final ColorRGBA store) { + ColorRGBA rVal = store; + if (rVal == null) { + rVal = new ColorRGBA(); + } + + // XXX: should we parse words too? eg 'red'... + if (!colorString.startsWith("#")) { + throw new IllegalArgumentException("must start with #."); + } + + float r = 1, g = 1, b = 1, a = 1; + final int length = colorString.length(); + if (length == 2) { + r = Integer.parseInt(colorString.substring(1, 2), 16) / 15f; + g = b = r; + a = 1; + } else if (length == 3) { + r = Integer.parseInt(colorString.substring(1, 2), 16) / 15f; + g = b = r; + a = Integer.parseInt(colorString.substring(2, 3), 16) / 15f; + } else if (length == 4) { + r = Integer.parseInt(colorString.substring(1, 2), 16) / 15f; + g = Integer.parseInt(colorString.substring(2, 3), 16) / 15f; + b = Integer.parseInt(colorString.substring(3, 4), 16) / 15f; + a = 1; + } else if (length == 5) { + r = Integer.parseInt(colorString.substring(1, 2), 16) / 15f; + g = Integer.parseInt(colorString.substring(2, 3), 16) / 15f; + b = Integer.parseInt(colorString.substring(3, 4), 16) / 15f; + a = Integer.parseInt(colorString.substring(4, 5), 16) / 15f; + } else if (length == 7) { + r = Integer.parseInt(colorString.substring(1, 3), 16) / 255f; + g = Integer.parseInt(colorString.substring(3, 5), 16) / 255f; + b = Integer.parseInt(colorString.substring(5, 7), 16) / 255f; + a = 1; + } else if (length == 9) { + r = Integer.parseInt(colorString.substring(1, 3), 16) / 255f; + g = Integer.parseInt(colorString.substring(3, 5), 16) / 255f; + b = Integer.parseInt(colorString.substring(5, 7), 16) / 255f; + a = Integer.parseInt(colorString.substring(7, 9), 16) / 255f; + } else { + throw new IllegalArgumentException("unsupported value, must be 1, 2, 3, 4, 5, 7 or 9 hexvalues: " + + colorString); + } + rVal.set(r, g, b, a); + + return rVal; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/FastMath.java b/ardor3d-math/src/main/java/com/ardor3d/math/FastMath.java new file mode 100644 index 0000000..6ef29eb --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/FastMath.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +/** + * A "close approximation" class for Math operations. + * + * References: + * <ul> + * <li>http://www.devmaster.net/forums/showthread.php?t=5784</li> + * <li>http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf</li> + * <li>http://stackoverflow.com/questions/523531/fast-transcendent-trigonometric-functions-for-java</li> + * <li>http://www.lightsoft.co.uk/PD/stu/stuchat37.html</li> + * <li>http://wiki.java.net/bin/view/Games/JeffGems</li> + * </ul> + * + * NB: With current improvements in hardware, I don't recommend using these and they will likely be deprecated/removed + * in the future. + */ +public enum FastMath { + ; + + public final static double EPSILON_SIN = 0.0011d; + public final static double EPSILON_COS = 0.0011d; + public final static double EPSILON_SIN2COS2 = 0.002d; + public final static double EPSILON_ASIN = 0.0014d; + public final static double EPSILON_ACOS = 0.0014d; + public final static double EPSILON_ATAN = 0.005d; // needs to be improved + public final static double EPSILON_SQRT = 0.025d; // guess + + private final static double _sin_a = -4 / MathUtils.SQUARED_PI; + private final static double _sin_b = 4 / MathUtils.PI; + private final static double _sin_p = 9d / 40; + + private final static double _asin_a = -0.0481295276831013447d; + private final static double _asin_b = -0.343835993947915197d; + private final static double _asin_c = 0.962761848425913169d; + private final static double _asin_d = 1.00138940860107040d; + + private final static double _atan_a = 0.280872d; + + /** sin: [-œÄ,œÄ] -> [-1,1] */ + public final static double sin(double x) { + x = FastMath._sin_a * x * Math.abs(x) + FastMath._sin_b * x; + return FastMath._sin_p * (x * Math.abs(x) - x) + x; + } + + /** cos: [-œÄ,œÄ] -> [-1,1] */ + public final static double cos(final double x) { + return sin(x + (x > MathUtils.HALF_PI ? -MathUtils.THREE_PI_HALVES : MathUtils.HALF_PI)); + } + + /** tan: [-œÄ,œÄ] \ {-œÄ/2,œÄ/2} -> R */ + public final static double tan(final double x) { + return sin(x) / cos(x); + } + + /** asin: [-1,1] -> [-œÄ/2,œÄ/2] */ + public final static double asin(final double x) { + return x * (Math.abs(x) * (Math.abs(x) * FastMath._asin_a + FastMath._asin_b) + FastMath._asin_c) + + Math.signum(x) * (FastMath._asin_d - Math.sqrt(1 - x * x)); + } + + /** acos: [-1,1] -> [0,œÄ] */ + public final static double acos(final double x) { + return MathUtils.HALF_PI - asin(x); + } + + /** atan: (-‚à û,‚à û) -> (-œÄ/2,œÄ/2) */ + public final static double atan(final double x) { + return Math.abs(x) < 1 ? x / (1 + FastMath._atan_a * x * x) : Math.signum(x) * MathUtils.HALF_PI - x + / (x * x + FastMath._atan_a); + } + + /** inverseSqrt: (0,‚à û) -> (0,‚à û) **/ + public final static double inverseSqrt(double x) { + final double xhalves = 0.5d * x; + x = Double.longBitsToDouble(0x5FE6EB50C7B537AAl - (Double.doubleToRawLongBits(x) >> 1)); + return x * (1.5d - xhalves * x * x); // more iterations possible + } + + public final static double sqrt(final double x) { + return x * inverseSqrt(x); + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/InvalidTransformException.java b/ardor3d-math/src/main/java/com/ardor3d/math/InvalidTransformException.java new file mode 100644 index 0000000..3d38c89 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/InvalidTransformException.java @@ -0,0 +1,23 @@ + +package com.ardor3d.math; + +public class InvalidTransformException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public InvalidTransformException() { + super(); + } + + public InvalidTransformException(final String desc) { + super(desc); + } + + public InvalidTransformException(final Throwable cause) { + super(cause); + } + + public InvalidTransformException(final String desc, final Throwable cause) { + super(desc, cause); + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Line3.java b/ardor3d-math/src/main/java/com/ardor3d/math/Line3.java new file mode 100644 index 0000000..453f48a --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Line3.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import com.ardor3d.math.type.ReadOnlyLine3; +import com.ardor3d.math.type.ReadOnlyVector3; + +public class Line3 extends Line3Base implements ReadOnlyLine3, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Line3> LINE3_POOL = ObjectPool.create(Line3.class, MathConstants.maxMathPoolSize); + + /** + * Constructs a new line with an origin at (0,0,0) and a direction of (0,0,1). + */ + public Line3() { + super(Vector3.ZERO, Vector3.UNIT_Z); + } + + /** + * Constructs a new line using the supplied origin point and unit length direction vector + * + * @param origin + * the origin of the line. + * @param direction + * the direction of the line. Should be of unit length. + */ + public Line3(final ReadOnlyVector3 origin, final ReadOnlyVector3 direction) { + super(origin, direction); + } + + /** + * Constructs a new line using the supplied source line + * + * @param source + */ + public Line3(final ReadOnlyLine3 source) { + super(source.getOrigin(), source.getDirection()); + } + + /** + * Copies the values of the given source line into this line. + * + * @param source + * @return this line for chaining + * @throws NullPointerException + * if source is null. + */ + public Line3 set(final ReadOnlyLine3 source) { + _origin.set(source.getOrigin()); + _direction.set(source.getDirection()); + return this; + } + + /** + * @param point + * @param store + * if not null, the closest point is stored in this param + * @return the squared distance from this line to the given point. + * @throws NullPointerException + * if the point is null. + */ + public double distanceSquared(final ReadOnlyVector3 point, final Vector3 store) { + final Vector3 vectorA = Vector3.fetchTempInstance(); + vectorA.set(point).subtractLocal(_origin); + + // Note: assumes direction is normalized + final double t0 = _direction.dot(vectorA); + // d = |P - (O + t*D)| + vectorA.set(_direction).multiplyLocal(t0); + vectorA.addLocal(_origin); + + // Save away the closest point if requested. + if (store != null) { + store.set(vectorA); + } + + point.subtract(vectorA, vectorA); + final double lSQ = vectorA.lengthSquared(); + Vector3.releaseTempInstance(vectorA); + return lSQ; + } + + /** + * Check a line... if it is null or the values of its origin or direction are NaN or infinite, return false. Else + * return true. + * + * @param line + * the line to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyLine3 line) { + if (line == null) { + return false; + } + + return Vector3.isValid(line.getDirection()) && Vector3.isValid(line.getOrigin()); + } + + /** + * @return the string representation of this line. + */ + @Override + public String toString() { + return "com.ardor3d.math.Line3 [Origin: " + _origin + " - Direction: " + _direction + "]"; + } + + /** + * @param o + * the object to compare for equality + * @return true if this line and the provided line have the same constant and normal values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyLine3)) { + return false; + } + final ReadOnlyLine3 comp = (ReadOnlyLine3) o; + return _origin.equals(comp.getOrigin()) && _direction.equals(comp.getDirection()); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Line3 clone() { + return new Line3(this); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Line3 that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Line3 fetchTempInstance() { + if (MathConstants.useMathPools) { + return LINE3_POOL.fetch(); + } else { + return new Line3(); + } + } + + /** + * Releases a Line3 back to be used by a future call to fetchTempInstance. TAKE CARE: this Line3 object should no + * longer have other classes referencing it or "Bad Things" will happen. + * + * @param line + * the Line3 to release. + */ + public final static void releaseTempInstance(final Line3 line) { + if (MathConstants.useMathPools) { + LINE3_POOL.release(line); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Line3Base.java b/ardor3d-math/src/main/java/com/ardor3d/math/Line3Base.java new file mode 100644 index 0000000..25f34fa --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Line3Base.java @@ -0,0 +1,130 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public abstract class Line3Base implements Savable, Externalizable {
+
+ protected final Vector3 _origin = new Vector3();
+ protected final Vector3 _direction = new Vector3();
+
+ public Line3Base(final ReadOnlyVector3 origin, final ReadOnlyVector3 direction) {
+ _origin.set(origin);
+ _direction.set(direction);
+ }
+
+ /**
+ * @return this line's origin point as a readable vector
+ */
+ public ReadOnlyVector3 getOrigin() {
+ return _origin;
+ }
+
+ /**
+ * @return this line's direction as a readable vector
+ */
+ public ReadOnlyVector3 getDirection() {
+ return _direction;
+ }
+
+ /**
+ * Sets the line's origin point to the values of the given vector.
+ *
+ * @param origin
+ * @throws NullPointerException
+ * if normal is null.
+ */
+ public void setOrigin(final ReadOnlyVector3 origin) {
+ _origin.set(origin);
+ }
+
+ /**
+ * Sets the line's direction to the values of the given vector.
+ *
+ * @param direction
+ * @throws NullPointerException
+ * if direction is null.
+ */
+ public void setDirection(final ReadOnlyVector3 direction) {
+ _direction.set(direction);
+ }
+
+ /**
+ * @return returns a unique code for this line3base object based on its values. If two line3base objects are
+ * numerically equal, they will return the same hash code value.
+ */
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result += 31 * result + _origin.hashCode();
+ result += 31 * result + _direction.hashCode();
+
+ return result;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public Class<? extends Line3Base> getClassTag() {
+ return this.getClass();
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_origin, "origin", new Vector3(Vector3.ZERO));
+ capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z));
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _origin.set((Vector3) capsule.readSavable("origin", new Vector3(Vector3.ZERO)));
+ _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z)));
+ }
+
+ // /////////////////
+ // Methods for Externalizable
+ // /////////////////
+
+ /**
+ * Used with serialization. Not to be called manually.
+ *
+ * @param in
+ * ObjectInput
+ * @throws IOException
+ * @throws ClassNotFoundException
+ */
+ public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+ setOrigin((Vector3) in.readObject());
+ setDirection((Vector3) in.readObject());
+ }
+
+ /**
+ * Used with serialization. Not to be called manually.
+ *
+ * @param out
+ * ObjectOutput
+ * @throws IOException
+ */
+ public void writeExternal(final ObjectOutput out) throws IOException {
+ out.writeObject(_origin);
+ out.writeObject(_direction);
+ }
+
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/LineSegment3.java b/ardor3d-math/src/main/java/com/ardor3d/math/LineSegment3.java new file mode 100644 index 0000000..aa62875 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/LineSegment3.java @@ -0,0 +1,334 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyLineSegment3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * LineSegment describes a line with a discrete length with an "origin" in the center, extending in the given + * "direction" and it's opposite by an "extent" amount. + */ +public class LineSegment3 extends Line3Base implements ReadOnlyLineSegment3, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<LineSegment3> LINESEG3_POOL = ObjectPool.create(LineSegment3.class, + MathConstants.maxMathPoolSize); + + protected double _extent; + + /** + * Constructs a new segment segment with an origin at (0,0,0) a direction of (0,0,1) and an extent of 0.5. + */ + public LineSegment3() { + super(Vector3.ZERO, Vector3.UNIT_Z); + _extent = 0.5; + } + + /** + * Copy constructor. + * + * @param source + * the line segment to copy from. + */ + public LineSegment3(final ReadOnlyLineSegment3 source) { + this(source.getOrigin(), source.getDirection(), source.getExtent()); + } + + /** + * Constructs a new segment segment using the supplied origin point, unit length direction vector and extent + * + * @param origin + * @param direction + * - unit length + * @param extent + */ + public LineSegment3(final ReadOnlyVector3 origin, final ReadOnlyVector3 direction, final double extent) { + super(origin, direction); + _extent = extent; + } + + /** + * Constructs a new segment segment using the supplied start and end points + * + * @param start + * @param end + */ + public LineSegment3(final ReadOnlyVector3 start, final ReadOnlyVector3 end) { + this(); + _origin.set(start).addLocal(end).multiplyLocal(0.5); + _direction.set(end).subtractLocal(start); + _extent = 0.5 * _direction.length(); + _direction.normalizeLocal(); + } + + /** + * Copies the values of the given source segment into this segment. + * + * @param source + * @return this segment for chaining + * @throws NullPointerException + * if source is null. + */ + public LineSegment3 set(final ReadOnlyLineSegment3 source) { + _origin.set(source.getOrigin()); + _direction.set(source.getDirection()); + return this; + } + + /** + * @return this segment's extent value + */ + @Override + public double getExtent() { + return _extent; + } + + /** + * Sets the segment's extent to the provided value. + * + * @param extent + */ + public void setExtent(final double extent) { + _extent = extent; + } + + public Vector3 getPositiveEnd(final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + result.set(getDirection()).multiplyLocal(_extent); + result.addLocal(getOrigin()); + return result; + } + + public Vector3 getNegativeEnd(final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + result.set(getDirection()).multiplyLocal(-_extent); + result.addLocal(getOrigin()); + return result; + } + + /** + * @param point + * @param store + * if not null, the closest point is stored in this param + * @return the squared distance from this segment to the given point. + * @throws NullPointerException + * if the point is null. + */ + @Override + public double distanceSquared(final ReadOnlyVector3 point, final Vector3 store) { + final Vector3 vectorA = Vector3.fetchTempInstance(); + vectorA.set(point).subtractLocal(_origin); + + // Note: assumes direction is normalized + final double t0 = _direction.dot(vectorA); + + if (-_extent < t0) { + if (t0 < _extent) { + // d = |P - (O + t*D)| + vectorA.set(getDirection()).multiplyLocal(t0); + vectorA.addLocal(getOrigin()); + } else { + // ray is closest to positive (end) end point + getPositiveEnd(vectorA); + } + } else { + // ray is closest to negative (start) end point + getNegativeEnd(vectorA); + } + + // Save away the closest point if requested. + if (store != null) { + store.set(vectorA); + } + + point.subtract(vectorA, vectorA); + final double lSQ = vectorA.lengthSquared(); + Vector3.releaseTempInstance(vectorA); + return lSQ; + } + + /** + * + * @param position + * a random position lying somewhere on this line segment. + */ + public Vector3 random(final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + final double rand = MathUtils.nextRandomDouble(); + + result.setX(2 * _extent * getOrigin().getX() * (1 - rand) + getDirection().getX() * _extent * (2 * rand - 1)); + result.setY(2 * _extent * getOrigin().getY() * (1 - rand) + getDirection().getY() * _extent * (2 * rand - 1)); + result.setZ(2 * _extent * getOrigin().getZ() * (1 - rand) + getDirection().getZ() * _extent * (2 * rand - 1)); + + return result; + } + + /** + * Check a segment... if it is null or the values of its origin or direction or extent are NaN or infinite, return + * false. Else return true. + * + * @param segment + * the segment to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyLineSegment3 segment) { + if (segment == null) { + return false; + } + + return Vector3.isValid(segment.getDirection()) && Vector3.isValid(segment.getOrigin()) + && !Double.isInfinite(segment.getExtent()) && !Double.isNaN(segment.getExtent()); + } + + /** + * @return the string representation of this segment. + */ + @Override + public String toString() { + return "com.ardor3d.math.LineSegment3 [Origin: " + _origin + " - Direction: " + _direction + " - Extent: " + + _extent + "]"; + } + + /** + * @param o + * the object to compare for equality + * @return true if this segment and the provided segment have the same constant and normal values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyLineSegment3)) { + return false; + } + final ReadOnlyLineSegment3 comp = (ReadOnlyLineSegment3) o; + return _origin.equals(comp.getOrigin()) && _direction.equals(comp.getDirection()) + && _extent == comp.getExtent(); + } + + /** + * @return returns a unique code for this segment object based on its values. + */ + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _origin.hashCode(); + result += 31 * result + _direction.hashCode(); + final long ex = Double.doubleToLongBits(_extent); + result += 31 * result + (int) (ex ^ ex >>> 32); + + return result; + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public LineSegment3 clone() { + return new LineSegment3(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_extent, "extent", 0.0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _extent = capsule.readDouble("extent", 0.0); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + _extent = in.readDouble(); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + super.writeExternal(out); + out.writeDouble(_extent); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of LineSegment3 that is intended for temporary use in calculations and so forth. Multiple + * calls to the method should return instances of this class that are not currently in use. + */ + public final static LineSegment3 fetchTempInstance() { + if (MathConstants.useMathPools) { + return LineSegment3.LINESEG3_POOL.fetch(); + } else { + return new LineSegment3(); + } + } + + /** + * Releases a LineSegment3 back to be used by a future call to fetchTempInstance. TAKE CARE: this LineSegment3 + * object should no longer have other classes referencing it or "Bad Things" will happen. + * + * @param segment + * the LineSegment3 to release. + */ + public final static void releaseTempInstance(final LineSegment3 segment) { + if (MathConstants.useMathPools) { + LineSegment3.LINESEG3_POOL.release(segment); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/MathConstants.java b/ardor3d-math/src/main/java/com/ardor3d/math/MathConstants.java new file mode 100644 index 0000000..ef518b3 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/MathConstants.java @@ -0,0 +1,45 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+/**
+ * Just a simple flag holder for runtime stripping of various ardor3d logging and debugging features.
+ */
+public class MathConstants {
+
+ public static final boolean useMathPools;
+
+ public static final boolean useFastMath;
+
+ public static final int maxMathPoolSize;
+
+ static {
+ boolean hasPropertyAccess = true;
+ try {
+ if (System.getSecurityManager() != null) {
+ System.getSecurityManager().checkPropertiesAccess();
+ }
+ } catch (final SecurityException e) {
+ hasPropertyAccess = false;
+ }
+
+ if (hasPropertyAccess) {
+ useMathPools = (System.getProperty("ardor3d.noMathPools") == null);
+ useFastMath = (System.getProperty("ardor3d.useFastMath") != null);
+ maxMathPoolSize = (System.getProperty("ardor3d.maxMathPoolSize") != null ? Integer.parseInt(System
+ .getProperty("ardor3d.maxMathPoolSize")) : 11);
+ } else {
+ useMathPools = true;
+ useFastMath = false;
+ maxMathPoolSize = 11;
+ }
+ }
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/MathUtils.java b/ardor3d-math/src/main/java/com/ardor3d/math/MathUtils.java new file mode 100644 index 0000000..02a745f --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/MathUtils.java @@ -0,0 +1,570 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.util.Random; + +import com.ardor3d.math.type.ReadOnlyVector3; + +public class MathUtils { + + /** A "close to zero" double epsilon value for use */ + public static final double EPSILON = 2.220446049250313E-16d; + + /** A "close to zero" double epsilon value for use */ + public static final double ZERO_TOLERANCE = 0.0001; + + public static final double ONE_THIRD = 1.0 / 3.0; + + /** The value PI as a double. (180 degrees) */ + public static final double PI = Math.PI; + + /** The value PI^2 as a double. */ + public final static double SQUARED_PI = MathUtils.PI * MathUtils.PI; + + /** The value 2PI as a double. (360 degrees) */ + public static final double TWO_PI = 2.0 * MathUtils.PI; + + /** The value PI/2 as a double. (90 degrees) */ + public static final double HALF_PI = 0.5 * MathUtils.PI; + + /** The value PI/4 as a double. (45 degrees) */ + public static final double QUARTER_PI = 0.25 * MathUtils.PI; + + /** The value 3/4 PI as a double. (135 degrees) */ + public final static double THREE_PI_HALVES = MathUtils.TWO_PI - MathUtils.HALF_PI; + + /** The value 1/PI as a double. */ + public static final double INV_PI = 1.0 / MathUtils.PI; + + /** The value 1/(2PI) as a double. */ + public static final double INV_TWO_PI = 1.0 / MathUtils.TWO_PI; + + /** A value to multiply a degree value by, to convert it to radians. */ + public static final double DEG_TO_RAD = MathUtils.PI / 180.0; + + /** A value to multiply a radian value by, to convert it to degrees. */ + public static final double RAD_TO_DEG = 180.0 / MathUtils.PI; + + /** A precreated random object for random numbers. */ + public static final Random rand = new Random(System.currentTimeMillis()); + + /** + * Fast Trig functions for x86. This forces the trig functiosn to stay within the safe area on the x86 processor + * (-45 degrees to +45 degrees) The results may be very slightly off from what the Math and StrictMath trig + * functions give due to rounding in the angle reduction but it will be very very close. + * + * note: code from wiki posting on java.net by jeffpk + */ + private static double reduceSinAngle(double radians) { + radians %= MathUtils.TWO_PI; // put us in -2PI to +2PI space + if (Math.abs(radians) > MathUtils.PI) { // put us in -PI to +PI space + radians = radians - MathUtils.TWO_PI; + } + if (Math.abs(radians) > MathUtils.HALF_PI) {// put us in -PI/2 to +PI/2 space + radians = MathUtils.PI - radians; + } + + return radians; + } + + /** + * Returns sine of a value. + * + * note: code from wiki posting on java.net by jeffpk + * + * @param dValue + * The value to sine, in radians. + * @return The sine of dValue. + * @see java.lang.Math#sin(double) + */ + public static double sin(double dValue) { + dValue = reduceSinAngle(dValue); // limits angle to between -PI/2 and +PI/2 + if (Math.abs(dValue) <= MathUtils.QUARTER_PI) { + return MathConstants.useFastMath ? FastMath.sin(dValue) : Math.sin(dValue); + } + + return MathConstants.useFastMath ? FastMath.cos(MathUtils.HALF_PI - dValue) : Math.cos(MathUtils.HALF_PI + - dValue); + } + + /** + * Returns cos of a value. + * + * @param dValue + * The value to cosine, in radians. + * @return The cosine of dValue. + * @see java.lang.Math#cos(double) + */ + public static double cos(final double dValue) { + return sin(dValue + MathUtils.HALF_PI); + } + + public static double sqrt(final double dValue) { + return MathConstants.useFastMath ? FastMath.sqrt(dValue) : Math.sqrt(dValue); + } + + public static double inverseSqrt(final double dValue) { + return MathConstants.useFastMath ? FastMath.inverseSqrt(dValue) : 1 / Math.sqrt(dValue); + } + + public static double atan(final double dValue) { + return MathConstants.useFastMath ? FastMath.atan(dValue) : Math.atan(dValue); + } + + public static double asin(final double dValue) { + return MathConstants.useFastMath ? FastMath.asin(dValue) : Math.asin(dValue); + } + + public static double tan(final double dValue) { + return MathConstants.useFastMath ? FastMath.tan(dValue) : Math.tan(dValue); + } + + public static double acos(final double dValue) { + return MathConstants.useFastMath ? FastMath.acos(dValue) : Math.acos(dValue); + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive Y as up) and stores the results in the + * store var. + * + * @param sphereCoords + * (Radius, Azimuth, Polar) + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + */ + public static Vector3 sphericalToCartesian(final ReadOnlyVector3 sphereCoords, final Vector3 store) { + final double a = sphereCoords.getX() * cos(sphereCoords.getZ()); + final double x = a * cos(sphereCoords.getY()); + final double y = sphereCoords.getX() * sin(sphereCoords.getZ()); + final double z = a * sin(sphereCoords.getY()); + + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + return rVal.set(x, y, z); + } + + /** + * Converts a point from Cartesian coordinates (using positive Y as up) to Spherical and stores the results in the + * store var. (Radius, Azimuth, Polar) + * + * @param cartCoords + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + */ + public static Vector3 cartesianToSpherical(final ReadOnlyVector3 cartCoords, final Vector3 store) { + final double cartX = Math.abs(cartCoords.getX()) <= MathUtils.EPSILON ? MathUtils.EPSILON : cartCoords.getX(); + final double cartY = cartCoords.getY(); + final double cartZ = cartCoords.getZ(); + + final double x = sqrt(cartX * cartX + cartY * cartY + cartZ * cartZ); + final double y = atan(cartZ / cartX) + (cartX < 0.0 ? MathUtils.PI : 0); + final double z = asin(cartY / x); + + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + return rVal.set(x, y, z); + } + + /** + * Converts a point from Spherical coordinates to Cartesian (using positive Z as up) and stores the results in the + * store var. + * + * @param sphereCoords + * (Radius, Azimuth, Polar) + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + */ + public static Vector3 sphericalToCartesianZ(final ReadOnlyVector3 sphereCoords, final Vector3 store) { + final double a = sphereCoords.getX() * cos(sphereCoords.getZ()); + final double x = a * cos(sphereCoords.getY()); + final double y = a * sin(sphereCoords.getY()); + final double z = sphereCoords.getX() * sin(sphereCoords.getZ()); + + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + return rVal.set(x, y, z); + } + + /** + * Converts a point from Cartesian coordinates (using positive Z as up) to Spherical and stores the results in the + * store var. (Radius, Azimuth, Polar) + * + * @param cartCoords + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + */ + public static Vector3 cartesianZToSpherical(final ReadOnlyVector3 cartCoords, final Vector3 store) { + final double cartX = Math.abs(cartCoords.getX()) <= MathUtils.EPSILON ? MathUtils.EPSILON : cartCoords.getX(); + final double cartY = cartCoords.getY(); + final double cartZ = cartCoords.getZ(); + + final double x = sqrt(cartX * cartX + cartY * cartY + cartZ * cartZ); + final double y = asin(cartY / x); + final double z = atan(cartZ / cartX) + (cartX < 0.0 ? MathUtils.PI : 0); + + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + return rVal.set(x, y, z); + } + + /** + * Returns true if the number is a power of 2 (2,4,8,16...) + * + * A good implementation found on the Java boards. note: a number is a power of two if and only if it is the + * smallest number with that number of significant bits. Therefore, if you subtract 1, you know that the new number + * will have fewer bits, so ANDing the original number with anything less than it will give 0. + * + * @param number + * The number to test. + * @return True if it is a power of two. + */ + public static boolean isPowerOfTwo(final int number) { + return number > 0 && (number & number - 1) == 0; + } + + /** + * @param number + * @return the closest power of two to the given number. + */ + public static int nearestPowerOfTwo(final int number) { + return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2))); + } + + /** + * @param value + * @param base + * @return the logarithm of value with given base, calculated as log(value)/log(base) such that pow(base, + * return)==value + */ + public static double log(final double value, final double base) { + return Math.log(value) / Math.log(base); + } + + /** + * Sets the seed to use for "random" operations. The default is the current system milliseconds. + * + * @param seed + */ + public static void setRandomSeed(final long seed) { + MathUtils.rand.setSeed(seed); + } + + /** + * Returns a random double between 0 and 1. + * + * @return A random double between <tt>0.0</tt> (inclusive) to <tt>1.0</tt> (exclusive). + */ + public static double nextRandomDouble() { + return MathUtils.rand.nextDouble(); + } + + /** + * Returns a random float between 0 and 1. + * + * @return A random float between <tt>0.0f</tt> (inclusive) to <tt>1.0f</tt> (exclusive). + */ + public static float nextRandomFloat() { + return MathUtils.rand.nextFloat(); + } + + /** + * @return A random int between Integer.MIN_VALUE and Integer.MAX_VALUE. + */ + public static int nextRandomInt() { + return MathUtils.rand.nextInt(); + } + + /** + * Returns a random int between min and max. + * + * @return A random int between <tt>min</tt> (inclusive) to <tt>max</tt> (inclusive). + */ + public static int nextRandomInt(final int min, final int max) { + return (int) (nextRandomFloat() * (max - min + 1)) + min; + } + + /** + * + * @param percent + * @param startValue + * @param endValue + * @return + */ + public static float lerp(final float percent, final float startValue, final float endValue) { + if (startValue == endValue) { + return startValue; + } + return (1 - percent) * startValue + percent * endValue; + } + + /** + * + * @param percent + * @param startValue + * @param endValue + * @return + */ + public static double lerp(final double percent, final double startValue, final double endValue) { + if (startValue == endValue) { + return startValue; + } + return (1 - percent) * startValue + percent * endValue; + } + + /** + * plot a given value on the cubic S-curve: 3t^2 - 2t^3 + * + * @param t + * our input value + * @return the plotted value + */ + public static float scurve3(final float t) { + final float t2 = t * t; + final float t3 = t * t2; + return 3f * t2 - 2f * t3; + } + + /** + * plot a given value on the cubic S-curve: 3t^2 - 2t^3 + * + * @param t + * our input value + * @return the plotted value + */ + public static double scurve3(final double t) { + final double t2 = t * t; + final double t3 = t * t2; + return 3. * t2 - 2. * t3; + } + + /** + * plot a given value on the quintic S-curve: 6t^5 - 15t^4 + 10t^3 + * + * @param t + * our input value + * @return the plotted value + */ + public static float scurve5(final float t) { + final float t3 = t * t * t; + final float t4 = t * t3; + final float t5 = t * t4; + return 6f * t5 - 15f * t4 + 10f * t3; + } + + /** + * plot a given value on the quintic S-curve: 6t^5 - 15t^4 + 10t^3 + * + * @param t + * our input value + * @return the plotted value + */ + public static double scurve5(final double t) { + final double t3 = t * t * t; + final double t4 = t * t3; + final double t5 = t * t4; + return 6. * t5 - 15. * t4 + 10. * t3; + } + + /** + * + * @param left + * @param right + * @param bottom + * @param top + * @param nearZ + * @param farZ + * @param store + */ + public static void matrixFrustum(final double left, final double right, final double bottom, final double top, + final double nearZ, final double farZ, final Matrix4 store) { + final double x = 2.0 * nearZ / (right - left); + final double y = 2.0 * nearZ / (top - bottom); + final double a = (right + left) / (right - left); + final double b = (top + bottom) / (top - bottom); + final double c = -(farZ + nearZ) / (farZ - nearZ); + final double d = -(2.0 * farZ * nearZ) / (farZ - nearZ); + + store.set(x, 0.0, 0.0, 0.0, 0.0, y, 0.0, 0.0, a, b, c, -1.0, 0.0, 0.0, d, 0.0); + } + + /** + * + * @param left + * @param right + * @param bottom + * @param top + * @param nearZ + * @param farZ + * @param store + */ + public static void matrixOrtho(final double left, final double right, final double bottom, final double top, + final double nearZ, final double farZ, final Matrix4 store) { + store.set(2.0 / (right - left), 0.0, 0.0, 0.0, 0.0, 2.0 / (top - bottom), 0.0, 0.0, 0.0, 0.0, -2.0 + / (farZ - nearZ), 0.0, -(right + left) / (right - left), -(top + bottom) / (top - bottom), + -(farZ + nearZ) / (farZ - nearZ), 1.0); + } + + /** + * + * @param fovY + * @param aspect + * @param zNear + * @param zFar + * @param store + */ + public static void matrixPerspective(final double fovY, final double aspect, final double zNear, final double zFar, + final Matrix4 store) { + final double height = zNear * tan(fovY * 0.5 * MathUtils.DEG_TO_RAD); + final double width = height * aspect; + + matrixFrustum(-width, width, -height, height, zNear, zFar, store); + } + + /** + * + * @param position + * @param target + * @param up + * @param store + */ + public static void matrixLookAt(final ReadOnlyVector3 position, final ReadOnlyVector3 target, + final ReadOnlyVector3 worldUp, final Matrix4 store) { + final Vector3 direction = Vector3.fetchTempInstance(); + final Vector3 side = Vector3.fetchTempInstance(); + final Vector3 up = Vector3.fetchTempInstance(); + + direction.set(target).subtractLocal(position).normalizeLocal(); + direction.cross(worldUp, side).normalizeLocal(); + side.cross(direction, up); + + store.set(side.getX(), up.getX(), -direction.getX(), 0.0, side.getY(), up.getY(), -direction.getY(), 0.0, + side.getZ(), up.getZ(), -direction.getZ(), 0.0, side.getX() * -position.getX() + side.getY() + * -position.getY() + side.getZ() * -position.getZ(), up.getX() * -position.getX() + up.getY() + * -position.getY() + up.getZ() * -position.getZ(), -direction.getX() * -position.getX() + + -direction.getY() * -position.getY() + -direction.getZ() * -position.getZ(), 1.0); + + Vector3.releaseTempInstance(up); + Vector3.releaseTempInstance(side); + Vector3.releaseTempInstance(direction); + } + + /** + * + * @param position + * @param target + * @param up + * @param store + */ + public static void matrixLookAt(final ReadOnlyVector3 position, final ReadOnlyVector3 target, + final ReadOnlyVector3 worldUp, final Matrix3 store) { + final Vector3 direction = Vector3.fetchTempInstance(); + final Vector3 side = Vector3.fetchTempInstance(); + final Vector3 up = Vector3.fetchTempInstance(); + + direction.set(target).subtractLocal(position).normalizeLocal(); + direction.cross(worldUp, side).normalizeLocal(); + side.cross(direction, up); + + store.set(side.getX(), up.getX(), -direction.getX(), side.getY(), up.getY(), -direction.getY(), side.getZ(), + up.getZ(), -direction.getZ()); + + Vector3.releaseTempInstance(up); + Vector3.releaseTempInstance(side); + Vector3.releaseTempInstance(direction); + } + + /** + * Faster floor function. Does not handle NaN and Infinity. (Not handled when doing Math.floor and just casting + * anyways, so question is if we want to handle it or not) + * + * @param val + * Value to floor + * @return Floored int value + */ + public static int floor(final float val) { + final int intVal = (int) val; + return val < 0 ? val == intVal ? intVal : intVal - 1 : intVal; + } + + /** + * Faster floor function. Does not handle NaN and Infinity. (Not handled when doing Math.floor and just casting + * anyways, so question is if we want to handle it or not) + * + * @param val + * Value to floor + * @return Floored long value + */ + public static long floor(final double val) { + final long longVal = (long) val; + return val < 0 ? val == longVal ? longVal : longVal - 1 : longVal; + } + + public static int round(final float val) { + return floor(val + 0.5f); + } + + public static long round(final double val) { + return floor(val + 0.5d); + } + + public static double clamp(final double val, final double min, final double max) { + return val < min ? min : val > max ? max : val; + } + + public static float clamp(final float val, final float min, final float max) { + return val < min ? min : val > max ? max : val; + } + + public static int clamp(final int val, final int min, final int max) { + return val < min ? min : val > max ? max : val; + } + + public static int moduloPositive(final int value, final int size) { + int wrappedValue = value % size; + wrappedValue += wrappedValue < 0 ? size : 0; + return wrappedValue; + } + + public static float moduloPositive(final float value, final float size) { + float wrappedValue = value % size; + wrappedValue += wrappedValue < 0 ? size : 0; + return wrappedValue; + } + + public static double moduloPositive(final double value, final double size) { + double wrappedValue = value % size; + wrappedValue += wrappedValue < 0 ? size : 0; + return wrappedValue; + } + + /** + * Simple 2^x + * + * @param x + * power + * @return 2^x + */ + public static int pow2(final int x) { + if (x <= 0) { + return 1; + } + return 2 << x - 1; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Matrix3.java b/ardor3d-math/src/main/java/com/ardor3d/math/Matrix3.java new file mode 100644 index 0000000..a13ddc4 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Matrix3.java @@ -0,0 +1,1959 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.nio.BufferOverflowException; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Matrix3 represents a double precision 3x3 matrix. + * + * Note: some algorithms in this class were ported from Eberly, Wolfram, Game Gems and others to Java. + */ +public class Matrix3 implements Cloneable, Savable, Externalizable, ReadOnlyMatrix3, Poolable { + + /** Used with equals method to determine if two Matrix3 objects are close enough to be considered equal. */ + public static final double ALLOWED_DEVIANCE = 0.00000001; + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Matrix3> MAT_POOL = ObjectPool.create(Matrix3.class, MathConstants.maxMathPoolSize); + + /** + * <pre> + * 1, 0, 0 + * 0, 1, 0 + * 0, 0, 1 + * </pre> + */ + public final static ReadOnlyMatrix3 IDENTITY = new Matrix3(1, 0, 0, 0, 1, 0, 0, 0, 1); + + protected double _m00, _m01, _m02, // + _m10, _m11, _m12, // + _m20, _m21, _m22; + + /** + * Constructs a new, mutable matrix set to identity. + */ + public Matrix3() { + this(Matrix3.IDENTITY); + } + + /** + * Constructs a new, mutable matrix using the given matrix values (names are mRC = m[ROW][COL]) + * + * @param m00 + * @param m01 + * @param m02 + * @param m10 + * @param m11 + * @param m12 + * @param m20 + * @param m21 + * @param m22 + */ + public Matrix3(final double m00, final double m01, final double m02, final double m10, final double m11, + final double m12, final double m20, final double m21, final double m22) { + + _m00 = m00; + _m01 = m01; + _m02 = m02; + _m10 = m10; + _m11 = m11; + _m12 = m12; + _m20 = m20; + _m21 = m21; + _m22 = m22; + } + + /** + * Constructs a new, mutable matrix using the values from the given matrix + * + * @param source + */ + public Matrix3(final ReadOnlyMatrix3 source) { + set(source); + } + + /** + * @param row + * @param column + * @return the value stored in this matrix at row, column. + * @throws IllegalArgumentException + * if row and column are not in bounds [0, 2] + */ + @Override + public double getValue(final int row, final int column) { + switch (row) { + case 0: + switch (column) { + case 0: + return _m00; + case 1: + return _m01; + case 2: + return _m02; + } + break; + case 1: + switch (column) { + case 0: + return _m10; + case 1: + return _m11; + case 2: + return _m12; + } + break; + + case 2: + switch (column) { + case 0: + return _m20; + case 1: + return _m21; + case 2: + return _m22; + } + break; + } + throw new IllegalArgumentException(); + } + + /** + * @param row + * @param column + * @return the value stored in this matrix at row, column, pre-cast to a float for convenience. + * @throws IllegalArgumentException + * if row and column are not in bounds [0, 2] + */ + @Override + public float getValuef(final int row, final int column) { + return (float) getValue(row, column); + } + + @Override + public double getM00() { + return _m00; + } + + @Override + public double getM01() { + return _m01; + } + + @Override + public double getM02() { + return _m02; + } + + @Override + public double getM10() { + return _m10; + } + + @Override + public double getM11() { + return _m11; + } + + @Override + public double getM12() { + return _m12; + } + + @Override + public double getM20() { + return _m20; + } + + @Override + public double getM21() { + return _m21; + } + + @Override + public double getM22() { + return _m22; + } + + public void setM00(final double m00) { + _m00 = m00; + } + + public void setM01(final double m01) { + _m01 = m01; + } + + public void setM02(final double m02) { + _m02 = m02; + } + + public void setM10(final double m10) { + _m10 = m10; + } + + public void setM11(final double m11) { + _m11 = m11; + } + + public void setM12(final double m12) { + _m12 = m12; + } + + public void setM20(final double m20) { + _m20 = m20; + } + + public void setM21(final double m21) { + _m21 = m21; + } + + public void setM22(final double m22) { + _m22 = m22; + } + + /** + * Same as set(IDENTITY) + * + * @return this matrix for chaining + */ + public Matrix3 setIdentity() { + return set(Matrix3.IDENTITY); + } + + /** + * @return true if this matrix equals the 3x3 identity matrix + */ + @Override + public boolean isIdentity() { + return strictEquals(Matrix3.IDENTITY); + } + + /** + * Sets the value of this matrix at row, column to the given value. + * + * @param row + * @param column + * @param value + * @return this matrix for chaining + * @throws IllegalArgumentException + * if row and column are not in bounds [0, 2] + */ + public Matrix3 setValue(final int row, final int column, final double value) { + switch (row) { + case 0: + switch (column) { + case 0: + _m00 = value; + break; + case 1: + _m01 = value; + break; + case 2: + _m02 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + case 1: + switch (column) { + case 0: + _m10 = value; + break; + case 1: + _m11 = value; + break; + case 2: + _m12 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + case 2: + switch (column) { + case 0: + _m20 = value; + break; + case 1: + _m21 = value; + break; + case 2: + _m22 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + default: + throw new IllegalArgumentException(); + } + + return this; + } + + /** + * Sets the values of this matrix to the values given. + * + * @param m00 + * @param m01 + * @param m02 + * @param m10 + * @param m11 + * @param m12 + * @param m20 + * @param m21 + * @param m22 + * @return this matrix for chaining + */ + public Matrix3 set(final double m00, final double m01, final double m02, final double m10, final double m11, + final double m12, final double m20, final double m21, final double m22) { + + _m00 = m00; + _m01 = m01; + _m02 = m02; + _m10 = m10; + _m11 = m11; + _m12 = m12; + _m20 = m20; + _m21 = m21; + _m22 = m22; + + return this; + } + + /** + * Sets the values of this matrix to the values of the provided source matrix. + * + * @param source + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + */ + public Matrix3 set(final ReadOnlyMatrix3 source) { + _m00 = source.getM00(); + _m10 = source.getM10(); + _m20 = source.getM20(); + + _m01 = source.getM01(); + _m11 = source.getM11(); + _m21 = source.getM21(); + + _m02 = source.getM02(); + _m12 = source.getM12(); + _m22 = source.getM22(); + + return this; + } + + /** + * Sets the values of this matrix to the rotational value of the given quaternion. + * + * @param quaternion + * @return this matrix for chaining + */ + public Matrix3 set(final ReadOnlyQuaternion quaternion) { + return quaternion.toRotationMatrix(this); + } + + /** + * @param source + * the buffer to read our matrix data from. + * @return this matrix for chaining. + */ + public Matrix3 fromDoubleBuffer(final DoubleBuffer source) { + return fromDoubleBuffer(source, true); + } + + /** + * @param source + * the buffer to read our matrix data from. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return this matrix for chaining. + */ + public Matrix3 fromDoubleBuffer(final DoubleBuffer source, final boolean rowMajor) { + if (rowMajor) { + _m00 = source.get(); + _m01 = source.get(); + _m02 = source.get(); + _m10 = source.get(); + _m11 = source.get(); + _m12 = source.get(); + _m20 = source.get(); + _m21 = source.get(); + _m22 = source.get(); + } else { + _m00 = source.get(); + _m10 = source.get(); + _m20 = source.get(); + _m01 = source.get(); + _m11 = source.get(); + _m21 = source.get(); + _m02 = source.get(); + _m12 = source.get(); + _m22 = source.get(); + } + + return this; + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to read our matrix data from. + * @return this matrix for chaining. + */ + public Matrix3 fromFloatBuffer(final FloatBuffer source) { + return fromFloatBuffer(source, true); + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to read our matrix data from. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return this matrix for chaining. + */ + public Matrix3 fromFloatBuffer(final FloatBuffer source, final boolean rowMajor) { + if (rowMajor) { + _m00 = source.get(); + _m01 = source.get(); + _m02 = source.get(); + _m10 = source.get(); + _m11 = source.get(); + _m12 = source.get(); + _m20 = source.get(); + _m21 = source.get(); + _m22 = source.get(); + } else { + _m00 = source.get(); + _m10 = source.get(); + _m20 = source.get(); + _m01 = source.get(); + _m11 = source.get(); + _m21 = source.get(); + _m02 = source.get(); + _m12 = source.get(); + _m22 = source.get(); + } + + return this; + } + + /** + * Sets the values of this matrix to the values of the provided double array. + * + * @param source + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + * @throws ArrayIndexOutOfBoundsException + * if source array has a length less than 9. + */ + public Matrix3 fromArray(final double[] source) { + return fromArray(source, true); + } + + /** + * Sets the values of this matrix to the values of the provided double array. + * + * @param source + * @param rowMajor + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + * @throws ArrayIndexOutOfBoundsException + * if source array has a length less than 9. + */ + public Matrix3 fromArray(final double[] source, final boolean rowMajor) { + if (rowMajor) { + _m00 = source[0]; + _m01 = source[1]; + _m02 = source[2]; + _m10 = source[3]; + _m11 = source[4]; + _m12 = source[5]; + _m20 = source[6]; + _m21 = source[7]; + _m22 = source[8]; + } else { + _m00 = source[0]; + _m10 = source[1]; + _m20 = source[2]; + _m01 = source[3]; + _m11 = source[4]; + _m21 = source[5]; + _m02 = source[6]; + _m12 = source[7]; + _m22 = source[8]; + } + return this; + } + + /** + * Replaces a column in this matrix with the values of the given vector. + * + * @param columnIndex + * @param columnData + * @return this matrix for chaining + * @throws NullPointerException + * if columnData is null. + * @throws IllegalArgumentException + * if columnIndex is not in [0, 2] + */ + public Matrix3 setColumn(final int columnIndex, final ReadOnlyVector3 columnData) { + switch (columnIndex) { + case 0: + _m00 = columnData.getX(); + _m10 = columnData.getY(); + _m20 = columnData.getZ(); + break; + case 1: + _m01 = columnData.getX(); + _m11 = columnData.getY(); + _m21 = columnData.getZ(); + break; + case 2: + _m02 = columnData.getX(); + _m12 = columnData.getY(); + _m22 = columnData.getZ(); + break; + default: + throw new IllegalArgumentException("Bad columnIndex: " + columnIndex); + } + return this; + } + + /** + * Replaces a row in this matrix with the values of the given vector. + * + * @param rowIndex + * @param rowData + * @return this matrix for chaining + * @throws NullPointerException + * if rowData is null. + * @throws IllegalArgumentException + * if rowIndex is not in [0, 2] + */ + public Matrix3 setRow(final int rowIndex, final ReadOnlyVector3 rowData) { + switch (rowIndex) { + case 0: + _m00 = rowData.getX(); + _m01 = rowData.getY(); + _m02 = rowData.getZ(); + break; + case 1: + _m10 = rowData.getX(); + _m11 = rowData.getY(); + _m12 = rowData.getZ(); + break; + case 2: + _m20 = rowData.getX(); + _m21 = rowData.getY(); + _m22 = rowData.getZ(); + break; + default: + throw new IllegalArgumentException("Bad rowIndex: " + rowIndex); + } + return this; + } + + /** + * Set the values of this matrix from the axes (columns) provided. + * + * @param uAxis + * @param vAxis + * @param wAxis + * @return this matrix for chaining + * @throws NullPointerException + * if any of the axes are null. + */ + public Matrix3 fromAxes(final ReadOnlyVector3 uAxis, final ReadOnlyVector3 vAxis, final ReadOnlyVector3 wAxis) { + setColumn(0, uAxis); + setColumn(1, vAxis); + setColumn(2, wAxis); + return this; + } + + /** + * Sets this matrix to the rotation indicated by the given angle and axis of rotation. Note: This method creates an + * object, so use fromAngleNormalAxis when possible, particularly if your axis is already normalized. + * + * @param angle + * the angle to rotate (in radians). + * @param axis + * the axis of rotation. + * @return this matrix for chaining + * @throws NullPointerException + * if axis is null. + */ + public Matrix3 fromAngleAxis(final double angle, final ReadOnlyVector3 axis) { + final Vector3 normAxis = Vector3.fetchTempInstance(); + axis.normalize(normAxis); + fromAngleNormalAxis(angle, normAxis); + Vector3.releaseTempInstance(normAxis); + return this; + } + + /** + * Sets this matrix to the rotation indicated by the given angle and a unit-length axis of rotation. + * + * @param angle + * the angle to rotate (in radians). + * @param axis + * the axis of rotation (already normalized). + * @return this matrix for chaining + * @throws NullPointerException + * if axis is null. + */ + public Matrix3 fromAngleNormalAxis(final double angle, final ReadOnlyVector3 axis) { + final double fCos = MathUtils.cos(angle); + final double fSin = MathUtils.sin(angle); + final double fOneMinusCos = 1.0 - fCos; + final double fX2 = axis.getX() * axis.getX(); + final double fY2 = axis.getY() * axis.getY(); + final double fZ2 = axis.getZ() * axis.getZ(); + final double fXYM = axis.getX() * axis.getY() * fOneMinusCos; + final double fXZM = axis.getX() * axis.getZ() * fOneMinusCos; + final double fYZM = axis.getY() * axis.getZ() * fOneMinusCos; + final double fXSin = axis.getX() * fSin; + final double fYSin = axis.getY() * fSin; + final double fZSin = axis.getZ() * fSin; + + _m00 = fX2 * fOneMinusCos + fCos; + _m01 = fXYM - fZSin; + _m02 = fXZM + fYSin; + _m10 = fXYM + fZSin; + _m11 = fY2 * fOneMinusCos + fCos; + _m12 = fYZM - fXSin; + _m20 = fXZM - fYSin; + _m21 = fYZM + fXSin; + _m22 = fZ2 * fOneMinusCos + fCos; + + return this; + } + + /** + * XXX: Need to redo this again... or at least correct the terms. YRP are arbitrary terms, based on a specific frame + * of axis. + * + * Updates this matrix from the given Euler rotation angles (y,r,p). Note that we are applying in order: roll, + * pitch, yaw but we've ordered them in x, y, and z for convenience. See: + * http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToMatrix/index.htm + * + * @param yaw + * the Euler yaw of rotation (in radians). (aka Bank, often rot around x) + * @param roll + * the Euler roll of rotation (in radians). (aka Heading, often rot around y) + * @param pitch + * the Euler pitch of rotation (in radians). (aka Attitude, often rot around z) + * @return this matrix for chaining + */ + public Matrix3 fromAngles(final double yaw, final double roll, final double pitch) { + final double ch = Math.cos(roll); + final double sh = Math.sin(roll); + final double cp = Math.cos(pitch); + final double sp = Math.sin(pitch); + final double cy = Math.cos(yaw); + final double sy = Math.sin(yaw); + + _m00 = ch * cp; + _m01 = sh * sy - ch * sp * cy; + _m02 = ch * sp * sy + sh * cy; + _m10 = sp; + _m11 = cp * cy; + _m12 = -cp * sy; + _m20 = -sh * cp; + _m21 = sh * sp * cy + ch * sy; + _m22 = -sh * sp * sy + ch * cy; + return this; + } + + public Matrix3 applyRotation(final double angle, final double x, final double y, final double z) { + final double m00 = _m00, m01 = _m01, m02 = _m02, // + m10 = _m10, m11 = _m11, m12 = _m12, // + m20 = _m20, m21 = _m21, m22 = _m22; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + final double oneMinusCos = 1.0f - cosAngle; + final double xyOneMinusCos = x * y * oneMinusCos; + final double xzOneMinusCos = x * z * oneMinusCos; + final double yzOneMinusCos = y * z * oneMinusCos; + final double xSin = x * sinAngle; + final double ySin = y * sinAngle; + final double zSin = z * sinAngle; + + final double r00 = x * x * oneMinusCos + cosAngle; + final double r01 = xyOneMinusCos - zSin; + final double r02 = xzOneMinusCos + ySin; + final double r10 = xyOneMinusCos + zSin; + final double r11 = y * y * oneMinusCos + cosAngle; + final double r12 = yzOneMinusCos - xSin; + final double r20 = xzOneMinusCos - ySin; + final double r21 = yzOneMinusCos + xSin; + final double r22 = z * z * oneMinusCos + cosAngle; + + _m00 = m00 * r00 + m01 * r10 + m02 * r20; + _m01 = m00 * r01 + m01 * r11 + m02 * r21; + _m02 = m00 * r02 + m01 * r12 + m02 * r22; + + _m10 = m10 * r00 + m11 * r10 + m12 * r20; + _m11 = m10 * r01 + m11 * r11 + m12 * r21; + _m12 = m10 * r02 + m11 * r12 + m12 * r22; + + _m20 = m20 * r00 + m21 * r10 + m22 * r20; + _m21 = m20 * r01 + m21 * r11 + m22 * r21; + _m22 = m20 * r02 + m21 * r12 + m22 * r22; + + return this; + } + + /** + * Apply rotation around X (Mrx * this) + * + * @param angle + * @return + */ + public Matrix3 applyRotationX(final double angle) { + final double m01 = _m01, m02 = _m02, // + m11 = _m11, m12 = _m12, // + m21 = _m21, m22 = _m22; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + + _m01 = m01 * cosAngle + m02 * sinAngle; + _m02 = m02 * cosAngle - m01 * sinAngle; + + _m11 = m11 * cosAngle + m12 * sinAngle; + _m12 = m12 * cosAngle - m11 * sinAngle; + + _m21 = m21 * cosAngle + m22 * sinAngle; + _m22 = m22 * cosAngle - m21 * sinAngle; + + return this; + } + + /** + * Apply rotation around Y (Mry * this) + * + * @param angle + * @return + */ + public Matrix3 applyRotationY(final double angle) { + final double m00 = _m00, m02 = _m02, // + m10 = _m10, m12 = _m12, // + m20 = _m20, m22 = _m22; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + + _m00 = m00 * cosAngle - m02 * sinAngle; + _m02 = m00 * sinAngle + m02 * cosAngle; + + _m10 = m10 * cosAngle - m12 * sinAngle; + _m12 = m10 * sinAngle + m12 * cosAngle; + + _m20 = m20 * cosAngle - m22 * sinAngle; + _m22 = m20 * sinAngle + m22 * cosAngle; + + return this; + } + + /** + * Apply rotation around Z (Mrz * this) + * + * @param angle + * @return + */ + public Matrix3 applyRotationZ(final double angle) { + final double m00 = _m00, m01 = _m01, // + m10 = _m10, m11 = _m11, // + m20 = _m20, m21 = _m21; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + + _m00 = m00 * cosAngle + m01 * sinAngle; + _m01 = m01 * cosAngle - m00 * sinAngle; + + _m10 = m10 * cosAngle + m11 * sinAngle; + _m11 = m11 * cosAngle - m10 * sinAngle; + + _m20 = m20 * cosAngle + m21 * sinAngle; + _m21 = m21 * cosAngle - m20 * sinAngle; + + return this; + } + + /** + * @param index + * @param store + * the vector object to store the result in. if null, a new one is created. + * @return the column specified by the index. + * @throws IllegalArgumentException + * if index is not in bounds [0, 2] + */ + @Override + public Vector3 getColumn(final int index, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + switch (index) { + case 0: + result.setX(_m00); + result.setY(_m10); + result.setZ(_m20); + break; + case 1: + result.setX(_m01); + result.setY(_m11); + result.setZ(_m21); + break; + case 2: + result.setX(_m02); + result.setY(_m12); + result.setZ(_m22); + break; + default: + throw new IllegalArgumentException("invalid column index: " + index); + } + + return result; + } + + /** + * @param index + * @param store + * the vector object to store the result in. if null, a new one is created. + * @return the row specified by the index. + * @throws IllegalArgumentException + * if index is not in bounds [0, 2] + */ + @Override + public Vector3 getRow(final int index, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + switch (index) { + case 0: + result.setX(_m00); + result.setY(_m01); + result.setZ(_m02); + break; + case 1: + result.setX(_m10); + result.setY(_m11); + result.setZ(_m12); + break; + case 2: + result.setX(_m20); + result.setY(_m21); + result.setZ(_m22); + break; + default: + throw new IllegalArgumentException("invalid row index: " + index); + } + return result; + } + + /** + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * position. + * @return matrix data as a DoubleBuffer in row major order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 9 values. + */ + @Override + public DoubleBuffer toDoubleBuffer(final DoubleBuffer store) { + return toDoubleBuffer(store, true); + } + + /** + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * position. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return matrix data as a DoubleBuffer in the specified order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 9 values. + */ + @Override + public DoubleBuffer toDoubleBuffer(final DoubleBuffer store, final boolean rowMajor) { + if (rowMajor) { + store.put(_m00); + store.put(_m01); + store.put(_m02); + store.put(_m10); + store.put(_m11); + store.put(_m12); + store.put(_m20); + store.put(_m21); + store.put(_m22); + } else { + store.put(_m00); + store.put(_m10); + store.put(_m20); + store.put(_m01); + store.put(_m11); + store.put(_m21); + store.put(_m02); + store.put(_m12); + store.put(_m22); + } + + return store; + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * position. + * @return matrix data as a FloatBuffer in row major order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 9 values. + */ + @Override + public FloatBuffer toFloatBuffer(final FloatBuffer store) { + return toFloatBuffer(store, true); + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * position. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return matrix data as a FloatBuffer in the specified order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 9 values. + */ + @Override + public FloatBuffer toFloatBuffer(final FloatBuffer store, final boolean rowMajor) { + if (rowMajor) { + store.put((float) _m00); + store.put((float) _m01); + store.put((float) _m02); + store.put((float) _m10); + store.put((float) _m11); + store.put((float) _m12); + store.put((float) _m20); + store.put((float) _m21); + store.put((float) _m22); + } else { + store.put((float) _m00); + store.put((float) _m10); + store.put((float) _m20); + store.put((float) _m01); + store.put((float) _m11); + store.put((float) _m21); + store.put((float) _m02); + store.put((float) _m12); + store.put((float) _m22); + } + + return store; + } + + /** + * @param store + * the double array to store our matrix data in. If null, a new array is created. + * @return matrix data as a double array in row major order. + * @throws IllegalArgumentException + * if the store is non-null and has a length < 9 + */ + @Override + public double[] toArray(final double[] store) { + return toArray(store, true); + } + + /** + * @param store + * the double array to store our matrix data in. If null, a new array is created. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return matrix data as a double array in the specified order. + * @throws IllegalArgumentException + * if the store is non-null and has a length < 9 + */ + @Override + public double[] toArray(final double[] store, final boolean rowMajor) { + double[] result = store; + if (result == null) { + result = new double[9]; + } else if (result.length < 9) { + throw new IllegalArgumentException("store must be at least length 9."); + } + + if (rowMajor) { + result[0] = _m00; + result[1] = _m01; + result[2] = _m02; + result[3] = _m10; + result[4] = _m11; + result[5] = _m12; + result[6] = _m20; + result[7] = _m21; + result[8] = _m22; + } else { + result[0] = _m00; + result[1] = _m10; + result[2] = _m20; + result[3] = _m01; + result[4] = _m11; + result[5] = _m21; + result[6] = _m02; + result[7] = _m12; + result[8] = _m22; + } + + return result; + } + + /** + * converts this matrix to Euler rotation angles (yaw, roll, pitch). See + * http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToEuler/index.htm + * + * @param store + * the double[] array to store the computed angles in. If null, a new double[] will be created + * @return the double[] array. + * @throws IllegalArgumentException + * if non-null store is not at least length 3 + * @see #fromAngles(double, double, double) + */ + @Override + public double[] toAngles(final double[] store) { + double[] result = store; + if (result == null) { + result = new double[3]; + } else if (result.length < 3) { + throw new IllegalArgumentException("store array must have at least three elements"); + } + + double heading, attitude, bank; + if (_m10 > 1 - MathUtils.ZERO_TOLERANCE) { // singularity at north pole + heading = Math.atan2(_m02, _m22); + attitude = Math.PI / 2; + bank = 0; + } else if (_m10 < -1 + MathUtils.ZERO_TOLERANCE) { // singularity at south pole + heading = Math.atan2(_m02, _m22); + attitude = -Math.PI / 2; + bank = 0; + } else { + heading = Math.atan2(-_m20, _m00); + bank = Math.atan2(-_m12, _m11); + attitude = Math.asin(_m10); + } + result[0] = bank; + result[1] = heading; + result[2] = attitude; + + return result; + } + + /** + * @param matrix + * @return This matrix for chaining, modified internally to reflect multiplication against the given matrix + * @throws NullPointerException + * if matrix is null + */ + public Matrix3 multiplyLocal(final ReadOnlyMatrix3 matrix) { + return multiply(matrix, this); + } + + /** + * @param matrix + * @param store + * a matrix to store the result in. if null, a new matrix is created. It is safe for the given matrix and + * this parameter to be the same object. + * @return this matrix multiplied by the given matrix. + * @throws NullPointerException + * if matrix is null. + */ + @Override + public Matrix3 multiply(final ReadOnlyMatrix3 matrix, final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + final double temp00 = _m00 * matrix.getM00() + _m01 * matrix.getM10() + _m02 * matrix.getM20(); + final double temp01 = _m00 * matrix.getM01() + _m01 * matrix.getM11() + _m02 * matrix.getM21(); + final double temp02 = _m00 * matrix.getM02() + _m01 * matrix.getM12() + _m02 * matrix.getM22(); + final double temp10 = _m10 * matrix.getM00() + _m11 * matrix.getM10() + _m12 * matrix.getM20(); + final double temp11 = _m10 * matrix.getM01() + _m11 * matrix.getM11() + _m12 * matrix.getM21(); + final double temp12 = _m10 * matrix.getM02() + _m11 * matrix.getM12() + _m12 * matrix.getM22(); + final double temp20 = _m20 * matrix.getM00() + _m21 * matrix.getM10() + _m22 * matrix.getM20(); + final double temp21 = _m20 * matrix.getM01() + _m21 * matrix.getM11() + _m22 * matrix.getM21(); + final double temp22 = _m20 * matrix.getM02() + _m21 * matrix.getM12() + _m22 * matrix.getM22(); + + result.set(temp00, temp01, temp02, temp10, temp11, temp12, temp20, temp21, temp22); + + return result; + } + + /** + * Multiplies this matrix by the diagonal matrix formed by the given vector (v^D * M). If supplied, the result is + * stored into the supplied "store" matrix. + * + * @param vec + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store matrix, or a new matrix if store is null. + * @throws NullPointerException + * if vec is null + */ + @Override + public Matrix3 multiplyDiagonalPre(final ReadOnlyVector3 vec, final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + result.set( // + vec.getX() * _m00, vec.getX() * _m01, vec.getX() * _m02, // + vec.getY() * _m10, vec.getY() * _m11, vec.getY() * _m12, // + vec.getZ() * _m20, vec.getZ() * _m21, vec.getZ() * _m22); + + return result; + } + + /** + * Multiplies this matrix by the diagonal matrix formed by the given vector (M * v^D). If supplied, the result is + * stored into the supplied "store" matrix. + * + * @param vec + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store matrix, or a new matrix if store is null. + * @throws NullPointerException + * if vec is null + */ + @Override + public Matrix3 multiplyDiagonalPost(final ReadOnlyVector3 vec, final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + result.set( // + vec.getX() * _m00, vec.getY() * _m01, vec.getZ() * _m02, // + vec.getX() * _m10, vec.getY() * _m11, vec.getZ() * _m12, // + vec.getX() * _m20, vec.getY() * _m21, vec.getZ() * _m22); + + return result; + } + + /** + * Internally scales all values of this matrix by the given scalar. + * + * @param scalar + * @return this matrix for chaining. + */ + public Matrix3 multiplyLocal(final double scalar) { + _m00 *= scalar; + _m01 *= scalar; + _m02 *= scalar; + _m10 *= scalar; + _m11 *= scalar; + _m12 *= scalar; + _m20 *= scalar; + _m21 *= scalar; + _m22 *= scalar; + return this; + } + + /** + * @param matrix + * the matrix to add to this. + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * matrix and store to be the same object. + * @return the result. + * @throws NullPointerException + * if matrix is null + */ + @Override + public Matrix3 add(final ReadOnlyMatrix3 matrix, final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + result._m00 = _m00 + matrix.getM00(); + result._m01 = _m01 + matrix.getM01(); + result._m02 = _m02 + matrix.getM02(); + result._m10 = _m10 + matrix.getM10(); + result._m11 = _m11 + matrix.getM11(); + result._m12 = _m12 + matrix.getM12(); + result._m20 = _m20 + matrix.getM20(); + result._m21 = _m21 + matrix.getM21(); + result._m22 = _m22 + matrix.getM22(); + + return result; + } + + /** + * Internally adds the values of the given matrix to this matrix. + * + * @param matrix + * the matrix to add to this. + * @return this matrix for chaining + * @throws NullPointerException + * if matrix is null + */ + public Matrix3 addLocal(final ReadOnlyMatrix3 matrix) { + return add(matrix, this); + } + + /** + * @param matrix + * the matrix to subtract from this. + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * matrix and store to be the same object. + * @return the result. + * @throws NullPointerException + * if matrix is null + */ + @Override + public Matrix3 subtract(final ReadOnlyMatrix3 matrix, final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + result._m00 = _m00 - matrix.getM00(); + result._m01 = _m01 - matrix.getM01(); + result._m02 = _m02 - matrix.getM02(); + result._m10 = _m10 - matrix.getM10(); + result._m11 = _m11 - matrix.getM11(); + result._m12 = _m12 - matrix.getM12(); + result._m20 = _m20 - matrix.getM20(); + result._m21 = _m21 - matrix.getM21(); + result._m22 = _m22 - matrix.getM22(); + + return result; + } + + /** + * Internally subtracts the values of the given matrix from this matrix. + * + * @param matrix + * the matrix to subtract from this. + * @return this matrix for chaining + * @throws NullPointerException + * if matrix is null + */ + public Matrix3 subtractLocal(final ReadOnlyMatrix3 matrix) { + return subtract(matrix, this); + } + + /** + * Applies the given scale to this matrix and returns the result as a new matrix + * + * @param scale + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. + * @return the new matrix + * @throws NullPointerException + * if scale is null. + */ + @Override + public Matrix3 scale(final ReadOnlyVector3 scale, final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + return result.set( // + _m00 * scale.getX(), _m01 * scale.getY(), _m02 * scale.getZ(), // + _m10 * scale.getX(), _m11 * scale.getY(), _m12 * scale.getZ(), // + _m20 * scale.getX(), _m21 * scale.getY(), _m22 * scale.getZ()); + } + + /** + * Applies the given scale to this matrix values internally + * + * @param scale + * @return this matrix for chaining. + * @throws NullPointerException + * if scale is null. + */ + public Matrix3 scaleLocal(final ReadOnlyVector3 scale) { + return scale(scale, this); + } + + /** + * transposes this matrix as a new matrix, basically flipping it across the diagonal + * + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. + * @return this matrix for chaining. + * @see <a href="http://en.wikipedia.org/wiki/Transpose">wikipedia.org-Transpose</a> + */ + @Override + public Matrix3 transpose(final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + result._m00 = _m00; + result._m01 = _m10; + result._m02 = _m20; + result._m10 = _m01; + result._m11 = _m11; + result._m12 = _m21; + result._m20 = _m02; + result._m21 = _m12; + result._m22 = _m22; + + return result; + } + + /** + * transposes this matrix in place + * + * @return this matrix for chaining. + * @see <a href="http://en.wikipedia.org/wiki/Transpose">wikipedia.org-Transpose</a> + */ + public Matrix3 transposeLocal() { + final double m01 = _m01; + final double m02 = _m02; + final double m12 = _m12; + _m01 = _m10; + _m02 = _m20; + _m12 = _m21; + _m10 = m01; + _m20 = m02; + _m21 = m12; + return this; + } + + /** + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * store == this. + * @return a matrix that represents this matrix, inverted. + * + * if store is not null and is read only + * @throws ArithmeticException + * if this matrix can not be inverted. + */ + @Override + public Matrix3 invert(final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + final double det = determinant(); + if (Math.abs(det) <= MathUtils.EPSILON) { + throw new ArithmeticException("This matrix cannot be inverted."); + } + + final double temp00 = _m11 * _m22 - _m12 * _m21; + final double temp01 = _m02 * _m21 - _m01 * _m22; + final double temp02 = _m01 * _m12 - _m02 * _m11; + final double temp10 = _m12 * _m20 - _m10 * _m22; + final double temp11 = _m00 * _m22 - _m02 * _m20; + final double temp12 = _m02 * _m10 - _m00 * _m12; + final double temp20 = _m10 * _m21 - _m11 * _m20; + final double temp21 = _m01 * _m20 - _m00 * _m21; + final double temp22 = _m00 * _m11 - _m01 * _m10; + result.set(temp00, temp01, temp02, temp10, temp11, temp12, temp20, temp21, temp22); + result.multiplyLocal(1.0 / det); + return result; + } + + /** + * Inverts this matrix locally. + * + * @return this matrix inverted internally. + * @throws ArithmeticException + * if this matrix can not be inverted. + */ + public Matrix3 invertLocal() { + return invert(this); + } + + /** + * @param store + * The matrix to store the result in. If null, a new matrix is created. + * @return The adjugate, or classical adjoint, of this matrix + * @see <a href="http://en.wikipedia.org/wiki/Adjugate_matrix">wikipedia.org-Adjugate_matrix</a> + */ + @Override + public Matrix3 adjugate(final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + final double temp00 = _m11 * _m22 - _m12 * _m21; + final double temp01 = _m02 * _m21 - _m01 * _m22; + final double temp02 = _m01 * _m12 - _m02 * _m11; + final double temp10 = _m12 * _m20 - _m10 * _m22; + final double temp11 = _m00 * _m22 - _m02 * _m20; + final double temp12 = _m02 * _m10 - _m00 * _m12; + final double temp20 = _m10 * _m21 - _m11 * _m20; + final double temp21 = _m01 * _m20 - _m00 * _m21; + final double temp22 = _m00 * _m11 - _m01 * _m10; + + return result.set(temp00, temp01, temp02, temp10, temp11, temp12, temp20, temp21, temp22); + } + + /** + * @return this matrix, modified to represent its adjugate, or classical adjoint + * @see <a href="http://en.wikipedia.org/wiki/Adjugate_matrix">wikipedia.org-Adjugate_matrix</a> + */ + public Matrix3 adjugateLocal() { + return adjugate(this); + } + + /** + * @return the determinate of this 3x3 matrix (aei+bfg+cdh-ceg-bdi-afh) + * @see <a href="http://en.wikipedia.org/wiki/Determinant">wikipedia.org-Determinant</a> + */ + @Override + public double determinant() { + return _m00 * _m11 * _m22 + _m01 * _m12 * _m20 + _m02 * _m10 * _m21 - // + _m02 * _m11 * _m20 - _m01 * _m10 * _m22 - _m00 * _m12 * _m21; + } + + /** + * A function for creating a rotation matrix that rotates a vector called "start" into another vector called "end". + * + * @param start + * normalized non-zero starting vector + * @param end + * normalized non-zero ending vector + * @return this matrix, for chaining + * @see "Tomas Möller, John Hughes 'Efficiently Building a Matrix to Rotate One Vector to Another' Journal of Graphics Tools, 4(4):1-4, 1999" + */ + public Matrix3 fromStartEndLocal(final ReadOnlyVector3 start, final ReadOnlyVector3 end) { + final Vector3 v = new Vector3(); + double e, h, f; + + start.cross(end, v); + e = start.dot(end); + f = e < 0 ? -e : e; + + // if "from" and "to" vectors are nearly parallel + if (f > 1.0 - MathUtils.ZERO_TOLERANCE) { + final Vector3 u = new Vector3(); + final Vector3 x = new Vector3(); + double c1, c2, c3; /* coefficients for later use */ + + x.setX(start.getX() > 0.0 ? start.getX() : -start.getX()); + x.setY(start.getY() > 0.0 ? start.getY() : -start.getY()); + x.setZ(start.getZ() > 0.0 ? start.getZ() : -start.getZ()); + + if (x.getX() < x.getY()) { + if (x.getX() < x.getZ()) { + x.set(1.0, 0.0, 0.0); + } else { + x.set(0.0, 0.0, 1.0); + } + } else { + if (x.getY() < x.getZ()) { + x.set(0.0, 1.0, 0.0); + } else { + x.set(0.0, 0.0, 1.0); + } + } + + u.set(x).subtractLocal(start); + v.set(x).subtractLocal(end); + + c1 = 2.0 / u.dot(u); + c2 = 2.0 / v.dot(v); + c3 = c1 * c2 * u.dot(v); + + _m00 = -c1 * u.getX() * u.getX() - c2 * v.getX() * v.getX() + c3 * v.getX() * u.getX() + 1.0; + _m01 = -c1 * u.getX() * u.getY() - c2 * v.getX() * v.getY() + c3 * v.getX() * u.getY(); + _m02 = -c1 * u.getX() * u.getZ() - c2 * v.getX() * v.getZ() + c3 * v.getX() * u.getZ(); + _m10 = -c1 * u.getY() * u.getX() - c2 * v.getY() * v.getX() + c3 * v.getY() * u.getX(); + _m11 = -c1 * u.getY() * u.getY() - c2 * v.getY() * v.getY() + c3 * v.getY() * u.getY() + 1.0; + _m12 = -c1 * u.getY() * u.getZ() - c2 * v.getY() * v.getZ() + c3 * v.getY() * u.getZ(); + _m20 = -c1 * u.getZ() * u.getX() - c2 * v.getZ() * v.getX() + c3 * v.getZ() * u.getX(); + _m21 = -c1 * u.getZ() * u.getY() - c2 * v.getZ() * v.getY() + c3 * v.getZ() * u.getY(); + _m22 = -c1 * u.getZ() * u.getZ() - c2 * v.getZ() * v.getZ() + c3 * v.getZ() * u.getZ() + 1.0; + } else { + // the most common case, unless "start"="end", or "start"=-"end" + double hvx, hvz, hvxy, hvxz, hvyz; + h = 1.0 / (1.0 + e); + hvx = h * v.getX(); + hvz = h * v.getZ(); + hvxy = hvx * v.getY(); + hvxz = hvx * v.getZ(); + hvyz = hvz * v.getY(); + _m00 = e + hvx * v.getX(); + _m01 = hvxy - v.getZ(); + _m02 = hvxz + v.getY(); + + _m10 = hvxy + v.getZ(); + _m11 = e + h * v.getY() * v.getY(); + _m12 = hvyz - v.getX(); + + _m20 = hvxz - v.getY(); + _m21 = hvyz + v.getX(); + _m22 = e + hvz * v.getZ(); + } + return this; + } + + /** + * Multiplies the given vector by this matrix (v * M). If supplied, the result is stored into the supplied "store" + * vector. + * + * @param vec + * the vector to multiply this matrix by. + * @param store + * a vector to store the result in. If store is null, a new vector is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store vector, or a new vector if store is null. + * @throws NullPointerException + * if vec is null + */ + @Override + public Vector3 applyPre(final ReadOnlyVector3 vec, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final double x = vec.getX(); + final double y = vec.getY(); + final double z = vec.getZ(); + + result.setX(_m00 * x + _m10 * y + _m20 * z); + result.setY(_m01 * x + _m11 * y + _m21 * z); + result.setZ(_m02 * x + _m12 * y + _m22 * z); + return result; + } + + /** + * Multiplies the given vector by this matrix (M * v). If supplied, the result is stored into the supplied "store" + * vector. + * + * @param vec + * the vector to multiply this matrix by. + * @param store + * a vector to store the result in. If store is null, a new vector is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store vector, or a new vector if store is null. + * @throws NullPointerException + * if vec is null + */ + @Override + public Vector3 applyPost(final ReadOnlyVector3 vec, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final double x = vec.getX(); + final double y = vec.getY(); + final double z = vec.getZ(); + + result.setX(_m00 * x + _m01 * y + _m02 * z); + result.setY(_m10 * x + _m11 * y + _m12 * z); + result.setZ(_m20 * x + _m21 * y + _m22 * z); + return result; + } + + /** + * Modifies this matrix to equal the rotation required to point the z-axis at 'direction' and the y-axis to 'up'. + * + * @param direction + * where to 'look' at + * @param up + * a vector indicating the local up direction. + * @return this matrix for chaining + */ + public Matrix3 lookAt(final ReadOnlyVector3 direction, final ReadOnlyVector3 up) { + final Vector3 xAxis = Vector3.fetchTempInstance(); + final Vector3 yAxis = Vector3.fetchTempInstance(); + final Vector3 zAxis = Vector3.fetchTempInstance(); + direction.normalize(zAxis); + up.normalize(xAxis).crossLocal(zAxis).normalizeLocal(); + zAxis.cross(xAxis, yAxis); + + fromAxes(xAxis, yAxis, zAxis); + + Vector3.releaseTempInstance(xAxis); + Vector3.releaseTempInstance(yAxis); + Vector3.releaseTempInstance(zAxis); + return this; + } + + /** + * Check a matrix... if it is null or its doubles are NaN or infinite, return false. Else return true. + * + * @param matrix + * the vector to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyMatrix3 matrix) { + if (matrix == null) { + return false; + } + + if (Double.isNaN(matrix.getM00()) || Double.isInfinite(matrix.getM00())) { + return false; + } else if (Double.isNaN(matrix.getM01()) || Double.isInfinite(matrix.getM01())) { + return false; + } else if (Double.isNaN(matrix.getM02()) || Double.isInfinite(matrix.getM02())) { + return false; + } else if (Double.isNaN(matrix.getM10()) || Double.isInfinite(matrix.getM10())) { + return false; + } else if (Double.isNaN(matrix.getM11()) || Double.isInfinite(matrix.getM11())) { + return false; + } else if (Double.isNaN(matrix.getM12()) || Double.isInfinite(matrix.getM12())) { + return false; + } else if (Double.isNaN(matrix.getM20()) || Double.isInfinite(matrix.getM20())) { + return false; + } else if (Double.isNaN(matrix.getM21()) || Double.isInfinite(matrix.getM21())) { + return false; + } else if (Double.isNaN(matrix.getM22()) || Double.isInfinite(matrix.getM22())) { + return false; + } + + return true; + } + + /** + * @return true if this Matrix is orthonormal - its rows are orthogonal, unit vectors. + */ + @Override + public boolean isOrthonormal() { + if (Math.abs(_m00 * _m00 + _m01 * _m01 + _m02 * _m02 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m00 * _m10 + _m01 * _m11 + _m02 * _m12 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m00 * _m20 + _m01 * _m21 + _m02 * _m22 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m10 * _m00 + _m11 * _m01 + _m12 * _m02 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m10 * _m10 + _m11 * _m11 + _m12 * _m12 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m10 * _m20 + _m11 * _m21 + _m12 * _m22 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m20 * _m00 + _m21 * _m01 + _m22 * _m02 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m20 * _m10 + _m21 * _m11 + _m22 * _m12 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } else if (Math.abs(_m20 * _m20 + _m21 * _m21 + _m22 * _m22 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + + return true; + } + + /** + * @return the string representation of this matrix. + */ + @Override + public String toString() { + final StringBuffer result = new StringBuffer("com.ardor3d.math.Matrix3\n[\n"); + result.append(" "); + result.append(_m00); + result.append(" "); + result.append(_m01); + result.append(" "); + result.append(_m02); + result.append(" \n"); + + result.append(" "); + result.append(_m10); + result.append(" "); + result.append(_m11); + result.append(" "); + result.append(_m12); + result.append(" \n"); + + result.append(" "); + result.append(_m20); + result.append(" "); + result.append(_m21); + result.append(" "); + result.append(_m22); + result.append(" \n"); + + result.append("]"); + return result.toString(); + } + + /** + * @return returns a unique code for this matrix object based on its values. If two matrices are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + long val = Double.doubleToLongBits(_m00); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m01); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m02); + result += 31 * result + (int) (val ^ val >>> 32); + + val = Double.doubleToLongBits(_m10); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m11); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m12); + result += 31 * result + (int) (val ^ val >>> 32); + + val = Double.doubleToLongBits(_m20); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m21); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m22); + result += 31 * result + (int) (val ^ val >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this matrix and the provided matrix have the double values that are within the + * MathUtils.ZERO_TOLERANCE. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyMatrix3)) { + return false; + } + final ReadOnlyMatrix3 comp = (ReadOnlyMatrix3) o; + if (Math.abs(getM00() - comp.getM00()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM01() - comp.getM01()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM02() - comp.getM02()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM10() - comp.getM10()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM11() - comp.getM11()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM12() - comp.getM12()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM20() - comp.getM20()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM21() - comp.getM21()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM22() - comp.getM22()) > Matrix3.ALLOWED_DEVIANCE) { + return false; + } + + return true; + } + + /** + * @param o + * the object to compare for equality + * @return true if this matrix and the provided matrix have the exact same double values. + */ + public boolean strictEquals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyMatrix3)) { + return false; + } + final ReadOnlyMatrix3 comp = (ReadOnlyMatrix3) o; + if (getM00() != comp.getM00()) { + return false; + } else if (getM01() != comp.getM01()) { + return false; + } else if (getM02() != comp.getM02()) { + return false; + } else if (getM10() != comp.getM10()) { + return false; + } else if (getM11() != comp.getM11()) { + return false; + } else if (getM12() != comp.getM12()) { + return false; + } else if (getM20() != comp.getM20()) { + return false; + } else if (getM21() != comp.getM21()) { + return false; + } else if (getM22() != comp.getM22()) { + return false; + } + + return true; + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Matrix3 clone() { + return new Matrix3(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Matrix3> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_m00, "m00", 1.0); + capsule.write(_m01, "m01", 0.0); + capsule.write(_m02, "m02", 0.0); + capsule.write(_m10, "m10", 0.0); + capsule.write(_m11, "m11", 1.0); + capsule.write(_m12, "m12", 0.0); + capsule.write(_m20, "m20", 0.0); + capsule.write(_m21, "m21", 0.0); + capsule.write(_m22, "m22", 1.0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _m00 = capsule.readDouble("m00", 1.0); + _m01 = capsule.readDouble("m01", 0.0); + _m02 = capsule.readDouble("m02", 0.0); + _m10 = capsule.readDouble("m10", 0.0); + _m11 = capsule.readDouble("m11", 1.0); + _m12 = capsule.readDouble("m12", 0.0); + _m20 = capsule.readDouble("m20", 0.0); + _m21 = capsule.readDouble("m21", 0.0); + _m22 = capsule.readDouble("m22", 1.0); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + _m00 = in.readDouble(); + _m01 = in.readDouble(); + _m02 = in.readDouble(); + _m10 = in.readDouble(); + _m11 = in.readDouble(); + _m12 = in.readDouble(); + _m20 = in.readDouble(); + _m21 = in.readDouble(); + _m22 = in.readDouble(); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeDouble(_m00); + out.writeDouble(_m01); + out.writeDouble(_m02); + out.writeDouble(_m10); + out.writeDouble(_m11); + out.writeDouble(_m12); + out.writeDouble(_m20); + out.writeDouble(_m21); + out.writeDouble(_m22); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Matrix3 that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Matrix3 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Matrix3.MAT_POOL.fetch(); + } else { + return new Matrix3(); + } + } + + /** + * Releases a Matrix3 back to be used by a future call to fetchTempInstance. TAKE CARE: this Matrix3 object should + * no longer have other classes referencing it or "Bad Things" will happen. + * + * @param mat + * the Matrix3 to release. + */ + public final static void releaseTempInstance(final Matrix3 mat) { + if (MathConstants.useMathPools) { + Matrix3.MAT_POOL.release(mat); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Matrix4.java b/ardor3d-math/src/main/java/com/ardor3d/math/Matrix4.java new file mode 100644 index 0000000..9b2131f --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Matrix4.java @@ -0,0 +1,2440 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.nio.BufferOverflowException; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.math.type.ReadOnlyVector4; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Matrix4 represents a double precision 4x4 matrix and contains a flag, set at object creation, indicating if the given + * Matrix4 object is mutable. + * + * Note: some algorithms in this class were ported from Eberly, Wolfram, Game Gems and others to Java. + */ +public class Matrix4 implements Cloneable, Savable, Externalizable, ReadOnlyMatrix4, Poolable { + /** Used with equals method to determine if two Matrix4 objects are close enough to be considered equal. */ + public static final double ALLOWED_DEVIANCE = 0.00000001; + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Matrix4> MAT_POOL = ObjectPool.create(Matrix4.class, MathConstants.maxMathPoolSize); + + /** + * <pre> + * 1, 0, 0, 0 + * 0, 1, 0, 0 + * 0, 0, 1, 0 + * 0, 0, 0, 1 + * </pre> + */ + public final static ReadOnlyMatrix4 IDENTITY = new Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + + protected double _m00, _m01, _m02, _m03, // + _m10, _m11, _m12, _m13, // + _m20, _m21, _m22, _m23, // + _m30, _m31, _m32, _m33; + + /** + * Constructs a new matrix set to identity. + */ + public Matrix4() { + this(Matrix4.IDENTITY); + } + + /** + * Constructs a new matrix set to the given matrix values. (names are mRC = m[ROW][COL]) + * + * @param m00 + * @param m01 + * @param m02 + * @param m03 + * @param m10 + * @param m11 + * @param m12 + * @param m13 + * @param m20 + * @param m21 + * @param m22 + * @param m23 + * @param m30 + * @param m31 + * @param m32 + * @param m33 + */ + public Matrix4(final double m00, final double m01, final double m02, final double m03, final double m10, + final double m11, final double m12, final double m13, final double m20, final double m21, final double m22, + final double m23, final double m30, final double m31, final double m32, final double m33) { + + _m00 = m00; + _m01 = m01; + _m02 = m02; + _m03 = m03; + _m10 = m10; + _m11 = m11; + _m12 = m12; + _m13 = m13; + _m20 = m20; + _m21 = m21; + _m22 = m22; + _m23 = m23; + _m30 = m30; + _m31 = m31; + _m32 = m32; + _m33 = m33; + } + + /** + * Constructs a new matrix set to the values of the given matrix. + * + * @param source + */ + public Matrix4(final ReadOnlyMatrix4 source) { + set(source); + } + + /** + * @param row + * @param column + * @return the value stored in this matrix at row, column. + * @throws IllegalArgumentException + * if row and column are not in bounds [0, 3] + */ + @Override + public double getValue(final int row, final int column) { + switch (row) { + case 0: + switch (column) { + case 0: + return _m00; + case 1: + return _m01; + case 2: + return _m02; + case 3: + return _m03; + } + break; + case 1: + switch (column) { + case 0: + return _m10; + case 1: + return _m11; + case 2: + return _m12; + case 3: + return _m13; + } + break; + + case 2: + switch (column) { + case 0: + return _m20; + case 1: + return _m21; + case 2: + return _m22; + case 3: + return _m23; + } + break; + + case 3: + switch (column) { + case 0: + return _m30; + case 1: + return _m31; + case 2: + return _m32; + case 3: + return _m33; + } + break; + } + throw new IllegalArgumentException(); + } + + /** + * @param row + * @param column + * @return the value stored in this matrix at row, column, pre-cast to a float for convenience. + * @throws IllegalArgumentException + * if row and column are not in bounds [0, 2] + */ + @Override + public float getValuef(final int row, final int column) { + return (float) getValue(row, column); + } + + @Override + public double getM00() { + return _m00; + } + + @Override + public double getM01() { + return _m01; + } + + @Override + public double getM02() { + return _m02; + } + + @Override + public double getM03() { + return _m03; + } + + @Override + public double getM10() { + return _m10; + } + + @Override + public double getM11() { + return _m11; + } + + @Override + public double getM12() { + return _m12; + } + + @Override + public double getM13() { + return _m13; + } + + @Override + public double getM20() { + return _m20; + } + + @Override + public double getM21() { + return _m21; + } + + @Override + public double getM22() { + return _m22; + } + + @Override + public double getM23() { + return _m23; + } + + @Override + public double getM30() { + return _m30; + } + + @Override + public double getM31() { + return _m31; + } + + @Override + public double getM32() { + return _m32; + } + + @Override + public double getM33() { + return _m33; + } + + public void setM00(final double m00) { + _m00 = m00; + } + + public void setM01(final double m01) { + _m01 = m01; + } + + public void setM02(final double m02) { + _m02 = m02; + } + + public void setM03(final double m03) { + _m03 = m03; + } + + public void setM10(final double m10) { + _m10 = m10; + } + + public void setM11(final double m11) { + _m11 = m11; + } + + public void setM12(final double m12) { + _m12 = m12; + } + + public void setM13(final double m13) { + _m13 = m13; + } + + public void setM20(final double m20) { + _m20 = m20; + } + + public void setM21(final double m21) { + _m21 = m21; + } + + public void setM22(final double m22) { + _m22 = m22; + } + + public void setM23(final double m23) { + _m23 = m23; + } + + public void setM30(final double m30) { + _m30 = m30; + } + + public void setM31(final double m31) { + _m31 = m31; + } + + public void setM32(final double m32) { + _m32 = m32; + } + + public void setM33(final double m33) { + _m33 = m33; + } + + /** + * Same as set(IDENTITY) + * + * @return this matrix for chaining + */ + public Matrix4 setIdentity() { + return set(Matrix4.IDENTITY); + } + + /** + * @return true if this matrix equals the 4x4 identity matrix + */ + @Override + public boolean isIdentity() { + return strictEquals(Matrix4.IDENTITY); + } + + /** + * Sets the value of this matrix at row, column to the given value. + * + * @param row + * @param column + * @param value + * @return this matrix for chaining + * @throws IllegalArgumentException + * if row and column are not in bounds [0, 3] + */ + public Matrix4 setValue(final int row, final int column, final double value) { + switch (row) { + case 0: + switch (column) { + case 0: + _m00 = value; + break; + case 1: + _m01 = value; + break; + case 2: + _m02 = value; + break; + case 3: + _m03 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + case 1: + switch (column) { + case 0: + _m10 = value; + break; + case 1: + _m11 = value; + break; + case 2: + _m12 = value; + break; + case 3: + _m13 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + case 2: + switch (column) { + case 0: + _m20 = value; + break; + case 1: + _m21 = value; + break; + case 2: + _m22 = value; + break; + case 3: + _m23 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + case 3: + switch (column) { + case 0: + _m30 = value; + break; + case 1: + _m31 = value; + break; + case 2: + _m32 = value; + break; + case 3: + _m33 = value; + break; + default: + throw new IllegalArgumentException(); + } + break; + + default: + throw new IllegalArgumentException(); + } + + return this; + } + + /** + * Sets the values of this matrix to the values given. + * + * @param m00 + * @param m01 + * @param m02 + * @param m03 + * @param m10 + * @param m11 + * @param m12 + * @param m13 + * @param m20 + * @param m21 + * @param m22 + * @param m23 + * @param m30 + * @param m31 + * @param m32 + * @param m33 + * @return this matrix for chaining + */ + public Matrix4 set(final double m00, final double m01, final double m02, final double m03, final double m10, + final double m11, final double m12, final double m13, final double m20, final double m21, final double m22, + final double m23, final double m30, final double m31, final double m32, final double m33) { + + _m00 = m00; + _m01 = m01; + _m02 = m02; + _m03 = m03; + _m10 = m10; + _m11 = m11; + _m12 = m12; + _m13 = m13; + _m20 = m20; + _m21 = m21; + _m22 = m22; + _m23 = m23; + _m30 = m30; + _m31 = m31; + _m32 = m32; + _m33 = m33; + + return this; + } + + /** + * Sets the values of this matrix to the values of the provided source matrix. + * + * @param source + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + */ + public Matrix4 set(final ReadOnlyMatrix4 source) { + _m00 = source.getM00(); + _m01 = source.getM01(); + _m02 = source.getM02(); + _m03 = source.getM03(); + + _m10 = source.getM10(); + _m11 = source.getM11(); + _m12 = source.getM12(); + _m13 = source.getM13(); + + _m20 = source.getM20(); + _m21 = source.getM21(); + _m22 = source.getM22(); + _m23 = source.getM23(); + + _m30 = source.getM30(); + _m31 = source.getM31(); + _m32 = source.getM32(); + _m33 = source.getM33(); + + return this; + } + + /** + * Sets the 3x3 rotation part of this matrix to the values of the provided source matrix. + * + * @param source + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + */ + public Matrix4 set(final ReadOnlyMatrix3 source) { + _m00 = source.getM00(); + _m01 = source.getM01(); + _m02 = source.getM02(); + _m10 = source.getM10(); + _m11 = source.getM11(); + _m12 = source.getM12(); + _m20 = source.getM20(); + _m21 = source.getM21(); + _m22 = source.getM22(); + return this; + } + + /** + * Sets the values of this matrix to the rotational value of the given quaternion. Only modifies the 3x3 rotation + * part of this matrix. + * + * @param quaternion + * @return this matrix for chaining + */ + public Matrix4 set(final ReadOnlyQuaternion quaternion) { + return quaternion.toRotationMatrix(this); + } + + /** + * @param source + * the buffer to read our matrix data from. + * @return this matrix for chaining. + */ + public Matrix4 fromDoubleBuffer(final DoubleBuffer source) { + return fromDoubleBuffer(source, true); + } + + /** + * @param source + * the buffer to read our matrix data from. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return this matrix for chaining. + */ + public Matrix4 fromDoubleBuffer(final DoubleBuffer source, final boolean rowMajor) { + if (rowMajor) { + _m00 = source.get(); + _m01 = source.get(); + _m02 = source.get(); + _m03 = source.get(); + _m10 = source.get(); + _m11 = source.get(); + _m12 = source.get(); + _m13 = source.get(); + _m20 = source.get(); + _m21 = source.get(); + _m22 = source.get(); + _m23 = source.get(); + _m30 = source.get(); + _m31 = source.get(); + _m32 = source.get(); + _m33 = source.get(); + } else { + _m00 = source.get(); + _m10 = source.get(); + _m20 = source.get(); + _m30 = source.get(); + _m01 = source.get(); + _m11 = source.get(); + _m21 = source.get(); + _m31 = source.get(); + _m02 = source.get(); + _m12 = source.get(); + _m22 = source.get(); + _m32 = source.get(); + _m03 = source.get(); + _m13 = source.get(); + _m23 = source.get(); + _m33 = source.get(); + } + + return this; + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to read our matrix data from. + * @return this matrix for chaining. + */ + public Matrix4 fromFloatBuffer(final FloatBuffer source) { + return fromFloatBuffer(source, true); + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to read our matrix data from. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return this matrix for chaining. + */ + public Matrix4 fromFloatBuffer(final FloatBuffer source, final boolean rowMajor) { + if (rowMajor) { + _m00 = source.get(); + _m01 = source.get(); + _m02 = source.get(); + _m03 = source.get(); + _m10 = source.get(); + _m11 = source.get(); + _m12 = source.get(); + _m13 = source.get(); + _m20 = source.get(); + _m21 = source.get(); + _m22 = source.get(); + _m23 = source.get(); + _m30 = source.get(); + _m31 = source.get(); + _m32 = source.get(); + _m33 = source.get(); + } else { + _m00 = source.get(); + _m10 = source.get(); + _m20 = source.get(); + _m30 = source.get(); + _m01 = source.get(); + _m11 = source.get(); + _m21 = source.get(); + _m31 = source.get(); + _m02 = source.get(); + _m12 = source.get(); + _m22 = source.get(); + _m32 = source.get(); + _m03 = source.get(); + _m13 = source.get(); + _m23 = source.get(); + _m33 = source.get(); + } + + return this; + } + + /** + * Sets the values of this matrix to the values of the provided double array. + * + * @param source + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + * @throws ArrayIndexOutOfBoundsException + * if source array has a length less than 16. + */ + public Matrix4 fromArray(final double[] source) { + return fromArray(source, true); + } + + /** + * Sets the values of this matrix to the values of the provided double array. + * + * @param source + * @param rowMajor + * @return this matrix for chaining + * @throws NullPointerException + * if source is null. + * @throws ArrayIndexOutOfBoundsException + * if source array has a length less than 16. + */ + public Matrix4 fromArray(final double[] source, final boolean rowMajor) { + if (rowMajor) { + _m00 = source[0]; + _m01 = source[1]; + _m02 = source[2]; + _m03 = source[3]; + _m10 = source[4]; + _m11 = source[5]; + _m12 = source[6]; + _m13 = source[7]; + _m20 = source[8]; + _m21 = source[9]; + _m22 = source[10]; + _m23 = source[11]; + _m30 = source[12]; + _m31 = source[13]; + _m32 = source[14]; + _m33 = source[15]; + } else { + _m00 = source[0]; + _m10 = source[1]; + _m20 = source[2]; + _m30 = source[3]; + _m01 = source[4]; + _m11 = source[5]; + _m21 = source[6]; + _m31 = source[7]; + _m02 = source[8]; + _m12 = source[9]; + _m22 = source[10]; + _m32 = source[11]; + _m03 = source[12]; + _m13 = source[13]; + _m23 = source[14]; + _m33 = source[15]; + } + return this; + } + + /** + * Replaces a column in this matrix with the values of the given array. + * + * @param columnIndex + * @param columnData + * @return this matrix for chaining + * @throws NullPointerException + * if columnData is null. + * @throws IllegalArgumentException + * if columnData has a length < 4 + * @throws IllegalArgumentException + * if columnIndex is not in [0, 3] + */ + public Matrix4 setColumn(final int columnIndex, final Vector4 columnData) { + switch (columnIndex) { + case 0: + _m00 = columnData.getX(); + _m10 = columnData.getY(); + _m20 = columnData.getZ(); + _m30 = columnData.getW(); + break; + case 1: + _m01 = columnData.getX(); + _m11 = columnData.getY(); + _m21 = columnData.getZ(); + _m31 = columnData.getW(); + break; + case 2: + _m02 = columnData.getX(); + _m12 = columnData.getY(); + _m22 = columnData.getZ(); + _m32 = columnData.getW(); + break; + case 3: + _m03 = columnData.getX(); + _m13 = columnData.getY(); + _m23 = columnData.getZ(); + _m33 = columnData.getW(); + break; + default: + throw new IllegalArgumentException("Bad columnIndex: " + columnIndex); + } + return this; + } + + /** + * Replaces a row in this matrix with the values of the given array. + * + * @param rowIndex + * @param rowData + * @return this matrix for chaining + * @throws NullPointerException + * if rowData is null. + * @throws IllegalArgumentException + * if rowData has a length < 4 + * @throws IllegalArgumentException + * if rowIndex is not in [0, 3] + */ + public Matrix4 setRow(final int rowIndex, final Vector4 rowData) { + switch (rowIndex) { + case 0: + _m00 = rowData.getX(); + _m01 = rowData.getY(); + _m02 = rowData.getZ(); + _m03 = rowData.getW(); + break; + case 1: + _m10 = rowData.getX(); + _m11 = rowData.getY(); + _m12 = rowData.getZ(); + _m13 = rowData.getW(); + break; + case 2: + _m20 = rowData.getX(); + _m21 = rowData.getY(); + _m22 = rowData.getZ(); + _m23 = rowData.getW(); + break; + case 3: + _m30 = rowData.getX(); + _m31 = rowData.getY(); + _m32 = rowData.getZ(); + _m33 = rowData.getW(); + break; + default: + throw new IllegalArgumentException("Bad rowIndex: " + rowIndex); + } + return this; + } + + /** + * Sets the 3x3 rotation portion of this matrix to the rotation indicated by the given angle and axis of rotation. + * Note: This method creates an object, so use fromAngleNormalAxis when possible, particularly if your axis is + * already normalized. + * + * @param angle + * the angle to rotate (in radians). + * @param axis + * the axis of rotation. + * @return this matrix for chaining + * @throws NullPointerException + * if axis is null. + */ + public Matrix4 fromAngleAxis(final double angle, final ReadOnlyVector3 axis) { + final Vector3 normAxis = Vector3.fetchTempInstance(); + axis.normalize(normAxis); + fromAngleNormalAxis(angle, normAxis); + Vector3.releaseTempInstance(normAxis); + return this; + } + + /** + * Sets the 3x3 rotation portion of this matrix to the rotation indicated by the given angle and a unit-length axis + * of rotation. + * + * @param angle + * the angle to rotate (in radians). + * @param axis + * the axis of rotation (already normalized). + * @return this matrix for chaining + * @throws NullPointerException + * if axis is null. + */ + public Matrix4 fromAngleNormalAxis(final double angle, final ReadOnlyVector3 axis) { + final double fCos = MathUtils.cos(angle); + final double fSin = MathUtils.sin(angle); + final double fOneMinusCos = 1.0 - fCos; + final double fX2 = axis.getX() * axis.getX(); + final double fY2 = axis.getY() * axis.getY(); + final double fZ2 = axis.getZ() * axis.getZ(); + final double fXYM = axis.getX() * axis.getY() * fOneMinusCos; + final double fXZM = axis.getX() * axis.getZ() * fOneMinusCos; + final double fYZM = axis.getY() * axis.getZ() * fOneMinusCos; + final double fXSin = axis.getX() * fSin; + final double fYSin = axis.getY() * fSin; + final double fZSin = axis.getZ() * fSin; + + _m00 = fX2 * fOneMinusCos + fCos; + _m01 = fXYM - fZSin; + _m02 = fXZM + fYSin; + _m10 = fXYM + fZSin; + _m11 = fY2 * fOneMinusCos + fCos; + _m12 = fYZM - fXSin; + _m20 = fXZM - fYSin; + _m21 = fYZM + fXSin; + _m22 = fZ2 * fOneMinusCos + fCos; + + return this; + } + + public Matrix4 applyRotation(final double angle, final double x, final double y, final double z) { + final double m00 = _m00, m01 = _m01, m02 = _m02, // + m10 = _m10, m11 = _m11, m12 = _m12, // + m20 = _m20, m21 = _m21, m22 = _m22, // + m30 = _m30, m31 = _m31, m32 = _m32; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + final double oneMinusCos = 1.0f - cosAngle; + final double xyOneMinusCos = x * y * oneMinusCos; + final double xzOneMinusCos = x * z * oneMinusCos; + final double yzOneMinusCos = y * z * oneMinusCos; + final double xSin = x * sinAngle; + final double ySin = y * sinAngle; + final double zSin = z * sinAngle; + + final double r00 = x * x * oneMinusCos + cosAngle; + final double r01 = xyOneMinusCos - zSin; + final double r02 = xzOneMinusCos + ySin; + final double r10 = xyOneMinusCos + zSin; + final double r11 = y * y * oneMinusCos + cosAngle; + final double r12 = yzOneMinusCos - xSin; + final double r20 = xzOneMinusCos - ySin; + final double r21 = yzOneMinusCos + xSin; + final double r22 = z * z * oneMinusCos + cosAngle; + + _m00 = m00 * r00 + m01 * r10 + m02 * r20; + _m01 = m00 * r01 + m01 * r11 + m02 * r21; + _m02 = m00 * r02 + m01 * r12 + m02 * r22; + // _m03 is unchanged + + _m10 = m10 * r00 + m11 * r10 + m12 * r20; + _m11 = m10 * r01 + m11 * r11 + m12 * r21; + _m12 = m10 * r02 + m11 * r12 + m12 * r22; + // _m13 is unchanged + + _m20 = m20 * r00 + m21 * r10 + m22 * r20; + _m21 = m20 * r01 + m21 * r11 + m22 * r21; + _m22 = m20 * r02 + m21 * r12 + m22 * r22; + // _m23 is unchanged + + _m30 = m30 * r00 + m31 * r10 + m32 * r20; + _m31 = m30 * r01 + m31 * r11 + m32 * r21; + _m32 = m30 * r02 + m31 * r12 + m32 * r22; + // _m33 is unchanged + + return this; + } + + public Matrix4 applyRotationX(final double angle) { + final double m01 = _m01, m02 = _m02, // + m11 = _m11, m12 = _m12, // + m21 = _m21, m22 = _m22, // + m31 = _m31, m32 = _m32; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + + _m01 = m01 * cosAngle + m02 * sinAngle; + _m02 = m02 * cosAngle - m01 * sinAngle; + + _m11 = m11 * cosAngle + m12 * sinAngle; + _m12 = m12 * cosAngle - m11 * sinAngle; + + _m21 = m21 * cosAngle + m22 * sinAngle; + _m22 = m22 * cosAngle - m21 * sinAngle; + + _m31 = m31 * cosAngle + m32 * sinAngle; + _m32 = m32 * cosAngle - m31 * sinAngle; + + return this; + } + + public Matrix4 applyRotationY(final double angle) { + final double m00 = _m00, m02 = _m02, // + m10 = _m10, m12 = _m12, // + m20 = _m20, m22 = _m22, // + m30 = _m30, m32 = _m32; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + + _m00 = m00 * cosAngle - m02 * sinAngle; + _m02 = m00 * sinAngle + m02 * cosAngle; + + _m10 = m10 * cosAngle - m12 * sinAngle; + _m12 = m10 * sinAngle + m12 * cosAngle; + + _m20 = m20 * cosAngle - m22 * sinAngle; + _m22 = m20 * sinAngle + m22 * cosAngle; + + _m30 = m30 * cosAngle - m32 * sinAngle; + _m32 = m30 * sinAngle + m32 * cosAngle; + + return this; + } + + public Matrix4 applyRotationZ(final double angle) { + final double m00 = _m00, m01 = _m01, // + m10 = _m10, m11 = _m11, // + m20 = _m20, m21 = _m21, // + m30 = _m30, m31 = _m31; + + final double cosAngle = Math.cos(angle); + final double sinAngle = Math.sin(angle); + + _m00 = m00 * cosAngle + m01 * sinAngle; + _m01 = m01 * cosAngle - m00 * sinAngle; + + _m10 = m10 * cosAngle + m11 * sinAngle; + _m11 = m11 * cosAngle - m10 * sinAngle; + + _m20 = m20 * cosAngle + m21 * sinAngle; + _m21 = m21 * cosAngle - m20 * sinAngle; + + _m20 = m30 * cosAngle + m31 * sinAngle; + _m21 = m31 * cosAngle - m30 * sinAngle; + + return this; + } + + /** + * M*T + * + * @param x + * @param y + * @param z + * @return + */ + public Matrix4 applyTranslationPost(final double x, final double y, final double z) { + _m03 = _m00 * x + _m01 * y + _m02 * z + _m03; + _m13 = _m10 * x + _m11 * y + _m12 * z + _m13; + _m23 = _m20 * x + _m21 * y + _m22 * z + _m23; + _m33 = _m30 * x + _m31 * y + _m32 * z + _m33; + + return this; + } + + /** + * T*M + * + * @param x + * @param y + * @param z + * @return + */ + public Matrix4 applyTranslationPre(final double x, final double y, final double z) { + _m03 = x; + _m13 = y; + _m23 = z; + + return this; + } + + /** + * @param index + * @param store + * the vector to store the result in. if null, a new one is created. + * @return the column specified by the index. + * @throws IllegalArgumentException + * if index is not in bounds [0, 3] + */ + @Override + public Vector4 getColumn(final int index, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + switch (index) { + case 0: + result.setX(_m00); + result.setY(_m10); + result.setZ(_m20); + result.setW(_m30); + break; + case 1: + result.setX(_m01); + result.setY(_m11); + result.setZ(_m21); + result.setW(_m31); + break; + case 2: + result.setX(_m02); + result.setY(_m12); + result.setZ(_m22); + result.setW(_m32); + break; + case 3: + result.setX(_m03); + result.setY(_m13); + result.setZ(_m23); + result.setW(_m33); + break; + default: + throw new IllegalArgumentException("invalid column index: " + index); + } + + return result; + } + + /** + * @param index + * @param store + * the vector to store the result in. if null, a new one is created. + * @return the row specified by the index. + * @throws IllegalArgumentException + * if index is not in bounds [0, 3] + */ + @Override + public Vector4 getRow(final int index, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + switch (index) { + case 0: + result.setX(_m00); + result.setY(_m01); + result.setZ(_m02); + result.setW(_m03); + break; + case 1: + result.setX(_m10); + result.setY(_m11); + result.setZ(_m12); + result.setW(_m13); + break; + case 2: + result.setX(_m20); + result.setY(_m21); + result.setZ(_m22); + result.setW(_m23); + break; + case 3: + result.setX(_m30); + result.setY(_m31); + result.setZ(_m32); + result.setW(_m33); + break; + default: + throw new IllegalArgumentException("invalid row index: " + index); + } + return result; + } + + /** + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * @return matrix data as a DoubleBuffer in row major order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 16 values. + */ + @Override + public DoubleBuffer toDoubleBuffer(final DoubleBuffer store) { + return toDoubleBuffer(store, true); + } + + /** + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * position. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return matrix data as a DoubleBuffer in the specified order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 16 values. + */ + @Override + public DoubleBuffer toDoubleBuffer(final DoubleBuffer store, final boolean rowMajor) { + if (rowMajor) { + store.put(_m00); + store.put(_m01); + store.put(_m02); + store.put(_m03); + store.put(_m10); + store.put(_m11); + store.put(_m12); + store.put(_m13); + store.put(_m20); + store.put(_m21); + store.put(_m22); + store.put(_m23); + store.put(_m30); + store.put(_m31); + store.put(_m32); + store.put(_m33); + } else { + store.put(_m00); + store.put(_m10); + store.put(_m20); + store.put(_m30); + store.put(_m01); + store.put(_m11); + store.put(_m21); + store.put(_m31); + store.put(_m02); + store.put(_m12); + store.put(_m22); + store.put(_m32); + store.put(_m03); + store.put(_m13); + store.put(_m23); + store.put(_m33); + } + + return store; + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * @return matrix data as a FloatBuffer in row major order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 16 values. + */ + @Override + public FloatBuffer toFloatBuffer(final FloatBuffer store) { + return toFloatBuffer(store, true); + } + + /** + * Note: data is cast to floats. + * + * @param store + * the buffer to store our matrix data in. Must not be null. Data is entered starting at current buffer + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return matrix data as a FloatBuffer in the specified order. The position is at the end of the inserted data. + * @throws NullPointerException + * if store is null. + * @throws BufferOverflowException + * if there is not enough room left in the buffer to write all 16 values. + */ + @Override + public FloatBuffer toFloatBuffer(final FloatBuffer store, final boolean rowMajor) { + if (rowMajor) { + store.put((float) _m00); + store.put((float) _m01); + store.put((float) _m02); + store.put((float) _m03); + store.put((float) _m10); + store.put((float) _m11); + store.put((float) _m12); + store.put((float) _m13); + store.put((float) _m20); + store.put((float) _m21); + store.put((float) _m22); + store.put((float) _m23); + store.put((float) _m30); + store.put((float) _m31); + store.put((float) _m32); + store.put((float) _m33); + } else { + store.put((float) _m00); + store.put((float) _m10); + store.put((float) _m20); + store.put((float) _m30); + store.put((float) _m01); + store.put((float) _m11); + store.put((float) _m21); + store.put((float) _m31); + store.put((float) _m02); + store.put((float) _m12); + store.put((float) _m22); + store.put((float) _m32); + store.put((float) _m03); + store.put((float) _m13); + store.put((float) _m23); + store.put((float) _m33); + } + + return store; + } + + /** + * @param store + * the double array to store our matrix data in. If null, a new array is created. + * @return matrix data as a double array in row major order. + * @throws IllegalArgumentException + * if the store is non-null and has a length < 16 + */ + @Override + public double[] toArray(final double[] store) { + return toArray(store, true); + } + + /** + * @param store + * the double array to store our matrix data in. If null, a new array is created. + * @param rowMajor + * if true, data is stored row by row. Otherwise it is stored column by column. + * @return matrix data as a double array in the specified order. + * @throws IllegalArgumentException + * if the store is non-null and has a length < 16 + */ + @Override + public double[] toArray(final double[] store, final boolean rowMajor) { + double[] result = store; + if (result == null) { + result = new double[16]; + } else if (result.length < 16) { + throw new IllegalArgumentException("store must be at least length 16."); + } + + if (rowMajor) { + result[0] = _m00; + result[1] = _m01; + result[2] = _m02; + result[3] = _m03; + result[4] = _m10; + result[5] = _m11; + result[6] = _m12; + result[7] = _m13; + result[8] = _m20; + result[9] = _m21; + result[10] = _m22; + result[11] = _m23; + result[12] = _m30; + result[13] = _m31; + result[14] = _m32; + result[15] = _m33; + } else { + result[0] = _m00; + result[1] = _m10; + result[2] = _m20; + result[3] = _m30; + result[4] = _m01; + result[5] = _m11; + result[6] = _m21; + result[7] = _m31; + result[8] = _m02; + result[9] = _m12; + result[10] = _m22; + result[11] = _m32; + result[12] = _m03; + result[13] = _m13; + result[14] = _m23; + result[15] = _m33; + } + + return result; + } + + /** + * Multiplies this matrix by the diagonal matrix formed by the given vector (v^D * M). If supplied, the result is + * stored into the supplied "store" matrix. + * + * @param vec + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store matrix, or a new matrix if store is null. + * @throws NullPointerException + * if vec is null + */ + @Override + public Matrix4 multiplyDiagonalPre(final ReadOnlyVector4 vec, final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + result.set( // + vec.getX() * _m00, vec.getX() * _m01, vec.getX() * _m02, vec.getX() * _m03, // + vec.getY() * _m10, vec.getY() * _m11, vec.getY() * _m12, vec.getY() * _m13, // + vec.getZ() * _m20, vec.getZ() * _m21, vec.getZ() * _m22, vec.getZ() * _m23, // + vec.getW() * _m30, vec.getW() * _m31, vec.getW() * _m32, vec.getW() * _m33); + + return result; + } + + /** + * Multiplies this matrix by the diagonal matrix formed by the given vector (M * v^D). If supplied, the result is + * stored into the supplied "store" matrix. + * + * @param vec + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store matrix, or a new matrix if store is null. + * @throws NullPointerException + * if vec is null + */ + @Override + public Matrix4 multiplyDiagonalPost(final ReadOnlyVector4 vec, final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + result.set( // + vec.getX() * _m00, vec.getY() * _m01, vec.getZ() * _m02, vec.getW() * _m03, // + vec.getX() * _m10, vec.getY() * _m11, vec.getZ() * _m12, vec.getW() * _m13, // + vec.getX() * _m20, vec.getY() * _m21, vec.getZ() * _m22, vec.getW() * _m23, // + vec.getX() * _m30, vec.getY() * _m31, vec.getZ() * _m32, vec.getW() * _m33); + + return result; + } + + /** + * @param matrix + * @return This matrix for chaining, modified internally to reflect multiplication against the given matrix + * @throws NullPointerException + * if matrix is null + */ + public Matrix4 multiplyLocal(final ReadOnlyMatrix4 matrix) { + return multiply(matrix, this); + } + + /** + * @param matrix + * @param store + * a matrix to store the result in. if null, a new matrix is created. Note that it IS safe for matrix and + * store to be the same object. + * @return this matrix multiplied by the given matrix. + * @throws NullPointerException + * if matrix is null. + */ + @Override + public Matrix4 multiply(final ReadOnlyMatrix4 matrix, final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + final double temp00 = _m00 * matrix.getM00() + _m01 * matrix.getM10() + _m02 * matrix.getM20() + _m03 + * matrix.getM30(); + final double temp01 = _m00 * matrix.getM01() + _m01 * matrix.getM11() + _m02 * matrix.getM21() + _m03 + * matrix.getM31(); + final double temp02 = _m00 * matrix.getM02() + _m01 * matrix.getM12() + _m02 * matrix.getM22() + _m03 + * matrix.getM32(); + final double temp03 = _m00 * matrix.getM03() + _m01 * matrix.getM13() + _m02 * matrix.getM23() + _m03 + * matrix.getM33(); + + final double temp10 = _m10 * matrix.getM00() + _m11 * matrix.getM10() + _m12 * matrix.getM20() + _m13 + * matrix.getM30(); + final double temp11 = _m10 * matrix.getM01() + _m11 * matrix.getM11() + _m12 * matrix.getM21() + _m13 + * matrix.getM31(); + final double temp12 = _m10 * matrix.getM02() + _m11 * matrix.getM12() + _m12 * matrix.getM22() + _m13 + * matrix.getM32(); + final double temp13 = _m10 * matrix.getM03() + _m11 * matrix.getM13() + _m12 * matrix.getM23() + _m13 + * matrix.getM33(); + + final double temp20 = _m20 * matrix.getM00() + _m21 * matrix.getM10() + _m22 * matrix.getM20() + _m23 + * matrix.getM30(); + final double temp21 = _m20 * matrix.getM01() + _m21 * matrix.getM11() + _m22 * matrix.getM21() + _m23 + * matrix.getM31(); + final double temp22 = _m20 * matrix.getM02() + _m21 * matrix.getM12() + _m22 * matrix.getM22() + _m23 + * matrix.getM32(); + final double temp23 = _m20 * matrix.getM03() + _m21 * matrix.getM13() + _m22 * matrix.getM23() + _m23 + * matrix.getM33(); + + final double temp30 = _m30 * matrix.getM00() + _m31 * matrix.getM10() + _m32 * matrix.getM20() + _m33 + * matrix.getM30(); + final double temp31 = _m30 * matrix.getM01() + _m31 * matrix.getM11() + _m32 * matrix.getM21() + _m33 + * matrix.getM31(); + final double temp32 = _m30 * matrix.getM02() + _m31 * matrix.getM12() + _m32 * matrix.getM22() + _m33 + * matrix.getM32(); + final double temp33 = _m30 * matrix.getM03() + _m31 * matrix.getM13() + _m32 * matrix.getM23() + _m33 + * matrix.getM33(); + + result._m00 = temp00; + result._m01 = temp01; + result._m02 = temp02; + result._m03 = temp03; + result._m10 = temp10; + result._m11 = temp11; + result._m12 = temp12; + result._m13 = temp13; + result._m20 = temp20; + result._m21 = temp21; + result._m22 = temp22; + result._m23 = temp23; + result._m30 = temp30; + result._m31 = temp31; + result._m32 = temp32; + result._m33 = temp33; + + return result; + } + + /** + * Internally scales all values of this matrix by the given scalar. + * + * @param scalar + * @return this matrix for chaining. + */ + public Matrix4 multiplyLocal(final double scalar) { + _m00 *= scalar; + _m01 *= scalar; + _m02 *= scalar; + _m03 *= scalar; + _m10 *= scalar; + _m11 *= scalar; + _m12 *= scalar; + _m13 *= scalar; + _m20 *= scalar; + _m21 *= scalar; + _m22 *= scalar; + _m23 *= scalar; + _m30 *= scalar; + _m31 *= scalar; + _m32 *= scalar; + _m33 *= scalar; + + return this; + } + + /** + * @param matrix + * the matrix to add to this. + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * matrix and store to be the same object. + * @return the result. + * @throws NullPointerException + * if matrix is null + */ + @Override + public Matrix4 add(final ReadOnlyMatrix4 matrix, final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + result._m00 = _m00 + matrix.getM00(); + result._m01 = _m01 + matrix.getM01(); + result._m02 = _m02 + matrix.getM02(); + result._m03 = _m03 + matrix.getM03(); + result._m10 = _m10 + matrix.getM10(); + result._m11 = _m11 + matrix.getM11(); + result._m12 = _m12 + matrix.getM12(); + result._m13 = _m13 + matrix.getM13(); + result._m20 = _m20 + matrix.getM20(); + result._m21 = _m21 + matrix.getM21(); + result._m22 = _m22 + matrix.getM22(); + result._m23 = _m23 + matrix.getM23(); + result._m30 = _m30 + matrix.getM30(); + result._m31 = _m31 + matrix.getM31(); + result._m32 = _m32 + matrix.getM32(); + result._m33 = _m33 + matrix.getM33(); + + return result; + } + + /** + * Internally adds the values of the given matrix to this matrix. + * + * @param matrix + * the matrix to add to this. + * @return this matrix for chaining + * @throws NullPointerException + * if matrix is null + */ + public Matrix4 addLocal(final ReadOnlyMatrix4 matrix) { + return add(matrix, this); + } + + /** + * @param matrix + * the matrix to subtract from this. + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * matrix and store to be the same object. + * @return the result. + * @throws NullPointerException + * if matrix is null + */ + @Override + public Matrix4 subtract(final ReadOnlyMatrix4 matrix, final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + result._m00 = _m00 - matrix.getM00(); + result._m01 = _m01 - matrix.getM01(); + result._m02 = _m02 - matrix.getM02(); + result._m03 = _m03 - matrix.getM03(); + result._m10 = _m10 - matrix.getM10(); + result._m11 = _m11 - matrix.getM11(); + result._m12 = _m12 - matrix.getM12(); + result._m13 = _m13 - matrix.getM13(); + result._m20 = _m20 - matrix.getM20(); + result._m21 = _m21 - matrix.getM21(); + result._m22 = _m22 - matrix.getM22(); + result._m23 = _m23 - matrix.getM23(); + result._m30 = _m30 - matrix.getM30(); + result._m31 = _m31 - matrix.getM31(); + result._m32 = _m32 - matrix.getM32(); + result._m33 = _m33 - matrix.getM33(); + + return result; + } + + /** + * Internally subtracts the values of the given matrix from this matrix. + * + * @param matrix + * the matrix to subtract from this. + * @return this matrix for chaining + * @throws NullPointerException + * if matrix is null + */ + public Matrix4 subtractLocal(final ReadOnlyMatrix4 matrix) { + return subtract(matrix, this); + } + + /** + * Applies the given scale to this matrix and returns the result as a new matrix + * + * @param scale + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. + * @return the new matrix + * @throws NullPointerException + * if scale is null. + */ + @Override + public Matrix4 scale(final ReadOnlyVector4 scale, final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + return result.set( // + _m00 * scale.getX(), _m01 * scale.getY(), _m02 * scale.getZ(), _m03 * scale.getW(), // + _m10 * scale.getX(), _m11 * scale.getY(), _m12 * scale.getZ(), _m13 * scale.getW(), // + _m20 * scale.getX(), _m21 * scale.getY(), _m22 * scale.getZ(), _m23 * scale.getW(), // + _m30 * scale.getX(), _m31 * scale.getY(), _m32 * scale.getZ(), _m33 * scale.getW()); + } + + /** + * Applies the given scale to this matrix values internally + * + * @param scale + * @return this matrix for chaining. + * @throws NullPointerException + * if scale is null. + */ + public Matrix4 scaleLocal(final ReadOnlyVector4 scale) { + return scale(scale, this); + } + + /** + * transposes this matrix as a new matrix, basically flipping it across the diagonal + * + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. It is NOT safe for store + * to == this. + * @return this matrix for chaining. + * @see <a href="http://en.wikipedia.org/wiki/Transpose">wikipedia.org-Transpose</a> + */ + @Override + public Matrix4 transpose(final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + result._m00 = _m00; + result._m01 = _m10; + result._m02 = _m20; + result._m03 = _m30; + result._m10 = _m01; + result._m11 = _m11; + result._m12 = _m21; + result._m13 = _m31; + result._m20 = _m02; + result._m21 = _m12; + result._m22 = _m22; + result._m23 = _m32; + result._m30 = _m03; + result._m31 = _m13; + result._m32 = _m23; + result._m33 = _m33; + + return result; + } + + /** + * transposes this matrix in place + * + * @return this matrix for chaining. + * @see <a href="http://en.wikipedia.org/wiki/Transpose">wikipedia.org-Transpose</a> + */ + public Matrix4 transposeLocal() { + final double m01 = _m01; + final double m02 = _m02; + final double m03 = _m03; + final double m12 = _m12; + final double m13 = _m13; + final double m23 = _m23; + _m01 = _m10; + _m02 = _m20; + _m03 = _m30; + _m12 = _m21; + _m13 = _m31; + _m23 = _m32; + _m10 = m01; + _m20 = m02; + _m30 = m03; + _m21 = m12; + _m31 = m13; + _m32 = m23; + return this; + } + + /** + * @param store + * a matrix to store the result in. If store is null, a new matrix is created. Note that it IS safe for + * store == this. + * @return a matrix that represents this matrix, inverted. + * @throws ArithmeticException + * if this matrix can not be inverted. + */ + @Override + public Matrix4 invert(final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + final double dA0 = _m00 * _m11 - _m01 * _m10; + final double dA1 = _m00 * _m12 - _m02 * _m10; + final double dA2 = _m00 * _m13 - _m03 * _m10; + final double dA3 = _m01 * _m12 - _m02 * _m11; + final double dA4 = _m01 * _m13 - _m03 * _m11; + final double dA5 = _m02 * _m13 - _m03 * _m12; + final double dB0 = _m20 * _m31 - _m21 * _m30; + final double dB1 = _m20 * _m32 - _m22 * _m30; + final double dB2 = _m20 * _m33 - _m23 * _m30; + final double dB3 = _m21 * _m32 - _m22 * _m31; + final double dB4 = _m21 * _m33 - _m23 * _m31; + final double dB5 = _m22 * _m33 - _m23 * _m32; + final double det = dA0 * dB5 - dA1 * dB4 + dA2 * dB3 + dA3 * dB2 - dA4 * dB1 + dA5 * dB0; + + if (Math.abs(det) <= MathUtils.EPSILON) { + throw new ArithmeticException("This matrix cannot be inverted"); + } + + final double temp00 = +_m11 * dB5 - _m12 * dB4 + _m13 * dB3; + final double temp10 = -_m10 * dB5 + _m12 * dB2 - _m13 * dB1; + final double temp20 = +_m10 * dB4 - _m11 * dB2 + _m13 * dB0; + final double temp30 = -_m10 * dB3 + _m11 * dB1 - _m12 * dB0; + final double temp01 = -_m01 * dB5 + _m02 * dB4 - _m03 * dB3; + final double temp11 = +_m00 * dB5 - _m02 * dB2 + _m03 * dB1; + final double temp21 = -_m00 * dB4 + _m01 * dB2 - _m03 * dB0; + final double temp31 = +_m00 * dB3 - _m01 * dB1 + _m02 * dB0; + final double temp02 = +_m31 * dA5 - _m32 * dA4 + _m33 * dA3; + final double temp12 = -_m30 * dA5 + _m32 * dA2 - _m33 * dA1; + final double temp22 = +_m30 * dA4 - _m31 * dA2 + _m33 * dA0; + final double temp32 = -_m30 * dA3 + _m31 * dA1 - _m32 * dA0; + final double temp03 = -_m21 * dA5 + _m22 * dA4 - _m23 * dA3; + final double temp13 = +_m20 * dA5 - _m22 * dA2 + _m23 * dA1; + final double temp23 = -_m20 * dA4 + _m21 * dA2 - _m23 * dA0; + final double temp33 = +_m20 * dA3 - _m21 * dA1 + _m22 * dA0; + + result.set(temp00, temp01, temp02, temp03, temp10, temp11, temp12, temp13, temp20, temp21, temp22, temp23, + temp30, temp31, temp32, temp33); + result.multiplyLocal(1.0 / det); + + return result; + } + + /** + * inverts this matrix locally. + * + * @return this matrix inverted internally. + * @throws ArithmeticException + * if this matrix can not be inverted. + */ + public Matrix4 invertLocal() { + return invert(this); + } + + /** + * @param store + * The matrix to store the result in. If null, a new matrix is created. + * @return The adjugate, or classical adjoint, of this matrix + * @see <a href="http://en.wikipedia.org/wiki/Adjugate_matrix">wikipedia.org-Adjugate_matrix</a> + */ + @Override + public Matrix4 adjugate(final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + final double dA0 = _m00 * _m11 - _m01 * _m10; + final double dA1 = _m00 * _m12 - _m02 * _m10; + final double dA2 = _m00 * _m13 - _m03 * _m10; + final double dA3 = _m01 * _m12 - _m02 * _m11; + final double dA4 = _m01 * _m13 - _m03 * _m11; + final double dA5 = _m02 * _m13 - _m03 * _m12; + final double dB0 = _m20 * _m31 - _m21 * _m30; + final double dB1 = _m20 * _m32 - _m22 * _m30; + final double dB2 = _m20 * _m33 - _m23 * _m30; + final double dB3 = _m21 * _m32 - _m22 * _m31; + final double dB4 = _m21 * _m33 - _m23 * _m31; + final double dB5 = _m22 * _m33 - _m23 * _m32; + + final double temp00 = +_m11 * dB5 - _m12 * dB4 + _m13 * dB3; + final double temp10 = -_m10 * dB5 + _m12 * dB2 - _m13 * dB1; + final double temp20 = +_m10 * dB4 - _m11 * dB2 + _m13 * dB0; + final double temp30 = -_m10 * dB3 + _m11 * dB1 - _m12 * dB0; + final double temp01 = -_m01 * dB5 + _m02 * dB4 - _m03 * dB3; + final double temp11 = +_m00 * dB5 - _m02 * dB2 + _m03 * dB1; + final double temp21 = -_m00 * dB4 + _m01 * dB2 - _m03 * dB0; + final double temp31 = +_m00 * dB3 - _m01 * dB1 + _m02 * dB0; + final double temp02 = +_m31 * dA5 - _m32 * dA4 + _m33 * dA3; + final double temp12 = -_m30 * dA5 + _m32 * dA2 - _m33 * dA1; + final double temp22 = +_m30 * dA4 - _m31 * dA2 + _m33 * dA0; + final double temp32 = -_m30 * dA3 + _m31 * dA1 - _m32 * dA0; + final double temp03 = -_m21 * dA5 + _m22 * dA4 - _m23 * dA3; + final double temp13 = +_m20 * dA5 - _m22 * dA2 + _m23 * dA1; + final double temp23 = -_m20 * dA4 + _m21 * dA2 - _m23 * dA0; + final double temp33 = +_m20 * dA3 - _m21 * dA1 + _m22 * dA0; + + return result.set(temp00, temp01, temp02, temp03, temp10, temp11, temp12, temp13, temp20, temp21, temp22, + temp23, temp30, temp31, temp32, temp33); + } + + /** + * @return this matrix, modified to represent its adjugate, or classical adjoint + * @see <a href="http://en.wikipedia.org/wiki/Adjugate_matrix">wikipedia.org-Adjugate_matrix</a> + */ + public Matrix4 adjugateLocal() { + return adjugate(this); + } + + /** + * @return the determinate of this matrix + * @see <a href="http://en.wikipedia.org/wiki/Determinant">wikipedia.org-Determinant</a> + */ + @Override + public double determinant() { + final double val1 = _m11 * _m22 * _m33 + _m12 * _m23 * _m31 + _m13 * _m21 * _m32 - // + _m13 * _m22 * _m31 - _m12 * _m21 * _m33 - _m11 * _m23 * _m32; + final double val2 = _m10 * _m22 * _m33 + _m12 * _m23 * _m30 + _m13 * _m20 * _m32 - // + _m13 * _m22 * _m30 - _m12 * _m20 * _m33 - _m10 * _m23 * _m32; + final double val3 = _m10 * _m21 * _m33 + _m11 * _m23 * _m30 + _m13 * _m20 * _m31 - // + _m13 * _m21 * _m30 - _m11 * _m20 * _m33 - _m10 * _m23 * _m31; + final double val4 = _m10 * _m21 * _m32 + _m11 * _m22 * _m30 + _m12 * _m20 * _m31 - // + _m12 * _m21 * _m30 - _m11 * _m20 * _m32 - _m10 * _m22 * _m31; + + return _m00 * val1 - _m01 * val2 + _m02 * val3 - _m03 * val4; + } + + /** + * Multiplies the given vector by this matrix (v * M). If supplied, the result is stored into the supplied "store" + * vector. + * + * @param vector + * the vector to multiply this matrix by. + * @param store + * the vector to store the result in. If store is null, a new vector is created. Note that it IS safe for + * vector and store to be the same object. + * @return the store vector, or a new vector if store is null. + * @throws NullPointerException + * if vector is null + */ + @Override + public Vector4 applyPre(final ReadOnlyVector4 vector, Vector4 store) { + if (store == null) { + store = new Vector4(); + } + + final double x = vector.getX(); + final double y = vector.getY(); + final double z = vector.getZ(); + final double w = vector.getW(); + + store.setX(_m00 * x + _m10 * y + _m20 * z + _m30 * w); + store.setY(_m01 * x + _m11 * y + _m21 * z + _m31 * w); + store.setZ(_m02 * x + _m12 * y + _m22 * z + _m32 * w); + store.setW(_m03 * x + _m13 * y + _m23 * z + _m33 * w); + + return store; + } + + /** + * Multiplies the given vector by this matrix (M * v). If supplied, the result is stored into the supplied "store" + * vector. + * + * @param vector + * the vector to multiply this matrix by. + * @param store + * the vector to store the result in. If store is null, a new vector is created. Note that it IS safe for + * vector and store to be the same object. + * @return the store vector, or a new vector if store is null. + * @throws NullPointerException + * if vector is null + */ + @Override + public Vector4 applyPost(final ReadOnlyVector4 vector, Vector4 store) { + if (store == null) { + store = new Vector4(); + } + + final double x = vector.getX(); + final double y = vector.getY(); + final double z = vector.getZ(); + final double w = vector.getW(); + + store.setX(_m00 * x + _m01 * y + _m02 * z + _m03 * w); + store.setY(_m10 * x + _m11 * y + _m12 * z + _m13 * w); + store.setZ(_m20 * x + _m21 * y + _m22 * z + _m23 * w); + store.setW(_m30 * x + _m31 * y + _m32 * z + _m33 * w); + + return store; + } + + /** + * Multiplies the given point by this matrix (M * p). If supplied, the result is stored into the supplied "store" + * vector. + * + * @param point + * the point to multiply against this matrix. + * @param store + * the point to store the result in. If store is null, a new Vector3 object is created. Note that it IS + * safe for point and store to be the same object. + * @return the store object, or a new Vector3 if store is null. + * @throws NullPointerException + * if point is null + */ + @Override + public Vector3 applyPostPoint(final ReadOnlyVector3 point, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + + final double x = point.getX(); + final double y = point.getY(); + final double z = point.getZ(); + + store.setX(_m00 * x + _m01 * y + _m02 * z + _m03); + store.setY(_m10 * x + _m11 * y + _m12 * z + _m13); + store.setZ(_m20 * x + _m21 * y + _m22 * z + _m23); + + return store; + } + + /** + * Multiplies the given vector by this matrix (M * v). If supplied, the result is stored into the supplied "store" + * vector. + * + * @param vector + * the vector to multiply this matrix by. + * @param store + * the vector to store the result in. If store is null, a new vector is created. Note that it IS safe for + * vector and store to be the same object. + * @return the store vector, or a new vector if store is null. + * @throws NullPointerException + * if vector is null + */ + @Override + public Vector3 applyPostVector(final ReadOnlyVector3 vector, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + + final double x = vector.getX(); + final double y = vector.getY(); + final double z = vector.getZ(); + + store.setX(_m00 * x + _m01 * y + _m02 * z); + store.setY(_m10 * x + _m11 * y + _m12 * z); + store.setZ(_m20 * x + _m21 * y + _m22 * z); + + return store; + } + + /** + * Check a matrix... if it is null or its doubles are NaN or infinite, return false. Else return true. + * + * @param matrix + * the vector to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyMatrix4 matrix) { + if (matrix == null) { + return false; + } + + if (Double.isNaN(matrix.getM00()) || Double.isInfinite(matrix.getM00())) { + return false; + } else if (Double.isNaN(matrix.getM01()) || Double.isInfinite(matrix.getM01())) { + return false; + } else if (Double.isNaN(matrix.getM02()) || Double.isInfinite(matrix.getM02())) { + return false; + } else if (Double.isNaN(matrix.getM03()) || Double.isInfinite(matrix.getM03())) { + return false; + } else if (Double.isNaN(matrix.getM10()) || Double.isInfinite(matrix.getM10())) { + return false; + } else if (Double.isNaN(matrix.getM11()) || Double.isInfinite(matrix.getM11())) { + return false; + } else if (Double.isNaN(matrix.getM12()) || Double.isInfinite(matrix.getM12())) { + return false; + } else if (Double.isNaN(matrix.getM13()) || Double.isInfinite(matrix.getM13())) { + return false; + } else if (Double.isNaN(matrix.getM20()) || Double.isInfinite(matrix.getM20())) { + return false; + } else if (Double.isNaN(matrix.getM21()) || Double.isInfinite(matrix.getM21())) { + return false; + } else if (Double.isNaN(matrix.getM22()) || Double.isInfinite(matrix.getM22())) { + return false; + } else if (Double.isNaN(matrix.getM23()) || Double.isInfinite(matrix.getM23())) { + return false; + } else if (Double.isNaN(matrix.getM30()) || Double.isInfinite(matrix.getM30())) { + return false; + } else if (Double.isNaN(matrix.getM31()) || Double.isInfinite(matrix.getM31())) { + return false; + } else if (Double.isNaN(matrix.getM32()) || Double.isInfinite(matrix.getM32())) { + return false; + } else if (Double.isNaN(matrix.getM33()) || Double.isInfinite(matrix.getM33())) { + return false; + } + + return true; + } + + /** + * @return true if this Matrix is orthonormal + * @see <a href="http://en.wikipedia.org/wiki/Orthogonal_matrix">wikipedia.org-Orthogonal matrix</a> + */ + public boolean isOrthonormal() { + final double m00 = _m00; + final double m01 = _m01; + final double m02 = _m02; + final double m03 = _m03; + final double m10 = _m10; + final double m11 = _m11; + final double m12 = _m12; + final double m13 = _m13; + final double m20 = _m20; + final double m21 = _m21; + final double m22 = _m22; + final double m23 = _m23; + final double m30 = _m30; + final double m31 = _m31; + final double m32 = _m32; + final double m33 = _m33; + + if (Math.abs(m00 * m00 + m01 * m01 + m02 * m02 + m03 * m03 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m00 * m10 + m01 * m11 + m02 * m12 + m03 * m13 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m00 * m20 + m01 * m21 + m02 * m22 + m03 * m23 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m00 * m30 + m01 * m31 + m02 * m32 + m03 * m33 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + + if (Math.abs(m10 * m00 + m11 * m01 + m12 * m02 + m13 * m03 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m10 * m10 + m11 * m11 + m12 * m12 + m13 * m13 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m10 * m20 + m11 * m21 + m12 * m22 + m13 * m23 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m10 * m30 + m11 * m31 + m12 * m32 + m13 * m33 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + + if (Math.abs(m20 * m00 + m21 * m01 + m22 * m02 + m23 * m03 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m20 * m10 + m21 * m11 + m22 * m12 + m23 * m13 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m20 * m20 + m21 * m21 + m22 * m22 + m23 * m23 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m20 * m30 + m21 * m31 + m22 * m32 + m23 * m33 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + + if (Math.abs(m30 * m00 + m31 * m01 + m32 * m02 + m33 * m03 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m30 * m10 + m31 * m11 + m32 * m12 + m33 * m13 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m30 * m20 + m31 * m21 + m32 * m22 + m33 * m23 - 0.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + if (Math.abs(m30 * m30 + m31 * m31 + m32 * m32 + m33 * m33 - 1.0) > MathUtils.ZERO_TOLERANCE) { + return false; + } + + return true; + } + + /** + * @return the string representation of this matrix. + */ + @Override + public String toString() { + final StringBuffer result = new StringBuffer("com.ardor3d.math.Matrix4\n[\n"); + result.append(" "); + result.append(_m00); + result.append(" "); + result.append(_m01); + result.append(" "); + result.append(_m02); + result.append(" "); + result.append(_m03); + result.append(" \n"); + + result.append(" "); + result.append(_m10); + result.append(" "); + result.append(_m11); + result.append(" "); + result.append(_m12); + result.append(" "); + result.append(_m13); + result.append(" \n"); + + result.append(" "); + result.append(_m20); + result.append(" "); + result.append(_m21); + result.append(" "); + result.append(_m22); + result.append(" "); + result.append(_m23); + result.append(" \n"); + + result.append(" "); + result.append(_m30); + result.append(" "); + result.append(_m31); + result.append(" "); + result.append(_m32); + result.append(" "); + result.append(_m33); + result.append(" \n"); + + result.append("]"); + return result.toString(); + } + + /** + * @return returns a unique code for this matrix object based on its values. If two matrices are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + long val = Double.doubleToLongBits(_m00); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m01); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m02); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m03); + result += 31 * result + (int) (val ^ val >>> 32); + + val = Double.doubleToLongBits(_m10); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m11); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m12); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m13); + result += 31 * result + (int) (val ^ val >>> 32); + + val = Double.doubleToLongBits(_m20); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m21); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m22); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m23); + result += 31 * result + (int) (val ^ val >>> 32); + + val = Double.doubleToLongBits(_m30); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m31); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m32); + result += 31 * result + (int) (val ^ val >>> 32); + val = Double.doubleToLongBits(_m33); + result += 31 * result + (int) (val ^ val >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this matrix and the provided matrix have the double values that are within the + * MathUtils.ZERO_TOLERANCE. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyMatrix4)) { + return false; + } + final ReadOnlyMatrix4 comp = (ReadOnlyMatrix4) o; + if (Math.abs(getM00() - comp.getM00()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM01() - comp.getM01()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM02() - comp.getM02()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM03() - comp.getM03()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM10() - comp.getM10()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM11() - comp.getM11()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM12() - comp.getM12()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM13() - comp.getM13()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM20() - comp.getM20()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM21() - comp.getM21()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM22() - comp.getM22()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM23() - comp.getM23()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM30() - comp.getM30()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM31() - comp.getM31()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM32() - comp.getM32()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } else if (Math.abs(getM33() - comp.getM33()) > Matrix4.ALLOWED_DEVIANCE) { + return false; + } + + return true; + } + + /** + * @param o + * the object to compare for equality + * @return true if this matrix and the provided matrix have the exact same double values. + */ + public boolean strictEquals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyMatrix4)) { + return false; + } + final ReadOnlyMatrix4 comp = (ReadOnlyMatrix4) o; + if (getM00() != comp.getM00()) { + return false; + } else if (getM01() != comp.getM01()) { + return false; + } else if (getM02() != comp.getM02()) { + return false; + } else if (getM03() != comp.getM03()) { + return false; + } else if (getM10() != comp.getM10()) { + return false; + } else if (getM11() != comp.getM11()) { + return false; + } else if (getM12() != comp.getM12()) { + return false; + } else if (getM13() != comp.getM13()) { + return false; + } else if (getM20() != comp.getM20()) { + return false; + } else if (getM21() != comp.getM21()) { + return false; + } else if (getM22() != comp.getM22()) { + return false; + } else if (getM23() != comp.getM23()) { + return false; + } else if (getM30() != comp.getM30()) { + return false; + } else if (getM31() != comp.getM31()) { + return false; + } else if (getM32() != comp.getM32()) { + return false; + } else if (getM33() != comp.getM33()) { + return false; + } + + return true; + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Matrix4 clone() { + return new Matrix4(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Matrix4> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_m00, "m00", 1.0); + capsule.write(_m01, "m01", 0.0); + capsule.write(_m02, "m02", 0.0); + capsule.write(_m03, "m03", 0.0); + capsule.write(_m10, "m10", 0.0); + capsule.write(_m11, "m11", 1.0); + capsule.write(_m12, "m12", 0.0); + capsule.write(_m13, "m13", 0.0); + capsule.write(_m20, "m20", 0.0); + capsule.write(_m21, "m21", 0.0); + capsule.write(_m22, "m22", 1.0); + capsule.write(_m23, "m23", 0.0); + capsule.write(_m30, "m30", 0.0); + capsule.write(_m31, "m31", 0.0); + capsule.write(_m32, "m32", 0.0); + capsule.write(_m33, "m33", 1.0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _m00 = capsule.readDouble("m00", 1.0); + _m01 = capsule.readDouble("m01", 0.0); + _m02 = capsule.readDouble("m02", 0.0); + _m03 = capsule.readDouble("m03", 0.0); + _m10 = capsule.readDouble("m10", 0.0); + _m11 = capsule.readDouble("m11", 1.0); + _m12 = capsule.readDouble("m12", 0.0); + _m13 = capsule.readDouble("m13", 0.0); + _m20 = capsule.readDouble("m20", 0.0); + _m21 = capsule.readDouble("m21", 0.0); + _m22 = capsule.readDouble("m22", 1.0); + _m23 = capsule.readDouble("m23", 0.0); + _m30 = capsule.readDouble("m30", 0.0); + _m31 = capsule.readDouble("m31", 0.0); + _m32 = capsule.readDouble("m32", 0.0); + _m33 = capsule.readDouble("m33", 1.0); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + _m00 = in.readDouble(); + _m01 = in.readDouble(); + _m02 = in.readDouble(); + _m03 = in.readDouble(); + _m10 = in.readDouble(); + _m11 = in.readDouble(); + _m12 = in.readDouble(); + _m13 = in.readDouble(); + _m20 = in.readDouble(); + _m21 = in.readDouble(); + _m22 = in.readDouble(); + _m23 = in.readDouble(); + _m30 = in.readDouble(); + _m31 = in.readDouble(); + _m32 = in.readDouble(); + _m33 = in.readDouble(); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeDouble(_m00); + out.writeDouble(_m01); + out.writeDouble(_m02); + out.writeDouble(_m03); + out.writeDouble(_m10); + out.writeDouble(_m11); + out.writeDouble(_m12); + out.writeDouble(_m13); + out.writeDouble(_m20); + out.writeDouble(_m21); + out.writeDouble(_m22); + out.writeDouble(_m23); + out.writeDouble(_m30); + out.writeDouble(_m31); + out.writeDouble(_m32); + out.writeDouble(_m33); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Matrix4 that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Matrix4 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Matrix4.MAT_POOL.fetch(); + } else { + return new Matrix4(); + } + } + + /** + * Releases a Matrix4 back to be used by a future call to fetchTempInstance. TAKE CARE: this Matrix4 object should + * no longer have other classes referencing it or "Bad Things" will happen. + * + * @param mat + * the Matrix4 to release. + */ + public final static void releaseTempInstance(final Matrix4 mat) { + if (MathConstants.useMathPools) { + Matrix4.MAT_POOL.release(mat); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/ObjectPool.java b/ardor3d-math/src/main/java/com/ardor3d/math/ObjectPool.java new file mode 100644 index 0000000..2c2110b --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/ObjectPool.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple Object pool for use with our Math Library to help reduce object creation during calculations. This class uses + * a ThreadLocal pool of objects to allow for fast multi-threaded use. + * + * @param <T> + * the type. + */ +public abstract class ObjectPool<T extends Poolable> { + private final ThreadLocal<List<T>> _pool = new ThreadLocal<List<T>>() { + @Override + protected List<T> initialValue() { + return new ArrayList<T>(_maxSize); + } + }; + + private final int _maxSize; + + protected ObjectPool(final int maxSize) { + _maxSize = maxSize; + } + + protected abstract T newInstance(); + + public final T fetch() { + final List<T> objects = _pool.get(); + return objects.isEmpty() ? newInstance() : objects.remove(objects.size() - 1); + } + + public final void release(final T object) { + if (object == null) { + throw new RuntimeException("Should not release null objects into ObjectPool."); + } + + final List<T> objects = _pool.get(); + if (objects.size() < _maxSize) { + objects.add(object); + } + } + + public static <T extends Poolable> ObjectPool<T> create(final Class<T> clazz, final int maxSize) { + return new ObjectPool<T>(maxSize) { + @Override + protected T newInstance() { + try { + return clazz.newInstance(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + }; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Plane.java b/ardor3d-math/src/main/java/com/ardor3d/math/Plane.java new file mode 100644 index 0000000..7567310 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Plane.java @@ -0,0 +1,338 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * A representation of a mathematical plane using a normal vector and a plane constant (d) whose absolute value + * represents the distance from the origin to the plane. It is generally calculated by taking a point (X) on the plane + * and finding its dot-product with the plane's normal vector. iow: d = N dot X + */ +public class Plane implements Cloneable, Savable, Externalizable, ReadOnlyPlane, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Plane> PLANE_POOL = ObjectPool.create(Plane.class, MathConstants.maxMathPoolSize); + + public static final ReadOnlyPlane XZ = new Plane(Vector3.UNIT_Y, 0); + public static final ReadOnlyPlane XY = new Plane(Vector3.UNIT_Z, 0); + public static final ReadOnlyPlane YZ = new Plane(Vector3.UNIT_X, 0); + + protected final Vector3 _normal = new Vector3(); + protected double _constant = 0; + + /** + * Constructs a new plane with a normal of (0, 1, 0) and a constant value of 0. + */ + public Plane() { + this(Vector3.UNIT_Y, 0); + } + + /** + * Copy constructor. + * + * @param source + * the plane to copy from. + */ + public Plane(final ReadOnlyPlane source) { + this(source.getNormal(), source.getConstant()); + } + + /** + * Constructs a new plane using the supplied normal vector and plane constant + * + * @param normal + * @param constant + */ + public Plane(final ReadOnlyVector3 normal, final double constant) { + _normal.set(normal); + _constant = constant; + } + + @Override + public double getConstant() { + return _constant; + } + + /** + * + * @return normal as a readable vector + */ + @Override + public ReadOnlyVector3 getNormal() { + return _normal; + } + + /** + * Sets the value of this plane to the constant and normal values of the provided source plane. + * + * @param source + * @return this plane for chaining + * @throws NullPointerException + * if source is null. + */ + public Plane set(final ReadOnlyPlane source) { + setConstant(source.getConstant()); + setNormal(source.getNormal()); + return this; + } + + /** + * Sets the constant value of this plane to the given double value. + * + * @param constant + */ + public void setConstant(final double constant) { + _constant = constant; + } + + /** + * Sets the plane normal to the values of the given vector. + * + * @param normal + * @throws NullPointerException + * if normal is null. + */ + public void setNormal(final ReadOnlyVector3 normal) { + _normal.set(normal); + } + + /** + * @param point + * @return the distance from this plane to a provided point. If the point is on the negative side of the plane the + * distance returned is negative, otherwise it is positive. If the point is on the plane, it is zero. + * @throws NullPointerException + * if point is null. + */ + @Override + public double pseudoDistance(final ReadOnlyVector3 point) { + return _normal.dot(point) - _constant; + } + + /** + * @param point + * @return the side of this plane on which the given point lies. + * @see Side + * @throws NullPointerException + * if point is null. + */ + @Override + public Side whichSide(final ReadOnlyVector3 point) { + final double dis = pseudoDistance(point); + if (dis < 0) { + return Side.Inside; + } else if (dis > 0) { + return Side.Outside; + } else { + return Side.Neither; + } + } + + /** + * Sets this plane to the plane defined by the given three points. + * + * @param pointA + * @param pointB + * @param pointC + * @return this plane for chaining + * @throws NullPointerException + * if one or more of the points are null. + */ + public Plane setPlanePoints(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, final ReadOnlyVector3 pointC) { + _normal.set(pointB).subtractLocal(pointA); + _normal.crossLocal(pointC.getX() - pointA.getX(), pointC.getY() - pointA.getY(), pointC.getZ() - pointA.getZ()) + .normalizeLocal(); + _constant = _normal.dot(pointA); + return this; + } + + /** + * Reflects an incoming vector across the normal of this Plane. + * + * @param unitVector + * the incoming vector. Must be a unit vector. + * @param store + * optional Vector to store the result in. May be the same as the unitVector. + * @return the reflected vector. + */ + @Override + public Vector3 reflectVector(final ReadOnlyVector3 unitVector, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final double dotProd = _normal.dot(unitVector) * 2; + result.set(unitVector).subtractLocal(_normal.getX() * dotProd, _normal.getY() * dotProd, + _normal.getZ() * dotProd); + return result; + } + + /** + * Check a plane... if it is null or its constant, or the doubles of its normal are NaN or infinite, return false. + * Else return true. + * + * @param plane + * the plane to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyPlane plane) { + if (plane == null) { + return false; + } + if (Double.isNaN(plane.getConstant()) || Double.isInfinite(plane.getConstant())) { + return false; + } + + return Vector3.isValid(plane.getNormal()); + } + + /** + * @return the string representation of this plane. + */ + @Override + public String toString() { + return "com.ardor3d.math.Plane [Normal: " + _normal + " - Constant: " + _constant + "]"; + } + + /** + * @return returns a unique code for this plane object based on its values. If two planes are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _normal.hashCode(); + + final long c = Double.doubleToLongBits(getConstant()); + result += 31 * result + (int) (c ^ c >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this plane and the provided plane have the same constant and normal values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyPlane)) { + return false; + } + final ReadOnlyPlane comp = (ReadOnlyPlane) o; + return getConstant() == comp.getConstant() && _normal.equals(comp.getNormal()); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Plane clone() { + return new Plane(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Plane> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_normal, "normal", new Vector3(Vector3.UNIT_Y)); + capsule.write(_constant, "constant", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _normal.set((Vector3) capsule.readSavable("normal", new Vector3(Vector3.UNIT_Y))); + _constant = capsule.readDouble("constant", 0); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setNormal((Vector3) in.readObject()); + setConstant(in.readDouble()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeObject(_normal); + out.writeDouble(getConstant()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Plane that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Plane fetchTempInstance() { + if (MathConstants.useMathPools) { + return Plane.PLANE_POOL.fetch(); + } else { + return new Plane(); + } + } + + /** + * Releases a Plane back to be used by a future call to fetchTempInstance. TAKE CARE: this Plane object should no + * longer have other classes referencing it or "Bad Things" will happen. + * + * @param plane + * the Plane to release. + */ + public final static void releaseTempInstance(final Plane plane) { + if (MathConstants.useMathPools) { + Plane.PLANE_POOL.release(plane); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Poolable.java b/ardor3d-math/src/main/java/com/ardor3d/math/Poolable.java new file mode 100644 index 0000000..46ec934 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Poolable.java @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +public interface Poolable {} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Quaternion.java b/ardor3d-math/src/main/java/com/ardor3d/math/Quaternion.java new file mode 100644 index 0000000..7cb1b3b --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Quaternion.java @@ -0,0 +1,1560 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Quaternion represents a 4 value math object used in Ardor3D to describe rotations. It has the advantage of being able + * to avoid lock by adding a 4th dimension to rotation. + * + * Note: some algorithms in this class were ported from Eberly, Wolfram, Game Gems and others to Java. + */ +public class Quaternion implements Cloneable, Savable, Externalizable, ReadOnlyQuaternion, Poolable { + + /** Used with equals method to determine if two Quaternions are close enough to be considered equal. */ + public static final double ALLOWED_DEVIANCE = 0.00000001; + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Quaternion> QUAT_POOL = ObjectPool.create(Quaternion.class, + MathConstants.maxMathPoolSize); + + /** + * x=0, y=0, z=0, w=1 + */ + public final static ReadOnlyQuaternion IDENTITY = new Quaternion(0, 0, 0, 1); + + protected double _x = 0; + protected double _y = 0; + protected double _z = 0; + protected double _w = 1; + + /** + * Constructs a new quaternion set to (0, 0, 0, 1). + */ + public Quaternion() { + this(Quaternion.IDENTITY); + } + + /** + * Constructs a new quaternion set to the (x, y, z, w) values of the given source quaternion. + * + * @param source + */ + public Quaternion(final ReadOnlyQuaternion source) { + this(source.getX(), source.getY(), source.getZ(), source.getW()); + } + + /** + * Constructs a new quaternion set to (x, y, z, w). + * + * @param x + * @param y + * @param z + * @param w + */ + public Quaternion(final double x, final double y, final double z, final double w) { + _x = x; + _y = y; + _z = z; + _w = w; + } + + @Override + public double getX() { + return _x; + } + + @Override + public double getY() { + return _y; + } + + @Override + public double getZ() { + return _z; + } + + @Override + public double getW() { + return _w; + } + + @Override + public float getXf() { + return (float) _x; + } + + @Override + public float getYf() { + return (float) _y; + } + + @Override + public float getZf() { + return (float) _z; + } + + @Override + public float getWf() { + return (float) _w; + } + + /** + * Stores the double values of this quaternion in the given double array as (x,y,z,w). + * + * @param store + * The array in which to store the values of this quaternion. If null, a new double[4] array is created. + * @return the double array + * @throws ArrayIndexOutOfBoundsException + * if store is not null and is not at least length 4 + */ + @Override + public double[] toArray(final double[] store) { + double[] result = store; + if (result == null) { + result = new double[4]; + } + result[3] = getW(); + result[2] = getZ(); + result[1] = getY(); + result[0] = getX(); + return result; + } + + /** + * Sets the x component of this quaternion to the given double value. + * + * @param x + */ + public void setX(final double x) { + _x = x; + } + + /** + * Sets the y component of this quaternion to the given double value. + * + * @param y + */ + public void setY(final double y) { + _y = y; + } + + /** + * Sets the z component of this quaternion to the given double value. + * + * @param z + */ + public void setZ(final double z) { + _z = z; + } + + /** + * Sets the w component of this quaternion to the given double value. + * + * @param w + */ + public void setW(final double w) { + _w = w; + } + + /** + * Sets the value of this quaternion to (x, y, z, w) + * + * @param x + * @param y + * @param z + * @param w + * @return this quaternion for chaining + */ + public Quaternion set(final double x, final double y, final double z, final double w) { + setX(x); + setY(y); + setZ(z); + setW(w); + return this; + } + + /** + * Sets the value of this quaternion to the (x, y, z, w) values of the provided source quaternion. + * + * @param source + * @return this quaternion for chaining + * @throws NullPointerException + * if source is null. + */ + public Quaternion set(final ReadOnlyQuaternion source) { + setX(source.getX()); + setY(source.getY()); + setZ(source.getZ()); + setW(source.getW()); + return this; + } + + /** + * Updates this quaternion from the given Euler rotation angles, applied in the given order: heading, attitude, + * bank. + * + * @param angles + * the Euler angles of rotation (in radians) stored as heading, attitude, and bank. + * @return this quaternion for chaining + * @throws ArrayIndexOutOfBoundsException + * if angles is less than length 3 + * @throws NullPointerException + * if angles is null. + */ + public Quaternion fromEulerAngles(final double[] angles) { + return fromEulerAngles(angles[0], angles[1], angles[2]); + } + + /** + * Updates this quaternion from the given Euler rotation angles, applied in the given order: heading, attitude, + * bank. + * + * @param heading + * the Euler heading angle in radians. (rotation about the y axis) + * @param attitude + * the Euler attitude angle in radians. (rotation about the z axis) + * @param bank + * the Euler bank angle in radians. (rotation about the x axis) + * @return this quaternion for chaining + * @see <a + * href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm">euclideanspace.com-eulerToQuaternion</a> + */ + public Quaternion fromEulerAngles(final double heading, final double attitude, final double bank) { + double angle = heading * 0.5; + final double sinHeading = MathUtils.sin(angle); + final double cosHeading = MathUtils.cos(angle); + angle = attitude * 0.5; + final double sinAttitude = MathUtils.sin(angle); + final double cosAttitude = MathUtils.cos(angle); + angle = bank * 0.5; + final double sinBank = MathUtils.sin(angle); + final double cosBank = MathUtils.cos(angle); + + // variables used to reduce multiplication calls. + final double cosHeadingXcosAttitude = cosHeading * cosAttitude; + final double sinHeadingXsinAttitude = sinHeading * sinAttitude; + final double cosHeadingXsinAttitude = cosHeading * sinAttitude; + final double sinHeadingXcosAttitude = sinHeading * cosAttitude; + + final double w = cosHeadingXcosAttitude * cosBank - sinHeadingXsinAttitude * sinBank; + final double x = cosHeadingXcosAttitude * sinBank + sinHeadingXsinAttitude * cosBank; + final double y = sinHeadingXcosAttitude * cosBank + cosHeadingXsinAttitude * sinBank; + final double z = cosHeadingXsinAttitude * cosBank - sinHeadingXcosAttitude * sinBank; + + set(x, y, z, w); + + return normalizeLocal(); + } + + /** + * Converts this quaternion to Euler rotation angles in radians (heading, attitude, bank). + * + * @param store + * the double[] array to store the computed angles in. If null, a new double[] will be created + * @return the double[] array, filled with heading, attitude and bank in that order.. + * @throws ArrayIndexOutOfBoundsException + * if non-null store is not at least length 3 + * @see <a + * href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm">euclideanspace.com-quaternionToEuler</a> + * @see #fromEulerAngles(double, double, double) + */ + @Override + public double[] toEulerAngles(final double[] store) { + double[] result = store; + if (result == null) { + result = new double[3]; + } else if (result.length < 3) { + throw new ArrayIndexOutOfBoundsException("store array must have at least three elements"); + } + + final double sqw = getW() * getW(); + final double sqx = getX() * getX(); + final double sqy = getY() * getY(); + final double sqz = getZ() * getZ(); + final double unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + final double test = getX() * getY() + getZ() * getW(); + if (test > 0.499 * unit) { // singularity at north pole + result[0] = 2 * Math.atan2(getX(), getW()); + result[1] = MathUtils.HALF_PI; + result[2] = 0; + } else if (test < -0.499 * unit) { // singularity at south pole + result[0] = -2 * Math.atan2(getX(), getW()); + result[1] = -MathUtils.HALF_PI; + result[2] = 0; + } else { + result[0] = Math.atan2(2 * getY() * getW() - 2 * getX() * getZ(), sqx - sqy - sqz + sqw); + result[1] = Math.asin(2 * test / unit); + result[2] = Math.atan2(2 * getX() * getW() - 2 * getY() * getZ(), -sqx + sqy - sqz + sqw); + } + return result; + } + + /** + * Sets the value of this quaternion to the rotation described by the given matrix. + * + * @param matrix + * @return this quaternion for chaining + * @throws NullPointerException + * if matrix is null. + */ + public Quaternion fromRotationMatrix(final ReadOnlyMatrix3 matrix) { + return fromRotationMatrix(matrix.getM00(), matrix.getM01(), matrix.getM02(), matrix.getM10(), matrix.getM11(), + matrix.getM12(), matrix.getM20(), matrix.getM21(), matrix.getM22()); + } + + /** + * Sets the value of this quaternion to the rotation described by the given matrix values. + * + * @param m00 + * @param m01 + * @param m02 + * @param m10 + * @param m11 + * @param m12 + * @param m20 + * @param m21 + * @param m22 + * @return this quaternion for chaining + */ + public Quaternion fromRotationMatrix(final double m00, final double m01, final double m02, final double m10, + final double m11, final double m12, final double m20, final double m21, final double m22) { + // Uses the Graphics Gems code, from + // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z + // *NOT* the "Matrix and Quaternions FAQ", which has errors! + + // the trace is the sum of the diagonal elements; see + // http://mathworld.wolfram.com/MatrixTrace.html + final double t = m00 + m11 + m22; + + // we protect the division by s by ensuring that s>=1 + double x, y, z, w; + if (t >= 0) { // |w| >= .5 + double s = Math.sqrt(t + 1); // |s|>=1 ... + w = 0.5 * s; + s = 0.5 / s; // so this division isn't bad + x = (m21 - m12) * s; + y = (m02 - m20) * s; + z = (m10 - m01) * s; + } else if (m00 > m11 && m00 > m22) { + double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1 + x = s * 0.5; // |x| >= .5 + s = 0.5 / s; + y = (m10 + m01) * s; + z = (m02 + m20) * s; + w = (m21 - m12) * s; + } else if (m11 > m22) { + double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1 + y = s * 0.5; // |y| >= .5 + s = 0.5 / s; + x = (m10 + m01) * s; + z = (m21 + m12) * s; + w = (m02 - m20) * s; + } else { + double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1 + z = s * 0.5; // |z| >= .5 + s = 0.5 / s; + x = (m02 + m20) * s; + y = (m21 + m12) * s; + w = (m10 - m01) * s; + } + + return set(x, y, z, w); + } + + /** + * @param store + * the matrix to store our result in. If null, a new matrix is created. + * @return the rotation matrix representation of this quaternion (normalized) + * + * if store is not null and is read only. + */ + @Override + public Matrix3 toRotationMatrix(final Matrix3 store) { + Matrix3 result = store; + if (result == null) { + result = new Matrix3(); + } + + final double norm = magnitudeSquared(); + final double s = norm > 0.0 ? 2.0 / norm : 0.0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + final double xs = getX() * s; + final double ys = getY() * s; + final double zs = getZ() * s; + final double xx = getX() * xs; + final double xy = getX() * ys; + final double xz = getX() * zs; + final double xw = getW() * xs; + final double yy = getY() * ys; + final double yz = getY() * zs; + final double yw = getW() * ys; + final double zz = getZ() * zs; + final double zw = getW() * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.setM00(1.0 - (yy + zz)); + result.setM01(xy - zw); + result.setM02(xz + yw); + result.setM10(xy + zw); + result.setM11(1.0 - (xx + zz)); + result.setM12(yz - xw); + result.setM20(xz - yw); + result.setM21(yz + xw); + result.setM22(1.0 - (xx + yy)); + + return result; + } + + /** + * @param store + * the matrix to store our result in. If null, a new matrix is created. + * @return the rotation matrix representation of this quaternion (normalized) + */ + @Override + public Matrix4 toRotationMatrix(final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + final double norm = magnitudeSquared(); + final double s = norm == 1.0 ? 2.0 : norm > 0.0 ? 2.0 / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + final double xs = getX() * s; + final double ys = getY() * s; + final double zs = getZ() * s; + final double xx = getX() * xs; + final double xy = getX() * ys; + final double xz = getX() * zs; + final double xw = getW() * xs; + final double yy = getY() * ys; + final double yz = getY() * zs; + final double yw = getW() * ys; + final double zz = getZ() * zs; + final double zw = getW() * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + result.setM00(1.0 - (yy + zz)); + result.setM01(xy - zw); + result.setM02(xz + yw); + result.setM10(xy + zw); + result.setM11(1.0 - (xx + zz)); + result.setM12(yz - xw); + result.setM20(xz - yw); + result.setM21(yz + xw); + result.setM22(1.0 - (xx + yy)); + + return result; + } + + /** + * @param index + * the 3x3 rotation matrix column to retrieve from this quaternion (normalized). Must be between 0 and 2. + * @param store + * the vector object to store the result in. if null, a new one is created. + * @return the column specified by the index. + */ + @Override + public Vector3 getRotationColumn(final int index, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final double norm = magnitudeSquared(); + final double s = norm == 1.0 ? 2.0 : norm > 0.0 ? 2.0 / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + final double xs = getX() * s; + final double ys = getY() * s; + final double zs = getZ() * s; + final double xx = getX() * xs; + final double xy = getX() * ys; + final double xz = getX() * zs; + final double xw = getW() * xs; + final double yy = getY() * ys; + final double yz = getY() * zs; + final double yw = getW() * ys; + final double zz = getZ() * zs; + final double zw = getW() * zs; + + // using s=2/norm (instead of 1/norm) saves 3 multiplications by 2 here + double x, y, z; + switch (index) { + case 0: + x = 1.0 - (yy + zz); + y = xy + zw; + z = xz - yw; + break; + case 1: + x = xy - zw; + y = 1.0 - (xx + zz); + z = yz + xw; + break; + case 2: + x = xz + yw; + y = yz - xw; + z = 1.0 - (xx + yy); + break; + default: + throw new IllegalArgumentException("Invalid column index. " + index); + } + + return result.set(x, y, z); + } + + /** + * Sets the values of this quaternion to the values represented by a given angle and axis of rotation. Note that + * this method creates an object, so use fromAngleNormalAxis if your axis is already normalized. If axis == 0,0,0 + * the quaternion is set to identity. + * + * @param angle + * the angle to rotate (in radians). + * @param axis + * the axis of rotation. + * @return this quaternion for chaining + * @throws NullPointerException + * if axis is null + */ + public Quaternion fromAngleAxis(final double angle, final ReadOnlyVector3 axis) { + final Vector3 temp = Vector3.fetchTempInstance(); + final Quaternion quat = fromAngleNormalAxis(angle, axis.normalize(temp)); + Vector3.releaseTempInstance(temp); + return quat; + } + + /** + * Sets the values of this quaternion to the values represented by a given angle and unit length axis of rotation. + * If axis == 0,0,0 the quaternion is set to identity. + * + * @param angle + * the angle to rotate (in radians). + * @param axis + * the axis of rotation (already normalized - unit length). + * @throws NullPointerException + * if axis is null + */ + public Quaternion fromAngleNormalAxis(final double angle, final ReadOnlyVector3 axis) { + if (axis.equals(Vector3.ZERO)) { + return setIdentity(); + } + + final double halfAngle = 0.5 * angle; + final double sin = MathUtils.sin(halfAngle); + final double w = MathUtils.cos(halfAngle); + final double x = sin * axis.getX(); + final double y = sin * axis.getY(); + final double z = sin * axis.getZ(); + return set(x, y, z, w); + } + + /** + * Returns the rotation angle represented by this quaternion. If a non-null vector is provided, the axis of rotation + * is stored in that vector as well. + * + * @param axisStore + * the object we'll store the computed axis in. If null, no computations are done to determine axis. + * @return the angle of rotation in radians. + */ + @Override + public double toAngleAxis(final Vector3 axisStore) { + final double sqrLength = getX() * getX() + getY() * getY() + getZ() * getZ(); + double angle; + if (Math.abs(sqrLength) <= MathUtils.EPSILON) { // length is ~0 + angle = 0.0; + if (axisStore != null) { + axisStore.setX(1.0); + axisStore.setY(0.0); + axisStore.setZ(0.0); + } + } else { + angle = 2.0 * Math.acos(getW()); + if (axisStore != null) { + final double invLength = 1.0 / Math.sqrt(sqrLength); + axisStore.setX(getX() * invLength); + axisStore.setY(getY() * invLength); + axisStore.setZ(getZ() * invLength); + } + } + + return angle; + } + + /** + * Sets this quaternion to that which will rotate vector "from" into vector "to". from and to do not have to be the + * same length. + * + * @param from + * the source vector to rotate + * @param to + * the destination vector into which to rotate the source vector + * @return this quaternion for chaining + */ + public Quaternion fromVectorToVector(final ReadOnlyVector3 from, final ReadOnlyVector3 to) { + final ReadOnlyVector3 a = from; + final ReadOnlyVector3 b = to; + final double factor = a.length() * b.length(); + if (Math.abs(factor) > MathUtils.EPSILON) { + // Vectors have length > 0 + final Vector3 pivotVector = Vector3.fetchTempInstance(); + try { + final double dot = a.dot(b) / factor; + final double theta = Math.acos(Math.max(-1.0, Math.min(dot, 1.0))); + a.cross(b, pivotVector); + if (dot < 0.0 && pivotVector.length() < MathUtils.EPSILON) { + // Vectors parallel and opposite direction, therefore a rotation of 180 degrees about any vector + // perpendicular to this vector will rotate vector a onto vector b. + // + // The following guarantees the dot-product will be 0.0. + int dominantIndex; + if (Math.abs(a.getX()) > Math.abs(a.getY())) { + if (Math.abs(a.getX()) > Math.abs(a.getZ())) { + dominantIndex = 0; + } else { + dominantIndex = 2; + } + } else { + if (Math.abs(a.getY()) > Math.abs(a.getZ())) { + dominantIndex = 1; + } else { + dominantIndex = 2; + } + } + pivotVector.setValue(dominantIndex, -a.getValue((dominantIndex + 1) % 3)); + pivotVector.setValue((dominantIndex + 1) % 3, a.getValue(dominantIndex)); + pivotVector.setValue((dominantIndex + 2) % 3, 0f); + } + return fromAngleAxis(theta, pivotVector); + } finally { + Vector3.releaseTempInstance(pivotVector); + } + } else { + return setIdentity(); + } + } + + /** + * @param store + * the Quaternion to store the result in. if null, a new one is created. + * @return a new quaternion that represents a unit length version of this Quaternion. + */ + @Override + public Quaternion normalize(final Quaternion store) { + Quaternion result = store; + if (result == null) { + result = new Quaternion(); + } + + final double n = 1.0 / magnitude(); + final double x = getX() * n; + final double y = getY() * n; + final double z = getZ() * n; + final double w = getW() * n; + return result.set(x, y, z, w); + } + + /** + * @return this quaternion, modified to be unit length, for chaining. + */ + public Quaternion normalizeLocal() { + final double n = 1.0 / magnitude(); + final double x = getX() * n; + final double y = getY() * n; + final double z = getZ() * n; + final double w = getW() * n; + return set(x, y, z, w); + } + + /** + * Calculates the <i>multiplicative inverse</i> <code>Q<sup>-1</sup></code> of this quaternion <code>Q</code> such + * that <code>QQ<sup>-1</sup> = [0,0,0,1]</code> (the identity quaternion). Note that for unit quaternions, a + * quaternion's inverse is equal to its (far easier to calculate) conjugate. + * + * @param store + * the <code>Quaternion</code> to store the result in. If <code>null</code>, a new one is created. + * @see #conjugate(Quaternion) + * @return the multiplicative inverse of this quaternion. + */ + public Quaternion invert(Quaternion store) { + if (store == null) { + store = new Quaternion(); + } + final double magnitudeSQ = magnitudeSquared(); + conjugate(store); + if (Math.abs(1.0 - magnitudeSQ) <= MathUtils.EPSILON) { + return store; + } else { + return store.multiplyLocal(1.0 / magnitudeSQ); + } + } + + /** + * Locally sets this quaternion <code>Q</code> to its <i>multiplicative inverse</i> <code>Q<sup>-1</sup></code> such + * that <code>QQ<sup>-1</sup> = [0,0,0,1]</code> (the identity quaternion). Note that for unit quaternions, a + * quaternion's inverse is equal to its (far easier to calculate) conjugate. + * + * @see #conjugate(Quaternion) + * + * @return this <code>Quaternion</code> for chaining. + */ + public Quaternion invertLocal() { + final double magnitudeSQ = magnitudeSquared(); + conjugateLocal(); + if (Math.abs(1.0 - magnitudeSQ) <= MathUtils.EPSILON) { + return this; + } else { + return multiplyLocal(1.0 / magnitudeSquared()); + } + } + + /** + * Creates a new quaternion that is the conjugate <code>[-x, -y, -z, w]</code> of this quaternion. + * + * @param store + * the <code>Quaternion</code> to store the result in. If <code>null</code>, a new one is created. + * @return the conjugate to this quaternion. + */ + @Override + public Quaternion conjugate(Quaternion store) { + if (store == null) { + store = new Quaternion(); + } + return store.set(-getX(), -getY(), -getZ(), getW()); + } + + /** + * Internally sets this quaternion to its conjugate <code>[-x, -y, -z, w]</code>. + * + * @return this <code>Quaternion</code> for chaining. + */ + public Quaternion conjugateLocal() { + return set(-getX(), -getY(), -getZ(), getW()); + } + + /** + * Adds this quaternion to another and places the result in the given store. + * + * @param quat + * @param store + * the Quaternion to store the result in. if null, a new one is created. + * @return a quaternion representing the fields of this quaternion added to those of the given quaternion. + */ + @Override + public Quaternion add(final ReadOnlyQuaternion quat, final Quaternion store) { + Quaternion result = store; + if (result == null) { + result = new Quaternion(); + } + + return result.set(getX() + quat.getX(), getY() + quat.getY(), getZ() + quat.getZ(), getW() + quat.getW()); + } + + /** + * Internally increments the fields of this quaternion with the field values of the given quaternion. + * + * @param quat + * @return this quaternion for chaining + */ + public Quaternion addLocal(final ReadOnlyQuaternion quat) { + setX(getX() + quat.getX()); + setY(getY() + quat.getY()); + setZ(getZ() + quat.getZ()); + setW(getW() + quat.getW()); + return this; + } + + /** + * @param quat + * @param store + * the Quaternion to store the result in. if null, a new one is created. + * @return a quaternion representing the fields of this quaternion subtracted from those of the given quaternion. + */ + @Override + public Quaternion subtract(final ReadOnlyQuaternion quat, final Quaternion store) { + Quaternion result = store; + if (result == null) { + result = new Quaternion(); + } + + return result.set(getX() - quat.getX(), getY() - quat.getY(), getZ() - quat.getZ(), getW() - quat.getW()); + } + + /** + * Internally decrements the fields of this quaternion by the field values of the given quaternion. + * + * @param quat + * @return this quaternion for chaining. + */ + public Quaternion subtractLocal(final ReadOnlyQuaternion quat) { + setX(getX() - quat.getX()); + setY(getY() - quat.getY()); + setZ(getZ() - quat.getZ()); + setW(getW() - quat.getW()); + return this; + } + + /** + * Multiplies each value of this quaternion by the given scalar value. + * + * @param scalar + * the quaternion to multiply this quaternion by. + * @param store + * the Quaternion to store the result in. if null, a new one is created. + * @return the resulting quaternion. + */ + @Override + public Quaternion multiply(final double scalar, final Quaternion store) { + Quaternion result = store; + if (result == null) { + result = new Quaternion(); + } + + return result.set(scalar * getX(), scalar * getY(), scalar * getZ(), scalar * getW()); + } + + /** + * Multiplies each value of this quaternion by the given scalar value. The result is stored in this quaternion. + * + * @param scalar + * the quaternion to multiply this quaternion by. + * @return this quaternion for chaining. + */ + public Quaternion multiplyLocal(final double scalar) { + setX(getX() * scalar); + setY(getY() * scalar); + setZ(getZ() * scalar); + setW(getW() * scalar); + return this; + } + + /** + * Multiplies this quaternion by the supplied quaternion. The result is stored in the given store quaternion or a + * new quaternion if store is null. + * + * It IS safe for quat and store to be the same object. + * + * @param quat + * the quaternion to multiply this quaternion by. + * @param store + * the quaternion to store the result in. + * @return the new quaternion. + * + * if the given store is read only. + */ + @Override + public Quaternion multiply(final ReadOnlyQuaternion quat, Quaternion store) { + if (store == null) { + store = new Quaternion(); + } + final double x = getX() * quat.getW() + getY() * quat.getZ() - getZ() * quat.getY() + getW() * quat.getX(); + final double y = -getX() * quat.getZ() + getY() * quat.getW() + getZ() * quat.getX() + getW() * quat.getY(); + final double z = getX() * quat.getY() - getY() * quat.getX() + getZ() * quat.getW() + getW() * quat.getZ(); + final double w = -getX() * quat.getX() - getY() * quat.getY() - getZ() * quat.getZ() + getW() * quat.getW(); + return store.set(x, y, z, w); + } + + /** + * Multiplies this quaternion by the supplied quaternion. The result is stored locally. + * + * @param quat + * The Quaternion to multiply this one by. + * @return this quaternion for chaining + * @throws NullPointerException + * if quat is null. + */ + public Quaternion multiplyLocal(final ReadOnlyQuaternion quat) { + return multiplyLocal(quat.getX(), quat.getY(), quat.getZ(), quat.getW()); + } + + /** + * Multiplies this quaternion by the supplied matrix. The result is stored locally. + * + * @param matrix + * the matrix to apply to this quaternion. + * @return this quaternion for chaining + * @throws NullPointerException + * if matrix is null. + */ + public Quaternion multiplyLocal(final ReadOnlyMatrix3 matrix) { + final double oldX = getX(), oldY = getY(), oldZ = getZ(), oldW = getW(); + fromRotationMatrix(matrix); + final double tempX = getX(), tempY = getY(), tempZ = getZ(), tempW = getW(); + + final double x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX; + final double y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY; + final double z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ; + final double w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW; + return set(x, y, z, w); + } + + /** + * Multiplies this quaternion by the supplied quaternion values. The result is stored locally. + * + * @param qx + * @param qy + * @param qz + * @param qw + * @return this quaternion for chaining + */ + public Quaternion multiplyLocal(final double qx, final double qy, final double qz, final double qw) { + final double x = getX() * qw + getY() * qz - getZ() * qy + getW() * qx; + final double y = -getX() * qz + getY() * qw + getZ() * qx + getW() * qy; + final double z = getX() * qy - getY() * qx + getZ() * qw + getW() * qz; + final double w = -getX() * qx - getY() * qy - getZ() * qz + getW() * qw; + return set(x, y, z, w); + } + + /** + * Multiply this quaternion by a rotational quaternion made from the given angle and axis. The axis must be a + * normalized vector. + * + * @param angle + * in radians + * @param x + * x coord of rotation axis + * @param y + * y coord of rotation axis + * @param z + * z coord of rotation axis + * @return this quaternion for chaining. + */ + public Quaternion applyRotation(final double angle, final double x, final double y, final double z) { + if (x == 0 && y == 0 && z == 0) { + // no change + return this; + } + + final double halfAngle = 0.5 * angle; + final double sin = MathUtils.sin(halfAngle); + final double qw = MathUtils.cos(halfAngle); + final double qx = sin * x; + final double qy = sin * y; + final double qz = sin * z; + + final double newX = getX() * qw + getY() * qz - getZ() * qy + getW() * qx; + final double newY = -getX() * qz + getY() * qw + getZ() * qx + getW() * qy; + final double newZ = getX() * qy - getY() * qx + getZ() * qw + getW() * qz; + final double newW = -getX() * qx - getY() * qy - getZ() * qz + getW() * qw; + + return set(newX, newY, newZ, newW); + } + + /** + * Apply rotation around X + * + * @param angle + * in radians + * @return this quaternion for chaining. + */ + public Quaternion applyRotationX(final double angle) { + final double halfAngle = 0.5 * angle; + final double sin = MathUtils.sin(halfAngle); + final double cos = MathUtils.cos(halfAngle); + + final double newX = getX() * cos + getW() * sin; + final double newY = getY() * cos + getZ() * sin; + final double newZ = -getY() * sin + getZ() * cos; + final double newW = -getX() * sin + getW() * cos; + + return set(newX, newY, newZ, newW); + } + + /** + * Apply rotation around Y + * + * @param angle + * in radians + * @return this quaternion for chaining. + */ + public Quaternion applyRotationY(final double angle) { + final double halfAngle = 0.5 * angle; + final double sin = MathUtils.sin(halfAngle); + final double cos = MathUtils.cos(halfAngle); + + final double newX = getX() * cos - getZ() * sin; + final double newY = getY() * cos + getW() * sin; + final double newZ = getX() * sin + getZ() * cos; + final double newW = -getY() * sin + getW() * cos; + + return set(newX, newY, newZ, newW); + } + + /** + * Apply rotation around Z + * + * @param angle + * in radians + * @return this quaternion for chaining. + */ + public Quaternion applyRotationZ(final double angle) { + final double halfAngle = 0.5 * angle; + final double sin = MathUtils.sin(halfAngle); + final double cos = MathUtils.cos(halfAngle); + + final double newX = getX() * cos + getY() * sin; + final double newY = -getX() * sin + getY() * cos; + final double newZ = getZ() * cos + getW() * sin; + final double newW = -getZ() * sin + getW() * cos; + + return set(newX, newY, newZ, newW); + } + + /** + * Rotates the given vector by this quaternion. If supplied, the result is stored into the supplied "store" vector. + * + * @param vec + * the vector to multiply this quaternion by. + * @param store + * the vector to store the result in. If store is null, a new vector is created. Note that it IS safe for + * vec and store to be the same object. + * @return the store vector, or a new vector if store is null. + * @throws NullPointerException + * if vec is null + * + * if the given store is read only. + */ + @Override + public Vector3 apply(final ReadOnlyVector3 vec, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + if (vec.equals(Vector3.ZERO)) { + store.set(0, 0, 0); + } else { + final double x = getW() * getW() * vec.getX() + 2 * getY() * getW() * vec.getZ() - 2 * getZ() * getW() + * vec.getY() + getX() * getX() * vec.getX() + 2 * getY() * getX() * vec.getY() + 2 * getZ() + * getX() * vec.getZ() - getZ() * getZ() * vec.getX() - getY() * getY() * vec.getX(); + final double y = 2 * getX() * getY() * vec.getX() + getY() * getY() * vec.getY() + 2 * getZ() * getY() + * vec.getZ() + 2 * getW() * getZ() * vec.getX() - getZ() * getZ() * vec.getY() + getW() * getW() + * vec.getY() - 2 * getX() * getW() * vec.getZ() - getX() * getX() * vec.getY(); + final double z = 2 * getX() * getZ() * vec.getX() + 2 * getY() * getZ() * vec.getY() + getZ() * getZ() + * vec.getZ() - 2 * getW() * getY() * vec.getX() - getY() * getY() * vec.getZ() + 2 * getW() + * getX() * vec.getY() - getX() * getX() * vec.getZ() + getW() * getW() * vec.getZ(); + store.set(x, y, z); + } + return store; + } + + /** + * Updates this quaternion to represent a rotation formed by the given three axes. These axes are assumed to be + * orthogonal and no error checking is applied. It is the user's job to insure that the three axes being provided + * indeed represent a proper right handed coordinate system. + * + * @param xAxis + * vector representing the x-axis of the coordinate system. + * @param yAxis + * vector representing the y-axis of the coordinate system. + * @param zAxis + * vector representing the z-axis of the coordinate system. + * @return this quaternion for chaining + */ + public Quaternion fromAxes(final ReadOnlyVector3 xAxis, final ReadOnlyVector3 yAxis, final ReadOnlyVector3 zAxis) { + return fromRotationMatrix(xAxis.getX(), yAxis.getX(), zAxis.getX(), xAxis.getY(), yAxis.getY(), zAxis.getY(), + xAxis.getZ(), yAxis.getZ(), zAxis.getZ()); + } + + /** + * Converts this quaternion to a rotation matrix and then extracts rotation axes. + * + * @param axes + * the array of vectors to be filled. + * @throws ArrayIndexOutOfBoundsException + * if the given axes array is smaller than 3 elements. + * @return the axes + */ + @Override + public Vector3[] toAxes(final Vector3[] axes) { + final Matrix3 tempMat = toRotationMatrix(Matrix3.fetchTempInstance()); + axes[2] = tempMat.getColumn(2, axes[2]); + axes[1] = tempMat.getColumn(1, axes[1]); + axes[0] = tempMat.getColumn(0, axes[0]); + Matrix3.releaseTempInstance(tempMat); + return axes; + } + + /** + * Does a spherical linear interpolation between this quaternion and the given end quaternion by the given change + * amount. + * + * @param endQuat + * @param changeAmnt + * @param store + * the quaternion to store the result in for return. If null, a new quaternion object is created and + * returned. + * @return a new quaternion containing the result. + */ + @Override + public Quaternion slerp(final ReadOnlyQuaternion endQuat, final double changeAmnt, final Quaternion store) { + return Quaternion.slerp(this, endQuat, changeAmnt, store); + } + + /** + * Does a spherical linear interpolation between this quaternion and the given end quaternion by the given change + * amount. Stores the results locally in this quaternion. + * + * @param endQuat + * @param changeAmnt + * @return this quaternion for chaining. + */ + public Quaternion slerpLocal(final ReadOnlyQuaternion endQuat, final double changeAmnt) { + return slerpLocal(this, endQuat, changeAmnt); + } + + /** + * Does a spherical linear interpolation between the given start and end quaternions by the given change amount. + * Returns the result as a new quaternion. + * + * @param startQuat + * @param endQuat + * @param changeAmnt + * @param store + * the quaternion to store the result in for return. If null, a new quaternion object is created and + * returned. + * @return the new quaternion + */ + public static Quaternion slerp(final ReadOnlyQuaternion startQuat, final ReadOnlyQuaternion endQuat, + final double changeAmnt, final Quaternion store) { + Quaternion result = store; + if (result == null) { + result = new Quaternion(); + } + + // check for weighting at either extreme + if (changeAmnt == 0.0) { + return result.set(startQuat); + } else if (changeAmnt == 1.0) { + return result.set(endQuat); + } + + result.set(endQuat); + // Check for equality and skip operation. + if (startQuat.equals(result)) { + return result; + } + + double dotP = startQuat.dot(result); + + if (dotP < 0.0) { + // Negate the second quaternion and the result of the dot product + result.multiplyLocal(-1); + dotP = -dotP; + } + + // Set the first and second scale for the interpolation + double scale0 = 1 - changeAmnt; + double scale1 = changeAmnt; + + // Check if the angle between the 2 quaternions was big enough to + // warrant such calculations + if (1 - dotP > 0.1) {// Get the angle between the 2 quaternions, + // and then store the sin() of that angle + final double theta = Math.acos(dotP); + final double invSinTheta = 1f / MathUtils.sin(theta); + + // Calculate the scale for q1 and q2, according to the angle and + // it's sine value + scale0 = MathUtils.sin((1 - changeAmnt) * theta) * invSinTheta; + scale1 = MathUtils.sin(changeAmnt * theta) * invSinTheta; + } + + // Calculate the x, y, z and w values for the quaternion by using a + // special form of linear interpolation for quaternions. + final double x = scale0 * startQuat.getX() + scale1 * result.getX(); + final double y = scale0 * startQuat.getY() + scale1 * result.getY(); + final double z = scale0 * startQuat.getZ() + scale1 * result.getZ(); + final double w = scale0 * startQuat.getW() + scale1 * result.getW(); + + // Return the interpolated quaternion + return result.set(x, y, z, w); + } + + /** + * Does a spherical linear interpolation between the given start and end quaternions by the given change amount. + * Stores the result locally. + * + * @param startQuat + * @param endQuat + * @param changeAmnt + * @param workQuat + * a Quaternion to use as scratchpad during calculation + * @return this quaternion for chaining. + * @throws NullPointerException + * if startQuat, endQuat or workQuat are null. + */ + public Quaternion slerpLocal(final ReadOnlyQuaternion startQuat, final ReadOnlyQuaternion endQuat, + final double changeAmnt, final Quaternion workQuat) { + + // check for weighting at either extreme + if (changeAmnt == 0.0) { + return set(startQuat); + } else if (changeAmnt == 1.0) { + return set(endQuat); + } + + // Check for equality and skip operation. + if (startQuat.equals(endQuat)) { + return set(startQuat); + } + + double result = startQuat.dot(endQuat); + workQuat.set(endQuat); + + if (result < 0.0) { + // Negate the second quaternion and the result of the dot product + workQuat.multiplyLocal(-1); + result = -result; + } + + // Set the first and second scale for the interpolation + double scale0 = 1 - changeAmnt; + double scale1 = changeAmnt; + + // Check if the angle between the 2 quaternions was big enough to + // warrant such calculations + if (1 - result > 0.1) {// Get the angle between the 2 quaternions, + // and then store the sin() of that angle + final double theta = MathUtils.acos(result); + final double invSinTheta = 1f / MathUtils.sin(theta); + + // Calculate the scale for q1 and q2, according to the angle and + // it's sine value + scale0 = MathUtils.sin((1 - changeAmnt) * theta) * invSinTheta; + scale1 = MathUtils.sin(changeAmnt * theta) * invSinTheta; + } + + // Calculate the x, y, z and w values for the quaternion by using a + // special form of linear interpolation for quaternions. + final double x = scale0 * startQuat.getX() + scale1 * workQuat.getX(); + final double y = scale0 * startQuat.getY() + scale1 * workQuat.getY(); + final double z = scale0 * startQuat.getZ() + scale1 * workQuat.getZ(); + final double w = scale0 * startQuat.getW() + scale1 * workQuat.getW(); + set(x, y, z, w); + + // Return the interpolated quaternion + return this; + } + + /** + * Does a spherical linear interpolation between the given start and end quaternions by the given change amount. + * Stores the result locally. + * + * @param startQuat + * @param endQuat + * @param changeAmnt + * @return this quaternion for chaining. + * @throws NullPointerException + * if startQuat or endQuat are null. + */ + public Quaternion slerpLocal(final ReadOnlyQuaternion startQuat, final ReadOnlyQuaternion endQuat, + final double changeAmnt) { + final Quaternion end = Quaternion.fetchTempInstance().set(endQuat); + slerpLocal(startQuat, endQuat, changeAmnt, end); + Quaternion.releaseTempInstance(end); + return this; + } + + /** + * Modifies this quaternion to equal the rotation required to point the z-axis at 'direction' and the y-axis to + * 'up'. + * + * @param direction + * where to 'look' at + * @param up + * a vector indicating the local up direction. + * @return this quaternion for chaining. + */ + public Quaternion lookAt(final ReadOnlyVector3 direction, final ReadOnlyVector3 up) { + final Vector3 xAxis = Vector3.fetchTempInstance(); + final Vector3 yAxis = Vector3.fetchTempInstance(); + final Vector3 zAxis = Vector3.fetchTempInstance(); + direction.normalize(zAxis); + up.normalize(xAxis).crossLocal(zAxis); + zAxis.cross(xAxis, yAxis); + + fromAxes(xAxis, yAxis, zAxis); + normalizeLocal(); + + Vector3.releaseTempInstance(xAxis); + Vector3.releaseTempInstance(yAxis); + Vector3.releaseTempInstance(zAxis); + return this; + } + + /** + * @return the squared magnitude of this quaternion. + */ + @Override + public double magnitudeSquared() { + return getW() * getW() + getX() * getX() + getY() * getY() + getZ() * getZ(); + } + + /** + * @return the magnitude of this quaternion. basically sqrt({@link #magnitude()}) + */ + @Override + public double magnitude() { + final double magnitudeSQ = magnitudeSquared(); + if (magnitudeSQ == 1.0) { + return 1.0; + } + + return MathUtils.sqrt(magnitudeSQ); + } + + /** + * @param x + * @param y + * @param z + * @param w + * @return the dot product of this quaternion with the given x,y,z and w values. + */ + @Override + public double dot(final double x, final double y, final double z, final double w) { + return getX() * x + getY() * y + getZ() * z + getW() * w; + } + + /** + * @param quat + * @return the dot product of this quaternion with the given quaternion. + */ + @Override + public double dot(final ReadOnlyQuaternion quat) { + return dot(quat.getX(), quat.getY(), quat.getZ(), quat.getW()); + } + + /** + * Sets the value of this quaternion to (0, 0, 0, 1). Equivalent to calling set(0, 0, 0, 1) + * + * @return this quaternion for chaining + */ + public Quaternion setIdentity() { + return set(0, 0, 0, 1); + } + + /** + * @return true if this quaternion is (0, 0, 0, 1) + */ + @Override + public boolean isIdentity() { + return strictEquals(Quaternion.IDENTITY); + } + + /** + * Check a quaternion... if it is null or its doubles are NaN or infinite, return false. Else return true. + * + * @param quat + * the quaternion to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyQuaternion quat) { + if (quat == null) { + return false; + } + if (Double.isNaN(quat.getX()) || Double.isInfinite(quat.getX())) { + return false; + } + if (Double.isNaN(quat.getY()) || Double.isInfinite(quat.getY())) { + return false; + } + if (Double.isNaN(quat.getZ()) || Double.isInfinite(quat.getZ())) { + return false; + } + if (Double.isNaN(quat.getW()) || Double.isInfinite(quat.getW())) { + return false; + } + return true; + } + + /** + * @return the string representation of this quaternion. + */ + @Override + public String toString() { + return "com.ardor3d.math.Quaternion [X=" + getX() + ", Y=" + getY() + ", Z=" + getZ() + ", W=" + getW() + "]"; + } + + /** + * @return returns a unique code for this quaternion object based on its values. If two quaternions are numerically + * equal, they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + final long x = Double.doubleToLongBits(getX()); + result += 31 * result + (int) (x ^ x >>> 32); + + final long y = Double.doubleToLongBits(getY()); + result += 31 * result + (int) (y ^ y >>> 32); + + final long z = Double.doubleToLongBits(getZ()); + result += 31 * result + (int) (z ^ z >>> 32); + + final long w = Double.doubleToLongBits(getW()); + result += 31 * result + (int) (w ^ w >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this quaternion and the provided quaternion have roughly the same x, y, z and w values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyQuaternion)) { + return false; + } + final ReadOnlyQuaternion comp = (ReadOnlyQuaternion) o; + return Math.abs(_x - comp.getX()) < Quaternion.ALLOWED_DEVIANCE + && Math.abs(_y - comp.getY()) < Quaternion.ALLOWED_DEVIANCE + && Math.abs(_z - comp.getZ()) < Quaternion.ALLOWED_DEVIANCE + && Math.abs(_w - comp.getW()) < Quaternion.ALLOWED_DEVIANCE; + } + + /** + * @param o + * the object to compare for equality + * @return true if this quaternion and the provided quaternion have the exact same double values. + */ + @Override + public boolean strictEquals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyQuaternion)) { + return false; + } + final ReadOnlyQuaternion comp = (ReadOnlyQuaternion) o; + return getX() == comp.getX() && getY() == comp.getY() && getZ() == comp.getZ() && getW() == comp.getW(); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Quaternion clone() { + return new Quaternion(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Quaternion> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(getX(), "x", 0); + capsule.write(getY(), "y", 0); + capsule.write(getZ(), "z", 0); + capsule.write(getW(), "w", 1); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + setX(capsule.readDouble("x", 0)); + setY(capsule.readDouble("y", 0)); + setZ(capsule.readDouble("z", 0)); + setW(capsule.readDouble("w", 1)); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setX(in.readDouble()); + setY(in.readDouble()); + setZ(in.readDouble()); + setW(in.readDouble()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeDouble(getX()); + out.writeDouble(getY()); + out.writeDouble(getZ()); + out.writeDouble(getW()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Quaternion that is intended for temporary use in calculations and so forth. Multiple calls + * to the method should return instances of this class that are not currently in use. + */ + public final static Quaternion fetchTempInstance() { + if (MathConstants.useMathPools) { + return Quaternion.QUAT_POOL.fetch(); + } else { + return new Quaternion(); + } + } + + /** + * Releases a Quaternion back to be used by a future call to fetchTempInstance. TAKE CARE: this Quaternion object + * should no longer have other classes referencing it or "Bad Things" will happen. + * + * @param mat + * the Quaternion to release. + */ + public final static void releaseTempInstance(final Quaternion mat) { + if (MathConstants.useMathPools) { + Quaternion.QUAT_POOL.release(mat); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Ray3.java b/ardor3d-math/src/main/java/com/ardor3d/math/Ray3.java new file mode 100644 index 0000000..9c1432f --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Ray3.java @@ -0,0 +1,395 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyRay3; +import com.ardor3d.math.type.ReadOnlyVector3; + +public class Ray3 extends Line3Base implements ReadOnlyRay3, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Ray3> RAY_POOL = ObjectPool.create(Ray3.class, MathConstants.maxMathPoolSize); + + /** + * Constructs a new ray with an origin at (0,0,0) and a direction of (0,0,1). + */ + public Ray3() { + super(Vector3.ZERO, Vector3.UNIT_Z); + } + + /** + * Copy constructor. + * + * @param source + * the ray to copy from. + */ + public Ray3(final ReadOnlyRay3 source) { + this(source.getOrigin(), source.getDirection()); + } + + /** + * Constructs a new ray using the supplied origin point and unit length direction vector + * + * @param origin + * @param direction + * - unit length + */ + public Ray3(final ReadOnlyVector3 origin, final ReadOnlyVector3 direction) { + super(origin, direction); + } + + /** + * Copies the values of the given source ray into this ray. + * + * @param source + * @return this ray for chaining + * @throws NullPointerException + * if source is null. + */ + public Ray3 set(final ReadOnlyRay3 source) { + _origin.set(source.getOrigin()); + _direction.set(source.getDirection()); + return this; + } + + /** + * @param worldVertices + * an array of vectors describing a polygon + * @return the distance from our origin to the primitive or POSITIVE_INFINITY if we do not intersect. + */ + @Override + public double getDistanceToPrimitive(final Vector3[] worldVertices) { + // Intersection test + final Vector3 intersect = Vector3.fetchTempInstance(); + try { + if (intersects(worldVertices, intersect)) { + return getOrigin().distance(intersect); + } + } finally { + Vector3.releaseTempInstance(intersect); + } + return Double.POSITIVE_INFINITY; + } + + /** + * @param polygonVertices + * @param locationStore + * @return true if this ray intersects a polygon described by the given vertices. + */ + @Override + public boolean intersects(final Vector3[] polygonVertices, final Vector3 locationStore) { + if (polygonVertices.length == 3) { + // TRIANGLE + return intersectsTriangle(polygonVertices[0], polygonVertices[1], polygonVertices[2], locationStore); + } else if (polygonVertices.length == 4) { + // QUAD + return intersectsQuad(polygonVertices[0], polygonVertices[1], polygonVertices[2], polygonVertices[3], + locationStore); + } + // TODO: Add support for line and point + return false; + } + + /** + * @param pointA + * @param pointB + * @param pointC + * @param locationStore + * if not null, and this ray intersects, the point of intersection is calculated and stored in this + * Vector3 + * @return true if this ray intersects a triangle formed by the given three points. + * @throws NullPointerException + * if any of the points are null. + */ + @Override + public boolean intersectsTriangle(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, + final ReadOnlyVector3 pointC, final Vector3 locationStore) { + return intersects(pointA, pointB, pointC, locationStore, false); + } + + /** + * @param pointA + * @param pointB + * @param pointC + * @param locationStore + * if not null, and this ray intersects, the point of intersection is calculated and stored in this + * Vector3 as (t, u, v) where t is the distance from the _origin to the point of intersection and (u, v) + * is the intersection point on the triangle plane. + * @return true if this ray intersects a triangle formed by the given three points. + * @throws NullPointerException + * if any of the points are null. + */ + @Override + public boolean intersectsTrianglePlanar(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, + final ReadOnlyVector3 pointC, final Vector3 locationStore) { + return intersects(pointA, pointB, pointC, locationStore, true); + } + + /** + * @param pointA + * @param pointB + * @param pointC + * @param pointD + * @param locationStore + * if not null, and this ray intersects, the point of intersection is calculated and stored in this + * Vector3 + * @return true if this ray intersects a triangle formed by the given three points. The points are assumed to be + * coplanar. + * @throws NullPointerException + * if any of the points are null. + */ + @Override + public boolean intersectsQuad(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, + final ReadOnlyVector3 pointC, final ReadOnlyVector3 pointD, final Vector3 locationStore) { + return intersects(pointA, pointB, pointC, locationStore, false) + || intersects(pointA, pointC, pointD, locationStore, false); + } + + /** + * @param pointA + * @param pointB + * @param pointC + * @param pointD + * @param locationStore + * if not null, and this ray intersects, the point of intersection is calculated and stored in this + * Vector3 as (t, u, v) where t is the distance from the _origin to the point of intersection and (u, v) + * is the intersection point on the triangle plane. + * @return true if this ray intersects a quad formed by the given four points. The points are assumed to be + * coplanar. + * @throws NullPointerException + * if any of the points are null. + */ + @Override + public boolean intersectsQuadPlanar(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, + final ReadOnlyVector3 pointC, final ReadOnlyVector3 pointD, final Vector3 locationStore) { + return intersects(pointA, pointB, pointC, locationStore, true) + || intersects(pointA, pointC, pointD, locationStore, true); + } + + /** + * Ray vs triangle implementation. + * + * @param pointA + * @param pointB + * @param pointC + * @param locationStore + * @param doPlanar + * @return true if this ray intersects a triangle formed by the given three points. + * @throws NullPointerException + * if any of the points are null. + */ + protected boolean intersects(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, + final ReadOnlyVector3 pointC, final Vector3 locationStore, final boolean doPlanar) { + final Vector3 diff = Vector3.fetchTempInstance().set(_origin).subtractLocal(pointA); + final Vector3 edge1 = Vector3.fetchTempInstance().set(pointB).subtractLocal(pointA); + final Vector3 edge2 = Vector3.fetchTempInstance().set(pointC).subtractLocal(pointA); + final Vector3 norm = Vector3.fetchTempInstance().set(edge1).crossLocal(edge2); + + double dirDotNorm = _direction.dot(norm); + double sign; + if (dirDotNorm > MathUtils.EPSILON) { + sign = 1.0; + } else if (dirDotNorm < -MathUtils.EPSILON) { + sign = -1.0; + dirDotNorm = -dirDotNorm; + } else { + // ray and triangle/quad are parallel + return false; + } + + final double dirDotDiffxEdge2 = sign * _direction.dot(diff.cross(edge2, edge2)); + boolean result = false; + if (dirDotDiffxEdge2 >= 0.0) { + final double dirDotEdge1xDiff = sign * _direction.dot(edge1.crossLocal(diff)); + if (dirDotEdge1xDiff >= 0.0) { + if (dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm) { + final double diffDotNorm = -sign * diff.dot(norm); + if (diffDotNorm >= 0.0) { + // ray intersects triangle + // if storage vector is null, just return true, + if (locationStore == null) { + return true; + } + // else fill in. + final double inv = 1f / dirDotNorm; + final double t = diffDotNorm * inv; + if (!doPlanar) { + locationStore.set(_origin).addLocal(_direction.getX() * t, _direction.getY() * t, + _direction.getZ() * t); + } else { + // these weights can be used to determine + // interpolated values, such as texture coord. + // eg. texcoord s,t at intersection point: + // s = w0*s0 + w1*s1 + w2*s2; + // t = w0*t0 + w1*t1 + w2*t2; + final double w1 = dirDotDiffxEdge2 * inv; + final double w2 = dirDotEdge1xDiff * inv; + // double w0 = 1.0 - w1 - w2; + locationStore.set(t, w1, w2); + } + result = true; + } + } + } + } + Vector3.releaseTempInstance(diff); + Vector3.releaseTempInstance(edge1); + Vector3.releaseTempInstance(edge2); + Vector3.releaseTempInstance(norm); + return result; + } + + /** + * @param plane + * @param locationStore + * if not null, and this ray intersects the plane, the world location of the point of intersection is + * stored in this vector. + * @return true if the ray collides with the given Plane + * @throws NullPointerException + * if the plane is null. + */ + @Override + public boolean intersectsPlane(final ReadOnlyPlane plane, final Vector3 locationStore) { + final ReadOnlyVector3 normal = plane.getNormal(); + final double denominator = normal.dot(_direction); + + if (denominator > -MathUtils.EPSILON && denominator < MathUtils.EPSILON) { + return false; // coplanar + } + + final double numerator = -normal.dot(_origin) + plane.getConstant(); + final double ratio = numerator / denominator; + + if (ratio < MathUtils.EPSILON) { + return false; // intersects behind _origin + } + + if (locationStore != null) { + locationStore.set(_direction).multiplyLocal(ratio).addLocal(_origin); + } + + return true; + } + + /** + * @param point + * @param store + * if not null, the closest point is stored in this param + * @return the squared distance from this ray to the given point. + * @throws NullPointerException + * if the point is null. + */ + @Override + public double distanceSquared(final ReadOnlyVector3 point, final Vector3 store) { + final Vector3 vectorA = Vector3.fetchTempInstance(); + vectorA.set(point).subtractLocal(_origin); + final double t0 = _direction.dot(vectorA); + if (t0 > 0) { + // d = |P - (O + t*D)| + vectorA.set(_direction).multiplyLocal(t0); + vectorA.addLocal(_origin); + } else { + // ray is closest to origin point + vectorA.set(_origin); + } + + // Save away the closest point if requested. + if (store != null) { + store.set(vectorA); + } + + point.subtract(vectorA, vectorA); + final double lSQ = vectorA.lengthSquared(); + Vector3.releaseTempInstance(vectorA); + return lSQ; + } + + /** + * Check a ray... if it is null or the values of its origin or direction are NaN or infinite, return false. Else + * return true. + * + * @param ray + * the ray to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyRay3 ray) { + if (ray == null) { + return false; + } + + return Vector3.isValid(ray.getDirection()) && Vector3.isValid(ray.getOrigin()); + } + + /** + * @return the string representation of this ray. + */ + @Override + public String toString() { + return "com.ardor3d.math.Ray [Origin: " + _origin + " - Direction: " + _direction + "]"; + } + + /** + * @param o + * the object to compare for equality + * @return true if this ray and the provided ray have the same constant and normal values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyRay3)) { + return false; + } + final ReadOnlyRay3 comp = (ReadOnlyRay3) o; + return _origin.equals(comp.getOrigin()) && _direction.equals(comp.getDirection()); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Ray3 clone() { + return new Ray3(this); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Ray that is intended for temporary use in calculations and so forth. Multiple calls to the + * method should return instances of this class that are not currently in use. + */ + public final static Ray3 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Ray3.RAY_POOL.fetch(); + } else { + return new Ray3(); + } + } + + /** + * Releases a Ray back to be used by a future call to fetchTempInstance. TAKE CARE: this Ray object should no longer + * have other classes referencing it or "Bad Things" will happen. + * + * @param ray + * the Ray to release. + */ + public final static void releaseTempInstance(final Ray3 ray) { + if (MathConstants.useMathPools) { + Ray3.RAY_POOL.release(ray); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Rectangle2.java b/ardor3d-math/src/main/java/com/ardor3d/math/Rectangle2.java new file mode 100644 index 0000000..ced2fe8 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Rectangle2.java @@ -0,0 +1,295 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyRectangle2; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * <p> + * Defines a finite plane within two dimensional space that is specified via an origin (x,y - considered bottom left + * usually) and a width and height. + * </p> + * This class is at least partially patterned after awt's Rectangle class. + */ + +public class Rectangle2 implements Cloneable, Savable, Externalizable, ReadOnlyRectangle2, Poolable { + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Rectangle2> RECTANGLE_POOL = ObjectPool.create(Rectangle2.class, + MathConstants.maxMathPoolSize); + + private int _x; + private int _y; + private int _width; + private int _height; + + /** + * Constructor creates a new <code>Rectangle2</code> with its origin at 0,0 and width/height of 0. + */ + public Rectangle2() {} + + /** + * Constructor creates a new <code>Rectangle2</code> with using the given x,y,width and height values. + * + */ + public Rectangle2(final int x, final int y, final int width, final int height) { + setX(x); + setY(y); + setWidth(width); + setHeight(height); + } + + /** + * Constructor creates a new <code>Rectangle2</code> using the values of the provided source rectangle. + * + * @param source + * the rectangle to copy from + */ + public Rectangle2(final ReadOnlyRectangle2 source) { + set(source); + } + + @Override + public int getX() { + return _x; + } + + /** + * @param x + * the new x coordinate of the origin of this rectangle + */ + public void setX(final int x) { + _x = x; + } + + @Override + public int getY() { + return _y; + } + + /** + * @param y + * the new y coordinate of the origin of this rectangle + */ + public void setY(final int y) { + _y = y; + } + + @Override + public int getWidth() { + return _width; + } + + /** + * @param width + * the new width of this rectangle + */ + public void setWidth(final int width) { + _width = width; + } + + @Override + public int getHeight() { + return _height; + } + + /** + * @param height + * the new height of this rectangle + */ + public void setHeight(final int height) { + _height = height; + } + + public Rectangle2 set(final int x, final int y, final int width, final int height) { + _x = x; + _y = y; + _width = width; + _height = height; + return this; + } + + public Rectangle2 set(final ReadOnlyRectangle2 rect) { + return set(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + } + + public Rectangle2 intersect(final ReadOnlyRectangle2 other, final Rectangle2 store) { + Rectangle2 rVal = store; + if (rVal == null) { + rVal = new Rectangle2(); + } + final int x1 = Math.max(getX(), other.getX()); + final int y1 = Math.max(getY(), other.getY()); + final int x2 = Math.min(getX() + getWidth(), other.getX() + other.getWidth()); + final int y2 = Math.min(getY() + getHeight(), other.getY() + other.getHeight()); + rVal.set(x1, y1, x2 - x1, y2 - y1); + return rVal; + } + + public static Rectangle2 intersect(final ReadOnlyRectangle2 src1, final ReadOnlyRectangle2 src2, + final Rectangle2 store) { + Rectangle2 rVal = store; + if (rVal == null) { + rVal = new Rectangle2(); + } + rVal.set(src1); + return rVal.intersect(src2, rVal); + } + + /** + * @return the string representation of this rectangle. + */ + @Override + public String toString() { + return "com.ardor3d.math.Rectangle2 [origin: " + _x + ", " + _y + " width: " + _width + " height: " + _height + + "]"; + } + + /** + * @return returns a unique code for this rectangle object based on its values. If two rectangles are numerically + * equal, they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _x; + result += 31 * result + _y; + result += 31 * result + _width; + result += 31 * result + _height; + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this rectangle and the provided rectangle have the same origin and dimensions + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyRectangle2)) { + return false; + } + final ReadOnlyRectangle2 comp = (ReadOnlyRectangle2) o; + return comp.getX() == getX() && comp.getY() == getY() && comp.getWidth() == getWidth() + && comp.getHeight() == getHeight(); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Rectangle2 clone() { + return new Rectangle2(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_x, "x", 0); + capsule.write(_y, "y", 0); + capsule.write(_width, "width", 0); + capsule.write(_height, "height", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _x = capsule.readInt("x", 0); + _y = capsule.readInt("y", 0); + _width = capsule.readInt("width", 0); + _height = capsule.readInt("height", 0); + } + + @Override + public Class<? extends Rectangle2> getClassTag() { + return this.getClass(); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setX(in.readInt()); + setY(in.readInt()); + setWidth(in.readInt()); + setHeight(in.readInt()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeInt(_x); + out.writeInt(_y); + out.writeInt(_width); + out.writeInt(_height); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Rectangle2 that is intended for temporary use in calculations and so forth. Multiple calls + * to the method should return instances of this class that are not currently in use. + */ + public final static Rectangle2 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Rectangle2.RECTANGLE_POOL.fetch(); + } else { + return new Rectangle2(); + } + } + + /** + * Releases a Rectangle2 back to be used by a future call to fetchTempInstance. TAKE CARE: this object should no + * longer have other classes referencing it or "Bad Things" will happen. + * + * @param rectangle + * the Rectangle2 to release. + */ + public final static void releaseTempInstance(final Rectangle2 rectangle) { + if (MathConstants.useMathPools) { + Rectangle2.RECTANGLE_POOL.release(rectangle); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Rectangle3.java b/ardor3d-math/src/main/java/com/ardor3d/math/Rectangle3.java new file mode 100644 index 0000000..3454c4e --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Rectangle3.java @@ -0,0 +1,306 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import com.ardor3d.math.type.ReadOnlyRectangle3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Defines a finite plane within three dimensional space that is specified via three points (A, B, C). These three
+ * points define a triangle with the forth point defining the rectangle ((B + C) - A.
+ */
+
+public class Rectangle3 implements Cloneable, Savable, Externalizable, ReadOnlyRectangle3, Poolable {
+ private static final long serialVersionUID = 1L;
+
+ private static final ObjectPool<Rectangle3> RECTANGLE_POOL = ObjectPool.create(Rectangle3.class,
+ MathConstants.maxMathPoolSize);
+
+ private final Vector3 _a = new Vector3();
+ private final Vector3 _b = new Vector3();
+ private final Vector3 _c = new Vector3();
+
+ /**
+ * Constructor creates a new Rectangle3 with corners at origin.
+ */
+ public Rectangle3() {}
+
+ /**
+ * Constructor creates a new Rectangle3 using the values of the provided source rectangle.
+ *
+ * @param source
+ * the rectangle to copy from
+ */
+ public Rectangle3(final ReadOnlyRectangle3 source) {
+ this(source.getA(), source.getB(), source.getC());
+ }
+
+ /**
+ * Constructor creates a new Rectangle3 with defined A, B, and C points that define the area of the rectangle.
+ *
+ * @param a
+ * the first corner of the rectangle.
+ * @param b
+ * the second corner of the rectangle.
+ * @param c
+ * the third corner of the rectangle.
+ */
+ public Rectangle3(final ReadOnlyVector3 a, final ReadOnlyVector3 b, final ReadOnlyVector3 c) {
+ setA(a);
+ setB(b);
+ setC(c);
+ }
+
+ /**
+ * Copy the value of the given source Rectangle3 into this Rectangle3.
+ *
+ * @param source
+ * the source of the data to copy.
+ * @return this rectangle for chaining
+ * @throws NullPointerException
+ * if source is null.
+ */
+ public Rectangle3 set(final ReadOnlyRectangle3 source) {
+ _a.set(source.getA());
+ _b.set(source.getB());
+ _c.set(source.getC());
+ return this;
+ }
+
+ /**
+ * getA returns the first point of the rectangle.
+ *
+ * @return the first point of the rectangle.
+ */
+ @Override
+ public ReadOnlyVector3 getA() {
+ return _a;
+ }
+
+ /**
+ * setA sets the first point of the rectangle.
+ *
+ * @param a
+ * the first point of the rectangle.
+ */
+ public void setA(final ReadOnlyVector3 a) {
+ _a.set(a);
+ }
+
+ /**
+ * getB returns the second point of the rectangle.
+ *
+ * @return the second point of the rectangle.
+ */
+ @Override
+ public ReadOnlyVector3 getB() {
+ return _b;
+ }
+
+ /**
+ * setB sets the second point of the rectangle.
+ *
+ * @param b
+ * the second point of the rectangle.
+ */
+ public void setB(final ReadOnlyVector3 b) {
+ _b.set(b);
+ }
+
+ /**
+ * getC returns the third point of the rectangle.
+ *
+ * @return the third point of the rectangle.
+ */
+ @Override
+ public ReadOnlyVector3 getC() {
+ return _c;
+ }
+
+ /**
+ * setC sets the third point of the rectangle.
+ *
+ * @param c
+ * the third point of the rectangle.
+ */
+ public void setC(final ReadOnlyVector3 c) {
+ _c.set(c);
+ }
+
+ /**
+ * random returns a random point within the plane defined by: A, B, C, and (B + C) - A.
+ *
+ * @param result
+ * Vector to store result in
+ * @return a random point within the rectangle.
+ */
+ @Override
+ public Vector3 random(Vector3 result) {
+ if (result == null) {
+ result = new Vector3();
+ }
+
+ final double s = MathUtils.nextRandomFloat();
+ final double t = MathUtils.nextRandomFloat();
+
+ final double aMod = 1.0 - s - t;
+ final Vector3 temp1 = Vector3.fetchTempInstance();
+ final Vector3 temp2 = Vector3.fetchTempInstance();
+ final Vector3 temp3 = Vector3.fetchTempInstance();
+ result.set(_a.multiply(aMod, temp1).addLocal(_b.multiply(s, temp2).addLocal(_c.multiply(t, temp3))));
+ Vector3.releaseTempInstance(temp1);
+ Vector3.releaseTempInstance(temp2);
+ Vector3.releaseTempInstance(temp3);
+ return result;
+ }
+
+ /**
+ * @return the string representation of this rectangle.
+ */
+ @Override
+ public String toString() {
+ return "com.ardor3d.math.Rectangle3 [A: " + _a + " B: " + _b + " C: " + _c + "]";
+ }
+
+ /**
+ * @return returns a unique code for this rectangle object based on its values. If two rectangles are numerically
+ * equal, they will return the same hash code value.
+ */
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result += 31 * result + _a.hashCode();
+ result += 31 * result + _b.hashCode();
+ result += 31 * result + _c.hashCode();
+
+ return result;
+ }
+
+ /**
+ * @param o
+ * the object to compare for equality
+ * @return true if this rectangle and the provided rectangle have the same corner values.
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ReadOnlyRectangle3)) {
+ return false;
+ }
+ final ReadOnlyRectangle3 comp = (ReadOnlyRectangle3) o;
+ return _a.equals(comp.getA()) && _b.equals(comp.getB()) && _c.equals(comp.getC());
+ }
+
+ // /////////////////
+ // Method for Cloneable
+ // /////////////////
+
+ @Override
+ public Rectangle3 clone() {
+ return new Rectangle3(this);
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_a, "a", new Vector3(Vector3.ZERO));
+ capsule.write(_b, "b", new Vector3(Vector3.ZERO));
+ capsule.write(_c, "c", new Vector3(Vector3.ZERO));
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ _a.set((Vector3) capsule.readSavable("a", new Vector3(Vector3.ZERO)));
+ _b.set((Vector3) capsule.readSavable("b", new Vector3(Vector3.ZERO)));
+ _c.set((Vector3) capsule.readSavable("c", new Vector3(Vector3.ZERO)));
+ }
+
+ @Override
+ public Class<? extends Rectangle3> getClassTag() {
+ return this.getClass();
+ }
+
+ // /////////////////
+ // Methods for Externalizable
+ // /////////////////
+
+ /**
+ * Used with serialization. Not to be called manually.
+ *
+ * @param in
+ * ObjectInput
+ * @throws IOException
+ * @throws ClassNotFoundException
+ */
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+ setA((Vector3) in.readObject());
+ setB((Vector3) in.readObject());
+ setC((Vector3) in.readObject());
+ }
+
+ /**
+ * Used with serialization. Not to be called manually.
+ *
+ * @param out
+ * ObjectOutput
+ * @throws IOException
+ */
+ @Override
+ public void writeExternal(final ObjectOutput out) throws IOException {
+ out.writeObject(_a);
+ out.writeObject(_b);
+ out.writeObject(_c);
+ }
+
+ // /////////////////
+ // Methods for creating temp variables (pooling)
+ // /////////////////
+
+ /**
+ * @return An instance of Rectangle3 that is intended for temporary use in calculations and so forth. Multiple calls
+ * to the method should return instances of this class that are not currently in use.
+ */
+ public final static Rectangle3 fetchTempInstance() {
+ if (MathConstants.useMathPools) {
+ return Rectangle3.RECTANGLE_POOL.fetch();
+ } else {
+ return new Rectangle3();
+ }
+ }
+
+ /**
+ * Releases a Rectangle3 back to be used by a future call to fetchTempInstance. TAKE CARE: this object should no
+ * longer have other classes referencing it or "Bad Things" will happen.
+ *
+ * @param rectangle
+ * the Rectangle3 to release.
+ */
+ public final static void releaseTempInstance(final Rectangle3 rectangle) {
+ if (MathConstants.useMathPools) {
+ Rectangle3.RECTANGLE_POOL.release(rectangle);
+ }
+ }
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Ring.java b/ardor3d-math/src/main/java/com/ardor3d/math/Ring.java new file mode 100644 index 0000000..d734003 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Ring.java @@ -0,0 +1,380 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyRing; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * <code>Ring</code> defines a flat ring or disk within three dimensional space that is specified via the ring's center + * point, an up vector, an inner radius, and an outer radius. + */ + +public class Ring implements Cloneable, Savable, Externalizable, ReadOnlyRing, Poolable { + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Ring> RING_POOL = ObjectPool.create(Ring.class, MathConstants.maxMathPoolSize); + + private final Vector3 _center = new Vector3(); + private final Vector3 _up = new Vector3(Vector3.UNIT_Y); + private double _innerRadius, _outerRadius; + + /** + * Constructor creates a new <code>Ring</code> lying on the XZ plane, centered at the origin, with an inner radius + * of zero and an outer radius of one (a unit disk). + */ + public Ring() { + _innerRadius = 0.0; + _outerRadius = 1.0; + } + + /** + * Copy constructor. + * + * @param source + * the ring to copy from. + */ + public Ring(final ReadOnlyRing source) { + this(source.getCenter(), source.getUp(), source.getInnerRadius(), source.getOuterRadius()); + } + + /** + * Constructor creates a new <code>Ring</code> with defined center point, up vector, and inner and outer radii. + * + * @param center + * the center of the ring. + * @param up + * the unit up vector defining the ring's orientation. + * @param innerRadius + * the ring's inner radius. + * @param outerRadius + * the ring's outer radius. + */ + public Ring(final ReadOnlyVector3 center, final ReadOnlyVector3 up, final double innerRadius, + final double outerRadius) { + _center.set(center); + _up.set(up); + _innerRadius = innerRadius; + _outerRadius = outerRadius; + } + + /** + * Copy the value of the given source Ring into this Ring. + * + * @param source + * the source of the data to copy. + * @return this ring for chaining + * @throws NullPointerException + * if source is null. + */ + public Ring set(final ReadOnlyRing source) { + _center.set(source.getCenter()); + _up.set(source.getUp()); + _innerRadius = source.getInnerRadius(); + _outerRadius = source.getOuterRadius(); + return this; + } + + /** + * <code>getCenter</code> returns the center of the ring. + * + * @return the center of the ring. + */ + @Override + public ReadOnlyVector3 getCenter() { + return _center; + } + + /** + * <code>setCenter</code> sets the center of the ring. + * + * @param center + * the center of the ring. + */ + public void setCenter(final ReadOnlyVector3 center) { + _center.set(center); + } + + /** + * <code>getUp</code> returns the ring's up vector. + * + * @return the ring's up vector. + */ + @Override + public ReadOnlyVector3 getUp() { + return _up; + } + + /** + * <code>setUp</code> sets the ring's up vector. + * + * @param up + * the ring's up vector. + */ + public void setUp(final ReadOnlyVector3 up) { + _up.set(up); + } + + /** + * <code>getInnerRadius</code> returns the ring's inner radius. + * + * @return the ring's inner radius. + */ + @Override + public double getInnerRadius() { + return _innerRadius; + } + + /** + * <code>setInnerRadius</code> sets the ring's inner radius. + * + * @param innerRadius + * the ring's inner radius. + */ + public void setInnerRadius(final double innerRadius) { + _innerRadius = innerRadius; + } + + /** + * <code>getOuterRadius</code> returns the ring's outer radius. + * + * @return the ring's outer radius. + */ + @Override + public double getOuterRadius() { + return _outerRadius; + } + + /** + * <code>setOuterRadius</code> sets the ring's outer radius. + * + * @param outerRadius + * the ring's outer radius. + */ + public void setOuterRadius(final double outerRadius) { + _outerRadius = outerRadius; + } + + /** + * + * <code>random</code> returns a random point within the ring. + * + * @param store + * Vector to store result in + * @return a random point within the ring. + */ + @Override + public Vector3 random(final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final Vector3 b1 = Vector3.fetchTempInstance(); + final Vector3 b2 = Vector3.fetchTempInstance(); + + // compute a random radius according to the ring area distribution + final double inner2 = _innerRadius * _innerRadius; + final double outer2 = _outerRadius * _outerRadius; + final double r = Math.sqrt(inner2 + MathUtils.nextRandomFloat() * (outer2 - inner2)); + final double theta = MathUtils.nextRandomFloat() * MathUtils.TWO_PI; + + _up.cross(Vector3.UNIT_X, b1); + if (b1.lengthSquared() < MathUtils.EPSILON) { + _up.cross(Vector3.UNIT_Y, b1); + } + b1.normalizeLocal(); + _up.cross(b1, b2); + result.set(b1).multiplyLocal(r * MathUtils.cos(theta)).addLocal(_center); + b2.scaleAdd(r * MathUtils.sin(theta), result, result); + + Vector3.releaseTempInstance(b1); + Vector3.releaseTempInstance(b2); + + return result; + } + + /** + * Check a ring... if it is null or its radii, or the doubles of its center or up are NaN or infinite, return false. + * Else return true. + * + * @param ring + * the ring to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyRing ring) { + if (ring == null) { + return false; + } + if (Double.isNaN(ring.getInnerRadius()) || Double.isInfinite(ring.getInnerRadius())) { + return false; + } + if (Double.isNaN(ring.getOuterRadius()) || Double.isInfinite(ring.getOuterRadius())) { + return false; + } + + return Vector3.isValid(ring.getCenter()) && Vector3.isValid(ring.getUp()); + } + + /** + * @return the string representation of this ring. + */ + @Override + public String toString() { + return "com.ardor3d.math.Ring [Center: " + _center + " Up: " + _up + " - radii, outer: " + _outerRadius + + " inner: " + _innerRadius + "]"; + } + + /** + * @return returns a unique code for this ring object based on its values. If two rings are numerically equal, they + * will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _center.hashCode(); + result += 31 * result + _up.hashCode(); + + final long ir = Double.doubleToLongBits(getInnerRadius()); + result += 31 * result + (int) (ir ^ ir >>> 32); + + final long or = Double.doubleToLongBits(getOuterRadius()); + result += 31 * result + (int) (or ^ or >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this ring and the provided ring have the same constant and normal values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyRing)) { + return false; + } + final ReadOnlyRing comp = (ReadOnlyRing) o; + return getInnerRadius() == comp.getInnerRadius() && getOuterRadius() == comp.getOuterRadius() + && _up.equals(comp.getUp()) && _center.equals(comp.getCenter()); + + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Ring clone() { + return new Ring(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Ring> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + capsule.write(_up, "up", new Vector3(Vector3.UNIT_Z)); + capsule.write(_innerRadius, "innerRadius", 0.0); + capsule.write(_outerRadius, "outerRadius", 1.0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO))); + _up.set((Vector3) capsule.readSavable("up", new Vector3(Vector3.UNIT_Z))); + _innerRadius = capsule.readDouble("innerRadius", 0.0); + _outerRadius = capsule.readDouble("outerRadius", 1.0); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setCenter((Vector3) in.readObject()); + setUp((Vector3) in.readObject()); + setInnerRadius(in.readDouble()); + setOuterRadius(in.readDouble()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeObject(_center); + out.writeObject(_up); + out.writeDouble(_innerRadius); + out.writeDouble(_outerRadius); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Ring that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Ring fetchTempInstance() { + if (MathConstants.useMathPools) { + return Ring.RING_POOL.fetch(); + } else { + return new Ring(); + } + } + + /** + * Releases a Ring back to be used by a future call to fetchTempInstance. TAKE CARE: this Ring object should no + * longer have other classes referencing it or "Bad Things" will happen. + * + * @param ring + * the Ring to release. + */ + public final static void releaseTempInstance(final Ring ring) { + if (MathConstants.useMathPools) { + Ring.RING_POOL.release(ring); + } + } +}
\ No newline at end of file diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Transform.java b/ardor3d-math/src/main/java/com/ardor3d/math/Transform.java new file mode 100644 index 0000000..47d0a88 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Transform.java @@ -0,0 +1,1061 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Transform models a transformation in 3d space as: Y = M*X+T, with M being a Matrix3 and T is a Vector3. Generally M + * will be a rotation only matrix in which case it is represented by the matrix and scale fields as R*S, where S is a + * positive scale vector. For non-uniform scales and reflections, use setMatrix, which will consider M as being a + * general 3x3 matrix and disregard anything set in scale. + */ +public class Transform implements Cloneable, Savable, Externalizable, ReadOnlyTransform, Poolable { + /** Used with equals method to determine if two Transforms are close enough to be considered equal. */ + public static final double ALLOWED_DEVIANCE = 0.00000001; + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Transform> TRANS_POOL = ObjectPool.create(Transform.class, + MathConstants.maxMathPoolSize); + + /** + * Identity transform. + */ + public static final ReadOnlyTransform IDENTITY = new Transform(Matrix3.IDENTITY, Vector3.ONE, Vector3.ZERO, true, + true, true); + + protected final Matrix3 _matrix = new Matrix3(Matrix3.IDENTITY); + protected final Vector3 _translation = new Vector3(Vector3.ZERO); + protected final Vector3 _scale = new Vector3(Vector3.ONE); + + /** + * true if this transform is guaranteed to be the identity matrix. + */ + protected boolean _identity; + + /** + * true if the matrix portion of this transform is only rotation. + */ + protected boolean _rotationMatrix; + + /** + * true if scale is used and scale is guaranteed to be uniform. + */ + protected boolean _uniformScale; + + /** + * Constructs a new Transform object. + */ + public Transform() { + _identity = true; + _rotationMatrix = true; + _uniformScale = true; + + } + + /** + * Constructs a new Transform object from the information stored in the given source Transform. + * + * @param source + * @throws NullPointerException + * if source is null. + */ + public Transform(final ReadOnlyTransform source) { + _matrix.set(source.getMatrix()); + _scale.set(source.getScale()); + _translation.set(source.getTranslation()); + + _identity = source.isIdentity(); + _rotationMatrix = source.isRotationMatrix(); + _uniformScale = source.isUniformScale(); + + } + + /** + * Internal only constructor, generally used for making an immutable transform. + * + * @param matrix + * @param scale + * @param translation + * @param identity + * @param rotationMatrix + * @param uniformScale + * @throws NullPointerException + * if a param is null. + */ + protected Transform(final ReadOnlyMatrix3 matrix, final ReadOnlyVector3 scale, final ReadOnlyVector3 translation, + final boolean identity, final boolean rotationMatrix, final boolean uniformScale) { + _matrix.set(matrix); + _scale.set(scale); + _translation.set(translation); + + _identity = identity; + _rotationMatrix = rotationMatrix; + _uniformScale = uniformScale; + } + + @Override + public ReadOnlyMatrix3 getMatrix() { + return _matrix; + } + + @Override + public ReadOnlyVector3 getTranslation() { + return _translation; + } + + @Override + public ReadOnlyVector3 getScale() { + return _scale; + } + + /** + * @return true if this transform is guaranteed to be the identity matrix. + */ + @Override + public boolean isIdentity() { + return _identity; + } + + /** + * @return true if the matrix portion of this transform is only rotation. + */ + @Override + public boolean isRotationMatrix() { + return _rotationMatrix; + } + + /** + * @return true if scale is used and scale is guaranteed to be uniform. + */ + @Override + public boolean isUniformScale() { + return _uniformScale; + } + + /** + * Resets this transform to identity and resets all flags. + * + * @return this Transform for chaining. + */ + public Transform setIdentity() { + _matrix.set(Matrix3.IDENTITY); + _scale.set(Vector3.ONE); + _translation.set(Vector3.ZERO); + _identity = true; + _rotationMatrix = true; + _uniformScale = true; + return this; + } + + /** + * Sets the matrix portion of this transform to the given value. + * + * NB: Calling this with a matrix that is not purely rotational (orthonormal) will result in a Transform whose scale + * comes from its matrix. Further attempts to set scale directly will throw an error. + * + * @param rotation + * our new matrix. + * @return this transform for chaining. + * @throws NullPointerException + * if rotation is null. + * @see Matrix3#isOrthonormal() + */ + public Transform setRotation(final ReadOnlyMatrix3 rotation) { + _matrix.set(rotation); + updateFlags(false); + return this; + } + + /** + * Sets the matrix portion of this transform to the rotational value of the given Quaternion. Calling this allows + * scale to be set and used. + * + * @param rotation + * @return this transform for chaining. + * @throws NullPointerException + * if rotation is null. + */ + public Transform setRotation(final ReadOnlyQuaternion rotation) { + _matrix.set(rotation); + updateFlags(true); + return this; + } + + /** + * Sets the translation portion of this transform to the given value. + * + * @param translation + * @return this transform for chaining. + * @throws NullPointerException + * if translation is null. + */ + public Transform setTranslation(final ReadOnlyVector3 translation) { + _translation.set(translation); + _identity = false; + return this; + } + + /** + * Sets the translation portion of this transform to the given values. + * + * @param x + * @param y + * @param z + * @return this transform for chaining. + */ + public Transform setTranslation(final double x, final double y, final double z) { + _translation.set(x, y, z); + _identity = false; + return this; + } + + /** + * Sets the scale portion of this transform to the given value. + * + * @param scale + * @return this transform for chaining. + * @throws NullPointerException + * if scale is null. + * @throws TransformException + * if this transform has a generic 3x3 matrix set. + * @throws IllegalArgumentException + * if scale is (0,0,0) + */ + public Transform setScale(final ReadOnlyVector3 scale) { + if (!_rotationMatrix) { + throw new TransformException( + "Scale is already provided by 3x3 matrix. If this is a mistake, consider using setRotation instead of setMatrix."); + } + if (scale.getX() == 0.0 && scale.getY() == 0.0 && scale.getZ() == 0.0) { + throw new IllegalArgumentException("scale may not be ZERO."); + } + + _scale.set(scale); + _identity = _identity && scale.getX() == 1.0 && scale.getY() == 1.0 && scale.getZ() == 1.0; + _uniformScale = scale.getX() == scale.getY() && scale.getY() == scale.getZ(); + return this; + } + + /** + * Sets the scale portion of this transform to the given values. + * + * @param x + * @param y + * @param z + * @return this transform for chaining. + * @throws NullPointerException + * if scale is null. + * @throws TransformException + * if this transform has a generic 3x3 matrix set. + * @throws IllegalArgumentException + * if scale is (0,0,0) + */ + public Transform setScale(final double x, final double y, final double z) { + if (!_rotationMatrix) { + throw new TransformException( + "Scale is already provided by 3x3 matrix. If this is a mistake, consider using setRotation instead of setMatrix."); + } + if (x == 0.0 && y == 0.0 && z == 0.0) { + throw new IllegalArgumentException("scale may not be ZERO."); + } + + _scale.set(x, y, z); + _identity = false; + _uniformScale = x == y && y == z; + return this; + } + + /** + * Sets the scale portion of this transform to the given value as a vector (u, u, u) + * + * @param uniformScale + * @return this transform for chaining. + * @throws TransformException + * if this transform has a generic 3x3 matrix set. + * @throws IllegalArgumentException + * if uniformScale is 0 + */ + public Transform setScale(final double uniformScale) { + if (!_rotationMatrix) { + throw new TransformException( + "Scale is already provided by 3x3 matrix. If this is a mistake, consider using setRotation instead of setMatrix."); + } + if (uniformScale == 0.0) { + throw new IllegalArgumentException("scale may not be ZERO."); + } + + _scale.set(uniformScale, uniformScale, uniformScale); + _identity = false; + _uniformScale = true; + return this; + } + + /** + * Copies the given transform values into this transform object. + * + * @param source + * @return this transform for chaining. + * @throws NullPointerException + * if source is null. + */ + public Transform set(final ReadOnlyTransform source) { + if (source.isIdentity()) { + setIdentity(); + } else { + _matrix.set(source.getMatrix()); + _scale.set(source.getScale()); + _translation.set(source.getTranslation()); + + _identity = false; + _rotationMatrix = source.isRotationMatrix(); + _uniformScale = source.isUniformScale(); + } + return this; + } + + /** + * Locally adds to the translation of this transform. + * + * @param x + * @param y + * @param z + * @return this transform for chaining. + */ + public Transform translate(final double x, final double y, final double z) { + _translation.addLocal(x, y, z); + _identity = _identity && _translation.equals(Vector3.ZERO); + return this; + } + + /** + * Locally adds to the translation of this transform. + * + * @param vec + * @return this transform for chaining. + */ + public Transform translate(final ReadOnlyVector3 vec) { + _translation.addLocal(vec); + _identity = _identity && _translation.equals(Vector3.ZERO); + return this; + } + + /** + * Locally applies this transform to the given point: P' = M*P+T + * + * @param point + * @return the transformed point. + * @throws NullPointerException + * if point is null. + */ + @Override + public Vector3 applyForward(final Vector3 point) { + if (point == null) { + throw new NullPointerException(); + } + + if (_identity) { + // No need to make changes + // Y = X + return point; + } + + if (_rotationMatrix) { + // Scale is separate from matrix + // Y = R*S*X + T + point.set(point.getX() * _scale.getX(), point.getY() * _scale.getY(), point.getZ() * _scale.getZ()); + _matrix.applyPost(point, point); + point.addLocal(_translation); + return point; + } + + // scale is part of matrix. + // Y = M*X + T + _matrix.applyPost(point, point); + point.addLocal(_translation); + return point; + + } + + /** + * Applies this transform to the given point and returns the result in the given store vector: P' = M*P+T + * + * @param point + * @param store + * the vector to store our result in. if null, a new vector will be created. + * @return the transformed point. + * @throws NullPointerException + * if point is null. + */ + @Override + public Vector3 applyForward(final ReadOnlyVector3 point, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + result.set(point); + return applyForward(result); + } + + /** + * Locally applies the inverse of this transform to the given point: P' = M^{-1}*(P-T) + * + * @param point + * @return the transformed point. + * @throws NullPointerException + * if point is null. + */ + @Override + public Vector3 applyInverse(final Vector3 point) { + if (point == null) { + throw new NullPointerException(); + } + + if (_identity) { + // No need to make changes + // P' = P + return point; + } + + // Back track translation + point.subtractLocal(_translation); + + if (_rotationMatrix) { + // Scale is separate from matrix so... + // P' = S^{-1}*R^t*(P - T) + _matrix.applyPre(point, point); + if (_uniformScale) { + point.divideLocal(_scale.getX()); + } else { + point.setX(point.getX() / _scale.getX()); + point.setY(point.getY() / _scale.getY()); + point.setZ(point.getZ() / _scale.getZ()); + } + } else { + // P' = M^{-1}*(P - T) + final Matrix3 invertedMatrix = _matrix.invert(Matrix3.fetchTempInstance()); + invertedMatrix.applyPost(point, point); + Matrix3.releaseTempInstance(invertedMatrix); + } + + return point; + } + + /** + * Applies the inverse of this transform to the given point and returns the result in the given store vector: P' = + * M^{-1}*(P-T) + * + * @param point + * @param store + * the vector to store our result in. if null, a new vector will be created. + * @return the transformed point. + * @throws NullPointerException + * if point is null. + */ + @Override + public Vector3 applyInverse(final ReadOnlyVector3 point, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + result.set(point); + return applyInverse(result); + } + + /** + * Locally applies this transform to the given vector: V' = M*V + * + * @param vector + * @return the transformed vector. + * @throws NullPointerException + * if vector is null. + */ + @Override + public Vector3 applyForwardVector(final Vector3 vector) { + if (vector == null) { + throw new NullPointerException(); + } + + if (_identity) { + // No need to make changes + // V' = V + return vector; + } + + if (_rotationMatrix) { + // Scale is separate from matrix + // V' = R*S*V + vector.set(vector.getX() * _scale.getX(), vector.getY() * _scale.getY(), vector.getZ() * _scale.getZ()); + _matrix.applyPost(vector, vector); + return vector; + } + + // scale is part of matrix. + // V' = M*V + _matrix.applyPost(vector, vector); + return vector; + + } + + /** + * Applies this transform to the given vector and returns the result in the given store vector: V' = M*V + * + * @param vector + * @param store + * the vector to store our result in. if null, a new vector will be created. + * @return the transformed vector. + * @throws NullPointerException + * if vector is null. + */ + @Override + public Vector3 applyForwardVector(final ReadOnlyVector3 vector, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + result.set(vector); + return applyForwardVector(result); + } + + /** + * Locally applies the inverse of this transform to the given vector: V' = M^{-1}*V + * + * @param vector + * @return the transformed vector. + * @throws NullPointerException + * if vector is null. + */ + @Override + public Vector3 applyInverseVector(final Vector3 vector) { + if (vector == null) { + throw new NullPointerException(); + } + + if (_identity) { + // No need to make changes + // V' = V + return vector; + } + + if (_rotationMatrix) { + // Scale is separate from matrix so... + // V' = S^{-1}*R^t*V + _matrix.applyPre(vector, vector); + if (_uniformScale) { + vector.divideLocal(_scale.getX()); + } else { + vector.setX(vector.getX() / _scale.getX()); + vector.setY(vector.getY() / _scale.getY()); + vector.setZ(vector.getZ() / _scale.getZ()); + } + } else { + // V' = M^{-1}*V + final Matrix3 invertedMatrix = _matrix.invert(Matrix3.fetchTempInstance()); + invertedMatrix.applyPost(vector, vector); + Matrix3.releaseTempInstance(invertedMatrix); + } + + return vector; + } + + /** + * Applies the inverse of this transform to the given vector and returns the result in the given store vector: V' = + * M^{-1}*V + * + * @param vector + * @param store + * the vector to store our result in. if null, a new vector will be created. + * @return the transformed vector. + * @throws NullPointerException + * if vector is null. + */ + @Override + public Vector3 applyInverseVector(final ReadOnlyVector3 vector, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + result.set(vector); + return applyInverseVector(result); + } + + /** + * Calculates the product of this transform with the given "transformBy" transform (P = this * T) and stores this in + * store. + * + * @param transformBy + * @param store + * the transform to store the result in for return. If null, a new transform object is created and + * returned. It is NOT safe for store to be the same object as transformBy or "this". + * @return the product + * @throws NullPointerException + * if transformBy is null. + */ + @Override + public Transform multiply(final ReadOnlyTransform transformBy, final Transform store) { + Transform result = store; + if (result == null) { + result = new Transform(); + } + + if (_identity) { + return result.set(transformBy); + } + + if (transformBy.isIdentity()) { + return result.set(this); + } + + if (_rotationMatrix && transformBy.isRotationMatrix() && _uniformScale) { + result._rotationMatrix = true; + final Matrix3 newRotation = result._matrix; + newRotation.set(_matrix).multiplyLocal(transformBy.getMatrix()); + + final Vector3 newTranslation = result._translation.set(transformBy.getTranslation()); + _matrix.applyPost(newTranslation, newTranslation); + // uniform scale, so just use X. + newTranslation.multiplyLocal(_scale.getX()); + newTranslation.addLocal(_translation); + + if (transformBy.isUniformScale()) { + result.setScale(_scale.getX() * transformBy.getScale().getX()); + } else { + final Vector3 scale = result._scale.set(transformBy.getScale()); + scale.multiplyLocal(_scale.getX()); + } + + // update our flags in one place. + result.updateFlags(true); + + return result; + } + + // In all remaining cases, the matrix cannot be written as R*S*X+T. + final ReadOnlyMatrix3 matrixA = isRotationMatrix() ? _matrix.multiplyDiagonalPost(_scale, + Matrix3.fetchTempInstance()) : _matrix; + + final ReadOnlyMatrix3 matrixB = transformBy.isRotationMatrix() ? transformBy.getMatrix().multiplyDiagonalPost( + transformBy.getScale(), Matrix3.fetchTempInstance()) : transformBy.getMatrix(); + + final Matrix3 newMatrix = result._matrix; + newMatrix.set(matrixA).multiplyLocal(matrixB); + + final Vector3 newTranslate = result._translation; + matrixA.applyPost(transformBy.getTranslation(), newTranslate).addLocal(getTranslation()); + + if (isRotationMatrix()) { + Matrix3.releaseTempInstance((Matrix3) matrixA); + } + if (transformBy.isRotationMatrix()) { + Matrix3.releaseTempInstance((Matrix3) matrixB); + } + + // prevent scale bleeding since we don't set it. + result._scale.set(1.0, 1.0, 1.0); + + // update our flags in one place. + result.updateFlags(false); + + return result; + } + + /** + * Updates _rotationMatrix, _uniformScale and _identity based on the current contents of this Transform. + * + * @param rotationMatrixGuaranteed + * true if we know for sure that the _matrix component is rotational. + */ + protected void updateFlags(final boolean rotationMatrixGuaranteed) { + _identity = _translation.equals(Vector3.ZERO) && _matrix.isIdentity() && _scale.equals(Vector3.ONE); + if (_identity) { + _rotationMatrix = true; + _uniformScale = true; + } else { + _rotationMatrix = rotationMatrixGuaranteed ? true : _matrix.isOrthonormal(); + _uniformScale = _rotationMatrix && _scale.getX() == _scale.getY() && _scale.getY() == _scale.getZ(); + } + } + + /** + * Calculates the inverse of this transform. + * + * @param store + * the transform to store the result in for return. If null, a new transform object is created and + * returned. It IS safe for store to be the same object as "this". + * @return the inverted transform + */ + @Override + public Transform invert(final Transform store) { + Transform result = store; + if (result == null) { + result = new Transform(); + } + + if (_identity) { + result.setIdentity(); + return result; + } + + final Matrix3 newMatrix = result._matrix.set(_matrix); + if (_rotationMatrix) { + if (_uniformScale) { + final double sx = _scale.getX(); + newMatrix.transposeLocal(); + if (sx != 1.0) { + newMatrix.multiplyLocal(1.0 / sx); + } + } else { + newMatrix.multiplyDiagonalPost(_scale, newMatrix).invertLocal(); + } + } else { + newMatrix.invertLocal(); + } + + result._matrix.applyPost(_translation, result._translation).negateLocal(); + result.updateFlags(_rotationMatrix); + + return result; + } + + /** + * @param store + * the matrix to store the result in for return. If null, a new matrix object is created and returned. + * @return this transform represented as a 4x4 matrix: + * + * <pre> + * R R R Tx + * R R R Ty + * R R R Tz + * 0 0 0 1 + * </pre> + */ + @Override + public Matrix4 getHomogeneousMatrix(final Matrix4 store) { + Matrix4 result = store; + if (result == null) { + result = new Matrix4(); + } + + if (_rotationMatrix) { + result._m00 = _scale.getX() * _matrix._m00; + result._m01 = _scale.getX() * _matrix._m01; + result._m02 = _scale.getX() * _matrix._m02; + result._m10 = _scale.getY() * _matrix._m10; + result._m11 = _scale.getY() * _matrix._m11; + result._m12 = _scale.getY() * _matrix._m12; + result._m20 = _scale.getZ() * _matrix._m20; + result._m21 = _scale.getZ() * _matrix._m21; + result._m22 = _scale.getZ() * _matrix._m22; + } else { + result._m00 = _matrix._m00; + result._m01 = _matrix._m01; + result._m02 = _matrix._m02; + result._m10 = _matrix._m10; + result._m11 = _matrix._m11; + result._m12 = _matrix._m12; + result._m20 = _matrix._m20; + result._m21 = _matrix._m21; + result._m22 = _matrix._m22; + } + + result._m30 = 0.0; + result._m31 = 0.0; + result._m32 = 0.0; + + result._m03 = _translation.getX(); + result._m13 = _translation.getY(); + result._m23 = _translation.getZ(); + result._m33 = 1.0; + + return result; + } + + @Override + public void getGLApplyMatrix(final DoubleBuffer store) { + if (_rotationMatrix) { + store.put(0, _scale.getX() * _matrix._m00); + store.put(1, _scale.getX() * _matrix._m10); + store.put(2, _scale.getX() * _matrix._m20); + store.put(4, _scale.getY() * _matrix._m01); + store.put(5, _scale.getY() * _matrix._m11); + store.put(6, _scale.getY() * _matrix._m21); + store.put(8, _scale.getZ() * _matrix._m02); + store.put(9, _scale.getZ() * _matrix._m12); + store.put(10, _scale.getZ() * _matrix._m22); + } else { + store.put(0, _matrix._m00); + store.put(1, _matrix._m10); + store.put(2, _matrix._m20); + store.put(4, _matrix._m01); + store.put(5, _matrix._m11); + store.put(6, _matrix._m21); + store.put(8, _matrix._m02); + store.put(9, _matrix._m12); + store.put(10, _matrix._m22); + } + + store.put(12, _translation.getX()); + store.put(13, _translation.getY()); + store.put(14, _translation.getZ()); + store.put(15, 1.0); + } + + @Override + public void getGLApplyMatrix(final FloatBuffer store) { + if (_rotationMatrix) { + store.put(0, (float) (_scale.getX() * _matrix._m00)); + store.put(1, (float) (_scale.getX() * _matrix._m10)); + store.put(2, (float) (_scale.getX() * _matrix._m20)); + store.put(4, (float) (_scale.getY() * _matrix._m01)); + store.put(5, (float) (_scale.getY() * _matrix._m11)); + store.put(6, (float) (_scale.getY() * _matrix._m21)); + store.put(8, (float) (_scale.getZ() * _matrix._m02)); + store.put(9, (float) (_scale.getZ() * _matrix._m12)); + store.put(10, (float) (_scale.getZ() * _matrix._m22)); + } else { + store.put(0, (float) _matrix._m00); + store.put(1, (float) _matrix._m10); + store.put(2, (float) _matrix._m20); + store.put(4, (float) _matrix._m01); + store.put(5, (float) _matrix._m11); + store.put(6, (float) _matrix._m21); + store.put(8, (float) _matrix._m02); + store.put(9, (float) _matrix._m12); + store.put(10, (float) _matrix._m22); + } + + store.put(12, _translation.getXf()); + store.put(13, _translation.getYf()); + store.put(14, _translation.getZf()); + store.put(15, 1.0f); + } + + /** + * Reads in a 4x4 matrix as a 3x3 matrix and translation. + * + * @param matrix + * @return this matrix for chaining. + * @throws NullPointerException + * if matrix is null. + */ + public Transform fromHomogeneousMatrix(final ReadOnlyMatrix4 matrix) { + _matrix.set(matrix.getM00(), matrix.getM01(), matrix.getM02(), matrix.getM10(), matrix.getM11(), + matrix.getM12(), matrix.getM20(), matrix.getM21(), matrix.getM22()); + _translation.set(matrix.getM03(), matrix.getM13(), matrix.getM23()); + + updateFlags(false); + return this; + } + + /** + * Check a transform... if it is null or one of its members are invalid, return false. Else return true. + * + * @param transform + * the transform to check + * + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyTransform transform) { + if (transform == null) { + return false; + } + if (!Vector3.isValid(transform.getScale()) || !Vector3.isValid(transform.getTranslation()) + || !Matrix3.isValid(transform.getMatrix())) { + return false; + } + + return true; + } + + /** + * @return the string representation of this triangle. + */ + @Override + public String toString() { + return "com.ardor3d.math.Transform [\n M: " + _matrix + "\n S: " + _scale + "\n T: " + _translation + "\n]"; + } + + /** + * @return returns a unique code for this transform object based on its values. + */ + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _matrix.hashCode(); + result += 31 * result + _scale.hashCode(); + result += 31 * result + _translation.hashCode(); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this transform and the provided transform have the same values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyTransform)) { + return false; + } + final ReadOnlyTransform comp = (ReadOnlyTransform) o; + return _matrix.equals(comp.getMatrix()) + && Math.abs(_translation.getX() - comp.getTranslation().getX()) < Transform.ALLOWED_DEVIANCE + && Math.abs(_translation.getY() - comp.getTranslation().getY()) < Transform.ALLOWED_DEVIANCE + && Math.abs(_translation.getZ() - comp.getTranslation().getZ()) < Transform.ALLOWED_DEVIANCE + && Math.abs(_scale.getX() - comp.getScale().getX()) < Transform.ALLOWED_DEVIANCE + && Math.abs(_scale.getY() - comp.getScale().getY()) < Transform.ALLOWED_DEVIANCE + && Math.abs(_scale.getZ() - comp.getScale().getZ()) < Transform.ALLOWED_DEVIANCE; + } + + /** + * @param o + * the object to compare for equality + * @return true if this transform and the provided transform have the exact same double values. + */ + @Override + public boolean strictEquals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyTransform)) { + return false; + } + final ReadOnlyTransform comp = (ReadOnlyTransform) o; + return _matrix.strictEquals(comp.getMatrix()) && _scale.equals(comp.getScale()) + && _translation.equals(comp.getTranslation()); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Transform clone() { + return new Transform(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Transform> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_matrix, "rotation", new Matrix3(Matrix3.IDENTITY)); + capsule.write(_scale, "scale", new Vector3(Vector3.ONE)); + capsule.write(_translation, "translation", new Vector3(Vector3.ZERO)); + capsule.write(_identity, "identity", true); + capsule.write(_rotationMatrix, "rotationMatrix", true); + capsule.write(_uniformScale, "uniformScale", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _matrix.set((Matrix3) capsule.readSavable("rotation", new Matrix3(Matrix3.IDENTITY))); + _scale.set((Vector3) capsule.readSavable("scale", new Vector3(Vector3.ONE))); + _translation.set((Vector3) capsule.readSavable("translation", new Vector3(Vector3.ZERO))); + _identity = capsule.readBoolean("identity", true); + _rotationMatrix = capsule.readBoolean("rotationMatrix", true); + _uniformScale = capsule.readBoolean("uniformScale", true); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + _matrix.set((Matrix3) in.readObject()); + _scale.set((Vector3) in.readObject()); + _translation.set((Vector3) in.readObject()); + _identity = in.readBoolean(); + _rotationMatrix = in.readBoolean(); + _uniformScale = in.readBoolean(); + } + + /* + * Used with serialization. Not to be called manually. + * + * @param out ObjectOutput + * + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeObject(_matrix); + out.writeObject(_scale); + out.writeObject(_translation); + out.writeBoolean(_identity); + out.writeBoolean(_rotationMatrix); + out.writeBoolean(_uniformScale); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Transform that is intended for temporary use in calculations and so forth. Multiple calls + * to the method should return instances of this class that are not currently in use. + */ + public final static Transform fetchTempInstance() { + if (MathConstants.useMathPools) { + return Transform.TRANS_POOL.fetch(); + } else { + return new Transform(); + } + } + + /** + * Releases a Transform back to be used by a future call to fetchTempInstance. TAKE CARE: this Transform object + * should no longer have other classes referencing it or "Bad Things" will happen. + * + * @param trans + * the Transform to release. + */ + public final static void releaseTempInstance(final Transform trans) { + if (MathConstants.useMathPools) { + Transform.TRANS_POOL.release(trans); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/TransformException.java b/ardor3d-math/src/main/java/com/ardor3d/math/TransformException.java new file mode 100644 index 0000000..bfb43bc --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/TransformException.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +public class TransformException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public TransformException() {} + + public TransformException(final String message) { + super(message); + } + +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Triangle.java b/ardor3d-math/src/main/java/com/ardor3d/math/Triangle.java new file mode 100644 index 0000000..d1db728 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Triangle.java @@ -0,0 +1,417 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyTriangle; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Triangle is a math class defining a three sided polygon by three points in space. + */ +public class Triangle implements Cloneable, Savable, Externalizable, ReadOnlyTriangle, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Triangle> TRI_POOL = ObjectPool.create(Triangle.class, + MathConstants.maxMathPoolSize); + + protected final Vector3 _pointA = new Vector3(); + protected final Vector3 _pointB = new Vector3(); + protected final Vector3 _pointC = new Vector3(); + + protected transient Vector3 _center; + protected transient Vector3 _normal; + + protected int _index = 0; + + private boolean _dirtyNormal = true; + + private boolean _dirtyCenter = true; + + /** + * Construct a new, mutable triangle with all points at 0,0,0 and an index of 0. + */ + public Triangle() {} + + /** + * Copy constructor. + * + * @param source + * the triangle to copy from. + */ + public Triangle(final ReadOnlyTriangle source) { + this(source.getA(), source.getB(), source.getC(), source.getIndex()); + } + + /** + * Construct a new, mutable triangle using the given points and an index of 0. + * + * @param pointA + * @param pointB + * @param pointC + */ + public Triangle(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, final ReadOnlyVector3 pointC) { + this(pointA, pointB, pointC, 0); + } + + /** + * Constructs a new triangle using the given points and index. + * + * @param pointA + * @param pointB + * @param pointC + * @param index + */ + public Triangle(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB, final ReadOnlyVector3 pointC, + final int index) { + _pointA.set(pointA); + _pointB.set(pointB); + _pointC.set(pointC); + _index = index; + } + + /** + * Copies the values of the given source Triangle into this Triangle. + * + * @param source + * @return this Triangle for chaining + * @throws NullPointerException + * if source is null. + */ + public Triangle set(final ReadOnlyTriangle source) { + _pointA.set(source.getA()); + _pointB.set(source.getB()); + _pointC.set(source.getC()); + _index = source.getIndex(); + return this; + } + + @Override + public int getIndex() { + return _index; + } + + @Override + public ReadOnlyVector3 get(final int index) { + switch (index) { + case 0: + return getA(); + case 1: + return getB(); + case 2: + return getC(); + } + throw new IllegalArgumentException("invalid index: " + index); + } + + @Override + public ReadOnlyVector3 getA() { + return _pointA; + } + + @Override + public ReadOnlyVector3 getB() { + return _pointB; + } + + @Override + public ReadOnlyVector3 getC() { + return _pointC; + } + + /** + * Obtains the unit length normal vector of this triangle... Will create and recalculate this normal vector if this + * is the first request, or if one of the points on the triangle has changed since the last request. + * + * @return the normal vector + * @throws NullPointerException + * if store is null. + */ + @Override + public ReadOnlyVector3 getNormal() { + if (_dirtyNormal) { + calculateNormal(); + } + return _normal; + } + + /** + * Obtains the center point of this triangle... Will create and recalculate this point if this is the first request, + * or if one of the points on the triangle has changed since the last request. + */ + @Override + public ReadOnlyVector3 getCenter() { + if (_dirtyCenter) { + calculateCenter(); + } + return _center; + } + + /** + * Sets the index value of this triangle to the given int value. + * + * @param index + */ + public void setIndex(final int index) { + _index = index; + } + + /** + * Sets the first point of this triangle to the values of the given vector. + * + * @param pointA + */ + public void setA(final ReadOnlyVector3 pointA) { + _pointA.set(pointA); + _dirtyCenter = _dirtyNormal = true; + } + + /** + * Sets the second point of this triangle to the values of the given vector. + * + * @param pointB + */ + public void setB(final ReadOnlyVector3 pointB) { + _pointB.set(pointB); + _dirtyCenter = _dirtyNormal = true; + } + + /** + * Sets the third point of this triangle to the values of the given vector. + * + * @param pointC + */ + public void setC(final ReadOnlyVector3 pointC) { + _pointC.set(pointC); + _dirtyCenter = _dirtyNormal = true; + } + + /** + * Sets a point to a new value. + * + * @param index + * the index of the point to set (0-2, corresponding to A-C) + * @param point + * the new value + * @throws IllegalArgumentException + * if index is not in [0, 2] + */ + public void set(final int index, final ReadOnlyVector3 point) { + switch (index) { + case 0: + setA(point); + return; + case 1: + setB(point); + return; + case 2: + setC(point); + return; + } + throw new IllegalArgumentException("index must be 0, 1 or 2 (corresponding to A, B or C.)"); + } + + /** + * Recalculates the center point of this triangle by averaging the triangle's three points. + */ + protected void calculateCenter() { + if (_center == null) { + _center = _pointA.clone(); + } else { + _center.set(_pointA); + } + _center.addLocal(_pointB).addLocal(_pointC).multiplyLocal(MathUtils.ONE_THIRD); + _dirtyCenter = false; + } + + /** + * Recalculates the surface normal of the triangle by crossing the vectors formed by BA and CA. + */ + protected void calculateNormal() { + if (_normal == null) { + _normal = _pointB.clone(); + } else { + _normal.set(_pointB); + } + _normal.subtractLocal(_pointA).crossLocal(_pointC.getX() - _pointA.getX(), _pointC.getY() - _pointA.getY(), + _pointC.getZ() - _pointA.getZ()); + _normal.normalizeLocal(); + _dirtyNormal = false; + } + + /** + * Check a triangle... if it is null or its points are invalid, return false. Else return true. + * + * @param triangle + * the triangle to check + * @return true or false as stated above. + */ + public static boolean isValid(final Triangle triangle) { + if (triangle == null) { + return false; + } + if (!Vector3.isValid(triangle._pointA) || !Vector3.isValid(triangle._pointB) + || !Vector3.isValid(triangle._pointC)) { + return false; + } + + return true; + } + + /** + * @return the string representation of this triangle. + */ + @Override + public String toString() { + return "com.ardor3d.math.Triangle [A: " + _pointA + " - B: " + _pointB + " - C: " + _pointC + " - Index: " + + _index + "]"; + } + + /** + * @return returns a unique code for this triangle object based on its values. If two triangles have the same points + * and index, they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _pointA.hashCode(); + result += 31 * result + _pointB.hashCode(); + result += 31 * result + _pointC.hashCode(); + result += 31 * result + _index; + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this triangle and the provided triangle have the same index and point values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyTriangle)) { + return false; + } + final ReadOnlyTriangle comp = (ReadOnlyTriangle) o; + return _index == comp.getIndex() && _pointA.equals(comp.getA()) && _pointB.equals(comp.getB()) + && _pointC.equals(comp.getC()); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Triangle clone() { + return new Triangle(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Triangle> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_pointA, "a", new Vector3(Vector3.ZERO)); + capsule.write(_pointB, "b", new Vector3(Vector3.ZERO)); + capsule.write(_pointC, "c", new Vector3(Vector3.ZERO)); + capsule.write(_index, "index", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + _pointA.set((Vector3) capsule.readSavable("a", new Vector3(Vector3.ZERO))); + _pointB.set((Vector3) capsule.readSavable("b", new Vector3(Vector3.ZERO))); + _pointC.set((Vector3) capsule.readSavable("c", new Vector3(Vector3.ZERO))); + _index = capsule.readInt("index", 0); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setA((Vector3) in.readObject()); + setB((Vector3) in.readObject()); + setC((Vector3) in.readObject()); + setIndex(in.readInt()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeObject(_pointA); + out.writeObject(_pointB); + out.writeObject(_pointC); + out.writeInt(getIndex()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Triangle that is intended for temporary use in calculations and so forth. Multiple calls + * to the method should return instances of this class that are not currently in use. + */ + public final static Triangle fetchTempInstance() { + if (MathConstants.useMathPools) { + return Triangle.TRI_POOL.fetch(); + } else { + return new Triangle(); + } + } + + /** + * Releases a Triangle back to be used by a future call to fetchTempInstance. TAKE CARE: this Triangle object should + * no longer have other classes referencing it or "Bad Things" will happen. + * + * @param tri + * the Triangle to release. + */ + public final static void releaseTempInstance(final Triangle tri) { + if (MathConstants.useMathPools) { + Triangle.TRI_POOL.release(tri); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/ValidatingTransform.java b/ardor3d-math/src/main/java/com/ardor3d/math/ValidatingTransform.java new file mode 100644 index 0000000..09b43d4 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/ValidatingTransform.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; + +/** + * A Transform that checks if any of it's member values are null, NaN or Infinity, and throws an + * InvalidTransformException if so. + * + */ +public class ValidatingTransform extends Transform { + public ValidatingTransform() {} + + /** + * Copy constructor + * + * @param source + */ + public ValidatingTransform(final ReadOnlyTransform source) { + super(source); + validate(); + } + + private void validate() { + if (!Transform.isValid(this)) { + throw new InvalidTransformException("Transform is invalid: " + this); + } + } + + @Override + public Transform fromHomogeneousMatrix(final ReadOnlyMatrix4 matrix) { + super.fromHomogeneousMatrix(matrix); + validate(); + return this; + } + + @Override + public ValidatingTransform setRotation(final ReadOnlyMatrix3 rotation) { + super.setRotation(rotation); + validate(); + return this; + } + + @Override + public ValidatingTransform setRotation(final ReadOnlyQuaternion rotation) { + super.setRotation(rotation); + validate(); + return this; + } + + @Override + public ValidatingTransform setScale(final double x, final double y, final double z) { + super.setScale(x, y, z); + validate(); + return this; + } + + @Override + public ValidatingTransform setScale(final double uniformScale) { + super.setScale(uniformScale); + validate(); + return this; + } + + @Override + public ValidatingTransform setScale(final ReadOnlyVector3 scale) { + super.setScale(scale); + validate(); + return this; + } + + @Override + public ValidatingTransform setTranslation(final double x, final double y, final double z) { + super.setTranslation(x, y, z); + validate(); + return this; + } + + @Override + public ValidatingTransform setTranslation(final ReadOnlyVector3 translation) { + super.setTranslation(translation); + validate(); + return this; + } + + @Override + public Transform translate(final double x, final double y, final double z) { + super.translate(x, y, z); + validate(); + return this; + } + + @Override + public Transform translate(final ReadOnlyVector3 vec) { + super.translate(vec); + validate(); + return this; + } + + @Override + public Transform multiply(final ReadOnlyTransform transformBy, final Transform store) { + final Transform transform = super.multiply(transformBy, store); + if (!Transform.isValid(transform)) { + throw new InvalidTransformException("Transform is invalid"); + } + return transform; + } + + @Override + public Transform invert(final Transform store) { + final Transform transform = super.invert(store); + if (!Transform.isValid(transform)) { + throw new InvalidTransformException("Transform is invalid"); + } + return transform; + } + + @Override + public Transform set(final ReadOnlyTransform source) { + super.set(source); + validate(); + return this; + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public ValidatingTransform clone() { + return new ValidatingTransform(this); + } + +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Vector2.java b/ardor3d-math/src/main/java/com/ardor3d/math/Vector2.java new file mode 100644 index 0000000..c6f1d46 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Vector2.java @@ -0,0 +1,1024 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Vector2 represents a point or vector in a two dimensional system. This implementation stores its data in + * double-precision. + */ +public class Vector2 implements Cloneable, Savable, Externalizable, ReadOnlyVector2, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Vector2> VEC_POOL = ObjectPool.create(Vector2.class, MathConstants.maxMathPoolSize); + + /** + * 0, 0 + */ + public final static ReadOnlyVector2 ZERO = new Vector2(0, 0); + + /** + * 1, 1 + */ + public final static ReadOnlyVector2 ONE = new Vector2(1, 1); + + /** + * -1, -1 + */ + public final static ReadOnlyVector2 NEG_ONE = new Vector2(-1, -1); + + /** + * 1, 0 + */ + public final static ReadOnlyVector2 UNIT_X = new Vector2(1, 0); + + /** + * -1, 0 + */ + public final static ReadOnlyVector2 NEG_UNIT_X = new Vector2(-1, 0); + + /** + * 0, 1 + */ + public final static ReadOnlyVector2 UNIT_Y = new Vector2(0, 1); + + /** + * 0, -1 + */ + public final static ReadOnlyVector2 NEG_UNIT_Y = new Vector2(0, -1); + + protected double _x = 0; + protected double _y = 0; + + /** + * Constructs a new vector set to (0, 0). + */ + public Vector2() { + this(0, 0); + } + + /** + * Constructs a new vector set to the (x, y) values of the given source vector. + * + * @param src + */ + public Vector2(final ReadOnlyVector2 src) { + this(src.getX(), src.getY()); + } + + /** + * Constructs a new vector set to (x, y). + * + * @param x + * @param y + */ + public Vector2(final double x, final double y) { + _x = x; + _y = y; + } + + @Override + public double getX() { + return _x; + } + + @Override + public double getY() { + return _y; + } + + /** + * @return x as a float, to decrease need for explicit casts. + */ + @Override + public float getXf() { + return (float) _x; + } + + /** + * @return y as a float, to decrease need for explicit casts. + */ + @Override + public float getYf() { + return (float) _y; + } + + /** + * @param index + * @return x value if index == 0 or y value if index == 1 + * @throws IllegalArgumentException + * if index is not one of 0, 1. + */ + @Override + public double getValue(final int index) { + switch (index) { + case 0: + return getX(); + case 1: + return getY(); + } + throw new IllegalArgumentException("index must be either 0 or 1"); + } + + /** + * @param index + * which field index in this vector to set. + * @param value + * to set to one of x or y. + * @throws IllegalArgumentException + * if index is not one of 0, 1. + */ + public void setValue(final int index, final double value) { + switch (index) { + case 0: + setX(value); + return; + case 1: + setY(value); + return; + } + throw new IllegalArgumentException("index must be either 0 or 1"); + } + + /** + * Stores the double values of this vector in the given double array. + * + * @param store + * if null, a new double[2] array is created. + * @return the double array + * @throws ArrayIndexOutOfBoundsException + * if store is not at least length 2. + */ + @Override + public double[] toArray(double[] store) { + if (store == null) { + store = new double[2]; + } + // do last first to ensure size is correct before any edits occur. + store[1] = getY(); + store[0] = getX(); + return store; + } + + /** + * Sets the first component of this vector to the given double value. + * + * @param x + */ + public void setX(final double x) { + _x = x; + } + + /** + * Sets the second component of this vector to the given double value. + * + * @param y + */ + public void setY(final double y) { + _y = y; + } + + /** + * Sets the value of this vector to (x, y) + * + * @param x + * @param y + * @return this vector for chaining + */ + public Vector2 set(final double x, final double y) { + setX(x); + setY(y); + return this; + } + + /** + * Sets the value of this vector to the (x, y) values of the provided source vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector2 set(final ReadOnlyVector2 source) { + setX(source.getX()); + setY(source.getY()); + return this; + } + + /** + * Sets the value of this vector to (0, 0) + * + * @return this vector for chaining + */ + public Vector2 zero() { + return set(0, 0); + } + + /** + * Adds the given values to those of this vector and returns them in store * @param store the vector to store the + * result in for return. If null, a new vector object is created and returned. . + * + * @param x + * @param y + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x + x, this.y + y) + */ + @Override + public Vector2 add(final double x, final double y, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() + x, getY() + y); + } + + /** + * Increments the values of this vector with the given x and y values. + * + * @param x + * @param y + * @return this vector for chaining + */ + public Vector2 addLocal(final double x, final double y) { + return set(getX() + x, getY() + y); + } + + /** + * Adds the values of the given source vector to those of this vector and returns them in store. + * + * @param source + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x + source.x, this.y + source.y) + * @throws NullPointerException + * if source is null. + */ + @Override + public Vector2 add(final ReadOnlyVector2 source, final Vector2 store) { + return add(source.getX(), source.getY(), store); + } + + /** + * Increments the values of this vector with the x and y values of the given vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector2 addLocal(final ReadOnlyVector2 source) { + return addLocal(source.getX(), source.getY()); + } + + /** + * Subtracts the given values from those of this vector and returns them in store. + * + * @param x + * @param y + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x - x, this.y - y) + */ + @Override + public Vector2 subtract(final double x, final double y, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() - x, getY() - y); + } + + /** + * Decrements the values of this vector by the given x and y values. + * + * @param x + * @param y + * @return this vector for chaining + */ + public Vector2 subtractLocal(final double x, final double y) { + return set(getX() - x, getY() - y); + } + + /** + * Subtracts the values of the given source vector from those of this vector and returns them in store. + * + * @param source + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x - source.x, this.y - source.y) + * @throws NullPointerException + * if source is null. + */ + @Override + public Vector2 subtract(final ReadOnlyVector2 source, final Vector2 store) { + return subtract(source.getX(), source.getY(), store); + } + + /** + * Decrements the values of this vector by the x and y values from the given source vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector2 subtractLocal(final ReadOnlyVector2 source) { + return subtractLocal(source.getX(), source.getY()); + } + + /** + * Multiplies the values of this vector by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scalar, this.y * scalar) + */ + @Override + public Vector2 multiply(final double scalar, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() * scalar, getY() * scalar); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scalar value. + * + * @param scalar + * @return this vector for chaining + */ + public Vector2 multiplyLocal(final double scalar) { + return set(getX() * scalar, getY() * scalar); + } + + /** + * Multiplies the values of this vector by the given scale values and returns the result in store. + * + * @param scale + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scale.x, this.y * scale.y) + */ + @Override + public Vector2 multiply(final ReadOnlyVector2 scale, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() * scale.getX(), getY() * scale.getY()); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the values of the given scale. + * + * @param scale + * @return this vector for chaining + */ + public Vector2 multiplyLocal(final ReadOnlyVector2 scale) { + return set(getX() * scale.getX(), getY() * scale.getY()); + } + + /** + * Multiplies the values of this vector by the given scale values and returns the result in store. + * + * @param x + * @param y + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scale.x, this.y * scale.y) + */ + @Override + public Vector2 multiply(final double x, final double y, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() * x, getY() * y); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the values of the given scale. + * + * @param x + * @param y + * @return this vector for chaining + */ + public Vector2 multiplyLocal(final double x, final double y) { + return set(getX() * x, getY() * y); + } + + /** + * Divides the values of this vector by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scalar, this.y / scalar) + */ + @Override + public Vector2 divide(final double scalar, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() / scalar, getY() / scalar); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scalar value. + * + * @param scalar + * @return this vector for chaining + * @throws ArithmeticException + * if scalar is 0 + */ + public Vector2 divideLocal(final double scalar) { + final double invScalar = 1.0 / scalar; + + return set(getX() * invScalar, getY() * invScalar); + } + + /** + * Divides the values of this vector by the given scale values and returns the result in store. + * + * @param scale + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scale.x, this.y / scale.y) + */ + @Override + public Vector2 divide(final ReadOnlyVector2 scale, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() / scale.getX(), getY() / scale.getY()); + } + + /** + * Internally modifies the values of this vector by dividing them each by the values of the given scale. + * + * @param scale + * @return this vector for chaining + */ + public Vector2 divideLocal(final ReadOnlyVector2 scale) { + return set(getX() / scale.getX(), getY() / scale.getY()); + } + + /** + * Divides the values of this vector by the given scale values and returns the result in store. + * + * @param x + * @param y + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scale.x, this.y / scale.y) + */ + @Override + public Vector2 divide(final double x, final double y, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + return result.set(getX() / x, getY() / y); + } + + /** + * Internally modifies the values of this vector by dividing them each by the values of the given scale. + * + * @param x + * @param y + * @return this vector for chaining + */ + public Vector2 divideLocal(final double x, final double y) { + return set(getX() / x, getY() / y); + } + + /** + * + * Internally modifies this vector by multiplying its values with a given scale value, then adding a given "add" + * value. + * + * @param scale + * the value to multiply this vector by. + * @param add + * the value to add to the result + * @return this vector for chaining + */ + public Vector2 scaleAddLocal(final double scale, final ReadOnlyVector2 add) { + _x = _x * scale + add.getX(); + _y = _y * scale + add.getY(); + return this; + } + + /** + * Scales this vector by multiplying its values with a given scale value, then adding a given "add" value. The + * result is store in the given store parameter. + * + * @param scale + * the value to multiply by. + * @param add + * the value to add + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return the store variable + */ + @Override + public Vector2 scaleAdd(final double scale, final ReadOnlyVector2 add, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + result.setX(_x * scale + add.getX()); + result.setY(_y * scale + add.getY()); + return result; + } + + /** + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return same as multiply(-1, store) + */ + @Override + public Vector2 negate(final Vector2 store) { + return multiply(-1, store); + } + + /** + * @return same as multiplyLocal(-1) + */ + public Vector2 negateLocal() { + return multiplyLocal(-1); + } + + /** + * Creates a new unit length vector from this one by dividing by length. If the length is 0, (ie, if the vector is + * 0, 0) then a new vector (0, 0) is returned. + * + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new unit vector (or 0, 0 if this unit is 0 length) + */ + @Override + public Vector2 normalize(final Vector2 store) { + final double lengthSq = lengthSquared(); + if (Math.abs(lengthSq) > MathUtils.EPSILON) { + return multiply(MathUtils.inverseSqrt(lengthSq), store); + } + + return store != null ? store.set(Vector2.ZERO) : new Vector2(Vector2.ZERO); + } + + /** + * Converts this vector into a unit vector by dividing it internally by its length. If the length is 0, (ie, if the + * vector is 0, 0) then no action is taken. + * + * @return this vector for chaining + */ + public Vector2 normalizeLocal() { + final double lengthSq = lengthSquared(); + if (Math.abs(lengthSq) > MathUtils.EPSILON) { + return multiplyLocal(MathUtils.inverseSqrt(lengthSq)); + } + + return this; + } + + /** + * Creates a new vector representing this vector rotated around 0,0 by a specified angle in a given direction. + * + * @param angle + * in radians + * @param clockwise + * true to rotate in a clockwise direction + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return the new rotated vector + */ + @Override + public Vector2 rotateAroundOrigin(double angle, final boolean clockwise, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + if (clockwise) { + angle = -angle; + } + final double newX = MathUtils.cos(angle) * getX() - MathUtils.sin(angle) * getY(); + final double newY = MathUtils.sin(angle) * getX() + MathUtils.cos(angle) * getY(); + return result.set(newX, newY); + } + + /** + * Internally rotates this vector around 0,0 by a specified angle in a given direction. + * + * @param angle + * in radians + * @param clockwise + * true to rotate in a clockwise direction + * @return this vector for chaining + */ + public Vector2 rotateAroundOriginLocal(double angle, final boolean clockwise) { + if (clockwise) { + angle = -angle; + } + final double newX = MathUtils.cos(angle) * getX() - MathUtils.sin(angle) * getY(); + final double newY = MathUtils.sin(angle) * getX() + MathUtils.cos(angle) * getY(); + return set(newX, newY); + } + + /** + * Performs a linear interpolation between this vector and the given end vector, using the given scalar as a + * percent. iow, if changeAmnt is closer to 0, the result will be closer to the current value of this vector and if + * it is closer to 1, the result will be closer to the end value. The result is returned as a new vector object. + * + * @param endVec + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector as described above. + * @throws NullPointerException + * if endVec is null. + */ + @Override + public Vector2 lerp(final ReadOnlyVector2 endVec, final double scalar, final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + final double x = (1.0 - scalar) * getX() + scalar * endVec.getX(); + final double y = (1.0 - scalar) * getY() + scalar * endVec.getY(); + return result.set(x, y); + } + + /** + * Performs a linear interpolation between this vector and the given end vector, using the given scalar as a + * percent. iow, if changeAmnt is closer to 0, the result will be closer to the current value of this vector and if + * it is closer to 1, the result will be closer to the end value. The result is stored back in this vector. + * + * @param endVec + * @param scalar + * @return this vector for chaining + * @throws NullPointerException + * if endVec is null. + */ + public Vector2 lerpLocal(final ReadOnlyVector2 endVec, final double scalar) { + setX((1.0 - scalar) * getX() + scalar * endVec.getX()); + setY((1.0 - scalar) * getY() + scalar * endVec.getY()); + return this; + } + + /** + * Performs a linear interpolation between the given begin and end vectors, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the + * result will be closer to the end value. The result is returned as a new vector object. + * + * @param beginVec + * @param endVec + * @param scalar + * the scalar as a percent. + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector as described above. + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public static Vector2 lerp(final ReadOnlyVector2 beginVec, final ReadOnlyVector2 endVec, final double scalar, + final Vector2 store) { + Vector2 result = store; + if (result == null) { + result = new Vector2(); + } + + final double x = (1.0 - scalar) * beginVec.getX() + scalar * endVec.getX(); + final double y = (1.0 - scalar) * beginVec.getY() + scalar * endVec.getY(); + return result.set(x, y); + } + + /** + * Performs a linear interpolation between the given begin and end vectors, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the + * result will be closer to the end value. The result is stored back in this vector. + * + * @param beginVec + * @param endVec + * @param changeAmnt + * the scalar as a percent. + * @return this vector for chaining + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public Vector2 lerpLocal(final ReadOnlyVector2 beginVec, final ReadOnlyVector2 endVec, final double scalar) { + setX((1.0 - scalar) * beginVec.getX() + scalar * endVec.getX()); + setY((1.0 - scalar) * beginVec.getY() + scalar * endVec.getY()); + return this; + } + + /** + * @return the magnitude of this vector, or the distance between the origin (0, 0) and the point described by (x, + * y). Basically sqrt(x^2 + y^2) + */ + @Override + public double length() { + return MathUtils.sqrt(lengthSquared()); + } + + /** + * @return the squared magnitude of this vector. (x^2 + y^2) + */ + @Override + public double lengthSquared() { + return getX() * getX() + getY() * getY(); + } + + /** + * @param x + * @param y + * @return the squared distance between the point described by this vector and the given x, y point. When comparing + * the relative distance between two points it is usually sufficient to compare the squared distances, thus + * avoiding an expensive square root operation. + */ + @Override + public double distanceSquared(final double x, final double y) { + final double dx = getX() - x; + final double dy = getY() - y; + return dx * dx + dy * dy; + } + + /** + * @param destination + * @return the squared distance between the point described by this vector and the given destination point. When + * comparing the relative distance between two points it is usually sufficient to compare the squared + * distances, thus avoiding an expensive square root operation. + * @throws NullPointerException + * if destination is null. + */ + @Override + public double distanceSquared(final ReadOnlyVector2 destination) { + return distanceSquared(destination.getX(), destination.getY()); + } + + /** + * @param x + * @param y + * @return the distance between the point described by this vector and the given x, y point. + */ + @Override + public double distance(final double x, final double y) { + return MathUtils.sqrt(distanceSquared(x, y)); + } + + /** + * @param destination + * @return the distance between the point described by this vector and the given destination point. + * @throws NullPointerException + * if destination is null. + */ + @Override + public double distance(final ReadOnlyVector2 destination) { + return MathUtils.sqrt(distanceSquared(destination)); + } + + /** + * @param x + * @param y + * @return the dot product of this vector with the given x, y values. + */ + @Override + public double dot(final double x, final double y) { + return getX() * x + getY() * y; + } + + /** + * @param vec + * @return the dot product of this vector with the x, y values of the given vector. + * @throws NullPointerException + * if vec is null. + */ + @Override + public double dot(final ReadOnlyVector2 vec) { + return dot(vec.getX(), vec.getY()); + } + + /** + * @return the angle - in radians [-pi, pi) - represented by this Vector2 as expressed by a conversion from + * rectangular coordinates (<code>x</code>, <code>y</code>) to polar coordinates + * (r, <i>theta</i>). + */ + @Override + public double getPolarAngle() { + return -Math.atan2(getY(), getX()); + } + + /** + * @param otherVector + * the "destination" unit vector + * @return the angle (in radians) required to rotate a ray represented by this vector to lie co-linear to a ray + * described by the given vector. It is assumed that both this vector and the given vector are unit vectors + * (normalized). + * @throws NullPointerException + * if otherVector is null. + */ + @Override + public double angleBetween(final ReadOnlyVector2 otherVector) { + return Math.atan2(otherVector.getY(), otherVector.getX()) - Math.atan2(getY(), getX()); + } + + /** + * @param otherVector + * a unit vector to find the angle against + * @return the minimum angle (in radians) between two vectors. It is assumed that both this vector and the given + * vector are unit vectors (normalized). + * @throws NullPointerException + * if otherVector is null. + */ + @Override + public double smallestAngleBetween(final ReadOnlyVector2 otherVector) { + final double dotProduct = dot(otherVector); + return MathUtils.acos(dotProduct); + } + + /** + * Check a vector... if it is null or its doubles are NaN or infinite, return false. Else return true. + * + * @param vector + * the vector to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyVector2 vector) { + if (vector == null) { + return false; + } + if (Double.isNaN(vector.getX()) || Double.isNaN(vector.getY())) { + return false; + } + if (Double.isInfinite(vector.getX()) || Double.isInfinite(vector.getY())) { + return false; + } + return true; + } + + /** + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "com.ardor3d.math.Vector2 [X=" + getX() + ", Y=" + getY() + "]"; + } + + /** + * @return returns a unique code for this vector object based on its values. If two vectors are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + final long x = Double.doubleToLongBits(getX()); + result += 31 * result + (int) (x ^ x >>> 32); + + final long y = Double.doubleToLongBits(getY()); + result += 31 * result + (int) (y ^ y >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this vector and the provided vector have the same x and y values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyVector2)) { + return false; + } + final ReadOnlyVector2 comp = (ReadOnlyVector2) o; + return getX() == comp.getX() && getY() == comp.getY(); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Vector2 clone() { + return new Vector2(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Vector2> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(getX(), "x", 0); + capsule.write(getY(), "y", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + setX(capsule.readDouble("x", 0)); + setY(capsule.readDouble("y", 0)); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setX(in.readDouble()); + setY(in.readDouble()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeDouble(getX()); + out.writeDouble(getY()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Vector2 that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Vector2 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Vector2.VEC_POOL.fetch(); + } else { + return new Vector2(); + } + } + + /** + * Releases a Vector2 back to be used by a future call to fetchTempInstance. TAKE CARE: this Vector2 object should + * no longer have other classes referencing it or "Bad Things" will happen. + * + * @param vec + * the Vector2 to release. + */ + public final static void releaseTempInstance(final Vector2 vec) { + if (MathConstants.useMathPools) { + Vector2.VEC_POOL.release(vec); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Vector3.java b/ardor3d-math/src/main/java/com/ardor3d/math/Vector3.java new file mode 100644 index 0000000..f8754b2 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Vector3.java @@ -0,0 +1,1135 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Vector3 represents a point or vector in a three dimensional system. This implementation stores its data in + * double-precision. + */ +public class Vector3 implements Cloneable, Savable, Externalizable, ReadOnlyVector3, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Vector3> VEC_POOL = ObjectPool.create(Vector3.class, MathConstants.maxMathPoolSize); + + /** + * 0, 0, 0 + */ + public final static ReadOnlyVector3 ZERO = new Vector3(0, 0, 0); + + /** + * 1, 1, 1 + */ + public final static ReadOnlyVector3 ONE = new Vector3(1, 1, 1); + + /** + * -1, -1, -1 + */ + public final static ReadOnlyVector3 NEG_ONE = new Vector3(-1, -1, -1); + + /** + * 1, 0, 0 + */ + public final static ReadOnlyVector3 UNIT_X = new Vector3(1, 0, 0); + + /** + * -1, 0, 0 + */ + public final static ReadOnlyVector3 NEG_UNIT_X = new Vector3(-1, 0, 0); + + /** + * 0, 1, 0 + */ + public final static ReadOnlyVector3 UNIT_Y = new Vector3(0, 1, 0); + + /** + * 0, -1, 0 + */ + public final static ReadOnlyVector3 NEG_UNIT_Y = new Vector3(0, -1, 0); + + /** + * 0, 0, 1 + */ + public final static ReadOnlyVector3 UNIT_Z = new Vector3(0, 0, 1); + + /** + * 0, 0, -1 + */ + public final static ReadOnlyVector3 NEG_UNIT_Z = new Vector3(0, 0, -1); + + protected double _x = 0; + protected double _y = 0; + protected double _z = 0; + + /** + * Constructs a new vector set to (0, 0, 0). + */ + public Vector3() { + this(0, 0, 0); + } + + /** + * Constructs a new vector set to the (x, y, z) values of the given source vector. + * + * @param src + */ + public Vector3(final ReadOnlyVector3 src) { + this(src.getX(), src.getY(), src.getZ()); + } + + /** + * Constructs a new vector set to (x, y, z). + * + * @param x + * @param y + * @param z + */ + public Vector3(final double x, final double y, final double z) { + _x = x; + _y = y; + _z = z; + } + + @Override + public double getX() { + return _x; + } + + @Override + public double getY() { + return _y; + } + + @Override + public double getZ() { + return _z; + } + + /** + * @return x as a float, to decrease need for explicit casts. + */ + @Override + public float getXf() { + return (float) _x; + } + + /** + * @return y as a float, to decrease need for explicit casts. + */ + @Override + public float getYf() { + return (float) _y; + } + + /** + * @return z as a float, to decrease need for explicit casts. + */ + @Override + public float getZf() { + return (float) _z; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1 or z value if index == 2 + * @throws IllegalArgumentException + * if index is not one of 0, 1, 2. + */ + @Override + public double getValue(final int index) { + switch (index) { + case 0: + return getX(); + case 1: + return getY(); + case 2: + return getZ(); + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + /** + * @param index + * which field index in this vector to set. + * @param value + * to set to one of x, y or z. + * @throws IllegalArgumentException + * if index is not one of 0, 1, 2. + */ + public void setValue(final int index, final double value) { + switch (index) { + case 0: + setX(value); + return; + case 1: + setY(value); + return; + case 2: + setZ(value); + return; + } + throw new IllegalArgumentException("index must be either 0, 1 or 2"); + } + + /** + * Stores the double values of this vector in the given double array. + * + * @param store + * if null, a new double[3] array is created. + * @return the double array + * @throws ArrayIndexOutOfBoundsException + * if store is not at least length 3. + */ + @Override + public double[] toArray(double[] store) { + if (store == null) { + store = new double[3]; + } + + // do last first to ensure size is correct before any edits occur. + store[2] = getZ(); + store[1] = getY(); + store[0] = getX(); + return store; + } + + /** + * Stores the double values of this vector in the given float array. + * + * @param store + * if null, a new float[3] array is created. + * @return the float array + * @throws NullPointerException + * if store is null. + * @throws ArrayIndexOutOfBoundsException + * if store is not at least length 3. + */ + public float[] toFloatArray(float[] store) { + if (store == null) { + store = new float[3]; + } + + // do last first to ensure size is correct before any edits occur. + store[2] = (float) getZ(); + store[1] = (float) getY(); + store[0] = (float) getX(); + return store; + } + + /** + * Sets the first component of this vector to the given double value. + * + * @param x + */ + public void setX(final double x) { + _x = x; + } + + /** + * Sets the second component of this vector to the given double value. + * + * @param y + */ + public void setY(final double y) { + _y = y; + } + + /** + * Sets the third component of this vector to the given double value. + * + * @param z + */ + public void setZ(final double z) { + _z = z; + } + + /** + * Sets the value of this vector to (x, y, z) + * + * @param x + * @param y + * @param z + * @return this vector for chaining + */ + public Vector3 set(final double x, final double y, final double z) { + setX(x); + setY(y); + setZ(z); + return this; + } + + /** + * Sets the value of this vector to the (x, y, z) values of the provided source vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector3 set(final ReadOnlyVector3 source) { + setX(source.getX()); + setY(source.getY()); + setZ(source.getZ()); + return this; + } + + /** + * Sets the value of this vector to (0, 0, 0) + * + * @return this vector for chaining + */ + public Vector3 zero() { + return set(0, 0, 0); + } + + /** + * Adds the given values to those of this vector and returns them in store * @param store the vector to store the + * result in for return. If null, a new vector object is created and returned. . + * + * @param x + * @param y + * @param z + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x + x, this.y + y, this.z + z) + */ + @Override + public Vector3 add(final double x, final double y, final double z, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() + x, getY() + y, getZ() + z); + } + + /** + * Increments the values of this vector with the given x, y and z values. + * + * @param x + * @param y + * @param z + * @return this vector for chaining + */ + public Vector3 addLocal(final double x, final double y, final double z) { + return set(getX() + x, getY() + y, getZ() + z); + } + + /** + * Adds the values of the given source vector to those of this vector and returns them in store. + * + * @param source + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x + source.x, this.y + source.y, this.z + source.z) + * @throws NullPointerException + * if source is null. + */ + @Override + public Vector3 add(final ReadOnlyVector3 source, final Vector3 store) { + return add(source.getX(), source.getY(), source.getZ(), store); + } + + /** + * Increments the values of this vector with the x, y and z values of the given vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector3 addLocal(final ReadOnlyVector3 source) { + return addLocal(source.getX(), source.getY(), source.getZ()); + } + + /** + * Subtracts the given values from those of this vector and returns them in store. + * + * @param x + * @param y + * @param z + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x - x, this.y - y, this.z - z) + */ + @Override + public Vector3 subtract(final double x, final double y, final double z, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() - x, getY() - y, getZ() - z); + } + + /** + * Decrements the values of this vector by the given x, y and z values. + * + * @param x + * @param y + * @param z + * @return this vector for chaining + */ + public Vector3 subtractLocal(final double x, final double y, final double z) { + return set(getX() - x, getY() - y, getZ() - z); + } + + /** + * Subtracts the values of the given source vector from those of this vector and returns them in store. + * + * @param source + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x - source.x, this.y - source.y, this.z - source.z) + * @throws NullPointerException + * if source is null. + */ + @Override + public Vector3 subtract(final ReadOnlyVector3 source, final Vector3 store) { + return subtract(source.getX(), source.getY(), source.getZ(), store); + } + + /** + * Decrements the values of this vector by the x, y and z values from the given source vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector3 subtractLocal(final ReadOnlyVector3 source) { + return subtractLocal(source.getX(), source.getY(), source.getZ()); + } + + /** + * Multiplies the values of this vector by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scalar, this.y * scalar, this.z * scalar) + */ + @Override + public Vector3 multiply(final double scalar, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() * scalar, getY() * scalar, getZ() * scalar); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scalar value. + * + * @param scalar + * @return this vector for chaining + */ + public Vector3 multiplyLocal(final double scalar) { + return set(getX() * scalar, getY() * scalar, getZ() * scalar); + } + + /** + * Multiplies the values of this vector by the given scale values and returns the result in store. + * + * @param scale + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scale.x, this.y * scale.y, this.z * scale.z) + */ + @Override + public Vector3 multiply(final ReadOnlyVector3 scale, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() * scale.getX(), getY() * scale.getY(), getZ() * scale.getZ()); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scale values. + * + * @param scalar + * @return this vector for chaining + */ + public Vector3 multiplyLocal(final ReadOnlyVector3 scale) { + return set(getX() * scale.getX(), getY() * scale.getY(), getZ() * scale.getZ()); + } + + /** + * Multiplies the values of this vector by the given scale values and returns the result in store. + * + * @param x + * @param y + * @param z + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scale.x, this.y * scale.y, this.z * scale.z) + */ + @Override + public Vector3 multiply(final double x, final double y, final double z, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() * x, getY() * y, getZ() * z); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scale values. + * + * @param x + * @param y + * @param z + * @return this vector for chaining + */ + public Vector3 multiplyLocal(final double x, final double y, final double z) { + return set(getX() * x, getY() * y, getZ() * z); + } + + /** + * Divides the values of this vector by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scalar, this.y / scalar, this.z / scalar) + */ + @Override + public Vector3 divide(final double scalar, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() / scalar, getY() / scalar, getZ() / scalar); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scalar value. + * + * @param scalar + * @return this vector for chaining + * @throws ArithmeticException + * if scalar is 0 + */ + public Vector3 divideLocal(final double scalar) { + final double invScalar = 1.0 / scalar; + + return set(getX() * invScalar, getY() * invScalar, getZ() * invScalar); + } + + /** + * Divides the values of this vector by the given scale values and returns the result in store. + * + * @param scale + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scale.x, this.y / scale.y, this.z / scale.z) + */ + @Override + public Vector3 divide(final ReadOnlyVector3 scale, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() / scale.getX(), getY() / scale.getY(), getZ() / scale.getZ()); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scale values. + * + * @param scale + * @return this vector for chaining + */ + public Vector3 divideLocal(final ReadOnlyVector3 scale) { + return set(getX() / scale.getX(), getY() / scale.getY(), getZ() / scale.getZ()); + } + + /** + * Divides the values of this vector by the given scale values and returns the result in store. + * + * @param x + * @param y + * @param z + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scale.x, this.y / scale.y, this.z / scale.z) + */ + @Override + public Vector3 divide(final double x, final double y, final double z, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + return result.set(getX() / x, getY() / y, getZ() / z); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scale values. + * + * @param x + * @param y + * @param z + * @return this vector for chaining + */ + public Vector3 divideLocal(final double x, final double y, final double z) { + return set(getX() / x, getY() / y, getZ() / z); + } + + /** + * + * Internally modifies this vector by multiplying its values with a given scale value, then adding a given "add" + * value. + * + * @param scale + * the value to multiply this vector by. + * @param add + * the value to add to the result + * @return this vector for chaining + */ + public Vector3 scaleAddLocal(final double scale, final ReadOnlyVector3 add) { + _x = _x * scale + add.getX(); + _y = _y * scale + add.getY(); + _z = _z * scale + add.getZ(); + return this; + } + + /** + * Scales this vector by multiplying its values with a given scale value, then adding a given "add" value. The + * result is store in the given store parameter. + * + * @param scale + * the value to multiply by. + * @param add + * the value to add + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return the store variable + */ + @Override + public Vector3 scaleAdd(final double scale, final ReadOnlyVector3 add, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + result.setX(_x * scale + add.getX()); + result.setY(_y * scale + add.getY()); + result.setZ(_z * scale + add.getZ()); + return result; + } + + /** + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return same as multiply(-1, store) + */ + @Override + public Vector3 negate(final Vector3 store) { + return multiply(-1, store); + } + + /** + * @return same as multiplyLocal(-1) + */ + public Vector3 negateLocal() { + return multiplyLocal(-1); + } + + /** + * Creates a new unit length vector from this one by dividing by length. If the length is 0, (ie, if the vector is + * 0, 0, 0) then a new vector (0, 0, 0) is returned. + * + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new unit vector (or 0, 0, 0 if this unit is 0 length) + */ + @Override + public Vector3 normalize(final Vector3 store) { + final double lengthSq = lengthSquared(); + if (Math.abs(lengthSq) > MathUtils.EPSILON) { + return multiply(MathUtils.inverseSqrt(lengthSq), store); + } + + return store != null ? store.set(Vector3.ZERO) : new Vector3(Vector3.ZERO); + } + + /** + * Converts this vector into a unit vector by dividing it internally by its length. If the length is 0, (ie, if the + * vector is 0, 0, 0) then no action is taken. + * + * @return this vector for chaining + */ + public Vector3 normalizeLocal() { + final double lengthSq = lengthSquared(); + if (Math.abs(lengthSq) > MathUtils.EPSILON) { + return multiplyLocal(MathUtils.inverseSqrt(lengthSq)); + } + + return this; + } + + /** + * Performs a linear interpolation between this vector and the given end vector, using the given scalar as a + * percent. iow, if changeAmnt is closer to 0, the result will be closer to the current value of this vector and if + * it is closer to 1, the result will be closer to the end value. The result is returned as a new vector object. + * + * @param endVec + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector as described above. + * @throws NullPointerException + * if endVec is null. + */ + @Override + public Vector3 lerp(final ReadOnlyVector3 endVec, final double scalar, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final double x = (1.0 - scalar) * getX() + scalar * endVec.getX(); + final double y = (1.0 - scalar) * getY() + scalar * endVec.getY(); + final double z = (1.0 - scalar) * getZ() + scalar * endVec.getZ(); + return result.set(x, y, z); + } + + /** + * Performs a linear interpolation between this vector and the given end vector, using the given scalar as a + * percent. iow, if changeAmnt is closer to 0, the result will be closer to the current value of this vector and if + * it is closer to 1, the result will be closer to the end value. The result is stored back in this vector. + * + * @param endVec + * @param scalar + * @return this vector for chaining + * @throws NullPointerException + * if endVec is null. + */ + public Vector3 lerpLocal(final ReadOnlyVector3 endVec, final double scalar) { + setX((1.0 - scalar) * getX() + scalar * endVec.getX()); + setY((1.0 - scalar) * getY() + scalar * endVec.getY()); + setZ((1.0 - scalar) * getZ() + scalar * endVec.getZ()); + return this; + } + + /** + * Performs a linear interpolation between the given begin and end vectors, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the + * result will be closer to the end value. The result is returned as a new vector object. + * + * @param beginVec + * @param endVec + * @param scalar + * the scalar as a percent. + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. It + * IS safe for store to be the same as the begin or end vector. + * @return a new vector as described above. + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public static Vector3 lerp(final ReadOnlyVector3 beginVec, final ReadOnlyVector3 endVec, final double scalar, + final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + // Check for equality and skip operation if possible. + if (!beginVec.equals(endVec)) { + final double x = (1.0 - scalar) * beginVec.getX() + scalar * endVec.getX(); + final double y = (1.0 - scalar) * beginVec.getY() + scalar * endVec.getY(); + final double z = (1.0 - scalar) * beginVec.getZ() + scalar * endVec.getZ(); + return result.set(x, y, z); + } else { + return result.set(beginVec); + } + } + + /** + * Performs a linear interpolation between the given begin and end vectors, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the + * result will be closer to the end value. The result is stored back in this vector. + * + * @param beginVec + * @param endVec + * @param changeAmnt + * the scalar as a percent. + * @return this vector for chaining + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public Vector3 lerpLocal(final ReadOnlyVector3 beginVec, final ReadOnlyVector3 endVec, final double scalar) { + // Check for equality and skip operation if possible. + if (!beginVec.equals(endVec)) { + setX((1.0 - scalar) * beginVec.getX() + scalar * endVec.getX()); + setY((1.0 - scalar) * beginVec.getY() + scalar * endVec.getY()); + setZ((1.0 - scalar) * beginVec.getZ() + scalar * endVec.getZ()); + } else { + set(beginVec); + } + return this; + } + + /** + * @return the magnitude or distance between the origin (0, 0, 0) and the point described by this vector (x, y, z). + * Effectively the square root of the value returned by {@link #lengthSquared()}. + */ + @Override + public double length() { + return MathUtils.sqrt(lengthSquared()); + } + + /** + * @return the squared magnitude or squared distance between the origin (0, 0, 0) and the point described by this + * vector (x, y, z) + */ + @Override + public double lengthSquared() { + return getX() * getX() + getY() * getY() + getZ() * getZ(); + } + + /** + * @param x + * @param y + * @param z + * @return the squared distance between the point described by this vector and the given x, y, z point. When + * comparing the relative distance between two points it is usually sufficient to compare the squared + * distances, thus avoiding an expensive square root operation. + */ + @Override + public double distanceSquared(final double x, final double y, final double z) { + final double dx = getX() - x; + final double dy = getY() - y; + final double dz = getZ() - z; + return dx * dx + dy * dy + dz * dz; + } + + /** + * @param destination + * @return the squared distance between the point described by this vector and the given destination point. When + * comparing the relative distance between two points it is usually sufficient to compare the squared + * distances, thus avoiding an expensive square root operation. + * @throws NullPointerException + * if destination is null. + */ + @Override + public double distanceSquared(final ReadOnlyVector3 destination) { + return distanceSquared(destination.getX(), destination.getY(), destination.getZ()); + } + + /** + * @param x + * @param y + * @param z + * @return the distance between the point described by this vector and the given x, y, z point. + */ + @Override + public double distance(final double x, final double y, final double z) { + return MathUtils.sqrt(distanceSquared(x, y, z)); + } + + /** + * @param destination + * @return the distance between the point described by this vector and the given destination point. + * @throws NullPointerException + * if destination is null. + */ + @Override + public double distance(final ReadOnlyVector3 destination) { + return MathUtils.sqrt(distanceSquared(destination)); + } + + /** + * @param x + * @param y + * @param z + * @return the dot product of this vector with the given x, y, z values. + */ + @Override + public double dot(final double x, final double y, final double z) { + return getX() * x + getY() * y + getZ() * z; + } + + /** + * @param vec + * @return the dot product of this vector with the x, y, z values of the given vector. + * @throws NullPointerException + * if vec is null. + */ + @Override + public double dot(final ReadOnlyVector3 vec) { + return dot(vec.getX(), vec.getY(), vec.getZ()); + } + + /** + * @param x + * @param y + * @param z + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return the cross product of this vector with the given x, y, z values. + */ + @Override + public Vector3 cross(final double x, final double y, final double z, final Vector3 store) { + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + final double newX = getY() * z - getZ() * y; + final double newY = getZ() * x - getX() * z; + final double newZ = getX() * y - getY() * x; + result.set(newX, newY, newZ); + return result; + } + + /** + * @param vec + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return the cross product of this vector with the given vector's x, y, z values + * @throws NullPointerException + * if vec is null. + */ + @Override + public Vector3 cross(final ReadOnlyVector3 vec, final Vector3 store) { + return cross(vec.getX(), vec.getY(), vec.getZ(), store); + } + + /** + * @param x + * @param y + * @param z + * @return this vector, set to the cross product of this vector with the given x, y, z values. + */ + public Vector3 crossLocal(final double x, final double y, final double z) { + final double newX = getY() * z - getZ() * y; + final double newY = getZ() * x - getX() * z; + final double newZ = getX() * y - getY() * x; + set(newX, newY, newZ); + return this; + } + + /** + * @param vec + * @return this vector, set to the cross product of this vector with the given vector's x, y, z values + * @throws NullPointerException + * if vec is null. + */ + public Vector3 crossLocal(final ReadOnlyVector3 vec) { + return crossLocal(vec.getX(), vec.getY(), vec.getZ()); + } + + /** + * @param otherVector + * a unit vector to find the angle against + * @return the minimum angle (in radians) between two vectors. It is assumed that both this vector and the given + * vector are unit vectors (normalized). + * @throws NullPointerException + * if otherVector is null. + */ + @Override + public double smallestAngleBetween(final ReadOnlyVector3 otherVector) { + return MathUtils.acos(dot(otherVector)); + } + + /** + * Check a vector... if it is null or its doubles are NaN or infinite, return false. Else return true. + * + * @param vector + * the vector to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyVector3 vector) { + if (vector == null) { + return false; + } + if (Double.isNaN(vector.getX()) || Double.isNaN(vector.getY()) || Double.isNaN(vector.getZ())) { + return false; + } + if (Double.isInfinite(vector.getX()) || Double.isInfinite(vector.getY()) || Double.isInfinite(vector.getZ())) { + return false; + } + return true; + } + + /** + * Check if a vector is non-null and has infinite values. + * + * @param vector + * the vector to check + * @return true or false as stated above. + */ + public static boolean isInfinite(final ReadOnlyVector3 vector) { + if (vector == null) { + return false; + } + if (Double.isInfinite(vector.getX()) || Double.isInfinite(vector.getY()) || Double.isInfinite(vector.getZ())) { + return true; + } + return false; + } + + /** + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "com.ardor3d.math.Vector3 [X=" + getX() + ", Y=" + getY() + ", Z=" + getZ() + "]"; + } + + /** + * @return returns a unique code for this vector object based on its values. If two vectors are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + final long x = Double.doubleToLongBits(getX()); + result += 31 * result + (int) (x ^ x >>> 32); + + final long y = Double.doubleToLongBits(getY()); + result += 31 * result + (int) (y ^ y >>> 32); + + final long z = Double.doubleToLongBits(getZ()); + result += 31 * result + (int) (z ^ z >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this vector and the provided vector have the same x, y and z values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyVector3)) { + return false; + } + final ReadOnlyVector3 comp = (ReadOnlyVector3) o; + return getX() == comp.getX() && getY() == comp.getY() && getZ() == comp.getZ(); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Vector3 clone() { + return new Vector3(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Vector3> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(getX(), "x", 0); + capsule.write(getY(), "y", 0); + capsule.write(getZ(), "z", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + setX(capsule.readDouble("x", 0)); + setY(capsule.readDouble("y", 0)); + setZ(capsule.readDouble("z", 0)); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setX(in.readDouble()); + setY(in.readDouble()); + setZ(in.readDouble()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeDouble(getX()); + out.writeDouble(getY()); + out.writeDouble(getZ()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Vector3 that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Vector3 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Vector3.VEC_POOL.fetch(); + } else { + return new Vector3(); + } + } + + /** + * Releases a Vector3 back to be used by a future call to fetchTempInstance. TAKE CARE: this Vector3 object should + * no longer have other classes referencing it or "Bad Things" will happen. + * + * @param vec + * the Vector3 to release. + */ + public final static void releaseTempInstance(final Vector3 vec) { + if (MathConstants.useMathPools) { + Vector3.VEC_POOL.release(vec); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/Vector4.java b/ardor3d-math/src/main/java/com/ardor3d/math/Vector4.java new file mode 100644 index 0000000..74927d6 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/Vector4.java @@ -0,0 +1,1090 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import com.ardor3d.math.type.ReadOnlyVector4; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Vector4 represents a point or vector in a four dimensional system. This implementation stores its data in + * double-precision. + */ +public class Vector4 implements Cloneable, Savable, Externalizable, ReadOnlyVector4, Poolable { + + private static final long serialVersionUID = 1L; + + private static final ObjectPool<Vector4> VEC_POOL = ObjectPool.create(Vector4.class, MathConstants.maxMathPoolSize); + + /** + * 0, 0, 0, 0 + */ + public final static ReadOnlyVector4 ZERO = new Vector4(0, 0, 0, 0); + + /** + * 1, 1, 1, 1 + */ + public final static ReadOnlyVector4 ONE = new Vector4(1, 1, 1, 1); + + /** + * -1, -1, -1, -1 + */ + public final static ReadOnlyVector4 NEG_ONE = new Vector4(-1, -1, -1, -1); + + /** + * 1, 0, 0, 0 + */ + public final static ReadOnlyVector4 UNIT_X = new Vector4(1, 0, 0, 0); + + /** + * -1, 0, 0, 0 + */ + public final static ReadOnlyVector4 NEG_UNIT_X = new Vector4(-1, 0, 0, 0); + + /** + * 0, 1, 0, 0 + */ + public final static ReadOnlyVector4 UNIT_Y = new Vector4(0, 1, 0, 0); + + /** + * 0, -1, 0, 0 + */ + public final static ReadOnlyVector4 NEG_UNIT_Y = new Vector4(0, -1, 0, 0); + + /** + * 0, 0, 1, 0 + */ + public final static ReadOnlyVector4 UNIT_Z = new Vector4(0, 0, 1, 0); + + /** + * 0, 0, -1, 0 + */ + public final static ReadOnlyVector4 NEG_UNIT_Z = new Vector4(0, 0, -1, 0); + + /** + * 0, 0, 0, 1 + */ + public final static ReadOnlyVector4 UNIT_W = new Vector4(0, 0, 0, 1); + + /** + * 0, 0, 0, -1 + */ + public final static ReadOnlyVector4 NEG_UNIT_W = new Vector4(0, 0, 0, -1); + + protected double _x = 0; + protected double _y = 0; + protected double _z = 0; + protected double _w = 0; + + /** + * Constructs a new vector set to (0, 0, 0, 0). + */ + public Vector4() { + this(0, 0, 0, 0); + } + + /** + * Constructs a new vector set to the (x, y, z, w) values of the given source vector. + * + * @param src + */ + public Vector4(final ReadOnlyVector4 src) { + this(src.getX(), src.getY(), src.getZ(), src.getW()); + } + + /** + * Constructs a new vector set to (x, y, z, w). + * + * @param x + * @param y + * @param z + * @param w + */ + public Vector4(final double x, final double y, final double z, final double w) { + _x = x; + _y = y; + _z = z; + _w = w; + } + + @Override + public double getX() { + return _x; + } + + @Override + public double getY() { + return _y; + } + + @Override + public double getZ() { + return _z; + } + + @Override + public double getW() { + return _w; + } + + /** + * @return x as a float, to decrease need for explicit casts. + */ + @Override + public float getXf() { + return (float) _x; + } + + /** + * @return y as a float, to decrease need for explicit casts. + */ + @Override + public float getYf() { + return (float) _y; + } + + /** + * @return z as a float, to decrease need for explicit casts. + */ + @Override + public float getZf() { + return (float) _z; + } + + /** + * @return w as a float, to decrease need for explicit casts. + */ + @Override + public float getWf() { + return (float) _w; + } + + /** + * @param index + * @return x value if index == 0, y value if index == 1, z value if index == 2 or w value if index == 3 + * @throws IllegalArgumentException + * if index is not one of 0, 1, 2, 3. + */ + @Override + public double getValue(final int index) { + switch (index) { + case 0: + return getX(); + case 1: + return getY(); + case 2: + return getZ(); + case 3: + return getW(); + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + + /** + * @param index + * which field index in this vector to set. + * @param value + * to set to one of x, y, z or w. + * @throws IllegalArgumentException + * if index is not one of 0, 1, 2, 3. + * + * if this vector is read only + */ + public void setValue(final int index, final double value) { + switch (index) { + case 0: + setX(value); + return; + case 1: + setY(value); + return; + case 2: + setZ(value); + return; + case 3: + setW(value); + return; + } + throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); + } + + /** + * Stores the double values of this vector in the given double array. + * + * @param store + * if null, a new double[4] array is created. + * @return the double array + * @throws ArrayIndexOutOfBoundsException + * if store is not at least length 4. + */ + @Override + public double[] toArray(double[] store) { + if (store == null) { + store = new double[4]; + } + // do last first to ensure size is correct before any edits occur. + store[3] = getW(); + store[2] = getZ(); + store[1] = getY(); + store[0] = getX(); + return store; + } + + /** + * Sets the first component of this vector to the given double value. + * + * @param x + */ + public void setX(final double x) { + _x = x; + } + + /** + * Sets the second component of this vector to the given double value. + * + * @param y + */ + public void setY(final double y) { + _y = y; + } + + /** + * Sets the third component of this vector to the given double value. + * + * @param z + */ + public void setZ(final double z) { + _z = z; + } + + /** + * Sets the fourth component of this vector to the given double value. + * + * @param w + */ + public void setW(final double w) { + _w = w; + } + + /** + * Sets the value of this vector to (x, y, z, w) + * + * @param x + * @param y + * @param z + * @param w + * @return this vector for chaining + */ + public Vector4 set(final double x, final double y, final double z, final double w) { + setX(x); + setY(y); + setZ(z); + setW(w); + return this; + } + + /** + * Sets the value of this vector to the (x, y, z, w) values of the provided source vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector4 set(final ReadOnlyVector4 source) { + setX(source.getX()); + setY(source.getY()); + setZ(source.getZ()); + setW(source.getW()); + return this; + } + + /** + * Sets the value of this vector to (0, 0, 0, 0) + * + * @return this vector for chaining + */ + public Vector4 zero() { + return set(0, 0, 0, 0); + } + + /** + * Adds the given values to those of this vector and returns them in store. + * + * @param x + * @param y + * @param z + * @param w + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x + x, this.y + y, this.z + z, this.w + w) + */ + @Override + public Vector4 add(final double x, final double y, final double z, final double w, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() + x, getY() + y, getZ() + z, getW() + w); + } + + /** + * Increments the values of this vector with the given x, y, z and w values. + * + * @param x + * @param y + * @param z + * @param w + * @return this vector for chaining + */ + public Vector4 addLocal(final double x, final double y, final double z, final double w) { + return set(getX() + x, getY() + y, getZ() + z, getW() + w); + } + + /** + * Adds the values of the given source vector to those of this vector and returns them in store. + * + * @param source + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x + source.x, this.y + source.y, this.z + source.z, this.w + source.w) + * @throws NullPointerException + * if source is null. + */ + @Override + public Vector4 add(final ReadOnlyVector4 source, final Vector4 store) { + return add(source.getX(), source.getY(), source.getZ(), source.getW(), store); + } + + /** + * Increments the values of this vector with the x, y, z and w values of the given vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector4 addLocal(final ReadOnlyVector4 source) { + return addLocal(source.getX(), source.getY(), source.getZ(), source.getW()); + } + + /** + * Subtracts the given values from those of this vector and returns them in store. + * + * @param x + * @param y + * @param z + * @param w + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x - x, this.y - y, this.z - z, this.w - w) + */ + @Override + public Vector4 subtract(final double x, final double y, final double z, final double w, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() - x, getY() - y, getZ() - z, getW() - w); + } + + /** + * Decrements the values of this vector by the given x, y, z and w values. + * + * @param x + * @param y + * @param z + * @param w + * @return this vector for chaining + */ + public Vector4 subtractLocal(final double x, final double y, final double z, final double w) { + return set(getX() - x, getY() - y, getZ() - z, getW() - w); + } + + /** + * Subtracts the values of the given source vector from those of this vector and returns them in store. + * + * @param source + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return (this.x - source.x, this.y - source.y, this.z - source.z, this.w - source.w) + * @throws NullPointerException + * if source is null. + */ + @Override + public Vector4 subtract(final ReadOnlyVector4 source, final Vector4 store) { + return subtract(source.getX(), source.getY(), source.getZ(), source.getW(), store); + } + + /** + * Decrements the values of this vector by the x, y, z and w values from the given source vector. + * + * @param source + * @return this vector for chaining + * @throws NullPointerException + * if source is null. + */ + public Vector4 subtractLocal(final ReadOnlyVector4 source) { + return subtractLocal(source.getX(), source.getY(), source.getZ(), source.getW()); + } + + /** + * Multiplies the values of this vector by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scalar, this.y * scalar, this.z * scalar, this.w * scalar) + */ + @Override + public Vector4 multiply(final double scalar, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() * scalar, getY() * scalar, getZ() * scalar, getW() * scalar); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scalar value. + * + * @param scalar + * @return this vector for chaining + */ + public Vector4 multiplyLocal(final double scalar) { + return set(getX() * scalar, getY() * scalar, getZ() * scalar, getW() * scalar); + } + + /** + * Multiplies the values of this vector by the given scalar value and returns the result in store. + * + * @param scale + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scale.x, this.y * scale.y, this.z * scale.z, this.w * scale.w) + */ + @Override + public Vector4 multiply(final ReadOnlyVector4 scale, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() * scale.getX(), getY() * scale.getY(), getZ() * scale.getZ(), getW() * scale.getW()); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scale values. + * + * @param scale + * @return this vector for chaining + */ + public Vector4 multiplyLocal(final ReadOnlyVector4 scale) { + return set(getX() * scale.getX(), getY() * scale.getY(), getZ() * scale.getZ(), getW() * scale.getW()); + } + + /** + * Multiplies the values of this vector by the given scalar value and returns the result in store. + * + * @param x + * @param y + * @param z + * @param w + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x * scale.x, this.y * scale.y, this.z * scale.z, this.w * scale.w) + */ + @Override + public Vector4 multiply(final double x, final double y, final double z, final double w, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() * x, getY() * y, getZ() * z, getW() * w); + } + + /** + * Internally modifies the values of this vector by multiplying them each by the given scale values. + * + * @param x + * @param y + * @param z + * @param w + * @return this vector for chaining + */ + public Vector4 multiplyLocal(final double x, final double y, final double z, final double w) { + return set(getX() * x, getY() * y, getZ() * z, getW() * w); + } + + /** + * Divides the values of this vector by the given scalar value and returns the result in store. + * + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scalar, this.y / scalar, this.z / scalar, this.w / scalar) + */ + @Override + public Vector4 divide(final double scalar, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() / scalar, getY() / scalar, getZ() / scalar, getW() / scalar); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scalar value. + * + * @param scalar + * @return this vector for chaining + * + * + * @throws ArithmeticException + * if scalar is 0 + */ + public Vector4 divideLocal(final double scalar) { + final double invScalar = 1.0 / scalar; + + return set(getX() * invScalar, getY() * invScalar, getZ() * invScalar, getW() * invScalar); + } + + /** + * Divides the values of this vector by the given scale values and returns the result in store. + * + * @param scale + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scale.x, this.y / scale.y, this.z / scale.z, this.w / scale.w) + */ + @Override + public Vector4 divide(final ReadOnlyVector4 scale, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() / scale.getX(), getY() / scale.getY(), getZ() / scale.getZ(), getW() / scale.getW()); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scale values. + * + * @param scale + * @return this vector for chaining + */ + public Vector4 divideLocal(final ReadOnlyVector4 scale) { + return set(getX() / scale.getX(), getY() / scale.getY(), getZ() / scale.getZ(), getW() / scale.getW()); + } + + /** + * Divides the values of this vector by the given scale values and returns the result in store. + * + * @param x + * @param y + * @param z + * @param w + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector (this.x / scale.x, this.y / scale.y, this.z / scale.z, this.w / scale.w) + */ + @Override + public Vector4 divide(final double x, final double y, final double z, final double w, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + return result.set(getX() / x, getY() / y, getZ() / z, getW() / w); + } + + /** + * Internally modifies the values of this vector by dividing them each by the given scale values. + * + * @param x + * @param y + * @param z + * @param w + * @return this vector for chaining + */ + public Vector4 divideLocal(final double x, final double y, final double z, final double w) { + return set(getX() / x, getY() / y, getZ() / z, getW() / w); + } + + /** + * + * Internally modifies this vector by multiplying its values with a given scale value, then adding a given "add" + * value. + * + * @param scale + * the value to multiply this vector by. + * @param add + * the value to add to the result + * @return this vector for chaining + */ + public Vector4 scaleAddLocal(final double scale, final Vector4 add) { + _x = _x * scale + add.getX(); + _y = _y * scale + add.getY(); + _z = _z * scale + add.getZ(); + _w = _w * scale + add.getW(); + return this; + } + + /** + * Scales this vector by multiplying its values with a given scale value, then adding a given "add" value. The + * result is store in the given store parameter. + * + * @param scale + * the value to multiply by. + * @param add + * the value to add + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return the store variable + */ + @Override + public Vector4 scaleAdd(final double scale, final ReadOnlyVector4 add, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + result.setX(_x * scale + add.getX()); + result.setY(_y * scale + add.getY()); + result.setZ(_z * scale + add.getZ()); + result.setW(_w * scale + add.getW()); + return result; + } + + /** + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return same as multiply(-1, store) + */ + @Override + public Vector4 negate(final Vector4 store) { + return multiply(-1, store); + } + + /** + * @return same as multiplyLocal(-1) + */ + public Vector4 negateLocal() { + return multiplyLocal(-1); + } + + /** + * Creates a new unit length vector from this one by dividing by length. If the length is 0, (ie, if the vector is + * 0, 0, 0, 0) then a new vector (0, 0, 0, 0) is returned. + * + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new unit vector (or 0, 0, 0, 0 if this unit is 0 length) + */ + @Override + public Vector4 normalize(final Vector4 store) { + final double lengthSq = lengthSquared(); + if (Math.abs(lengthSq) > MathUtils.EPSILON) { + return multiply(MathUtils.inverseSqrt(lengthSq), store); + } + + return store != null ? store.set(Vector4.ZERO) : new Vector4(Vector4.ZERO); + } + + /** + * Converts this vector into a unit vector by dividing it internally by its length. If the length is 0, (ie, if the + * vector is 0, 0, 0, 0) then no action is taken. + * + * @return this vector for chaining + */ + public Vector4 normalizeLocal() { + final double lengthSq = lengthSquared(); + if (Math.abs(lengthSq) > MathUtils.EPSILON) { + return multiplyLocal(MathUtils.inverseSqrt(lengthSq)); + } + + return this; + } + + /** + * Performs a linear interpolation between this vector and the given end vector, using the given scalar as a + * percent. iow, if changeAmnt is closer to 0, the result will be closer to the current value of this vector and if + * it is closer to 1, the result will be closer to the end value. The result is returned as a new vector object. + * + * @param endVec + * @param scalar + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector as described above. + * @throws NullPointerException + * if endVec is null. + */ + @Override + public Vector4 lerp(final ReadOnlyVector4 endVec, final double scalar, final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + final double x = (1.0 - scalar) * getX() + scalar * endVec.getX(); + final double y = (1.0 - scalar) * getY() + scalar * endVec.getY(); + final double z = (1.0 - scalar) * getZ() + scalar * endVec.getZ(); + final double w = (1.0 - scalar) * getW() + scalar * endVec.getW(); + return result.set(x, y, z, w); + } + + /** + * Performs a linear interpolation between this vector and the given end vector, using the given scalar as a + * percent. iow, if changeAmnt is closer to 0, the result will be closer to the current value of this vector and if + * it is closer to 1, the result will be closer to the end value. The result is stored back in this vector. + * + * @param endVec + * @param scalar + * @return this vector for chaining + * + * + * @throws NullPointerException + * if endVec is null. + */ + public Vector4 lerpLocal(final ReadOnlyVector4 endVec, final double scalar) { + setX((1.0 - scalar) * getX() + scalar * endVec.getX()); + setY((1.0 - scalar) * getY() + scalar * endVec.getY()); + setZ((1.0 - scalar) * getZ() + scalar * endVec.getZ()); + setW((1.0 - scalar) * getW() + scalar * endVec.getW()); + return this; + } + + /** + * Performs a linear interpolation between the given begin and end vectors, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the + * result will be closer to the end value. The result is returned as a new vector object. + * + * @param beginVec + * @param endVec + * @param scalar + * the scalar as a percent. + * @param store + * the vector to store the result in for return. If null, a new vector object is created and returned. + * @return a new vector as described above. + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public static Vector4 lerp(final ReadOnlyVector4 beginVec, final ReadOnlyVector4 endVec, final double scalar, + final Vector4 store) { + Vector4 result = store; + if (result == null) { + result = new Vector4(); + } + + final double x = (1.0 - scalar) * beginVec.getX() + scalar * endVec.getX(); + final double y = (1.0 - scalar) * beginVec.getY() + scalar * endVec.getY(); + final double z = (1.0 - scalar) * beginVec.getZ() + scalar * endVec.getZ(); + final double w = (1.0 - scalar) * beginVec.getW() + scalar * endVec.getW(); + return result.set(x, y, z, w); + } + + /** + * Performs a linear interpolation between the given begin and end vectors, using the given scalar as a percent. + * iow, if changeAmnt is closer to 0, the result will be closer to the begin value and if it is closer to 1, the + * result will be closer to the end value. The result is stored back in this vector. + * + * @param beginVec + * @param endVec + * @param changeAmnt + * the scalar as a percent. + * @return this vector for chaining + * + * + * @throws NullPointerException + * if beginVec or endVec are null. + */ + public Vector4 lerpLocal(final ReadOnlyVector4 beginVec, final ReadOnlyVector4 endVec, final double scalar) { + setX((1.0 - scalar) * beginVec.getX() + scalar * endVec.getX()); + setY((1.0 - scalar) * beginVec.getY() + scalar * endVec.getY()); + setZ((1.0 - scalar) * beginVec.getZ() + scalar * endVec.getZ()); + setW((1.0 - scalar) * beginVec.getW() + scalar * endVec.getW()); + return this; + } + + /** + * @return the magnitude or distance between the origin (0, 0, 0, 0) and the point described by this vector (x, y, + * z, w). Effectively the square root of the value returned by {@link #lengthSquared()}. + */ + @Override + public double length() { + return MathUtils.sqrt(lengthSquared()); + } + + /** + * @return the squared magnitude or squared distance between the origin (0, 0, 0, 0) and the point described by this + * vector (x, y, z, w) + */ + @Override + public double lengthSquared() { + return getX() * getX() + getY() * getY() + getZ() * getZ() + getW() * getW(); + } + + /** + * @param x + * @param y + * @param z + * @param w + * @return the squared distance between the point described by this vector and the given x, y, z, w point. When + * comparing the relative distance between two points it is usually sufficient to compare the squared + * distances, thus avoiding an expensive square root operation. + */ + @Override + public double distanceSquared(final double x, final double y, final double z, final double w) { + final double dx = getX() - x; + final double dy = getY() - y; + final double dz = getZ() - z; + final double dw = getW() - w; + return dx * dx + dy * dy + dz * dz + dw * dw; + } + + /** + * @param destination + * @return the squared distance between the point described by this vector and the given destination point. When + * comparing the relative distance between two points it is usually sufficient to compare the squared + * distances, thus avoiding an expensive square root operation. + * @throws NullPointerException + * if destination is null. + */ + @Override + public double distanceSquared(final ReadOnlyVector4 destination) { + return distanceSquared(destination.getX(), destination.getY(), destination.getZ(), destination.getW()); + } + + /** + * @param x + * @param y + * @param z + * @param w + * @return the distance between the point described by this vector and the given x, y, z, w point. + */ + @Override + public double distance(final double x, final double y, final double z, final double w) { + return MathUtils.sqrt(distanceSquared(x, y, z, w)); + } + + /** + * @param destination + * @return the distance between the point described by this vector and the given destination point. + * @throws NullPointerException + * if destination is null. + */ + @Override + public double distance(final ReadOnlyVector4 destination) { + return MathUtils.sqrt(distanceSquared(destination)); + } + + /** + * @param x + * @param y + * @param z + * @param w + * @return the dot product of this vector with the given x, y, z, w values. + */ + @Override + public double dot(final double x, final double y, final double z, final double w) { + return getX() * x + getY() * y + getZ() * z + getW() * w; + } + + /** + * @param vec + * @return the dot product of this vector with the x, y, z, w values of the given vector. + * @throws NullPointerException + * if vec is null. + */ + @Override + public double dot(final ReadOnlyVector4 vec) { + return dot(vec.getX(), vec.getY(), vec.getZ(), vec.getW()); + } + + /** + * Check a vector... if it is null or its doubles are NaN or infinite, return false. Else return true. + * + * @param vector + * the vector to check + * @return true or false as stated above. + */ + public static boolean isValid(final ReadOnlyVector4 vector) { + if (vector == null) { + return false; + } + if (Double.isNaN(vector.getX()) || Double.isNaN(vector.getY()) || Double.isNaN(vector.getZ()) + || Double.isNaN(vector.getW())) { + return false; + } + if (Double.isInfinite(vector.getX()) || Double.isInfinite(vector.getY()) || Double.isInfinite(vector.getZ()) + || Double.isInfinite(vector.getW())) { + return false; + } + return true; + } + + /** + * @return the string representation of this vector. + */ + @Override + public String toString() { + return "com.ardor3d.math.Vector4 [X=" + getX() + ", Y=" + getY() + ", Z=" + getZ() + ", W=" + getW() + "]"; + } + + /** + * @return returns a unique code for this vector object based on its values. If two vectors are numerically equal, + * they will return the same hash code value. + */ + @Override + public int hashCode() { + int result = 17; + + final long x = Double.doubleToLongBits(getX()); + result += 31 * result + (int) (x ^ x >>> 32); + + final long y = Double.doubleToLongBits(getY()); + result += 31 * result + (int) (y ^ y >>> 32); + + final long z = Double.doubleToLongBits(getZ()); + result += 31 * result + (int) (z ^ z >>> 32); + + final long w = Double.doubleToLongBits(getW()); + result += 31 * result + (int) (w ^ w >>> 32); + + return result; + } + + /** + * @param o + * the object to compare for equality + * @return true if this vector and the provided vector have the same x, y, z and w values. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReadOnlyVector4)) { + return false; + } + final ReadOnlyVector4 comp = (ReadOnlyVector4) o; + return getX() == comp.getX() && getY() == comp.getY() && getZ() == comp.getZ() && getW() == comp.getW(); + } + + // ///////////////// + // Method for Cloneable + // ///////////////// + + @Override + public Vector4 clone() { + return new Vector4(this); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Vector4> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(getX(), "x", 0); + capsule.write(getY(), "y", 0); + capsule.write(getZ(), "z", 0); + capsule.write(getW(), "w", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + setX(capsule.readDouble("x", 0)); + setY(capsule.readDouble("y", 0)); + setZ(capsule.readDouble("z", 0)); + setW(capsule.readDouble("w", 0)); + } + + // ///////////////// + // Methods for Externalizable + // ///////////////// + + /** + * Used with serialization. Not to be called manually. + * + * @param in + * ObjectInput + * @throws IOException + * @throws ClassNotFoundException + */ + @Override + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + setX(in.readDouble()); + setY(in.readDouble()); + setZ(in.readDouble()); + setW(in.readDouble()); + } + + /** + * Used with serialization. Not to be called manually. + * + * @param out + * ObjectOutput + * @throws IOException + */ + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeDouble(getX()); + out.writeDouble(getY()); + out.writeDouble(getZ()); + out.writeDouble(getW()); + } + + // ///////////////// + // Methods for creating temp variables (pooling) + // ///////////////// + + /** + * @return An instance of Vector4 that is intended for temporary use in calculations and so forth. Multiple calls to + * the method should return instances of this class that are not currently in use. + */ + public final static Vector4 fetchTempInstance() { + if (MathConstants.useMathPools) { + return Vector4.VEC_POOL.fetch(); + } else { + return new Vector4(); + } + } + + /** + * Releases a Vector4 back to be used by a future call to fetchTempInstance. TAKE CARE: this Vector4 object should + * no longer have other classes referencing it or "Bad Things" will happen. + * + * @param vec + * the Vector4 to release. + */ + public final static void releaseTempInstance(final Vector4 vec) { + if (MathConstants.useMathPools) { + Vector4.VEC_POOL.release(vec); + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/ArchimedeanSpiralFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/ArchimedeanSpiralFunction3D.java new file mode 100644 index 0000000..47c783a --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/ArchimedeanSpiralFunction3D.java @@ -0,0 +1,83 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.functions;
+
+import com.ardor3d.math.MathUtils;
+
+/**
+ * An implementation of an Archimedean spiral which supports any number of spiral arms and optional turbulence.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Archimedean_spiral">wikipedia entry</a>
+ */
+public class ArchimedeanSpiralFunction3D implements Function3D {
+
+ private static final int DEFAULT_ROUGHNESS = 1;
+ private static final double DEFAULT_FREQUENCY = 0.2;
+ private static final Function3D DEFAULT_TURBULENCE = new FbmFunction3D(Functions.simplexNoise(), DEFAULT_ROUGHNESS,
+ DEFAULT_FREQUENCY, 0.5, 2.0);
+
+ private final int _numArms;
+ private final Function3D _turbulenceFunction;
+
+ /**
+ * Create the function for the specified number of arms, and optionally use the default turbulence.
+ *
+ * @param numArms
+ * The number of arms of the spiral (1 or more).
+ * @param useDefaultTurbulence
+ * True if the default turbulence should be used; false for no turbulence.
+ */
+ public ArchimedeanSpiralFunction3D(final int numArms, final boolean useDefaultTurbulence) {
+ this(numArms, useDefaultTurbulence ? DEFAULT_TURBULENCE : null);
+ }
+
+ /**
+ * Create the function for the specified number of arms, with the specified turbulence.
+ *
+ * @param numArms
+ * The number of arms of the spiral (1 or more).
+ * @param turbulenceFunction
+ * The turbulence function to use (can be null).
+ */
+ public ArchimedeanSpiralFunction3D(final int numArms, final Function3D turbulenceFunction) {
+ _numArms = numArms;
+ _turbulenceFunction = turbulenceFunction;
+ }
+
+ /**
+ * Evaluate the function.
+ *
+ * @return A result which is generally, but not always, in the -1 to 1 range.
+ */
+ public double eval(final double x, final double y, final double z) {
+ final double radius = Math.sqrt(x * x + y * y);
+
+ double phi;
+ if (radius == 0.0) {
+ phi = 0.0;
+ } else {
+ if (x < 0.0) {
+ phi = 3.0 * MathUtils.HALF_PI - Math.asin(y / radius);
+ } else {
+ phi = MathUtils.HALF_PI + Math.asin(y / radius);
+ }
+ }
+
+ final double turbulence = (_turbulenceFunction != null) ? _turbulenceFunction.eval(x, y, z) : 0.0;
+
+ double value = z + radius + ((_numArms * phi) / MathUtils.TWO_PI) + turbulence;
+
+ value = value % 1.0;
+ value = (value * 2.0) - 1.0;
+
+ return value;
+ }
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/BrickGridFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/BrickGridFunction3D.java new file mode 100644 index 0000000..d66c133 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/BrickGridFunction3D.java @@ -0,0 +1,113 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.functions;
+
+import com.ardor3d.math.MathUtils;
+
+/**
+ * Creates a brick-like pattern in the XY plane. This is not really 3D, since it ignores the z factor.
+ *
+ * The brick and mortar values returned by this function can (and do by default) have some amount of variation for
+ * visual interest. The specified variation represents a maximum -- actual variation is determined randomly within this
+ * maximum variation. Since actual variation is determined randomly, the brick and mortar values are likely to change a
+ * bit for each instance of this class. In other words, if you do not want the variation to change at all, you should
+ * instantiate this class once and use the same object each time.
+ */
+public class BrickGridFunction3D extends GridPatternFunction3D {
+
+ private static final int DEFAULT_BRICK_LENGTH = 12;
+ private static final int DEFAULT_BRICK_HEIGHT = 6;
+ private static final int DEFAULT_MORTAR_THICKNESS = 1;
+ private static final double DEFAULT_BRICK_VALUE = 0.0;
+ private static final double DEFAULT_MORTAR_VALUE = 0.9;
+ private static final double DEFAULT_BRICK_VARIATION = 0.1;
+ private static final double DEFAULT_MORTAR_VARIATION = 0.05;
+
+ /**
+ * Create a brick pattern using all default values.
+ */
+ public BrickGridFunction3D() {
+ this(DEFAULT_BRICK_LENGTH, DEFAULT_BRICK_HEIGHT, DEFAULT_MORTAR_THICKNESS, DEFAULT_BRICK_VALUE,
+ DEFAULT_MORTAR_VALUE, DEFAULT_BRICK_VARIATION, DEFAULT_MORTAR_VARIATION);
+ }
+
+ /**
+ * Create a brick pattern with values specified by the caller.
+ *
+ * @param brickLength
+ * The number of grid cells used for the length of a brick (default 12).
+ * @param brickHeight
+ * The number of grid cells used for the height of a brick (default 6).
+ * @param mortarThickness
+ * The number of grid cells used for the thickness of mortar (default 1).
+ * @param brickValue
+ * The value returned from the function for a brick coordinate (default 0.0).
+ * @param mortarValue
+ * The value returned from the function for a mortar coordinate (default 0.9).
+ * @param brickVariationAmount
+ * The amount by which the brick value can vary (default 0.1). Use 0.0 for no variation.
+ * @param mortarVariationAmount
+ * The amount by which the mortar value can vary (default 0.05). Use 0.0 for no variation.
+ */
+ public BrickGridFunction3D(final int brickLength, final int brickHeight, final int mortarThickness,
+ final double brickValue, final double mortarValue, final double brickVariationAmount,
+ final double mortarVariationAmount) {
+ super(createBrickGrid(brickLength, brickHeight, mortarThickness, brickValue, mortarValue, brickVariationAmount,
+ mortarVariationAmount));
+ }
+
+ /**
+ * A private utility method to build the grid used to represent the bricks.
+ */
+ private static double[][] createBrickGrid(final int brickLength, final int brickHeight, final int mortarThickness,
+ final double brickValue, final double mortarValue, final double brickVariationAmount,
+ final double mortarVariationAmount) {
+ final int xTotal = brickLength + mortarThickness;
+ final int yTotal = (brickHeight * 2) + (mortarThickness * 2);
+ final double[][] grid = new double[xTotal][yTotal];
+
+ // for simplicity, initialize all cells to the brick-value since that is by far the most
+ // common value.
+ for (int x = 0; x < xTotal; x++) {
+ for (int y = 0; y < yTotal; y++) {
+ grid[x][y] = createGridValue(brickValue, brickVariationAmount);
+ }
+ }
+
+ // now put the mortar value into those cells that need it.
+ // first, create the horizontal mortar lines...
+ for (int x = 0; x < xTotal; x++) {
+ for (int y = brickHeight; y < (brickHeight + mortarThickness); y++) {
+ grid[x][y] = createGridValue(mortarValue, mortarVariationAmount);
+ grid[x][y + brickHeight + 1] = createGridValue(mortarValue, mortarVariationAmount);
+ }
+ }
+
+ // ... then create the vertical mortar lines.
+ for (int y = 0; y < brickHeight; y++) {
+ grid[xTotal / 2][y + brickHeight + 1] = createGridValue(mortarValue, mortarVariationAmount);
+ grid[xTotal - 1][y] = createGridValue(mortarValue, mortarVariationAmount);
+ }
+
+ return grid;
+ }
+
+ private static double createGridValue(final double baseValue, final double variationAmount) {
+ double gridValue = baseValue;
+
+ if (variationAmount > 0.0) {
+ final double variation = (MathUtils.nextRandomDouble() * (variationAmount * 2.0)) - variationAmount;
+ gridValue += variation;
+ }
+
+ return gridValue;
+ }
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/CheckerFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/CheckerFunction3D.java new file mode 100644 index 0000000..b782051 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/CheckerFunction3D.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import com.ardor3d.math.MathUtils; + +/** + * A simple checker board pattern, with each unit cube alternating between -1 and 1 in value. + */ +public class CheckerFunction3D implements Function3D { + + public double eval(final double x, final double y, final double z) { + if ((MathUtils.floor(x) + MathUtils.floor(y) + MathUtils.floor(z)) % 2 == 0) { + return -1; + } else { + return 1; + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/CloudsFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/CloudsFunction3D.java new file mode 100644 index 0000000..10dcb39 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/CloudsFunction3D.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +/** + * A variation of fBm that uses absolute value to recast the extreme ends to the upper range and values near 0 to the + * lower range. This bunches together values into clusters that can resemble clouds or cotton balls. + */ +public class CloudsFunction3D extends FbmFunction3D { + + public static final int MAX_OCTAVES = 32; + + public CloudsFunction3D(final Function3D source, final int octaves, final double frequency, + final double persistence, final double lacunarity) { + super(source, octaves, frequency, persistence, lacunarity); + } + + @Override + protected double getValue(final double dx, final double dy, final double dz) { + // Basically the "cloudy" |f(x)| you can find in perlin's docs and elsewhere, but rescaled to [-1, 1] to keep it + // in the range used by most of our function. + return 2.0 * Math.abs(getSource().eval(dx, dy, dz)) - 1.0; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/CylinderFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/CylinderFunction3D.java new file mode 100644 index 0000000..6ec23b0 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/CylinderFunction3D.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import com.ardor3d.math.MathUtils; + +/** + * Function describing a set of concentric rings, centered around the origin on the x/z plane, each ring stretching + * infinitely along the Y axis. The spacing between rings is controlled by the frequency. A higher frequency gives more + * rings in the same amount of space. + */ +public class CylinderFunction3D implements Function3D { + private double _frequency; + + /** + * Construct a new CylinderFunction3D with the given frequency + * + * @param frequency + * the number of rings per unit + */ + public CylinderFunction3D(final double frequency) { + setFrequency(frequency); + } + + public double eval(final double x, final double y, final double z) { + final double dx = x * _frequency; + final double dz = z * _frequency; + + // get the radius to our point -- see the equation of a circle + double radius = MathUtils.sqrt(dx * dx + dz * dz); + + // get fractional part + radius = radius - MathUtils.floor(radius); + + // now get the distance to the closest integer, radius is now [0, .5] + radius = Math.min(radius, 1 - radius); + + // return a value between -1 and 1, where 1 means the radius length was on an integer value and -1 means it was + // halfway between two integers. + return 1.0 - radius * 4; // [-1, 1] + } + + public void setFrequency(final double frequency) { + _frequency = frequency; + } + + public double getFrequency() { + return _frequency; + } + +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/FbmFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/FbmFunction3D.java new file mode 100644 index 0000000..7aa48b4 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/FbmFunction3D.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +/** + * An implementation of Fractional Brownian Motion (fBm), with configurable persistence and lacunarity (or tightness of + * the fractal). This function basically recursively adds the given source function (often a noise function) onto + * itself, shifting and scaling the sample point and value on each iteration (or octave). + * + * @see <a href="http://en.wikipedia.org/wiki/Fractional_Brownian_motion">wikipedia entry</a> + */ +public class FbmFunction3D implements Function3D { + + private Function3D _source; + private int _octaves; + private double _frequency; + private double _persistence; + private double _lacunarity; + + /** + * Construct a new FbmFunction with the given params. + * + * @param source + * the source function we will operate on. + * @param octaves + * the number of iterations we will perform. Called octaves because the default for each octave is double + * the frequency of the previous octave, a property shared by music tones. Generally a value between 4 + * and 8 is good. + * @param frequency + * a scale value applied to the incoming tuple before we apply fBm. It is the number of cycles per unit + * for the function. Default would be 1. + * @param persistence + * a scale value that determines how fast the amplitude decreases for each octave. Generally should be in + * the range [0, 1]. A lower persistence value will decrease the effect of each octave. A traditional + * value is 0.5 + * @param lacunarity + * a scale value that determines how fast the frequency increases for each octave (freq = prevFreq * + * lac.) A traditional value is 2. + */ + public FbmFunction3D(final Function3D source, final int octaves, final double frequency, final double persistence, + final double lacunarity) { + _source = source; + _octaves = octaves; + _frequency = frequency; + _persistence = persistence; + _lacunarity = lacunarity; + } + + public double eval(final double x, final double y, final double z) { + double sum = 0; + double dx = x * _frequency, dy = y * _frequency, dz = z * _frequency; + double dPersistence = 1; + for (int i = 0; i < _octaves; i++) { + final double value = getValue(dx, dy, dz); + + sum += dPersistence * value; + + dPersistence *= _persistence; + dx *= _lacunarity; + dy *= _lacunarity; + dz *= _lacunarity; + } + return sum; + } + + protected double getValue(final double dx, final double dy, final double dz) { + return _source.eval(dx, dy, dz); + } + + public Function3D getSource() { + return _source; + } + + public void setSource(final Function3D source) { + _source = source; + } + + public int getOctaves() { + return _octaves; + } + + public void setOctaves(final int octaves) { + _octaves = octaves; + } + + public double getFrequency() { + return _frequency; + } + + public void setFrequency(final double frequency) { + _frequency = frequency; + } + + public double getPersistence() { + return _persistence; + } + + public void setPersistence(final double persistence) { + _persistence = persistence; + } + + public double getLacunarity() { + return _lacunarity; + } + + public void setLacunarity(final double lacunarity) { + _lacunarity = lacunarity; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/Function3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/Function3D.java new file mode 100644 index 0000000..143d858 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/Function3D.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +/** + * Simple interface describing a function that receives a 3 value tuple and returns a value. + */ +public interface Function3D { + + /** + * @param x + * the 1st value in our tuple + * @param y + * the 2nd value in our tuple + * @param z + * the 3rd value in our tuple + * @return some value, generally (but not necessarily) in [-1, 1] + */ + double eval(double x, double y, double z); + +}
\ No newline at end of file diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/Functions.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/Functions.java new file mode 100644 index 0000000..b0ba25d --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/Functions.java @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyMatrix3; + +/** + * Utility class containing a set of easy to use functions. + */ +public class Functions { + + /** + * @param constant + * @return a function that will always return the given constant value regardless of input + */ + public static Function3D constant(final double constant) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return constant; + } + }; + } + + /** + * @param source + * @param scale + * @param bias + * @return a function that returns (src.eval * scale) + bias. + */ + public static Function3D scaleBias(final Function3D source, final double scale, final double bias) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return source.eval(x, y, z) * scale + bias; + } + }; + } + + /** + * @param source + * @return a function that returns |src.eval| + */ + public static Function3D abs(final Function3D source) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return Math.abs(source.eval(x, y, z)); + } + }; + } + + /** + * @param source + * @param min + * @param max + * @return a function that returns src.eval clamped to [min, max] + */ + public static Function3D clamp(final Function3D source, final double min, final double max) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return MathUtils.clamp(source.eval(x, y, z), min, max); + } + }; + } + + /** + * @param source + * @return a function that returns -(src.eval) + */ + public static Function3D invert(final Function3D source) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return -source.eval(x, y, z); + } + }; + } + + /** + * @param sourceA + * @param sourceB + * @return a function the returns srcA.eval + srcB.eval + */ + public static Function3D add(final Function3D sourceA, final Function3D sourceB) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return sourceA.eval(x, y, z) + sourceB.eval(x, y, z); + } + }; + } + + /** + * @param sourceA + * @param sourceB + * @return a function the returns srcA.eval * srcB.eval + */ + public static Function3D multiply(final Function3D sourceA, final Function3D sourceB) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return sourceA.eval(x, y, z) * sourceB.eval(x, y, z); + } + }; + } + + /** + * @param sourceA + * @param sourceB + * @return a function the returns min(srcA.eval, srcB.eval) + */ + public static Function3D min(final Function3D sourceA, final Function3D sourceB) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return Math.min(sourceA.eval(x, y, z), sourceB.eval(x, y, z)); + } + }; + } + + /** + * @param sourceA + * @param sourceB + * @return a function the returns max(srcA.eval, srcB.eval) + */ + public static Function3D max(final Function3D sourceA, final Function3D sourceB) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return Math.max(sourceA.eval(x, y, z), sourceB.eval(x, y, z)); + } + }; + } + + /** + * (1-amount) * srcA.eval + (amount) * srcB.eval + * + * @param sourceA + * @param sourceB + * @param amount + * @return a function the linear interpolation of srcA.eval and srcB.eval using the given amount. + */ + public static Function3D lerp(final Function3D sourceA, final Function3D sourceB, final double amount) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return MathUtils.lerp(amount, sourceA.eval(x, y, z), sourceB.eval(x, y, z)); + } + }; + } + + /** + * @param source + * @param rotation + * @return a function that rotates the 3 value tuple as a vector using the given matrix before feeding it to + * src.eval. + */ + public static Function3D rotateInput(final Function3D source, final ReadOnlyMatrix3 rotation) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + final Vector3 temp = Vector3.fetchTempInstance(); + temp.set(x, y, z); + rotation.applyPost(temp, temp); + final double val = source.eval(temp.getX(), temp.getY(), temp.getZ()); + Vector3.releaseTempInstance(temp); + return val; + } + }; + } + + /** + * @param source + * @param scaleX + * @param scaleY + * @param scaleZ + * @return a function that scales the 3 value tuple using the given values before feeding it to src.eval. + */ + public static Function3D scaleInput(final Function3D source, final double scaleX, final double scaleY, + final double scaleZ) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return source.eval(x * scaleX, y * scaleY, z * scaleZ); + } + }; + } + + /** + * @param source + * @param transX + * @param transY + * @param transZ + * @return a function that translates the 3 value tuple using the given values before feeding it to src.eval. + */ + public static Function3D translateInput(final Function3D source, final double transX, final double transY, + final double transZ) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + return source.eval(x + transX, y + transY, z + transZ); + } + }; + } + + /** + * @param source + * @param oldLow + * @param oldHigh + * @param newLow + * @param newHigh + * @return a function that maps src.eval from a given range onto a new range. + */ + public static Function3D remap(final Function3D source, final double oldLow, final double oldHigh, + final double newLow, final double newHigh) { + return new Function3D() { + public double eval(final double x, final double y, final double z) { + double val = source.eval(x, y, z); + // Zero out old domain + val -= oldLow; + val /= (oldHigh - oldLow); + // Shift to new domain + val *= (newHigh - newLow); + val += newLow; + return val; + } + }; + } + + /** + * @return a function that returns simplex noise. + */ + public static Function3D simplexNoise() { + return new Function3D() { + SimplexNoise noiseGenerator = new SimplexNoise(); + + public double eval(final double x, final double y, final double z) { + return noiseGenerator.noise(x, y, z); + } + }; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/GridPatternFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/GridPatternFunction3D.java new file mode 100644 index 0000000..b47d85f --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/GridPatternFunction3D.java @@ -0,0 +1,80 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.functions;
+
+/**
+ * Creates a pattern in the XY plane that is defined by a user-provided grid of values. This is not really 3D, since it
+ * ignores the z factor.
+ */
+public class GridPatternFunction3D implements Function3D {
+
+ private final double[][] _grid;
+ private final double _xScaleFactor;
+ private final double _yScaleFactor;
+
+ /**
+ * Create the grid pattern function. The x-scale and y-scale will both default to 1.0.
+ *
+ * @param grid
+ * A grid of values in the range of -1 to 1.
+ */
+ public GridPatternFunction3D(final double[][] grid) {
+ this(grid, 1.0, 1.0);
+ }
+
+ /**
+ * Create the grid pattern function.
+ *
+ * @param grid
+ * A grid of values in the range of -1 to 1.
+ * @param xScaleFactor
+ * The amount by which to scale grid cells along their x axis.
+ * @param yScaleFactor
+ * The amount by which to scale grid cells along their y axis.
+ */
+ public GridPatternFunction3D(final double[][] grid, final double xScaleFactor, final double yScaleFactor) {
+ _grid = grid;
+ _xScaleFactor = xScaleFactor;
+ _yScaleFactor = yScaleFactor;
+ }
+
+ /**
+ * Evaluate the x and y valus (ignores z) to determine the value to return.
+ *
+ * @return
+ */
+ public double eval(double x, double y, final double z) {
+ x = Math.abs(x);
+ y = Math.abs(y);
+
+ double scaledX = x / _xScaleFactor;
+ double scaledY = y / _yScaleFactor;
+
+ final int numXCells = _grid.length;
+ final int numYCells = _grid[0].length;
+ scaledX -= Math.floor(scaledX / numXCells) * numXCells;
+ scaledY -= Math.floor(scaledY / numYCells) * numYCells;
+
+ final int gridX = (int) (Math.floor(scaledX) % numXCells);
+ final int gridY = (int) (Math.floor(scaledY) % numYCells);
+
+ return getCellValue(gridX, gridY, scaledX, scaledY);
+ }
+
+ /**
+ * Gets a value from the user-defined grid. This is an extension point for subclasses which may need to perform a
+ * calculation of some type on that value before returning it. The default implementation simply returns the value
+ * from the grid at gridX/gridY.
+ */
+ protected double getCellValue(final int gridX, final int gridY, final double scaledX, final double scaledY) {
+ return _grid[gridX][gridY];
+ }
+}
\ No newline at end of file diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/HexGridFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/HexGridFunction3D.java new file mode 100644 index 0000000..2aa2ce3 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/HexGridFunction3D.java @@ -0,0 +1,89 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.functions;
+
+import com.ardor3d.math.MathUtils;
+
+/**
+ * Creates a hexagon pattern in the XY plane. This is not really 3D, since it ignores the z factor.
+ */
+public class HexGridFunction3D extends GridPatternFunction3D {
+
+ private static final double EQUILATERAL_TRIANGLE_HEIGHT = Math.sqrt(3.0) / 2.0;
+
+ private static final double[][] FIXED_VALUES = new double[6][6];
+
+ static {
+ FIXED_VALUES[0][0] = FIXED_VALUES[5][0] = -1;
+ FIXED_VALUES[0][1] = FIXED_VALUES[5][1] = 0;
+ FIXED_VALUES[0][2] = FIXED_VALUES[5][2] = 0;
+ FIXED_VALUES[0][3] = FIXED_VALUES[5][3] = 1;
+ FIXED_VALUES[0][4] = FIXED_VALUES[5][4] = 1;
+ FIXED_VALUES[0][5] = FIXED_VALUES[5][5] = -1;
+
+ FIXED_VALUES[2][0] = FIXED_VALUES[3][0] = 1;
+ FIXED_VALUES[2][1] = FIXED_VALUES[3][1] = 1;
+ FIXED_VALUES[2][2] = FIXED_VALUES[3][2] = -1;
+ FIXED_VALUES[2][3] = FIXED_VALUES[3][3] = -1;
+ FIXED_VALUES[2][4] = FIXED_VALUES[3][4] = 0;
+ FIXED_VALUES[2][5] = FIXED_VALUES[3][5] = 0;
+
+ FIXED_VALUES[1][0] = FIXED_VALUES[4][0] = -1;
+ FIXED_VALUES[1][1] = FIXED_VALUES[4][1] = 1;
+ FIXED_VALUES[1][2] = FIXED_VALUES[4][2] = 0;
+ FIXED_VALUES[1][3] = FIXED_VALUES[4][3] = -1;
+ FIXED_VALUES[1][4] = FIXED_VALUES[4][4] = 1;
+ FIXED_VALUES[1][5] = FIXED_VALUES[4][5] = 0;
+ }
+
+ private static final double[] ALTERNATE_VALUES = { 1, 0, -1, 1, 0, -1 };
+
+ public HexGridFunction3D() {
+ super(FIXED_VALUES, 0.5, EQUILATERAL_TRIANGLE_HEIGHT);
+ }
+
+ @Override
+ protected double getCellValue(final int gridX, final int gridY, final double scaledX, final double scaledY) {
+ double value = 0.0;
+
+ switch (gridX) {
+ case 0:
+ case 2:
+ case 3:
+ case 5:
+ value = super.getCellValue(gridX, gridY, scaledX, scaledY);
+ break;
+ case 1:
+ case 4:
+ double x = scaledX - gridX;
+ final double y = scaledY - gridY;
+
+ // If a grid block has a negative slope, flip it.
+ if (((gridX + gridY) % 2) == 1) {
+ x = 1.0 - x;
+ }
+
+ if (x == 0.0) {
+ x = MathUtils.ZERO_TOLERANCE;
+ }
+
+ final boolean lowAngle = ((y / x) < 1.0);
+ if (lowAngle) {
+ value = super.getCellValue(gridX, gridY, scaledX, scaledY);
+ } else {
+ value = ALTERNATE_VALUES[gridY];
+ }
+ break;
+ }
+
+ return value;
+ }
+}
\ No newline at end of file diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/MandelbrotFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/MandelbrotFunction3D.java new file mode 100644 index 0000000..d81c105 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/MandelbrotFunction3D.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +/** + * A function that returns the famous Mandelbrot set. This is not really 3d... The z factor is ignored. + * + * @see <a href="http://en.wikipedia.org/wiki/Mandelbrot_set">wikipedia entry</a> + */ +public class MandelbrotFunction3D implements Function3D { + + private int _iterations; + + public MandelbrotFunction3D(final int iterations) { + setIterations(iterations); + } + + public double eval(final double x, final double y, final double z) { + double dx = 0; + double dy = 0; + + int iteration = 0; + + while (dx * dx + dy * dy <= (2 * 2) && iteration < _iterations) { + final double xtemp = dx * dx - dy * dy + x; + dy = 2 * dx * dy + y; + dx = xtemp; + iteration++; + } + + if (iteration == _iterations) { + return 1; + } else { + // returns a value in [-1, 1] + return 2 * (iteration / (double) _iterations) - 1; + } + } + + public void setIterations(final int iterations) { + _iterations = iterations; + } + + public int getIterations() { + return _iterations; + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/MapperFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/MapperFunction3D.java new file mode 100644 index 0000000..5502f7f --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/MapperFunction3D.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.math.MathUtils; + +/** + * This function takes a "map" function and uses the value it returns at a certain point to look up and evaluate another + * function from a set of ranged functions. + */ +public class MapperFunction3D implements Function3D { + + private Function3D _mapFunction; + private final List<Entry> _entries = new ArrayList<Entry>(); + private double _domainStart, _domainEnd; + + /** + * Construct a mapper function using the given map function and a start and end for the domain we'll use. + * + * @param mapFunction + * @param domainStart + * @param domainEnd + */ + public MapperFunction3D(final Function3D mapFunction, final double domainStart, final double domainEnd) { + _mapFunction = mapFunction; + _domainStart = domainStart; + _domainEnd = domainEnd; + } + + public double eval(final double x, final double y, final double z) { + // grab a value from our map function. + final double mappingValue = MathUtils.clamp(_mapFunction.eval(x, y, z), _domainStart, _domainEnd); + + // Walk through our entries until we get which entries we are working with (1 or 2 if we are blending between + // entries) + Entry prev = null, current = null, next = _entries.get(0); + double start, end = _domainStart + next.offsetStart; + for (int i = 1; i <= _entries.size(); i++) { + prev = current; + current = next; + next = i < _entries.size() ? _entries.get(i) : null; + start = end; + end = next != null ? end + next.offsetStart : _domainEnd; + + // check if we are in the right main interval + if (mappingValue <= end) { + // check if we are in the ease-in region and have a prev function. + if (prev != null && mappingValue < start + current.easeIn) { + // ...interpolate with a quintic S-curve. + final double ratio = (mappingValue - start) / current.easeIn; + final double amount = MathUtils.scurve5(ratio); + return MathUtils.lerp(amount, prev.source.eval(x, y, z), current.source.eval(x, y, z)); + } + // check if we are in the ease-out region and have a next function. + else if (next != null && mappingValue > end - current.easeOut) { + // ...interpolate with a quintic S-curve. + final double ratio = ((mappingValue - end) / current.easeOut) + 1; + final double amount = MathUtils.scurve5(ratio); + return MathUtils.lerp(amount, current.source.eval(x, y, z), next.source.eval(x, y, z)); + } + // else we are in the no-ease region (or did not have a next/prev to blend with)... + else { + // ...so just return source func + return current.source.eval(x, y, z); + } + } + } + // outside of range... just return an eval of the very last function + return current.source.eval(x, y, z); + } + + public Function3D getMapFunction() { + return _mapFunction; + } + + public void setMapFunction(final Function3D mapFunction) { + _mapFunction = mapFunction; + } + + public double getDomainStart() { + return _domainStart; + } + + public void setDomainStart(final double start) { + _domainStart = start; + } + + public double getDomainEnd() { + return _domainEnd; + } + + public void setDomainEnd(final double end) { + _domainEnd = end; + } + + /** + * Add a new source function to the end of our set of ranged functions. Our place in the range is based on the place + * of the previous source function and the offsetStart provided. + * + * @param source + * the new function to add + * @param offsetStart + * our offset from the previous entry + * @param easeIn + * a "fade in" range between the previous function and this function, starting at offsetState. Over this + * range we'll lerp between the two functions. + * @param easeOut + * a "fade out" range between this function and the next function, starting at the next function's + * offsetStart - easeOut. Over this range we'll lerp between the two functions. + */ + public void addFunction(final Function3D source, final double offsetStart, final double easeIn, final double easeOut) { + final Entry e = new Entry(); + e.source = source; + e.offsetStart = offsetStart; + e.easeIn = easeIn; + e.easeOut = easeOut; + _entries.add(e); + } + + public void removeFunction(final int index) { + _entries.remove(index); + } + + public void clearFunctions() { + _entries.clear(); + } + + private static class Entry { + double offsetStart; + double easeIn, easeOut; + Function3D source; + } + +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/MeshFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/MeshFunction3D.java new file mode 100644 index 0000000..c314c4c --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/MeshFunction3D.java @@ -0,0 +1,45 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.functions;
+
+/**
+ * Function which creates a diagonal grid of rounded 'holes'. The term "Mesh" is used here in its common form, and does
+ * not refer to 3D geometry... the resulting pattern simply resembles a screen or sieve.
+ */
+public class MeshFunction3D implements Function3D {
+
+ private final double _lineSize;
+
+ /**
+ * Create a MeshFunction3D with a default lineSize of 0.5.
+ */
+ public MeshFunction3D() {
+ this(0.5);
+ }
+
+ /**
+ * Create a MeshFunction3D with the specified lineSize. Lower lineSize values will result in thinner lines.
+ *
+ * @param lineSize
+ * The line size, which should be greater than zero.
+ */
+ public MeshFunction3D(final double lineSize) {
+ _lineSize = lineSize;
+ }
+
+ /**
+ * Evaluate the function.
+ */
+ public double eval(final double x, final double y, final double z) {
+ final double value = (Math.sin(x) + Math.sin(y) + Math.sin(z)) / _lineSize;
+ return ((value * value) * 2.0) - 1.0;
+ }
+}
\ No newline at end of file diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/RidgeFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/RidgeFunction3D.java new file mode 100644 index 0000000..669c09b --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/RidgeFunction3D.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import com.ardor3d.math.MathUtils; + +/** + * Based on Multifractal code originally written by F. Kenton "Doc Mojo" Musgrave, 1998. Modified by jas for use with + * libnoise, then modified for use in Ardor3D in 2009. + */ +public class RidgeFunction3D implements Function3D { + + public static final int MAX_OCTAVES = 32; + + private Function3D _source; + private double _octaves = 6; + private double _frequency = 1; + private double _lacunarity = 2; + private double _gain = 2; + private double _offset = 1; + private double _h = 1; + private final double[] _spectralWeights = new double[MAX_OCTAVES]; + + public RidgeFunction3D() { + setSource(Functions.simplexNoise()); + updateWeights(); + } + + public RidgeFunction3D(final Function3D source, final double octaves, final double frequency, + final double lacunarity) { + setSource(source); + setOctaves(octaves); + setFrequency(frequency); + // to not trigger weight calc + _lacunarity = lacunarity; + updateWeights(); + } + + public double eval(final double x, final double y, final double z) { + double value = 0, signal = 0, weight = 1; + double dx = x * _frequency, dy = y * _frequency, dz = z * _frequency; + for (int i = 0; i < _octaves; i++) { + signal = _source.eval(dx, dy, dz); + + signal = Math.abs(signal); + signal = _offset - signal; + + // Square the signal to increase the sharpness of the ridges. + signal *= signal; + + // The weighting from the previous octave is applied to the signal. + // Larger values have higher weights, producing sharp points along the + // ridges. + signal *= weight; + + // Weight successive contributions by the previous signal. (clamp to [0, 1]) + weight = MathUtils.clamp(signal * _gain, 0, 1); + + // Add the signal to the output value. + value += signal * _spectralWeights[i]; + + // Next! + dx *= _lacunarity; + dy *= _lacunarity; + dz *= _lacunarity; + } + + return (value * 1.25) - 1.0; + } + + public Function3D getSource() { + return _source; + } + + public void setSource(final Function3D source) { + _source = source; + } + + public double getOctaves() { + return _octaves; + } + + public void setOctaves(final double octaves) { + _octaves = octaves; + } + + public double getFrequency() { + return _frequency; + } + + public void setFrequency(final double frequency) { + _frequency = frequency; + } + + public double getLacunarity() { + return _lacunarity; + } + + public void setLacunarity(final double lacunarity) { + _lacunarity = lacunarity; + updateWeights(); + } + + public double getGain() { + return _gain; + } + + public void setGain(final double gain) { + _gain = gain; + } + + public double getOffset() { + return _offset; + } + + public void setOffset(final double offset) { + _offset = offset; + } + + public double getH() { + return _h; + } + + public void setH(final double h) { + _h = h; + updateWeights(); + } + + private void updateWeights() { + double dFreq = 1; + for (int i = 0; i < MAX_OCTAVES; i++) { + // Compute weight for each frequency. + _spectralWeights[i] = Math.pow(dFreq, -_h); + dFreq *= _lacunarity; + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/SimplexNoise.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/SimplexNoise.java new file mode 100644 index 0000000..ee0ec96 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/SimplexNoise.java @@ -0,0 +1,423 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import java.util.BitSet; + +import com.ardor3d.math.MathUtils; + +/** + * Simplex noise in 2D, 3D and 4D + * <p> + * Based on the implementation of Ken Perlin's Simplex Noise done by Stefan Gustavson in 2005. See the paper at + * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf + * </p> + */ +public class SimplexNoise { + + private static int grad3[][] = { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, + { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 } }; + + private static int grad4[][] = { { 0, 1, 1, 1 }, { 0, 1, 1, -1 }, { 0, 1, -1, 1 }, { 0, 1, -1, -1 }, + { 0, -1, 1, 1 }, { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 }, { 1, 0, 1, 1 }, { 1, 0, 1, -1 }, + { 1, 0, -1, 1 }, { 1, 0, -1, -1 }, { -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, { -1, 0, -1, -1 }, + { 1, 1, 0, 1 }, { 1, 1, 0, -1 }, { 1, -1, 0, 1 }, { 1, -1, 0, -1 }, { -1, 1, 0, 1 }, { -1, 1, 0, -1 }, + { -1, -1, 0, 1 }, { -1, -1, 0, -1 }, { 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 }, + { -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 } }; + + // A lookup table to traverse the simplex around a given point in 4D. + // Details can be found where this table is used, in the 4D noise method. + private static int simplex[][] = { { 0, 1, 2, 3 }, { 0, 1, 3, 2 }, { 0, 0, 0, 0 }, { 0, 2, 3, 1 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 2, 3, 0 }, { 0, 2, 1, 3 }, { 0, 0, 0, 0 }, { 0, 3, 1, 2 }, + { 0, 3, 2, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 1, 3, 2, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 1, 2, 0, 3 }, { 0, 0, 0, 0 }, { 1, 3, 0, 2 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 2, 3, 0, 1 }, { 2, 3, 1, 0 }, { 1, 0, 2, 3 }, { 1, 0, 3, 2 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 2, 0, 3, 1 }, { 0, 0, 0, 0 }, { 2, 1, 3, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 2, 0, 1, 3 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 3, 0, 1, 2 }, + { 3, 0, 2, 1 }, { 0, 0, 0, 0 }, { 3, 1, 2, 0 }, { 2, 1, 0, 3 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, { 3, 1, 0, 2 }, { 0, 0, 0, 0 }, { 3, 2, 0, 1 }, { 3, 2, 1, 0 } }; + + private static double dot(final int g[], final double x, final double y) { + return g[0] * x + g[1] * y; + } + + private static double dot(final int g[], final double x, final double y, final double z) { + return g[0] * x + g[1] * y + g[2] * z; + } + + private static double dot(final int g[], final double x, final double y, final double z, final double w) { + return g[0] * x + g[1] * y + g[2] * z + g[3] * w; + } + + // To remove the need for index wrapping, double the permutation table length + private final int perm[] = new int[512]; + + public SimplexNoise() { + final int p[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, + 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, + 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, + 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, + 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, + 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, + 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, + 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, + 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, + 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, + 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 }; + + setPermutations(p); + } + + public void setPermutations(final int[] permutations) { + if (permutations.length != 256) { + throw new IllegalArgumentException( + "not enough data, permutations should contain 0 thru 255 each exactly once"); + } + + final BitSet set = new BitSet(256); + for (final int i : permutations) { + set.set(i); + } + + if (set.cardinality() != 256) { + throw new IllegalArgumentException("permutations should contain 0 thru 255 each exactly once"); + } + + resetPerm(permutations); + } + + private void resetPerm(final int[] p) { + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 255]; + } + } + + // 2D simplex noise + public double noise(final double xin, final double yin) { + double n0, n1, n2; // Noise contributions from the three corners + // Skew the input space to determine which simplex cell we're in + final double F2 = 0.5 * (Math.sqrt(3.0) - 1.0); + final double s = (xin + yin) * F2; // Hairy factor for 2D + final int i = (int) MathUtils.floor(xin + s); + final int j = (int) MathUtils.floor(yin + s); + final double G2 = (3.0 - Math.sqrt(3.0)) / 6.0; + final double t = (i + j) * G2; + final double X0 = i - t; // Unskew the cell origin back to (x,y) space + final double Y0 = j - t; + final double x0 = xin - X0; // The x,y distances from the cell origin + final double y0 = yin - Y0; + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + final double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + final double y1 = y0 - j1 + G2; + final double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords + final double y2 = y0 - 1.0 + 2.0 * G2; + // Work out the hashed gradient indices of the three simplex corners + final int ii = i & 255; + final int jj = j & 255; + final int gi0 = perm[ii + perm[jj]] % 12; + final int gi1 = perm[ii + i1 + perm[jj + j1]] % 12; + final int gi2 = perm[ii + 1 + perm[jj + 1]] % 12; + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient + } + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + // 3D simplex noise + public double noise(final double xin, final double yin, final double zin) { + double n0, n1, n2, n3; // Noise contributions from the four corners + // Skew the input space to determine which simplex cell we're in + final double F3 = 1.0 / 3.0; + final double s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D + final int i = (int) MathUtils.floor(xin + s); + final int j = (int) MathUtils.floor(yin + s); + final int k = (int) MathUtils.floor(zin + s); + final double G3 = 1.0 / 6.0; // Very nice and simple unskew factor, too + final double t = (i + j + k) * G3; + final double X0 = i - t; // Unskew the cell origin back to (x,y,z) space + final double Y0 = j - t; + final double Z0 = k - t; + final double x0 = xin - X0; // The x,y,z distances from the cell origin + final double y0 = yin - Y0; + final double z0 = zin - Z0; + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } else { // x0<y0 + if (y0 < z0) { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 0; + j2 = 1; + k2 = 1; + } // Z Y X order + else if (x0 < z0) { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 0; + j2 = 1; + k2 = 1; + } // Y Z X order + else { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // Y X Z order + } + // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), + // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and + // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where + // c = 1/6. + final double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords + final double y1 = y0 - j1 + G3; + final double z1 = z0 - k1 + G3; + final double x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords + final double y2 = y0 - j2 + 2.0 * G3; + final double z2 = z0 - k2 + 2.0 * G3; + final double x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords + final double y3 = y0 - 1.0 + 3.0 * G3; + final double z3 = z0 - 1.0 + 3.0 * G3; + // Work out the hashed gradient indices of the four simplex corners + final int ii = i & 255; + final int jj = j & 255; + final int kk = k & 255; + final int gi0 = perm[ii + perm[jj + perm[kk]]] % 12; + final int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12; + final int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12; + final int gi3 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12; + // Calculate the contribution from the four corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + if (t3 < 0) { + n3 = 0.0; + } else { + t3 *= t3; + n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to stay just inside [-1,1] + return 32.0 * (n0 + n1 + n2 + n3); + } + + // 4D simplex noise + double noise(final double x, final double y, final double z, final double w) { + + // The skewing and unskewing factors are hairy again for the 4D case + final double F4 = (Math.sqrt(5.0) - 1.0) / 4.0; + final double G4 = (5.0 - Math.sqrt(5.0)) / 20.0; + double n0, n1, n2, n3, n4; // Noise contributions from the five corners + // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in + final double s = (x + y + z + w) * F4; // Factor for 4D skewing + final int i = (int) MathUtils.floor(x + s); + final int j = (int) MathUtils.floor(y + s); + final int k = (int) MathUtils.floor(z + s); + final int l = (int) MathUtils.floor(w + s); + final double t = (i + j + k + l) * G4; // Factor for 4D unskewing + final double X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space + final double Y0 = j - t; + final double Z0 = k - t; + final double W0 = l - t; + final double x0 = x - X0; // The x,y,z,w distances from the cell origin + final double y0 = y - Y0; + final double z0 = z - Z0; + final double w0 = w - W0; + // For the 4D case, the simplex is a 4D shape I won't even try to describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex we’re in. + // First, six pair-wise comparisons are performed between each possible pair + // of the four coordinates, and the results are used to add up binary bits + // for an integer index. + final int c1 = (x0 > y0) ? 32 : 0; + final int c2 = (x0 > z0) ? 16 : 0; + final int c3 = (y0 > z0) ? 8 : 0; + final int c4 = (x0 > w0) ? 4 : 0; + final int c5 = (y0 > w0) ? 2 : 0; + final int c6 = (z0 > w0) ? 1 : 0; + final int c = c1 + c2 + c3 + c4 + c5 + c6; + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w + // impossible. Only the 24 indices which have non-zero entries make any sense. + // We use a thresholding to set the coordinates in turn from the largest magnitude. + // The number 3 in the "simplex" array is at the position of the largest coordinate. + i1 = simplex[c][0] >= 3 ? 1 : 0; + j1 = simplex[c][1] >= 3 ? 1 : 0; + k1 = simplex[c][2] >= 3 ? 1 : 0; + l1 = simplex[c][3] >= 3 ? 1 : 0; + // The number 2 in the "simplex" array is at the second largest coordinate. + i2 = simplex[c][0] >= 2 ? 1 : 0; + j2 = simplex[c][1] >= 2 ? 1 : 0; + k2 = simplex[c][2] >= 2 ? 1 : 0; + l2 = simplex[c][3] >= 2 ? 1 : 0; + // The number 1 in the "simplex" array is at the second smallest coordinate. + i3 = simplex[c][0] >= 1 ? 1 : 0; + j3 = simplex[c][1] >= 1 ? 1 : 0; + k3 = simplex[c][2] >= 1 ? 1 : 0; + l3 = simplex[c][3] >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to look that up. + final double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + final double y1 = y0 - j1 + G4; + final double z1 = z0 - k1 + G4; + final double w1 = w0 - l1 + G4; + final double x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords + final double y2 = y0 - j2 + 2.0 * G4; + final double z2 = z0 - k2 + 2.0 * G4; + final double w2 = w0 - l2 + 2.0 * G4; + final double x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords + final double y3 = y0 - j3 + 3.0 * G4; + final double z3 = z0 - k3 + 3.0 * G4; + final double w3 = w0 - l3 + 3.0 * G4; + final double x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords + final double y4 = y0 - 1.0 + 4.0 * G4; + final double z4 = z0 - 1.0 + 4.0 * G4; + final double w4 = w0 - 1.0 + 4.0 * G4; + // Work out the hashed gradient indices of the five simplex corners + final int ii = i & 255; + final int jj = j & 255; + final int kk = k & 255; + final int ll = l & 255; + final int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + final int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + final int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + final int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + final int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + // Calculate the contribution from the five corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 < 0) { + n0 = 0.0; + } else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 < 0) { + n1 = 0.0; + } else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 < 0) { + n2 = 0.0; + } else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 < 0) { + n3 = 0.0; + } else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 < 0) { + n4 = 0.0; + } else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } +}
\ No newline at end of file diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/TurbulenceFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/TurbulenceFunction3D.java new file mode 100644 index 0000000..be32008 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/TurbulenceFunction3D.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +/** + * Function that uses a fBm function to distort incoming coordinates on each of the 3 axis before feeding them to a + * source function for evaluation. The amount of distortion depends on the given power, roughness and frequency. + */ +public class TurbulenceFunction3D implements Function3D { + + private double _power; + private Function3D _source; + private final FbmFunction3D _distortModule; + + /** + * Construct a new turbulence function with the given values. + * + * @param source + * the source function to send our distorted coordinates to. + * @param power + * a scalar that controls how much the distortion is applied to the input coordinates. 0 == no + * distortion. + * @param roughness + * how "choppy" the distortion is. Lower values produce more gradual shifts in distortion whereas higher + * values produce more choppy transitions. + * @param frequency + * how rapidly the distortion value changes. + */ + public TurbulenceFunction3D(final Function3D source, final double power, final int roughness, final double frequency) { + _power = power; + _source = source; + _distortModule = new FbmFunction3D(Functions.simplexNoise(), roughness, frequency, 0.5, 2.0); + } + + public double eval(final double x, final double y, final double z) { + // tweak the incoming x, y, and z with some magic numbers to prevent singularities as integer boundaries. + final double x0 = x + .1985; + final double y0 = y + .9958; + final double z0 = z + .5284; + + final double x1 = x + .4106; + final double y1 = y + .2672; + final double z1 = z + .9529; + + final double x2 = x + .8297; + final double y2 = y + .1921; + final double z2 = z + .7123; + + // Use tweaked values to feed our distortion module. Use our power field to scale the amount of distortion and + // add it to the original values. + final double xDistort = x + (_distortModule.eval(x0, y0, z0) * _power); + final double yDistort = y + (_distortModule.eval(x1, y1, z1) * _power); + final double zDistort = z + (_distortModule.eval(x2, y2, z2) * _power); + + // Get the output value at the distorted location + return _source.eval(xDistort, yDistort, zDistort); + } + + public double getPower() { + return _power; + } + + public void setPower(final double power) { + _power = power; + } + + public Function3D getSource() { + return _source; + } + + public void setSource(final Function3D source) { + _source = source; + } + + public void setRoughness(final int roughness) { + _distortModule.setOctaves(roughness); + } + + public void setFrequency(final double frequency) { + _distortModule.setFrequency(frequency); + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/functions/VoroniFunction3D.java b/ardor3d-math/src/main/java/com/ardor3d/math/functions/VoroniFunction3D.java new file mode 100644 index 0000000..f17ba34 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/functions/VoroniFunction3D.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.math.functions; + +import java.util.HashMap; +import java.util.Map; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; + +/** + * Function that produces a <a href="http://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi graph</a> by placing a random + * point in every 1x1x1 unit cube in space and then finding the closest of these points at each eval location. + * + */ +public class VoroniFunction3D implements Function3D { + + private static final int SEARCH_RADIUS = 2; // can miss corner cases with only a radius of 1 + + private double _frequency = 1; + private boolean _useDistance = false; + private double _displacement = 1; + private int _seed = 0; + + // A cache for cube values + private final Map<Key, Vector3> _points = new HashMap<Key, Vector3>(); + + /** + * Construct with default values. + */ + public VoroniFunction3D() {} + + /** + * Construct a new Voronoi graph function. + * + * @param frequency + * used to modulate input coordinates + * @param displacement + * use to modulate the contribution of the random points in each unit cube. + * @param useDistance + * if true, we will add the distance from the closest point to our output, giving us variation across the + * cell. + * @param seed + * the random seed value to give to our integer random function. + */ + public VoroniFunction3D(final double frequency, final double displacement, final boolean useDistance, final int seed) { + _frequency = frequency; + _displacement = displacement; + _useDistance = useDistance; + _seed = seed; + } + + public double eval(final double x, final double y, final double z) { + final double dx = x * _frequency, dy = y * _frequency, dz = z * _frequency; + + // find which integer based unit cube we're in + final int ix = (int) MathUtils.floor(dx), iy = (int) MathUtils.floor(dy), iz = (int) MathUtils.floor(dz); + + final Key k = new Key(); + final Vector3 minPoint = new Vector3(); + double nearestSq = Double.MAX_VALUE; + // Each cube has a point... Walk through all nearby cubes and see where our closest point lies. + for (int a = ix - SEARCH_RADIUS; a <= ix + SEARCH_RADIUS; a++) { + k.x = a; + for (int b = iy - SEARCH_RADIUS; b <= iy + SEARCH_RADIUS; b++) { + k.y = b; + for (int c = iz - SEARCH_RADIUS; c <= iz + SEARCH_RADIUS; c++) { + k.z = c; + Vector3 point = _points.get(k); + if (point == null) { + final double pX = a + point(a, b, c, _seed); + final double pY = b + point(a, b, c, _seed + 1); + final double pZ = c + point(a, b, c, _seed + 2); + point = new Vector3(pX, pY, pZ); + // cache for future lookups + _points.put(new Key(k), point); + } + final double xDist = point.getX() - dx; + final double yDist = point.getY() - dy; + final double zDist = point.getZ() - dz; + final double distSq = xDist * xDist + yDist * yDist + zDist * zDist; + + // check distance + if (distSq < nearestSq) { + nearestSq = distSq; + minPoint.set(point); + } + } + } + } + + double value; + if (_useDistance) { + // Determine the distance to the nearest point. + value = MathUtils.sqrt(nearestSq); + } else { + value = 0.0; + } + + // Return the calculated distance + with the displacement value applied using the value of our cube. + return value + + (_displacement * point(MathUtils.floor(minPoint.getXf()), MathUtils.floor(minPoint.getYf()), + MathUtils.floor(minPoint.getZf()), 0)); + } + + /** + * Calc the "random" point in unit cube that starts at the given coords. + */ + private double point(final int unitX, final int unitY, final int unitZ, final int seed) { + int n = (4241 * unitX + 7817 * unitY + 38261 * unitZ + 1979 * seed) & 0x7fffffff; + n = (n >> 13) ^ n; + return 1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / (double) 0x40000000; + } + + public double getFrequency() { + return _frequency; + } + + public void setFrequency(final double frequency) { + _frequency = frequency; + } + + public boolean isUseDistance() { + return _useDistance; + } + + public void setUseDistance(final boolean useDistance) { + _useDistance = useDistance; + } + + public double getDisplacement() { + return _displacement; + } + + public void setDisplacement(final double displacement) { + _displacement = displacement; + } + + public int getSeed() { + return _seed; + } + + public void setSeed(final int seed) { + _seed = seed; + } + + private static class Key { + int x, y, z; + + public Key() {} + + public Key(final Key k) { + x = k.x; + y = k.y; + z = k.z; + } + + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + x; + result += 31 * result + y; + result += 31 * result + z; + + return result; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Key)) { + return false; + } + final Key comp = (Key) o; + return x == comp.x && y == comp.y && z == comp.z; + } + } +} diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyColorRGBA.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyColorRGBA.java new file mode 100644 index 0000000..fda4c4e --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyColorRGBA.java @@ -0,0 +1,56 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.ColorRGBA;
+
+public interface ReadOnlyColorRGBA {
+
+ float getRed();
+
+ float getGreen();
+
+ float getBlue();
+
+ float getAlpha();
+
+ float getValue(int index);
+
+ float[] toArray(float[] store);
+
+ ColorRGBA clamp(ColorRGBA store);
+
+ int asIntARGB();
+
+ int asIntRGBA();
+
+ ColorRGBA add(float r, float g, float b, float a, ColorRGBA store);
+
+ ColorRGBA add(ReadOnlyColorRGBA source, ColorRGBA store);
+
+ ColorRGBA subtract(float r, float g, float b, float a, ColorRGBA store);
+
+ ColorRGBA subtract(ReadOnlyColorRGBA source, ColorRGBA store);
+
+ ColorRGBA multiply(float scalar, ColorRGBA store);
+
+ ColorRGBA multiply(ReadOnlyColorRGBA scale, ColorRGBA store);
+
+ ColorRGBA divide(float scalar, ColorRGBA store);
+
+ ColorRGBA divide(ReadOnlyColorRGBA scale, ColorRGBA store);
+
+ ColorRGBA lerp(ReadOnlyColorRGBA endColor, float scalar, ColorRGBA store);
+
+ String asHexRRGGBBAA();
+
+ ColorRGBA clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3.java new file mode 100644 index 0000000..c2bd892 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3.java @@ -0,0 +1,19 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Line3;
+
+public interface ReadOnlyLine3 extends ReadOnlyLine3Base {
+
+ Line3 clone();
+
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3Base.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3Base.java new file mode 100644 index 0000000..4a0d544 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3Base.java @@ -0,0 +1,23 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyLine3Base {
+
+ ReadOnlyVector3 getOrigin();
+
+ ReadOnlyVector3 getDirection();
+
+ double distanceSquared(ReadOnlyVector3 point, Vector3 store);
+
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLineSegment3.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLineSegment3.java new file mode 100644 index 0000000..961e0a1 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLineSegment3.java @@ -0,0 +1,20 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.LineSegment3;
+
+public interface ReadOnlyLineSegment3 extends ReadOnlyLine3Base {
+
+ double getExtent();
+
+ LineSegment3 clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix3.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix3.java new file mode 100644 index 0000000..e000e7a --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix3.java @@ -0,0 +1,90 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyMatrix3 {
+
+ double getValue(int row, int column);
+
+ float getValuef(int row, int column);
+
+ boolean isIdentity();
+
+ Vector3 getColumn(int index, Vector3 store);
+
+ Vector3 getRow(int index, Vector3 store);
+
+ DoubleBuffer toDoubleBuffer(DoubleBuffer store);
+
+ DoubleBuffer toDoubleBuffer(DoubleBuffer store, boolean rowMajor);
+
+ FloatBuffer toFloatBuffer(FloatBuffer store);
+
+ FloatBuffer toFloatBuffer(FloatBuffer store, boolean rowMajor);
+
+ double[] toArray(double[] store);
+
+ double[] toArray(double[] store, boolean rowMajor);
+
+ double[] toAngles(double[] store);
+
+ Matrix3 multiply(ReadOnlyMatrix3 matrix, Matrix3 store);
+
+ Vector3 applyPre(ReadOnlyVector3 vec, Vector3 store);
+
+ Vector3 applyPost(ReadOnlyVector3 vec, Vector3 store);
+
+ Matrix3 multiplyDiagonalPre(ReadOnlyVector3 vec, Matrix3 store);
+
+ Matrix3 multiplyDiagonalPost(ReadOnlyVector3 vec, Matrix3 store);
+
+ Matrix3 add(ReadOnlyMatrix3 matrix, Matrix3 store);
+
+ Matrix3 subtract(ReadOnlyMatrix3 matrix, Matrix3 store);
+
+ Matrix3 scale(ReadOnlyVector3 scale, Matrix3 store);
+
+ Matrix3 transpose(Matrix3 store);
+
+ Matrix3 invert(Matrix3 store);
+
+ Matrix3 adjugate(Matrix3 store);
+
+ double determinant();
+
+ boolean isOrthonormal();
+
+ Matrix3 clone();
+
+ double getM00();
+
+ double getM01();
+
+ double getM02();
+
+ double getM10();
+
+ double getM11();
+
+ double getM12();
+
+ double getM20();
+
+ double getM21();
+
+ double getM22();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix4.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix4.java new file mode 100644 index 0000000..860d168 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix4.java @@ -0,0 +1,105 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+
+public interface ReadOnlyMatrix4 {
+
+ double getValue(int row, int column);
+
+ float getValuef(int row, int column);
+
+ boolean isIdentity();
+
+ Vector4 getColumn(int index, Vector4 store);
+
+ Vector4 getRow(int index, Vector4 store);
+
+ DoubleBuffer toDoubleBuffer(DoubleBuffer store);
+
+ DoubleBuffer toDoubleBuffer(DoubleBuffer store, boolean rowMajor);
+
+ FloatBuffer toFloatBuffer(FloatBuffer store);
+
+ FloatBuffer toFloatBuffer(FloatBuffer store, boolean rowMajor);
+
+ double[] toArray(double[] store);
+
+ double[] toArray(double[] store, boolean rowMajor);
+
+ Matrix4 multiply(ReadOnlyMatrix4 matrix, Matrix4 store);
+
+ Vector4 applyPre(ReadOnlyVector4 vec, Vector4 store);
+
+ Vector4 applyPost(ReadOnlyVector4 vec, Vector4 store);
+
+ Vector3 applyPostPoint(ReadOnlyVector3 point, Vector3 store);
+
+ Vector3 applyPostVector(ReadOnlyVector3 vector, Vector3 store);
+
+ Matrix4 multiplyDiagonalPre(ReadOnlyVector4 vec, Matrix4 store);
+
+ Matrix4 multiplyDiagonalPost(ReadOnlyVector4 vec, Matrix4 store);
+
+ Matrix4 add(ReadOnlyMatrix4 matrix, Matrix4 store);
+
+ Matrix4 subtract(ReadOnlyMatrix4 matrix, Matrix4 store);
+
+ Matrix4 scale(ReadOnlyVector4 scale, Matrix4 store);
+
+ Matrix4 transpose(Matrix4 store);
+
+ Matrix4 invert(Matrix4 store);
+
+ Matrix4 adjugate(Matrix4 store);
+
+ double determinant();
+
+ Matrix4 clone();
+
+ double getM00();
+
+ double getM01();
+
+ double getM02();
+
+ double getM03();
+
+ double getM10();
+
+ double getM11();
+
+ double getM12();
+
+ double getM13();
+
+ double getM20();
+
+ double getM21();
+
+ double getM22();
+
+ double getM23();
+
+ double getM30();
+
+ double getM31();
+
+ double getM32();
+
+ double getM33();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyPlane.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyPlane.java new file mode 100644 index 0000000..297fe3b --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyPlane.java @@ -0,0 +1,46 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyPlane {
+
+ public enum Side {
+ /**
+ * On the side of the plane opposite of the plane's normal vector.
+ */
+ Inside,
+
+ /**
+ * On the same side of the plane as the plane's normal vector.
+ */
+ Outside,
+
+ /**
+ * Not on either side - in other words, on the plane itself.
+ */
+ Neither;
+ }
+
+ double getConstant();
+
+ ReadOnlyVector3 getNormal();
+
+ double pseudoDistance(ReadOnlyVector3 point);
+
+ Side whichSide(ReadOnlyVector3 point);
+
+ Vector3 reflectVector(ReadOnlyVector3 unitVector, Vector3 store);
+
+ Plane clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyQuaternion.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyQuaternion.java new file mode 100644 index 0000000..44c7897 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyQuaternion.java @@ -0,0 +1,79 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyQuaternion {
+
+ double getX();
+
+ double getY();
+
+ double getZ();
+
+ double getW();
+
+ float getXf();
+
+ float getYf();
+
+ float getZf();
+
+ float getWf();
+
+ double[] toArray(double[] store);
+
+ double[] toEulerAngles(double[] store);
+
+ Matrix3 toRotationMatrix(Matrix3 store);
+
+ Matrix4 toRotationMatrix(Matrix4 store);
+
+ Vector3 getRotationColumn(int index, Vector3 store);
+
+ double toAngleAxis(Vector3 axisStore);
+
+ Quaternion normalize(Quaternion store);
+
+ Quaternion conjugate(Quaternion store);
+
+ Quaternion add(ReadOnlyQuaternion quat, Quaternion store);
+
+ Quaternion subtract(ReadOnlyQuaternion quat, Quaternion store);
+
+ Quaternion multiply(double scalar, Quaternion store);
+
+ Quaternion multiply(ReadOnlyQuaternion quat, Quaternion store);
+
+ Vector3 apply(ReadOnlyVector3 vec, Vector3 store);
+
+ Vector3[] toAxes(Vector3 axes[]);
+
+ Quaternion slerp(ReadOnlyQuaternion endQuat, double changeAmnt, Quaternion store);
+
+ double magnitudeSquared();
+
+ double magnitude();
+
+ double dot(double x, double y, double z, double w);
+
+ double dot(ReadOnlyQuaternion quat);
+
+ boolean isIdentity();
+
+ Quaternion clone();
+
+ boolean strictEquals(Object o);
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRay3.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRay3.java new file mode 100644 index 0000000..78391cf --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRay3.java @@ -0,0 +1,37 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyRay3 extends ReadOnlyLine3Base {
+
+ double getDistanceToPrimitive(Vector3[] worldVertices);
+
+ boolean intersects(final Vector3[] polygonVertices, final Vector3 locationStore);
+
+ boolean intersectsTriangle(ReadOnlyVector3 pointA, ReadOnlyVector3 pointB, ReadOnlyVector3 pointC,
+ Vector3 locationStore);
+
+ boolean intersectsTrianglePlanar(ReadOnlyVector3 pointA, ReadOnlyVector3 pointB, ReadOnlyVector3 pointC,
+ Vector3 locationStore);
+
+ boolean intersectsQuad(ReadOnlyVector3 pointA, ReadOnlyVector3 pointB, ReadOnlyVector3 pointC,
+ ReadOnlyVector3 pointD, Vector3 locationStore);
+
+ boolean intersectsQuadPlanar(ReadOnlyVector3 pointA, ReadOnlyVector3 pointB, ReadOnlyVector3 pointC,
+ ReadOnlyVector3 pointD, Vector3 locationStore);
+
+ boolean intersectsPlane(ReadOnlyPlane plane, Vector3 locationStore);
+
+ Ray3 clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle2.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle2.java new file mode 100644 index 0000000..1ef21f2 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle2.java @@ -0,0 +1,41 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Rectangle2;
+
+public interface ReadOnlyRectangle2 {
+
+ /**
+ * @return the x coordinate of the origin of this rectangle.
+ */
+ int getX();
+
+ /**
+ * @return the y coordinate of the origin of this rectangle.
+ */
+ int getY();
+
+ /**
+ * @return the width of this rectangle.
+ */
+ int getWidth();
+
+ /**
+ * @return the height of this rectangle.
+ */
+ int getHeight();
+
+ /**
+ * @return a new instance of Rectangle2 with the same value as this object.
+ */
+ Rectangle2 clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle3.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle3.java new file mode 100644 index 0000000..cc6a603 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle3.java @@ -0,0 +1,27 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Rectangle3;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyRectangle3 {
+
+ ReadOnlyVector3 getA();
+
+ ReadOnlyVector3 getB();
+
+ ReadOnlyVector3 getC();
+
+ Vector3 random(Vector3 result);
+
+ Rectangle3 clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRing.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRing.java new file mode 100644 index 0000000..a877fd1 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRing.java @@ -0,0 +1,29 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Ring;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyRing {
+
+ ReadOnlyVector3 getCenter();
+
+ ReadOnlyVector3 getUp();
+
+ double getInnerRadius();
+
+ double getOuterRadius();
+
+ Vector3 random(Vector3 store);
+
+ Ring clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTransform.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTransform.java new file mode 100644 index 0000000..eb8a922 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTransform.java @@ -0,0 +1,78 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyTransform {
+ ReadOnlyMatrix3 getMatrix();
+
+ ReadOnlyVector3 getTranslation();
+
+ ReadOnlyVector3 getScale();
+
+ boolean isIdentity();
+
+ boolean isRotationMatrix();
+
+ boolean isUniformScale();
+
+ Vector3 applyForward(Vector3 point);
+
+ Vector3 applyForward(ReadOnlyVector3 point, Vector3 store);
+
+ Vector3 applyInverse(Vector3 point);
+
+ Vector3 applyInverse(ReadOnlyVector3 point, Vector3 store);
+
+ Vector3 applyForwardVector(Vector3 vector);
+
+ Vector3 applyForwardVector(ReadOnlyVector3 vector, Vector3 store);
+
+ Vector3 applyInverseVector(Vector3 vector);
+
+ Vector3 applyInverseVector(ReadOnlyVector3 vector, Vector3 store);
+
+ Transform multiply(ReadOnlyTransform transformBy, Transform store);
+
+ Transform invert(Transform store);
+
+ Matrix4 getHomogeneousMatrix(Matrix4 store);
+
+ /**
+ * Populates an nio double buffer with data from this transform to use as a model view matrix in OpenGL. This is
+ * done as efficiently as possible, not touching positions 3, 7, 11 and 15.
+ *
+ * @param store
+ * double buffer to store in. Assumes a size of 16 and that position 3, 7 and 11 are already set as 0.0
+ * and 15 is already 1.0.
+ */
+ void getGLApplyMatrix(DoubleBuffer store);
+
+ /**
+ * Populates an nio float buffer with data from this transform to use as a model view matrix in OpenGL. This is done
+ * as efficiently as possible, not touching positions 3, 7, 11 and 15.
+ *
+ * @param store
+ * float buffer to store in. Assumes a size of 16 and that position 3, 7 and 11 are already set as 0.0f
+ * and 15 is already 1.0f.
+ */
+ void getGLApplyMatrix(FloatBuffer store);
+
+ Transform clone();
+
+ boolean strictEquals(Object o);
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTriangle.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTriangle.java new file mode 100644 index 0000000..0ca359e --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTriangle.java @@ -0,0 +1,31 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Triangle;
+
+public interface ReadOnlyTriangle {
+ int getIndex();
+
+ ReadOnlyVector3 get(int index);
+
+ ReadOnlyVector3 getA();
+
+ ReadOnlyVector3 getB();
+
+ ReadOnlyVector3 getC();
+
+ ReadOnlyVector3 getNormal();
+
+ ReadOnlyVector3 getCenter();
+
+ Triangle clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector2.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector2.java new file mode 100644 index 0000000..51e3c34 --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector2.java @@ -0,0 +1,82 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Vector2;
+
+public interface ReadOnlyVector2 {
+
+ double getX();
+
+ double getY();
+
+ float getXf();
+
+ float getYf();
+
+ double getValue(int index);
+
+ double[] toArray(double[] store);
+
+ Vector2 add(double x, double y, Vector2 store);
+
+ Vector2 add(ReadOnlyVector2 source, Vector2 store);
+
+ Vector2 subtract(double x, double y, Vector2 store);
+
+ Vector2 subtract(ReadOnlyVector2 source, Vector2 store);
+
+ Vector2 multiply(double scalar, Vector2 store);
+
+ Vector2 multiply(ReadOnlyVector2 scale, Vector2 store);
+
+ Vector2 multiply(double x, double y, Vector2 store);
+
+ Vector2 divide(double scalar, Vector2 store);
+
+ Vector2 divide(ReadOnlyVector2 scale, Vector2 store);
+
+ Vector2 divide(double x, double y, Vector2 store);
+
+ Vector2 scaleAdd(double scale, ReadOnlyVector2 add, Vector2 store);
+
+ Vector2 negate(Vector2 store);
+
+ Vector2 normalize(Vector2 store);
+
+ Vector2 rotateAroundOrigin(double angle, boolean clockwise, Vector2 store);
+
+ Vector2 lerp(ReadOnlyVector2 endVec, double scalar, Vector2 store);
+
+ double length();
+
+ double lengthSquared();
+
+ double distanceSquared(double x, double y);
+
+ double distanceSquared(ReadOnlyVector2 destination);
+
+ double distance(double x, double y);
+
+ double distance(ReadOnlyVector2 destination);
+
+ double dot(double x, double y);
+
+ double dot(ReadOnlyVector2 vec);
+
+ double getPolarAngle();
+
+ double angleBetween(ReadOnlyVector2 otherVector);
+
+ double smallestAngleBetween(ReadOnlyVector2 otherVector);
+
+ Vector2 clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector3.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector3.java new file mode 100644 index 0000000..60cb0ed --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector3.java @@ -0,0 +1,84 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Vector3;
+
+public interface ReadOnlyVector3 {
+
+ double getX();
+
+ double getY();
+
+ double getZ();
+
+ float getXf();
+
+ float getYf();
+
+ float getZf();
+
+ double getValue(int index);
+
+ Vector3 add(double x, double y, double z, Vector3 store);
+
+ Vector3 add(ReadOnlyVector3 source, Vector3 store);
+
+ Vector3 subtract(double x, double y, double z, Vector3 store);
+
+ Vector3 subtract(ReadOnlyVector3 source, Vector3 store);
+
+ Vector3 multiply(double scalar, Vector3 store);
+
+ Vector3 multiply(ReadOnlyVector3 scale, Vector3 store);
+
+ Vector3 multiply(double x, double y, double z, Vector3 store);
+
+ Vector3 divide(double scalar, Vector3 store);
+
+ Vector3 divide(ReadOnlyVector3 scale, Vector3 store);
+
+ Vector3 divide(double x, double y, double z, Vector3 store);
+
+ Vector3 scaleAdd(double scale, ReadOnlyVector3 add, Vector3 store);
+
+ Vector3 negate(Vector3 store);
+
+ Vector3 normalize(Vector3 store);
+
+ Vector3 lerp(ReadOnlyVector3 endVec, double scalar, Vector3 store);
+
+ double length();
+
+ double lengthSquared();
+
+ double distanceSquared(double x, double y, double z);
+
+ double distanceSquared(ReadOnlyVector3 destination);
+
+ double distance(double x, double y, double z);
+
+ double distance(ReadOnlyVector3 destination);
+
+ double dot(double x, double y, double z);
+
+ double dot(ReadOnlyVector3 vec);
+
+ Vector3 cross(double x, double y, double z, Vector3 store);
+
+ Vector3 cross(ReadOnlyVector3 vec, Vector3 store);
+
+ double smallestAngleBetween(ReadOnlyVector3 otherVector);
+
+ double[] toArray(double[] store);
+
+ Vector3 clone();
+}
diff --git a/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector4.java b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector4.java new file mode 100644 index 0000000..3d549fe --- /dev/null +++ b/ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector4.java @@ -0,0 +1,82 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math.type;
+
+import com.ardor3d.math.Vector4;
+
+public interface ReadOnlyVector4 {
+
+ double getX();
+
+ double getY();
+
+ double getZ();
+
+ double getW();
+
+ float getXf();
+
+ float getYf();
+
+ float getZf();
+
+ float getWf();
+
+ double getValue(int index);
+
+ Vector4 add(double x, double y, double z, double w, Vector4 store);
+
+ Vector4 add(ReadOnlyVector4 source, Vector4 store);
+
+ Vector4 subtract(double x, double y, double z, double w, Vector4 store);
+
+ Vector4 subtract(ReadOnlyVector4 source, Vector4 store);
+
+ Vector4 multiply(double scalar, Vector4 store);
+
+ Vector4 multiply(ReadOnlyVector4 scale, Vector4 store);
+
+ Vector4 multiply(double x, double y, double z, double w, Vector4 store);
+
+ Vector4 divide(double scalar, Vector4 store);
+
+ Vector4 divide(ReadOnlyVector4 scale, Vector4 store);
+
+ Vector4 divide(double x, double y, double z, double w, Vector4 store);
+
+ Vector4 scaleAdd(double scale, ReadOnlyVector4 add, Vector4 store);
+
+ Vector4 negate(Vector4 store);
+
+ Vector4 normalize(Vector4 store);
+
+ Vector4 lerp(ReadOnlyVector4 endVec, double scalar, Vector4 store);
+
+ double length();
+
+ double lengthSquared();
+
+ double distanceSquared(double x, double y, double z, double w);
+
+ double distanceSquared(ReadOnlyVector4 destination);
+
+ double distance(double x, double y, double z, double w);
+
+ double distance(ReadOnlyVector4 destination);
+
+ double dot(double x, double y, double z, double w);
+
+ double dot(ReadOnlyVector4 vec);
+
+ double[] toArray(double[] store);
+
+ Vector4 clone();
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestColorRGBA.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestColorRGBA.java new file mode 100644 index 0000000..84e2c7d --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestColorRGBA.java @@ -0,0 +1,387 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestColorRGBA {
+
+ @Test
+ public void test() {
+ final ColorRGBA clr1 = new ColorRGBA();
+ assertTrue(1f == clr1.getRed());
+ assertTrue(1f == clr1.getGreen());
+ assertTrue(1f == clr1.getBlue());
+ assertTrue(1f == clr1.getAlpha());
+ }
+
+ @Test
+ public void testGetSet() {
+ final ColorRGBA clr1 = new ColorRGBA();
+ clr1.setRed(0f);
+ assertTrue(clr1.getRed() == 0.0f);
+ clr1.setRed(Float.POSITIVE_INFINITY);
+ assertTrue(clr1.getRed() == Float.POSITIVE_INFINITY);
+ clr1.setRed(Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getRed() == Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getValue(0) == Float.NEGATIVE_INFINITY);
+
+ clr1.setGreen(0);
+ assertTrue(clr1.getGreen() == 0.0);
+ clr1.setGreen(Float.POSITIVE_INFINITY);
+ assertTrue(clr1.getGreen() == Float.POSITIVE_INFINITY);
+ clr1.setGreen(Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getGreen() == Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getValue(1) == Float.NEGATIVE_INFINITY);
+
+ clr1.setBlue(0);
+ assertTrue(clr1.getBlue() == 0.0);
+ clr1.setBlue(Float.POSITIVE_INFINITY);
+ assertTrue(clr1.getBlue() == Float.POSITIVE_INFINITY);
+ clr1.setBlue(Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getBlue() == Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getValue(2) == Float.NEGATIVE_INFINITY);
+
+ clr1.setAlpha(0);
+ assertTrue(clr1.getAlpha() == 0.0);
+ clr1.setAlpha(Float.POSITIVE_INFINITY);
+ assertTrue(clr1.getAlpha() == Float.POSITIVE_INFINITY);
+ clr1.setAlpha(Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getAlpha() == Float.NEGATIVE_INFINITY);
+ assertTrue(clr1.getValue(3) == Float.NEGATIVE_INFINITY);
+
+ clr1.set((float) Math.PI, (float) Math.PI, (float) Math.PI, (float) Math.PI);
+ assertTrue(clr1.getRed() == (float) Math.PI);
+ assertTrue(clr1.getGreen() == (float) Math.PI);
+ assertTrue(clr1.getBlue() == (float) Math.PI);
+ assertTrue(clr1.getAlpha() == (float) Math.PI);
+
+ final ColorRGBA clr2 = new ColorRGBA(ColorRGBA.BLACK);
+ clr2.set(clr1);
+ assertEquals(clr1, clr2);
+
+ clr1.setValue(0, 1);
+ clr1.setValue(1, 1);
+ clr1.setValue(2, 1);
+ clr1.setValue(3, 1);
+ assertEquals(ColorRGBA.WHITE, clr1);
+
+ clr1.zero();
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+
+ // catch a few expected exceptions
+ try {
+ clr2.getValue(4);
+ fail("getValue(4) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ clr2.getValue(-1);
+ fail("getValue(-1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ clr2.setValue(-1, 0);
+ fail("setValue(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ clr2.setValue(4, 0);
+ fail("setValue(4, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ // above exceptions shouldn't have altered vec2
+ assertEquals(new ColorRGBA((float) Math.PI, (float) Math.PI, (float) Math.PI, (float) Math.PI), clr2);
+ }
+
+ @Test
+ public void testToArray() {
+ final ColorRGBA clr1 = new ColorRGBA(ColorRGBA.DARK_GRAY);
+
+ final float[] farray = clr1.toArray(null);
+ final float[] farray2 = clr1.toArray(new float[4]);
+ assertNotNull(farray);
+ assertNotNull(farray2);
+ assertTrue(farray.length == 4);
+ assertTrue(farray[0] == .2f);
+ assertTrue(farray[1] == .2f);
+ assertTrue(farray[2] == .2f);
+ assertTrue(farray[3] == 1f);
+
+ try {
+ clr1.toArray(new float[1]);
+ fail("toFloatArray(d[1]) should have thrown ArrayIndexOutOfBoundsException.");
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testClamp() {
+ final ColorRGBA clr1 = new ColorRGBA(-1, -1, -1, -1);
+ final ColorRGBA clr2 = clr1.clamp(new ColorRGBA());
+ final ColorRGBA clr3 = clr1.clamp(null);
+ assertNotNull(clr2);
+ assertNotNull(clr3);
+ assertTrue(clr2.getRed() == 0);
+ assertTrue(clr2.getGreen() == 0);
+ assertTrue(clr2.getBlue() == 0);
+ assertTrue(clr2.getAlpha() == 0);
+
+ clr1.set(2, .5f, 1, 0);
+ clr1.clamp(clr2);
+ assertTrue(clr2.getRed() == 1);
+ assertTrue(clr2.getGreen() == .5f);
+ assertTrue(clr2.getBlue() == 1);
+ assertTrue(clr2.getAlpha() == 0);
+
+ clr1.set(2, 2, 2, 2);
+ clr1.clampLocal();
+ assertTrue(clr1.getRed() == 1);
+ assertTrue(clr1.getGreen() == 1);
+ assertTrue(clr1.getBlue() == 1);
+ assertTrue(clr1.getAlpha() == 1);
+
+ clr1.set(0.5f, 0.5f, 0.5f, 0.5f);
+ assertEquals(clr1, clr1.clamp(null));
+ }
+
+ @Test
+ public void testRandomColor() {
+ final ColorRGBA clr1 = new ColorRGBA(0, 0, 0, 0);
+ MathUtils.setRandomSeed(0);
+ ColorRGBA.randomColor(clr1);
+ assertEquals(new ColorRGBA(0.73096776f, 0.831441f, 0.24053639f, 1.0f), clr1);
+ final ColorRGBA clr2 = ColorRGBA.randomColor(null);
+ assertEquals(new ColorRGBA(0.6063452f, 0.6374174f, 0.30905056f, 1.0f), clr2);
+ }
+
+ @Test
+ public void testIntColor() {
+ assertTrue(ColorRGBA.BLACK.asIntARGB() == -16777216);
+ assertTrue(ColorRGBA.BLACK.asIntRGBA() == 255);
+ assertTrue(ColorRGBA.RED.asIntARGB() == -65536);
+ assertTrue(ColorRGBA.RED.asIntRGBA() == -16776961);
+
+ assertEquals(ColorRGBA.BLACK, new ColorRGBA().fromIntARGB(-16777216));
+ assertEquals(ColorRGBA.BLACK, new ColorRGBA().fromIntRGBA(255));
+ assertEquals(ColorRGBA.RED, new ColorRGBA().fromIntARGB(-65536));
+ assertEquals(ColorRGBA.RED, new ColorRGBA().fromIntRGBA(-16776961));
+ }
+
+ @Test
+ public void testHexColor() {
+ assertEquals("#00000000", ColorRGBA.BLACK_NO_ALPHA.asHexRRGGBBAA());
+ assertEquals("#412819ff", ColorRGBA.BROWN.asHexRRGGBBAA());
+ assertEquals("#fb8200ff", ColorRGBA.ORANGE.asHexRRGGBBAA());
+
+ assertEquals(ColorRGBA.BROWN, ColorRGBA.parseColor("#412819ff", new ColorRGBA()));
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, ColorRGBA.parseColor("#00", null));
+ assertEquals(ColorRGBA.WHITE, ColorRGBA.parseColor("#F", null));
+ assertEquals(ColorRGBA.BLACK, ColorRGBA.parseColor("#0F", null));
+ assertEquals(ColorRGBA.BLUE, ColorRGBA.parseColor("#00F", null));
+ assertEquals(ColorRGBA.YELLOW, ColorRGBA.parseColor("#FF0F", null));
+ assertEquals(ColorRGBA.MAGENTA, ColorRGBA.parseColor("#FF00FF", null));
+ assertEquals(ColorRGBA.CYAN, ColorRGBA.parseColor("#00FFFFFF", null));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testHexFail1() {
+ ColorRGBA.parseColor("000", null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testHexFail2() {
+ ColorRGBA.parseColor("#000000000000000000000", null);
+ }
+
+ @Test
+ public void testClone() {
+ final ColorRGBA clr1 = new ColorRGBA();
+ final ColorRGBA clr2 = clr1.clone();
+ assertEquals(clr1, clr2);
+ assertNotSame(clr1, clr2);
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final ColorRGBA clr1 = new ColorRGBA(1, 0, 0, 1);
+ final ColorRGBA clr2 = new ColorRGBA(1, 0, 0, 1);
+ final ColorRGBA clr3 = new ColorRGBA(1, 1, 1, 1);
+
+ assertTrue(clr1.hashCode() == clr2.hashCode());
+ assertTrue(clr1.hashCode() != clr3.hashCode());
+ }
+
+ @Test
+ public void testAdd() {
+ final ColorRGBA clr1 = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA);
+ final ColorRGBA clr2 = new ColorRGBA(ColorRGBA.WHITE);
+
+ clr1.addLocal(1, 2, 3, 4);
+ assertEquals(new ColorRGBA(1, 2, 3, 4), clr1);
+ clr1.addLocal(-1, -2, -3, -4);
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+
+ clr1.zero();
+ clr1.addLocal(clr2);
+ assertEquals(ColorRGBA.WHITE, clr1);
+
+ clr1.zero();
+ final ColorRGBA clr3 = clr1.add(clr2, new ColorRGBA());
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+ assertEquals(ColorRGBA.WHITE, clr3);
+
+ final ColorRGBA clr4 = clr1.add(0, 0, 0, 1, null);
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+ assertEquals(ColorRGBA.BLACK, clr4);
+ }
+
+ @Test
+ public void testSubtract() {
+ final ColorRGBA clr1 = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA);
+ final ColorRGBA clr2 = new ColorRGBA(ColorRGBA.WHITE);
+
+ clr1.subtractLocal(1, 2, 3, 4);
+ assertEquals(new ColorRGBA(-1, -2, -3, -4), clr1);
+ clr1.subtractLocal(-1, -2, -3, -4);
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+
+ clr1.zero();
+ clr1.subtractLocal(clr2);
+ assertEquals(new ColorRGBA(-1, -1, -1, -1), clr1);
+
+ clr1.zero();
+ final ColorRGBA clr3 = clr1.subtract(clr2, new ColorRGBA());
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+ assertEquals(new ColorRGBA(-1, -1, -1, -1), clr3);
+
+ final ColorRGBA clr4 = clr1.subtract(0, 0, 0, 1, null);
+ assertEquals(ColorRGBA.BLACK_NO_ALPHA, clr1);
+ assertEquals(new ColorRGBA(0, 0, 0, -1), clr4);
+ }
+
+ @Test
+ public void testMultiply() {
+ final ColorRGBA clr1 = new ColorRGBA(1, -1, 2, -2);
+ final ColorRGBA clr2 = clr1.multiply(2.0f, null);
+ final ColorRGBA clr2B = clr1.multiply(2.0f, new ColorRGBA());
+ assertEquals(new ColorRGBA(2.0f, -2.0f, 4.0f, -4.0f), clr2);
+ assertEquals(new ColorRGBA(2.0f, -2.0f, 4.0f, -4.0f), clr2B);
+
+ clr2.multiplyLocal(0.5f);
+ assertEquals(new ColorRGBA(1.0f, -1.0f, 2.0f, -2.0f), clr2);
+
+ final ColorRGBA clr3 = clr1.multiply(clr2, null);
+ final ColorRGBA clr3B = clr1.multiply(clr2, new ColorRGBA());
+ assertEquals(new ColorRGBA(1, 1, 4, 4), clr3);
+ assertEquals(new ColorRGBA(1, 1, 4, 4), clr3B);
+
+ clr1.multiplyLocal(clr2);
+ assertEquals(new ColorRGBA(1, 1, 4, 4), clr1);
+ }
+
+ @Test
+ public void testDivide() {
+ final ColorRGBA clr1 = new ColorRGBA(1, -1, 2, -2);
+ final ColorRGBA clr2 = clr1.divide(2.0f, null);
+ final ColorRGBA clr2B = clr1.divide(2.0f, new ColorRGBA());
+ assertEquals(new ColorRGBA(0.5f, -0.5f, 1.0f, -1.0f), clr2);
+ assertEquals(new ColorRGBA(0.5f, -0.5f, 1.0f, -1.0f), clr2B);
+
+ clr2.divideLocal(0.5f);
+ assertEquals(new ColorRGBA(1.0f, -1.0f, 2.0f, -2.0f), clr2);
+
+ final ColorRGBA clr3 = clr1.divide(clr2, null);
+ final ColorRGBA clr3B = clr1.divide(clr2, new ColorRGBA());
+ assertEquals(ColorRGBA.WHITE, clr3);
+ assertEquals(ColorRGBA.WHITE, clr3B);
+
+ clr1.divideLocal(clr2);
+ assertEquals(ColorRGBA.WHITE, clr1);
+ }
+
+ @Test
+ public void testLerp() {
+ final ColorRGBA clr1 = new ColorRGBA(8, 3, -2, 2);
+ final ColorRGBA clr2 = new ColorRGBA(2, 1, 0, -2);
+ assertEquals(new ColorRGBA(5, 2, -1, 0), clr1.lerp(clr2, 0.5f, null));
+ assertEquals(new ColorRGBA(5, 2, -1, 0), clr1.lerp(clr2, 0.5f, new ColorRGBA()));
+ assertEquals(new ColorRGBA(5, 2, -1, 0), ColorRGBA.lerp(clr1, clr2, 0.5f, null));
+ assertEquals(new ColorRGBA(5, 2, -1, 0), ColorRGBA.lerp(clr1, clr2, 0.5f, new ColorRGBA()));
+
+ clr1.set(14, 5, 4, 2);
+ clr1.lerpLocal(clr2, 0.25f);
+ assertEquals(new ColorRGBA(11, 4, 3, 1), clr1);
+
+ clr1.set(15, 7, 6, 8);
+ final ColorRGBA clr3 = new ColorRGBA(-1, -1, -1, -1);
+ clr3.lerpLocal(clr1, clr2, 0.5f);
+ assertEquals(new ColorRGBA(8.5f, 4.0f, 3.0f, 3.0f), clr3);
+
+ // coverage
+ assertEquals(clr1.lerp(clr1, .25f, null), clr1);
+ assertEquals(clr2.lerpLocal(clr2, .25f), clr2);
+ assertEquals(clr2.lerpLocal(clr2, clr2, .25f), clr2);
+ assertEquals(ColorRGBA.lerp(clr1, clr1, .25f, null), clr1);
+ }
+
+ @Test
+ public void testValid() {
+ final ColorRGBA clr1 = new ColorRGBA(0, 0, 0, 0);
+ final ColorRGBA clr2A = new ColorRGBA(Float.POSITIVE_INFINITY, 0, 0, 0);
+ final ColorRGBA clr2B = new ColorRGBA(0, Float.NEGATIVE_INFINITY, 0, 0);
+ final ColorRGBA clr2C = new ColorRGBA(0, 0, Float.POSITIVE_INFINITY, 0);
+ final ColorRGBA clr2D = new ColorRGBA(0, 0, 0, Float.POSITIVE_INFINITY);
+ final ColorRGBA clr3A = new ColorRGBA(Float.NaN, 0, 0, 0);
+ final ColorRGBA clr3B = new ColorRGBA(0, Float.NaN, 0, 0);
+ final ColorRGBA clr3C = new ColorRGBA(0, 0, Float.NaN, 0);
+ final ColorRGBA clr3D = new ColorRGBA(0, 0, 0, Float.NaN);
+
+ assertTrue(ColorRGBA.isValid(clr1));
+ assertFalse(ColorRGBA.isValid(clr2A));
+ assertFalse(ColorRGBA.isValid(clr2B));
+ assertFalse(ColorRGBA.isValid(clr2C));
+ assertFalse(ColorRGBA.isValid(clr2D));
+ assertFalse(ColorRGBA.isValid(clr3A));
+ assertFalse(ColorRGBA.isValid(clr3B));
+ assertFalse(ColorRGBA.isValid(clr3C));
+ assertFalse(ColorRGBA.isValid(clr3D));
+
+ clr3C.zero();
+ assertTrue(ColorRGBA.isValid(clr3C));
+
+ assertFalse(ColorRGBA.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(clr1, clr1);
+ assertFalse(clr1.equals(null));
+ assertFalse(clr1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final ColorRGBA clr6 = ColorRGBA.fetchTempInstance();
+ clr6.set(clr1);
+ assertEquals(clr1, clr6);
+ assertNotSame(clr1, clr6);
+ ColorRGBA.releaseTempInstance(clr6);
+
+ // cover more of equals
+ clr1.set(0, 1, 2, 3);
+ assertFalse(clr1.equals(new ColorRGBA(4, 4, 4, 4)));
+ assertFalse(clr1.equals(new ColorRGBA(0, 4, 4, 4)));
+ assertFalse(clr1.equals(new ColorRGBA(0, 1, 4, 4)));
+ assertFalse(clr1.equals(new ColorRGBA(0, 1, 2, 4)));
+ assertTrue(clr1.equals(new ColorRGBA(0, 1, 2, 3)));
+ }
+
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestFastMath.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestFastMath.java new file mode 100644 index 0000000..93c4bc1 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestFastMath.java @@ -0,0 +1,71 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestFastMath {
+
+ @Test
+ public void testSin() {
+ final double angle = MathUtils.DEG_TO_RAD * 45;
+ assertTrue(Math.abs(FastMath.sin(angle) - Math.sin(angle)) <= FastMath.EPSILON_SIN);
+ }
+
+ @Test
+ public void testCos() {
+ double angle = MathUtils.DEG_TO_RAD * 45;
+ assertTrue(Math.abs(FastMath.cos(angle) - Math.cos(angle)) <= FastMath.EPSILON_COS);
+ angle = MathUtils.DEG_TO_RAD * 135;
+ assertTrue(Math.abs(FastMath.cos(angle) - Math.cos(angle)) <= FastMath.EPSILON_COS);
+ }
+
+ @Test
+ public void testTan() {
+ final double angle = MathUtils.DEG_TO_RAD * 45;
+ assertTrue(Math.abs(FastMath.tan(angle) - Math.tan(angle)) <= FastMath.EPSILON_SIN2COS2);
+ }
+
+ @Test
+ public void testAsin() {
+ final double val = 0.5;
+ assertTrue(Math.abs(FastMath.asin(val) - Math.asin(val)) <= FastMath.EPSILON_ASIN);
+ }
+
+ @Test
+ public void testAcos() {
+ final double val = 0.5;
+ assertTrue(Math.abs(FastMath.acos(val) - Math.acos(val)) <= FastMath.EPSILON_ACOS);
+ }
+
+ @Test
+ public void testAtan() {
+ double val = 1;
+ assertTrue(Math.abs(FastMath.atan(val) - Math.atan(val)) <= FastMath.EPSILON_ATAN);
+ val = 0.5;
+ assertTrue(Math.abs(FastMath.atan(val) - Math.atan(val)) <= FastMath.EPSILON_ATAN);
+ }
+
+ @Test
+ public void testInverseSqrt() {
+ final double val = 173;
+ assertTrue(Math.abs(FastMath.inverseSqrt(val) - 1.0 / Math.sqrt(val)) <= FastMath.EPSILON_SQRT);
+ }
+
+ @Test
+ public void testSqrt() {
+ final double val = 173;
+ assertTrue(Math.abs(FastMath.sqrt(val) - Math.sqrt(val)) <= FastMath.EPSILON_SQRT);
+ }
+
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestLine3.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestLine3.java new file mode 100644 index 0000000..54fb4de --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestLine3.java @@ -0,0 +1,105 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestLine3 {
+
+ @Test
+ public void testGetSet() {
+ final Line3 line1 = new Line3();
+ assertEquals(Vector3.ZERO, line1.getOrigin());
+ assertEquals(Vector3.UNIT_Z, line1.getDirection());
+
+ line1.setOrigin(Vector3.NEG_ONE);
+ line1.setDirection(Vector3.UNIT_X);
+ assertEquals(Vector3.NEG_ONE, line1.getOrigin());
+ assertEquals(Vector3.UNIT_X, line1.getDirection());
+
+ final Line3 line2 = new Line3(line1);
+ assertEquals(Vector3.NEG_ONE, line2.getOrigin());
+ assertEquals(Vector3.UNIT_X, line2.getDirection());
+
+ final Line3 line3 = new Line3(Vector3.ONE, Vector3.UNIT_Y);
+ assertEquals(Vector3.ONE, line3.getOrigin());
+ assertEquals(Vector3.UNIT_Y, line3.getDirection());
+ }
+
+ @Test
+ public void testEquals() {
+ // couple of equals validity tests
+ final Line3 line1 = new Line3();
+ assertEquals(line1, line1);
+ assertFalse(line1.equals(null));
+ assertFalse(line1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Line3 line2 = Line3.fetchTempInstance();
+ line2.set(line1);
+ assertEquals(line1, line2);
+ assertNotSame(line1, line2);
+ Line3.releaseTempInstance(line2);
+
+ // cover more of equals
+ assertFalse(line1.equals(new Line3(Vector3.ZERO, Vector3.UNIT_X)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Line3 line1 = new Line3(Vector3.ZERO, Vector3.UNIT_Y);
+ final Line3 line2 = new Line3(Vector3.ZERO, Vector3.UNIT_Y);
+ final Line3 line3 = new Line3(Vector3.ZERO, Vector3.UNIT_Z);
+
+ assertTrue(line1.hashCode() == line2.hashCode());
+ assertTrue(line1.hashCode() != line3.hashCode());
+ }
+
+ @Test
+ public void testClone() {
+ final Line3 line1 = new Line3();
+ final Line3 line2 = line1.clone();
+ assertEquals(line1, line2);
+ assertNotSame(line1, line2);
+ }
+
+ @Test
+ public void testValid() {
+ final Line3 line1 = new Line3();
+ final Line3 line2 = new Line3(new Vector3(Double.NaN, 0, 0), Vector3.UNIT_Z);
+ final Line3 line3 = new Line3(Vector3.ZERO, new Vector3(Double.NaN, 0, 0));
+
+ assertTrue(Line3.isValid(line1));
+ assertFalse(Line3.isValid(line2));
+ assertFalse(Line3.isValid(line3));
+
+ line2.setOrigin(Vector3.ZERO);
+ assertTrue(Line3.isValid(line2));
+
+ assertFalse(Line3.isValid(null));
+ }
+
+ @Test
+ public void testDistance() {
+ final Line3 line1 = new Line3(Vector3.ZERO, Vector3.UNIT_Z);
+ final Vector3 store = new Vector3();
+ assertTrue(0.0 == line1.distanceSquared(new Vector3(0, 0, 5), store));
+ assertTrue(16.0 == line1.distanceSquared(new Vector3(0, 4, 1), store));
+ assertEquals(Vector3.UNIT_Z, store);
+ assertTrue(9.0 == line1.distanceSquared(new Vector3(0, -3, -1), store));
+ assertEquals(Vector3.NEG_UNIT_Z, store);
+ assertTrue(1.0 == line1.distanceSquared(Vector3.NEG_UNIT_X, null));
+ }
+
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestLineSegment3.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestLineSegment3.java new file mode 100644 index 0000000..9b3d9c8 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestLineSegment3.java @@ -0,0 +1,140 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestLineSegment3 {
+
+ @Test
+ public void testGetSet() {
+ final LineSegment3 seg1 = new LineSegment3();
+ assertEquals(Vector3.ZERO, seg1.getOrigin());
+ assertEquals(Vector3.UNIT_Z, seg1.getDirection());
+ assertTrue(seg1.getExtent() == 0.5);
+
+ seg1.setOrigin(Vector3.NEG_ONE);
+ seg1.setDirection(Vector3.UNIT_X);
+ seg1.setExtent(42.0);
+ assertEquals(Vector3.NEG_ONE, seg1.getOrigin());
+ assertEquals(Vector3.UNIT_X, seg1.getDirection());
+ assertTrue(seg1.getExtent() == 42.0);
+
+ final LineSegment3 seg2 = new LineSegment3(seg1);
+ assertEquals(Vector3.NEG_ONE, seg2.getOrigin());
+ assertEquals(Vector3.UNIT_X, seg2.getDirection());
+ assertTrue(seg2.getExtent() == 42.0);
+
+ final LineSegment3 seg3 = new LineSegment3(Vector3.ONE, Vector3.UNIT_Y, 2.5);
+ assertEquals(Vector3.ONE, seg3.getOrigin());
+ assertEquals(Vector3.UNIT_Y, seg3.getDirection());
+ assertTrue(seg3.getExtent() == 2.5);
+
+ final LineSegment3 seg4 = new LineSegment3(new Vector3(9, 2, 2), new Vector3(5, 2, 2));
+ assertEquals(new Vector3(7, 2, 2), seg4.getOrigin());
+ assertEquals(Vector3.NEG_UNIT_X, seg4.getDirection());
+ assertTrue(seg4.getExtent() == 2);
+
+ assertEquals(new Vector3(9, 2, 2), seg4.getNegativeEnd(null));
+ assertEquals(new Vector3(1, -1.5, 1), seg3.getNegativeEnd(new Vector3()));
+ assertEquals(new Vector3(5, 2, 2), seg4.getPositiveEnd(null));
+ assertEquals(new Vector3(1, 3.5, 1), seg3.getPositiveEnd(new Vector3()));
+
+ assertFalse(seg3.equals(new LineSegment3(Vector3.ONE, Vector3.ONE, 42)));
+ assertFalse(seg3.equals(new LineSegment3(Vector3.ONE, Vector3.UNIT_Y, 42)));
+ }
+
+ @Test
+ public void testEquals() {
+ // couple of equals validity tests
+ final LineSegment3 seg1 = new LineSegment3();
+ assertEquals(seg1, seg1);
+ assertFalse(seg1.equals(null));
+ assertFalse(seg1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final LineSegment3 seg2 = LineSegment3.fetchTempInstance();
+ seg2.set(seg1);
+ assertEquals(seg1, seg2);
+ assertNotSame(seg1, seg2);
+ LineSegment3.releaseTempInstance(seg2);
+
+ // cover more of equals
+ assertFalse(seg1.equals(new LineSegment3(Vector3.ZERO, Vector3.UNIT_X, 2)));
+ assertFalse(seg1.equals(new LineSegment3(Vector3.ZERO, Vector3.UNIT_Z, 2)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final LineSegment3 seg1 = new LineSegment3(Vector3.ZERO, Vector3.UNIT_Y, 2);
+ final LineSegment3 seg2 = new LineSegment3(Vector3.ZERO, Vector3.UNIT_Y, 2);
+ final LineSegment3 seg3 = new LineSegment3(Vector3.ZERO, Vector3.UNIT_Y, 4);
+
+ assertTrue(seg1.hashCode() == seg2.hashCode());
+ assertTrue(seg1.hashCode() != seg3.hashCode());
+ }
+
+ @Test
+ public void testClone() {
+ final LineSegment3 seg1 = new LineSegment3();
+ final LineSegment3 seg2 = seg1.clone();
+ assertEquals(seg1, seg2);
+ assertNotSame(seg1, seg2);
+ }
+
+ @Test
+ public void testRandom() {
+ MathUtils.setRandomSeed(0);
+ final LineSegment3 seg1 = new LineSegment3();
+ final Vector3 store = seg1.random(null);
+ assertEquals(new Vector3(0.0, 0.0, 0.23096778737665702), store);
+
+ seg1.random(store);
+ assertEquals(new Vector3(0.0, 0.0, -0.25946358432851413), store);
+ }
+
+ @Test
+ public void testValid() {
+ final LineSegment3 seg1 = new LineSegment3();
+ final LineSegment3 seg2 = new LineSegment3(new Vector3(Double.NaN, 0, 0), Vector3.UNIT_Z, 0.5);
+ final LineSegment3 seg3 = new LineSegment3(Vector3.ZERO, new Vector3(Double.NaN, 0, 0), 0.5);
+ final LineSegment3 seg4 = new LineSegment3(Vector3.ZERO, Vector3.UNIT_Z, Double.NaN);
+ final LineSegment3 seg5 = new LineSegment3(Vector3.ZERO, Vector3.UNIT_Z, Double.POSITIVE_INFINITY);
+
+ assertTrue(LineSegment3.isValid(seg1));
+ assertFalse(LineSegment3.isValid(seg2));
+ assertFalse(LineSegment3.isValid(seg3));
+ assertFalse(LineSegment3.isValid(seg4));
+ assertFalse(LineSegment3.isValid(seg5));
+
+ seg5.setExtent(1);
+ assertTrue(LineSegment3.isValid(seg5));
+
+ assertFalse(LineSegment3.isValid(null));
+ }
+
+ @Test
+ public void testDistance() {
+ final LineSegment3 seg1 = new LineSegment3(Vector3.ZERO, Vector3.UNIT_Z, 1.0);
+ final Vector3 store = new Vector3();
+ assertTrue(16.0 == seg1.distanceSquared(new Vector3(0, 0, 5), store));
+ assertEquals(Vector3.UNIT_Z, store);
+ assertTrue(9.0 == seg1.distanceSquared(new Vector3(0, 0, -4), store));
+ assertEquals(Vector3.NEG_UNIT_Z, store);
+ assertTrue(4.0 == seg1.distanceSquared(new Vector3(2, 0, 0), store));
+ assertEquals(Vector3.ZERO, store);
+ assertTrue(1.0 == seg1.distanceSquared(Vector3.NEG_UNIT_X, null));
+ }
+
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestMathExceptions.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestMathExceptions.java new file mode 100644 index 0000000..74b3c06 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestMathExceptions.java @@ -0,0 +1,41 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestMathExceptions {
+ @Test
+ public void testInvalidTransformException() {
+ final InvalidTransformException ex1 = new InvalidTransformException();
+ final InvalidTransformException ex2 = new InvalidTransformException("ABC");
+ final Exception a = new Exception();
+ final InvalidTransformException ex3 = new InvalidTransformException(a);
+ final InvalidTransformException ex4 = new InvalidTransformException("DEF", a);
+
+ assertNotNull(ex1);
+ assertEquals("ABC", ex2.getMessage());
+ assertEquals(a, ex3.getCause());
+ assertEquals("DEF", ex4.getMessage());
+ assertEquals(a, ex4.getCause());
+ }
+
+ @Test
+ public void testTransformException() {
+ final TransformException ex1 = new TransformException();
+ final TransformException ex2 = new TransformException("ABC");
+
+ assertNotNull(ex1);
+ assertEquals("ABC", ex2.getMessage());
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix3.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix3.java new file mode 100644 index 0000000..6684c30 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix3.java @@ -0,0 +1,808 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+
+import org.junit.Test;
+
+public class TestMatrix3 {
+
+ @Test
+ public void testGetSet() {
+ final Matrix3 mat3A = new Matrix3();
+ assertEquals(Matrix3.IDENTITY, mat3A);
+
+ mat3A.setM00(0.0);
+ mat3A.setM01(0.1);
+ mat3A.setM02(0.2);
+ mat3A.setM10(1.0);
+ mat3A.setM11(1.1);
+ mat3A.setM12(1.2);
+ mat3A.setM20(2.0);
+ mat3A.setM21(2.1);
+ mat3A.setM22(2.2);
+
+ assertTrue(0.0 == mat3A.getM00());
+ assertTrue(0.1 == mat3A.getM01());
+ assertTrue(0.2 == mat3A.getM02());
+ assertTrue(1.0 == mat3A.getM10());
+ assertTrue(1.1 == mat3A.getM11());
+ assertTrue(1.2 == mat3A.getM12());
+ assertTrue(2.0 == mat3A.getM20());
+ assertTrue(2.1 == mat3A.getM21());
+ assertTrue(2.2 == mat3A.getM22());
+
+ final Matrix3 mat3B = new Matrix3(mat3A);
+ assertTrue(0.0 == mat3B.getM00());
+ assertTrue(0.1 == mat3B.getM01());
+ assertTrue(0.2 == mat3B.getM02());
+ assertTrue(1.0 == mat3B.getM10());
+ assertTrue(1.1 == mat3B.getM11());
+ assertTrue(1.2 == mat3B.getM12());
+ assertTrue(2.0 == mat3B.getM20());
+ assertTrue(2.1 == mat3B.getM21());
+ assertTrue(2.2 == mat3B.getM22());
+
+ final Matrix3 mat3C = new Matrix3(0.0, 0.1, 0.2, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2);
+ assertTrue(0.0 == mat3C.getM00());
+ assertTrue(0.1 == mat3C.getM01());
+ assertTrue(0.2 == mat3C.getM02());
+ assertTrue(1.0 == mat3C.getM10());
+ assertTrue(1.1 == mat3C.getM11());
+ assertTrue(1.2 == mat3C.getM12());
+ assertTrue(2.0 == mat3C.getM20());
+ assertTrue(2.1 == mat3C.getM21());
+ assertTrue(2.2 == mat3C.getM22());
+
+ mat3C.setIdentity();
+ assertTrue(mat3C.isIdentity());
+
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ final double value = (10 * x + y) / 10.;
+ mat3C.setValue(x, y, value);
+ assertTrue(value == mat3C.getValue(x, y));
+ }
+ }
+
+ mat3C.setIdentity();
+ mat3C.set(0.0, 0.1, 0.2, 2.0, 2.1, 2.2, 4.0, 4.1, 4.2);
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ assertTrue((20 * x + y) / 10.f == mat3C.getValuef(x, y));
+ }
+ }
+
+ final Matrix3 store = new Matrix3(mat3C);
+ // catch a few expected exceptions
+ try {
+ mat3C.getValue(-1, 0);
+ fail("getValue(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.getValue(0, 3);
+ fail("getValue(0, 3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.getValue(1, -1);
+ fail("getValue(1, -1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.getValue(2, 3);
+ fail("getValue(2, 3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.getValue(3, 0);
+ fail("getValue(3, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+
+ try {
+ mat3C.setValue(-1, 0, 0);
+ fail("setValue(-1, 0, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.setValue(0, -1, 0);
+ fail("setValue(0, -1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.setValue(1, 3, 0);
+ fail("setValue(1, 3, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.setValue(2, -1, 0);
+ fail("setValue(2, -1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3C.setValue(3, 0, 0);
+ fail("setValue(3, 0, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ // above exceptions shouldn't have altered mat3C
+ assertEquals(store, mat3C);
+ }
+
+ @Test
+ public void testColumns() {
+ final Matrix3 mat3A = new Matrix3();
+ mat3A.setColumn(0, new Vector3(0, 3, 6));
+ mat3A.setColumn(1, new Vector3(1, 4, 7));
+ mat3A.setColumn(2, new Vector3(2, 5, 8));
+ assertEquals(new Vector3(0, 3, 6), mat3A.getColumn(0, new Vector3()));
+ assertEquals(new Vector3(1, 4, 7), mat3A.getColumn(1, null));
+ assertEquals(new Vector3(2, 5, 8), mat3A.getColumn(2, null));
+ try {
+ mat3A.getColumn(-1, null);
+ fail("getColumn(-1, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3A.getColumn(3, null);
+ fail("getColumn(3, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3A.setColumn(-1, new Vector3());
+ fail("setColumn(-1, Vector3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3A.setColumn(4, new Vector3());
+ fail("setColumn(4, Vector3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+
+ mat3A.fromAxes(new Vector3(1, 2, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9));
+ mat3A.setColumn(0, new Vector3(1, 2, 3));
+ mat3A.setColumn(1, new Vector3(4, 5, 6));
+ mat3A.setColumn(2, new Vector3(7, 8, 9));
+ }
+
+ @Test
+ public void testRows() {
+ final Matrix3 mat3A = new Matrix3();
+ mat3A.setRow(0, new Vector3(0, 1, 2));
+ mat3A.setRow(1, new Vector3(3, 4, 5));
+ mat3A.setRow(2, new Vector3(6, 7, 8));
+ assertEquals(new Vector3(0, 1, 2), mat3A.getRow(0, new Vector3()));
+ assertEquals(new Vector3(3, 4, 5), mat3A.getRow(1, null));
+ assertEquals(new Vector3(6, 7, 8), mat3A.getRow(2, null));
+ try {
+ mat3A.getRow(-1, null);
+ fail("getRow(-1, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3A.getRow(3, null);
+ fail("getRow(3, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3A.setRow(-1, new Vector3());
+ fail("setRow(-1, Vector3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat3A.setRow(3, new Vector3());
+ fail("setRow(3, Vector3]) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testSetRotation() {
+ final Matrix3 mat3A = new Matrix3();
+ // rotate identity 90 degrees around Y
+ final double a = MathUtils.HALF_PI;
+ final Quaternion quaternion = new Quaternion();
+ quaternion.fromAngleAxis(a, Vector3.UNIT_Y);
+ mat3A.set(quaternion);
+
+ assertEquals(new Matrix3( //
+ Math.cos(a), 0, Math.sin(a), //
+ 0, 1, 0, //
+ -Math.sin(a), 0, Math.cos(a)), mat3A);
+ }
+
+ @Test
+ public void testFromBuffer() {
+ final FloatBuffer fb = FloatBuffer.allocate(9);
+ fb.put(new float[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
+ fb.flip();
+ // row major
+ final Matrix3 mat3A = new Matrix3().fromFloatBuffer(fb);
+ assertTrue(0 == mat3A.getM00());
+ assertTrue(1 == mat3A.getM01());
+ assertTrue(2 == mat3A.getM02());
+ assertTrue(3 == mat3A.getM10());
+ assertTrue(4 == mat3A.getM11());
+ assertTrue(5 == mat3A.getM12());
+ assertTrue(6 == mat3A.getM20());
+ assertTrue(7 == mat3A.getM21());
+ assertTrue(8 == mat3A.getM22());
+
+ // column major
+ fb.rewind();
+ mat3A.setIdentity();
+ mat3A.fromFloatBuffer(fb, false);
+ assertTrue(0 == mat3A.getM00());
+ assertTrue(3 == mat3A.getM01());
+ assertTrue(6 == mat3A.getM02());
+ assertTrue(1 == mat3A.getM10());
+ assertTrue(4 == mat3A.getM11());
+ assertTrue(7 == mat3A.getM12());
+ assertTrue(2 == mat3A.getM20());
+ assertTrue(5 == mat3A.getM21());
+ assertTrue(8 == mat3A.getM22());
+
+ final DoubleBuffer db = DoubleBuffer.allocate(16);
+ db.put(new double[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 });
+ db.flip();
+ // row major
+ mat3A.setIdentity();
+ mat3A.fromDoubleBuffer(db);
+ assertTrue(0 == mat3A.getM00());
+ assertTrue(1 == mat3A.getM01());
+ assertTrue(2 == mat3A.getM02());
+ assertTrue(3 == mat3A.getM10());
+ assertTrue(4 == mat3A.getM11());
+ assertTrue(5 == mat3A.getM12());
+ assertTrue(6 == mat3A.getM20());
+ assertTrue(7 == mat3A.getM21());
+ assertTrue(8 == mat3A.getM22());
+
+ // column major
+ db.rewind();
+ mat3A.setIdentity();
+ mat3A.fromDoubleBuffer(db, false);
+ assertTrue(0 == mat3A.getM00());
+ assertTrue(3 == mat3A.getM01());
+ assertTrue(6 == mat3A.getM02());
+ assertTrue(1 == mat3A.getM10());
+ assertTrue(4 == mat3A.getM11());
+ assertTrue(7 == mat3A.getM12());
+ assertTrue(2 == mat3A.getM20());
+ assertTrue(5 == mat3A.getM21());
+ assertTrue(8 == mat3A.getM22());
+ }
+
+ @Test
+ public void testToBuffer() {
+ final double[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
+ final double[] colmajor = { 0, 3, 6, 1, 4, 7, 2, 5, 8 };
+
+ final Matrix3 mat3A = new Matrix3().fromArray(values);
+
+ // row major
+ final FloatBuffer fb = mat3A.toFloatBuffer(FloatBuffer.allocate(9));
+ fb.flip();
+ for (int i = 0; i < 9; i++) {
+ assertTrue(values[i] == fb.get());
+ }
+
+ // column major
+ fb.rewind();
+ mat3A.toFloatBuffer(fb, false);
+ fb.flip();
+ for (int i = 0; i < 9; i++) {
+ assertTrue(colmajor[i] == fb.get());
+ }
+
+ // row major
+ final DoubleBuffer db = mat3A.toDoubleBuffer(DoubleBuffer.allocate(9));
+ db.flip();
+ for (int i = 0; i < 9; i++) {
+ assertTrue(values[i] == db.get());
+ }
+
+ // column major
+ db.rewind();
+ mat3A.toDoubleBuffer(db, false);
+ db.flip();
+ for (int i = 0; i < 9; i++) {
+ assertTrue(colmajor[i] == db.get());
+ }
+ }
+
+ @Test
+ public void testFromArray() {
+ final double[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
+ final Matrix3 mat3A = new Matrix3();
+
+ // row major
+ mat3A.fromArray(values);
+ assertTrue(0 == mat3A.getM00());
+ assertTrue(1 == mat3A.getM01());
+ assertTrue(2 == mat3A.getM02());
+ assertTrue(3 == mat3A.getM10());
+ assertTrue(4 == mat3A.getM11());
+ assertTrue(5 == mat3A.getM12());
+ assertTrue(6 == mat3A.getM20());
+ assertTrue(7 == mat3A.getM21());
+ assertTrue(8 == mat3A.getM22());
+
+ // column major
+ mat3A.setIdentity();
+ mat3A.fromArray(values, false);
+ assertTrue(0 == mat3A.getM00());
+ assertTrue(3 == mat3A.getM01());
+ assertTrue(6 == mat3A.getM02());
+ assertTrue(1 == mat3A.getM10());
+ assertTrue(4 == mat3A.getM11());
+ assertTrue(7 == mat3A.getM12());
+ assertTrue(2 == mat3A.getM20());
+ assertTrue(5 == mat3A.getM21());
+ assertTrue(8 == mat3A.getM22());
+ }
+
+ @Test
+ public void testToArray() {
+ final double[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
+ final Matrix3 mat3A = new Matrix3().fromArray(values);
+
+ // row major
+ final double[] dbls1 = mat3A.toArray(null);
+ for (int i = 0; i < 9; i++) {
+ assertTrue(values[i] == dbls1[i]);
+ }
+
+ // column major
+ final double[] colmajor = { 0, 3, 6, 1, 4, 7, 2, 5, 8 };
+ mat3A.toArray(dbls1, false);
+ for (int i = 0; i < 9; i++) {
+ assertTrue(colmajor[i] == dbls1[i]);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadArray() {
+ final Matrix3 mat3A = new Matrix3();
+ mat3A.toArray(new double[4]);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadAnglesArray() {
+ final Matrix3 mat3A = new Matrix3();
+ mat3A.toAngles(new double[2]);
+ }
+
+ @Test
+ public void testAngleAxis() {
+ final Matrix3 mat3A = new Matrix3();
+ // rotate identity 90 degrees around X
+ final double angle = MathUtils.HALF_PI;
+ mat3A.fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X);
+ assertEquals(new Matrix3( //
+ 1, 0, 0, //
+ 0, Math.cos(angle), -Math.sin(angle), //
+ 0, Math.sin(angle), Math.cos(angle)), mat3A);
+ }
+
+ @Test
+ public void testRotations() {
+ final Vector3 rotated = new Vector3(1, 1, 1);
+ final Vector3 expected = new Vector3(1, 1, 1);
+
+ // rotated
+ final Matrix3 mat3A = new Matrix3().fromAngles(MathUtils.HALF_PI, MathUtils.QUARTER_PI, MathUtils.PI);
+ mat3A.applyPost(rotated, rotated);
+
+ // expected - post
+ final Matrix3 worker = new Matrix3().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X);
+ worker.applyPost(expected, expected);
+ worker.fromAngleAxis(MathUtils.PI, Vector3.UNIT_Z);
+ worker.applyPost(expected, expected);
+ worker.fromAngleAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Y);
+ worker.applyPost(expected, expected);
+
+ // test how close it came out
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Try a new way with new angles...
+ final Matrix3 mat3B = new Matrix3().fromAngles(MathUtils.QUARTER_PI, MathUtils.PI, MathUtils.HALF_PI);
+ rotated.set(1, 1, 1);
+ mat3B.applyPost(rotated, rotated);
+
+ // expected
+ expected.set(1, 1, 1);
+ worker.setIdentity();
+ // put together matrix, then apply to vector, so YZX
+ worker.applyRotationY(MathUtils.PI);
+ worker.applyRotationZ(MathUtils.HALF_PI);
+ worker.applyRotationX(MathUtils.QUARTER_PI);
+ worker.applyPost(expected, expected);
+
+ // test how close it came out
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // test axis rotation methods against general purpose
+ // X AXIS
+ expected.set(1, 1, 1);
+ rotated.set(1, 1, 1);
+ worker.setIdentity().applyRotationX(MathUtils.QUARTER_PI).applyPost(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 1, 0, 0).applyPost(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Y AXIS
+ expected.set(1, 1, 1);
+ rotated.set(1, 1, 1);
+ worker.setIdentity().applyRotationY(MathUtils.QUARTER_PI).applyPost(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 0, 1, 0).applyPost(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Z AXIS
+ expected.set(1, 1, 1);
+ rotated.set(1, 1, 1);
+ worker.setIdentity().applyRotationZ(MathUtils.QUARTER_PI).applyPost(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 0, 0, 1).applyPost(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // test toAngles - not necessarily the same values as fromAngles, but should be same resulting Matrix.
+ mat3A.fromAngles(MathUtils.HALF_PI, MathUtils.QUARTER_PI, MathUtils.PI);
+ final double[] angles = mat3A.toAngles(null);
+ worker.fromAngles(angles[0], angles[1], angles[2]);
+ assertEquals(mat3A, worker);
+
+ mat3A.fromAngles(MathUtils.HALF_PI, MathUtils.QUARTER_PI, MathUtils.HALF_PI);
+ mat3A.toAngles(angles);
+ worker.fromAngles(angles[0], angles[1], angles[2]);
+ assertEquals(mat3A, worker);
+
+ mat3A.fromAngles(MathUtils.HALF_PI, MathUtils.QUARTER_PI, -MathUtils.HALF_PI);
+ mat3A.toAngles(angles);
+ worker.fromAngles(angles[0], angles[1], angles[2]);
+ assertEquals(mat3A, worker);
+ }
+
+ @Test
+ public void testMultiplyDiagonal() {
+ final Matrix3 mat3A = new Matrix3();
+ Matrix3 result = mat3A.multiplyDiagonalPost(new Vector3(2, 4, 6), null);
+ assertEquals(new Matrix3( //
+ 2, 0, 0, //
+ 0, 4, 0, //
+ 0, 0, 6), result);
+ mat3A.multiplyDiagonalPre(new Vector3(-2, -4, -6), result);
+ assertEquals(new Matrix3( //
+ -2, 0, 0, //
+ 0, -4, 0, //
+ 0, 0, -6), result);
+
+ final double a = MathUtils.HALF_PI;
+ mat3A.applyRotationY(a);
+ mat3A.multiplyDiagonalPost(new Vector3(2, 4, 6), result);
+ assertEquals(new Matrix3( //
+ 2 * Math.cos(a), 4 * 0, 6 * Math.sin(a), //
+ 2 * 0, 4 * 1, 6 * 0, //
+ 2 * -Math.sin(a), 4 * 0, 6 * Math.cos(a)), result);
+ result = mat3A.multiplyDiagonalPre(new Vector3(-2, -4, -6), null);
+ assertEquals(new Matrix3( //
+ -2 * Math.cos(a), -2 * 0, -2 * Math.sin(a), //
+ -4 * 0, -4 * 1, -4 * 0, //
+ -6 * -Math.sin(a), -6 * 0, -6 * Math.cos(a)), result);
+ }
+
+ @Test
+ public void testMultiply() {
+ final Matrix3 mat3A = new Matrix3( //
+ 0.01, 0.1, 0.2, //
+ 1.0, 1.1, 1.2, //
+ 2.0, 2.1, 2.2);
+ mat3A.multiplyLocal(2);
+ assertEquals(new Matrix3( //
+ 0.02, 0.2, 0.4, //
+ 2.0, 2.2, 2.4, //
+ 4.0, 4.2, 4.4), mat3A);
+
+ final Matrix3 mat3B = new Matrix3( //
+ 0.5, 1, 2, //
+ 4, 5, 6, //
+ 8, 9, 10);
+ final Matrix3 result = mat3A.multiply(mat3B, null);
+ assertTrue(0.02 * 0.5 + 0.2 * 4 + 0.4 * 8 == result.getM00());
+ assertTrue(0.02 * 1 + 0.2 * 5 + 0.4 * 9 == result.getM01());
+ assertTrue(0.02 * 2 + 0.2 * 6 + 0.4 * 10 == result.getM02());
+ assertTrue(2.0 * 0.5 + 2.2 * 4 + 2.4 * 8 == result.getM10());
+ assertTrue(2.0 * 1 + 2.2 * 5 + 2.4 * 9 == result.getM11());
+ assertTrue(2.0 * 2 + 2.2 * 6 + 2.4 * 10 == result.getM12());
+ assertTrue(4.0 * 0.5 + 4.2 * 4 + 4.4 * 8 == result.getM20());
+ assertTrue(4.0 * 1 + 4.2 * 5 + 4.4 * 9 == result.getM21());
+ assertTrue(4.0 * 2 + 4.2 * 6 + 4.4 * 10 == result.getM22());
+ mat3A.multiplyLocal(mat3B);
+ assertTrue(0.02 * 0.5 + 0.2 * 4 + 0.4 * 8 == mat3A.getM00());
+ assertTrue(0.02 * 1 + 0.2 * 5 + 0.4 * 9 == mat3A.getM01());
+ assertTrue(0.02 * 2 + 0.2 * 6 + 0.4 * 10 == mat3A.getM02());
+ assertTrue(2.0 * 0.5 + 2.2 * 4 + 2.4 * 8 == mat3A.getM10());
+ assertTrue(2.0 * 1 + 2.2 * 5 + 2.4 * 9 == mat3A.getM11());
+ assertTrue(2.0 * 2 + 2.2 * 6 + 2.4 * 10 == mat3A.getM12());
+ assertTrue(4.0 * 0.5 + 4.2 * 4 + 4.4 * 8 == mat3A.getM20());
+ assertTrue(4.0 * 1 + 4.2 * 5 + 4.4 * 9 == mat3A.getM21());
+ assertTrue(4.0 * 2 + 4.2 * 6 + 4.4 * 10 == mat3A.getM22());
+ }
+
+ @Test
+ public void testAddSubtract() {
+ final Matrix3 mat3A = new Matrix3( //
+ 0.0, 0.1, 0.2, //
+ 1.0, 1.1, 1.2, //
+ 2.0, 2.1, 2.2);
+
+ final Matrix3 result1 = mat3A.add(new Matrix3(//
+ 1, 2, 3,//
+ 5, 6, 7, //
+ 9, 10, 11), null);
+ assertEquals(new Matrix3( //
+ 1.0, 2.1, 3.2, //
+ 6.0, 7.1, 8.2, //
+ 11.0, 12.1, 13.2), result1);
+
+ final Matrix3 result2 = result1.subtract(new Matrix3(//
+ 1, 2, 3, //
+ 5, 6, 7, //
+ 9, 10, 11), null);
+ assertEquals(mat3A, result2);
+ result2.addLocal(Matrix3.IDENTITY);
+ assertEquals(new Matrix3( //
+ 1.0, 0.1, 0.2, //
+ 1.0, 2.1, 1.2, //
+ 2.0, 2.1, 3.2), result2);
+
+ result1.subtractLocal(Matrix3.IDENTITY);
+ assertEquals(new Matrix3( //
+ 0.0, 2.1, 3.2, //
+ 6.0, 6.1, 8.2, //
+ 11.0, 12.1, 12.2), result1);
+ }
+
+ @Test
+ public void testScale() {
+ final Matrix3 mat3A = new Matrix3( //
+ 0.01, 0.1, 0.2, //
+ 1.0, 1.1, 1.2, //
+ 2.0, 2.1, 2.2);
+ final Matrix3 result = mat3A.scale(new Vector3(-1, 2, 3), null);
+ assertEquals(new Matrix3( //
+ 0.01 * -1, 0.1 * 2, 0.2 * 3, //
+ 1.0 * -1, 1.1 * 2, 1.2 * 3, //
+ 2.0 * -1, 2.1 * 2, 2.2 * 3), result);
+
+ result.scaleLocal(new Vector3(-1, 0.5, 1 / 3.));
+ assertEquals(mat3A, result);
+ }
+
+ @Test
+ public void testTranspose() {
+ final Matrix3 mat3A = new Matrix3( //
+ 0.01, 0.1, 0.2, //
+ 1.0, 1.1, 1.2, //
+ 2.0, 2.1, 2.2);
+ final Matrix3 result = mat3A.transpose(null);
+ assertEquals(new Matrix3( //
+ 0.01, 1.0, 2.0, //
+ 0.1, 1.1, 2.1, //
+ 0.2, 1.2, 2.2), result);
+ assertEquals(new Matrix3( //
+ 0.01, 0.1, 0.2, //
+ 1.0, 1.1, 1.2, //
+ 2.0, 2.1, 2.2), result.transposeLocal());
+ // coverage
+ final Matrix3 result2 = result.transposeLocal().transpose(new Matrix3());
+ assertEquals(mat3A, result2);
+ }
+
+ @Test
+ public void testInvert() {
+ final Matrix3 mat3A = new Matrix3().applyRotationX(MathUtils.QUARTER_PI);
+ final Matrix3 inverted = mat3A.invert(null);
+ assertEquals(Matrix3.IDENTITY, mat3A.multiply(inverted, null));
+ assertEquals(mat3A, inverted.invertLocal());
+ }
+
+ @Test(expected = ArithmeticException.class)
+ public void testBadInvert() {
+ final Matrix3 mat3A = new Matrix3(0, 0, 0, 0, 0, 0, 0, 0, 0);
+ mat3A.invertLocal();
+ }
+
+ @Test
+ public void testAdjugate() {
+ final double //
+ a = -3, b = 2, c = -5, //
+ d = -1, e = 0, f = -2, //
+ g = 3, h = -4, i = 1;
+
+ final Matrix3 mat3A = new Matrix3( //
+ a, b, c, //
+ d, e, f, //
+ g, h, i);
+
+ final Matrix3 testValue = new Matrix3( //
+ e * i - h * f, -(b * i - h * c), b * f - e * c, //
+ -(d * i - g * f), a * i - g * c, -(a * f - d * c),//
+ d * h - g * e, -(a * h - g * b), a * e - d * b);
+
+ assertEquals(testValue, mat3A.adjugate(null));
+ assertEquals(testValue, mat3A.adjugateLocal());
+ }
+
+ @Test
+ public void testDeterminant() {
+ {
+ final double //
+ a = -3, b = 2, c = -5, //
+ d = -1, e = 0, f = -2, //
+ g = 3, h = -4, i = 1;
+
+ final Matrix3 mat3A = new Matrix3( //
+ a, b, c, //
+ d, e, f, //
+ g, h, i);
+ final double determinant = a * e * i + b * f * g + c * d * h - c * e * g - b * d * i - a * f * h;
+ assertTrue(determinant == mat3A.determinant());
+ }
+
+ {
+ final double //
+ a = -1, b = 2, c = -3, //
+ d = 4, e = -5, f = 6, //
+ g = -7, h = 8, i = -9;
+
+ final Matrix3 mat3A = new Matrix3( //
+ a, b, c, //
+ d, e, f, //
+ g, h, i);
+ final double determinant = a * e * i + b * f * g + c * d * h - c * e * g - b * d * i - a * f * h;
+ assertTrue(determinant == mat3A.determinant());
+ }
+ }
+
+ @Test
+ public void testClone() {
+ final Matrix3 mat1 = new Matrix3();
+ final Matrix3 mat2 = mat1.clone();
+ assertEquals(mat1, mat2);
+ assertNotSame(mat1, mat2);
+ }
+
+ @Test
+ public void testValid() {
+ final Matrix3 mat3 = new Matrix3();
+ assertTrue(Matrix3.isValid(mat3));
+ for (int i = 0; i < 9; i++) {
+ mat3.setIdentity();
+ mat3.setValue(i / 3, i % 3, Double.NaN);
+ assertFalse(Matrix3.isValid(mat3));
+ mat3.setIdentity();
+ mat3.setValue(i / 3, i % 3, Double.POSITIVE_INFINITY);
+ assertFalse(Matrix3.isValid(mat3));
+ }
+
+ mat3.setIdentity();
+ assertTrue(Matrix3.isValid(mat3));
+
+ assertFalse(Matrix3.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(mat3, mat3);
+ assertTrue(mat3.strictEquals(mat3));
+ assertFalse(mat3.equals(null));
+ assertFalse(mat3.strictEquals(null));
+ assertFalse(mat3.equals(new Vector2()));
+ assertFalse(mat3.strictEquals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Matrix3 matTemp = Matrix3.fetchTempInstance();
+ matTemp.set(mat3);
+ assertEquals(mat3, matTemp);
+ assertNotSame(mat3, matTemp);
+ Matrix3.releaseTempInstance(matTemp);
+
+ // cover more of equals
+ mat3.set(0, 1, 2, 3, 4, 5, 6, 7, 8);
+ final Matrix3 comp = new Matrix3(-1, -1, -1, -1, -1, -1, -1, -1, -1);
+ assertFalse(mat3.equals(comp));
+ assertFalse(mat3.strictEquals(comp));
+ for (int i = 0; i < 8; i++) {
+ comp.setValue(i / 3, i % 3, i);
+ assertFalse(mat3.equals(comp));
+ assertFalse(mat3.strictEquals(comp));
+ }
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Matrix3 mat1 = new Matrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ final Matrix3 mat2 = new Matrix3(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ final Matrix3 mat3 = new Matrix3(1, 2, 3, 4, 5, 6, 7, 8, 0);
+
+ assertTrue(mat1.hashCode() == mat2.hashCode());
+ assertTrue(mat1.hashCode() != mat3.hashCode());
+ }
+
+ @Test
+ public void testOrthonormal() {
+ final Matrix3 mat3 = new Matrix3();
+ assertTrue(mat3.isOrthonormal());
+ // just rotation
+ mat3.applyRotationX(MathUtils.QUARTER_PI);
+ assertTrue(mat3.isOrthonormal());
+ // non-uniform scale
+ mat3.setIdentity();
+ mat3.scaleLocal(new Vector3(1, 2, 3));
+ assertFalse(mat3.isOrthonormal());
+ // non-uniform scale + rotation
+ mat3.setIdentity();
+ mat3.scaleLocal(new Vector3(1, 2, 3));
+ mat3.applyRotationX(MathUtils.QUARTER_PI);
+ assertFalse(mat3.isOrthonormal());
+ }
+
+ @Test
+ public void testApplyVector3() {
+ final Matrix3 mat3 = new Matrix3().applyRotationX(MathUtils.HALF_PI);
+ final Vector3 vec3 = new Vector3(0, 1, 0);
+ final Vector3 result = mat3.applyPost(vec3, null);
+ assertTrue(Math.abs(new Vector3(0, 0, 1).distance(result)) <= MathUtils.EPSILON);
+ vec3.set(0, 1, 1);
+ mat3.applyPost(vec3, result);
+ assertTrue(Math.abs(new Vector3(0, -1, 1).distance(result)) <= MathUtils.EPSILON);
+
+ vec3.set(0, 1, 1);
+ mat3.applyPre(vec3, result);
+ assertTrue(Math.abs(new Vector3(0, 1, -1).distance(result)) <= MathUtils.EPSILON);
+
+ vec3.set(1, 1, 1);
+ assertTrue(Math.abs(new Vector3(1, 1, -1).distance(mat3.applyPre(vec3, null))) <= MathUtils.EPSILON);
+ }
+
+ @Test
+ public void testStartEnd() {
+ final Matrix3 mat3 = new Matrix3();
+ mat3.fromStartEndLocal(Vector3.UNIT_X, Vector3.UNIT_Y); // should be a 90 degree turn around Z
+ assertEquals(new Vector3(-1, 1, 1), mat3.applyPost(new Vector3(1, 1, 1), null));
+
+ // coverage
+ mat3.fromStartEndLocal(new Vector3(1, 0, 0), new Vector3(1 + Double.MIN_VALUE, 0, 0));
+ assertTrue(mat3.applyPost(Vector3.ONE, null).distance(Vector3.ONE) < MathUtils.ZERO_TOLERANCE);
+ mat3.fromStartEndLocal(new Vector3(0, 1, 0), new Vector3(0, 1 + Double.MIN_VALUE, 0));
+ assertTrue(mat3.applyPost(Vector3.ONE, null).distance(Vector3.ONE) < MathUtils.ZERO_TOLERANCE);
+ mat3.fromStartEndLocal(new Vector3(0, 0, 1), new Vector3(0, 0, 1 + Double.MIN_VALUE));
+ assertTrue(mat3.applyPost(Vector3.ONE, null).distance(Vector3.ONE) < MathUtils.ZERO_TOLERANCE);
+ }
+
+ @Test
+ public void testLookAt() {
+ final Vector3 direction = new Vector3(-1, 0, 0);
+ final Matrix3 mat3 = new Matrix3().lookAt(direction, Vector3.UNIT_Y);
+ assertEquals(direction, mat3.applyPost(Vector3.UNIT_Z, null));
+
+ direction.set(1, 1, 1).normalizeLocal();
+ mat3.lookAt(direction, Vector3.UNIT_Y);
+ assertEquals(direction, mat3.applyPost(Vector3.UNIT_Z, null));
+
+ direction.set(-1, 2, -1).normalizeLocal();
+ mat3.lookAt(direction, Vector3.UNIT_Y);
+ assertEquals(direction, mat3.applyPost(Vector3.UNIT_Z, new Vector3()));
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix4.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix4.java new file mode 100644 index 0000000..9774434 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix4.java @@ -0,0 +1,935 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+
+import org.junit.Test;
+
+public class TestMatrix4 {
+
+ @Test
+ public void testGetSet() {
+ final Matrix4 mat4A = new Matrix4();
+ assertEquals(Matrix4.IDENTITY, mat4A);
+
+ mat4A.setM00(0.0);
+ mat4A.setM01(0.1);
+ mat4A.setM02(0.2);
+ mat4A.setM03(0.3);
+ mat4A.setM10(1.0);
+ mat4A.setM11(1.1);
+ mat4A.setM12(1.2);
+ mat4A.setM13(1.3);
+ mat4A.setM20(2.0);
+ mat4A.setM21(2.1);
+ mat4A.setM22(2.2);
+ mat4A.setM23(2.3);
+ mat4A.setM30(3.0);
+ mat4A.setM31(3.1);
+ mat4A.setM32(3.2);
+ mat4A.setM33(3.3);
+
+ assertTrue(0.0 == mat4A.getM00());
+ assertTrue(0.1 == mat4A.getM01());
+ assertTrue(0.2 == mat4A.getM02());
+ assertTrue(0.3 == mat4A.getM03());
+ assertTrue(1.0 == mat4A.getM10());
+ assertTrue(1.1 == mat4A.getM11());
+ assertTrue(1.2 == mat4A.getM12());
+ assertTrue(1.3 == mat4A.getM13());
+ assertTrue(2.0 == mat4A.getM20());
+ assertTrue(2.1 == mat4A.getM21());
+ assertTrue(2.2 == mat4A.getM22());
+ assertTrue(2.3 == mat4A.getM23());
+ assertTrue(3.0 == mat4A.getM30());
+ assertTrue(3.1 == mat4A.getM31());
+ assertTrue(3.2 == mat4A.getM32());
+ assertTrue(3.3 == mat4A.getM33());
+
+ final Matrix4 mat4B = new Matrix4(mat4A);
+ assertTrue(0.0 == mat4B.getM00());
+ assertTrue(0.1 == mat4B.getM01());
+ assertTrue(0.2 == mat4B.getM02());
+ assertTrue(0.3 == mat4B.getM03());
+ assertTrue(1.0 == mat4B.getM10());
+ assertTrue(1.1 == mat4B.getM11());
+ assertTrue(1.2 == mat4B.getM12());
+ assertTrue(1.3 == mat4B.getM13());
+ assertTrue(2.0 == mat4B.getM20());
+ assertTrue(2.1 == mat4B.getM21());
+ assertTrue(2.2 == mat4B.getM22());
+ assertTrue(2.3 == mat4B.getM23());
+ assertTrue(3.0 == mat4B.getM30());
+ assertTrue(3.1 == mat4B.getM31());
+ assertTrue(3.2 == mat4B.getM32());
+ assertTrue(3.3 == mat4B.getM33());
+
+ final Matrix4 mat4C = new Matrix4(0.0, 0.1, 0.2, 0.3, 1.0, 1.1, 1.2, 1.3, 2.0, 2.1, 2.2, 2.3, 3.0, 3.1, 3.2,
+ 3.3);
+ assertTrue(0.0 == mat4C.getM00());
+ assertTrue(0.1 == mat4C.getM01());
+ assertTrue(0.2 == mat4C.getM02());
+ assertTrue(0.3 == mat4C.getM03());
+ assertTrue(1.0 == mat4C.getM10());
+ assertTrue(1.1 == mat4C.getM11());
+ assertTrue(1.2 == mat4C.getM12());
+ assertTrue(1.3 == mat4C.getM13());
+ assertTrue(2.0 == mat4C.getM20());
+ assertTrue(2.1 == mat4C.getM21());
+ assertTrue(2.2 == mat4C.getM22());
+ assertTrue(2.3 == mat4C.getM23());
+ assertTrue(3.0 == mat4C.getM30());
+ assertTrue(3.1 == mat4C.getM31());
+ assertTrue(3.2 == mat4C.getM32());
+ assertTrue(3.3 == mat4C.getM33());
+
+ mat4C.setIdentity();
+ assertTrue(mat4C.isIdentity());
+
+ for (int x = 0; x < 4; x++) {
+ for (int y = 0; y < 4; y++) {
+ final double value = (10 * x + y) / 10.;
+ mat4C.setValue(x, y, value);
+ assertTrue(value == mat4C.getValue(x, y));
+ }
+ }
+
+ mat4C.setIdentity();
+ mat4C.set(0.0, 0.1, 0.2, 0.3, 2.0, 2.1, 2.2, 2.3, 4.0, 4.1, 4.2, 4.3, 6.0, 6.1, 6.2, 6.3);
+ for (int x = 0; x < 4; x++) {
+ for (int y = 0; y < 4; y++) {
+ assertTrue((20 * x + y) / 10.f == mat4C.getValuef(x, y));
+ }
+ }
+
+ final Matrix4 store = new Matrix4(mat4C);
+ // catch a few expected exceptions
+ try {
+ mat4C.getValue(-1, 0);
+ fail("getValue(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.getValue(0, 4);
+ fail("getValue(0, 4) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.getValue(1, -1);
+ fail("getValue(1, -1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.getValue(2, 4);
+ fail("getValue(2, 4) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.getValue(3, -1);
+ fail("getValue(3, -1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.getValue(4, 0);
+ fail("getValue(4, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+
+ try {
+ mat4C.setValue(-1, 0, 0);
+ fail("setValue(-1, 0, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.setValue(0, -1, 0);
+ fail("setValue(0, -1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.setValue(1, 4, 0);
+ fail("setValue(1, 4, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.setValue(2, -1, 0);
+ fail("setValue(2, -1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.setValue(3, 4, 0);
+ fail("setValue(3, 4, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4C.setValue(4, 0, 0);
+ fail("setValue(4, 0, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ // above exceptions shouldn't have altered mat4C
+ assertEquals(store, mat4C);
+ }
+
+ @Test
+ public void testColumns() {
+ final Matrix4 mat4A = new Matrix4();
+ mat4A.setColumn(0, new Vector4(0, 4, 8, 12));
+ mat4A.setColumn(1, new Vector4(1, 5, 9, 13));
+ mat4A.setColumn(2, new Vector4(2, 6, 10, 14));
+ mat4A.setColumn(3, new Vector4(3, 7, 11, 15));
+ assertEquals(new Vector4(0, 4, 8, 12), mat4A.getColumn(0, new Vector4()));
+ assertEquals(new Vector4(1, 5, 9, 13), mat4A.getColumn(1, null));
+ assertEquals(new Vector4(2, 6, 10, 14), mat4A.getColumn(2, null));
+ assertEquals(new Vector4(3, 7, 11, 15), mat4A.getColumn(3, null));
+ try {
+ mat4A.getColumn(-1, null);
+ fail("getColumn(-1, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4A.getColumn(4, null);
+ fail("getColumn(4, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4A.setColumn(-1, new Vector4());
+ fail("setColumn(-1, double[]) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4A.setColumn(4, new Vector4());
+ fail("setColumn(4, double[]) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testRows() {
+ final Matrix4 mat4A = new Matrix4();
+ mat4A.setRow(0, new Vector4(0, 1, 2, 3));
+ mat4A.setRow(1, new Vector4(4, 5, 6, 7));
+ mat4A.setRow(2, new Vector4(8, 9, 10, 11));
+ mat4A.setRow(3, new Vector4(12, 13, 14, 15));
+ assertEquals(new Vector4(0, 1, 2, 3), mat4A.getRow(0, new Vector4()));
+ assertEquals(new Vector4(4, 5, 6, 7), mat4A.getRow(1, null));
+ assertEquals(new Vector4(8, 9, 10, 11), mat4A.getRow(2, null));
+ assertEquals(new Vector4(12, 13, 14, 15), mat4A.getRow(3, null));
+ try {
+ mat4A.getRow(-1, null);
+ fail("getRow(-1, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4A.getRow(4, null);
+ fail("getRow(4, null) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4A.setRow(-1, new Vector4());
+ fail("setRow(-1, double[]) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ mat4A.setRow(4, new Vector4());
+ fail("setRow(4, double[]) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testSetRotation() {
+ final Matrix4 mat4A = new Matrix4(0.0, 0.1, 0.2, 0.3, 1.0, 1.1, 1.2, 1.3, 2.0, 2.1, 2.2, 2.3, 3.0, 3.1, 3.2,
+ 3.3);
+ mat4A.set(Matrix3.IDENTITY);
+ assertTrue(1.0 == mat4A.getM00());
+ assertTrue(0.0 == mat4A.getM01());
+ assertTrue(0.0 == mat4A.getM02());
+ assertTrue(0.3 == mat4A.getM03());
+ assertTrue(0.0 == mat4A.getM10());
+ assertTrue(1.0 == mat4A.getM11());
+ assertTrue(0.0 == mat4A.getM12());
+ assertTrue(1.3 == mat4A.getM13());
+ assertTrue(0.0 == mat4A.getM20());
+ assertTrue(0.0 == mat4A.getM21());
+ assertTrue(1.0 == mat4A.getM22());
+ assertTrue(2.3 == mat4A.getM23());
+ assertTrue(3.0 == mat4A.getM30());
+ assertTrue(3.1 == mat4A.getM31());
+ assertTrue(3.2 == mat4A.getM32());
+ assertTrue(3.3 == mat4A.getM33());
+
+ mat4A.setIdentity();
+ // rotate identity 90 degrees around Y
+ final double a = MathUtils.HALF_PI;
+ final Quaternion quaternion = new Quaternion();
+ quaternion.fromAngleAxis(a, Vector3.UNIT_Y);
+ mat4A.set(quaternion);
+
+ assertEquals(new Matrix4( //
+ Math.cos(a), 0, Math.sin(a), 0, //
+ 0, 1, 0, 0, //
+ -Math.sin(a), 0, Math.cos(a), 0, //
+ 0, 0, 0, 1), mat4A);
+ }
+
+ @Test
+ public void testFromBuffer() {
+ final FloatBuffer fb = FloatBuffer.allocate(16);
+ fb.put(new float[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 });
+ fb.flip();
+ // row major
+ final Matrix4 mat4A = new Matrix4().fromFloatBuffer(fb);
+ assertTrue(0 == mat4A.getM00());
+ assertTrue(1 == mat4A.getM01());
+ assertTrue(2 == mat4A.getM02());
+ assertTrue(3 == mat4A.getM03());
+ assertTrue(4 == mat4A.getM10());
+ assertTrue(5 == mat4A.getM11());
+ assertTrue(6 == mat4A.getM12());
+ assertTrue(7 == mat4A.getM13());
+ assertTrue(8 == mat4A.getM20());
+ assertTrue(9 == mat4A.getM21());
+ assertTrue(10 == mat4A.getM22());
+ assertTrue(11 == mat4A.getM23());
+ assertTrue(12 == mat4A.getM30());
+ assertTrue(13 == mat4A.getM31());
+ assertTrue(14 == mat4A.getM32());
+ assertTrue(15 == mat4A.getM33());
+
+ // column major
+ fb.rewind();
+ mat4A.setIdentity();
+ mat4A.fromFloatBuffer(fb, false);
+ assertTrue(0 == mat4A.getM00());
+ assertTrue(4 == mat4A.getM01());
+ assertTrue(8 == mat4A.getM02());
+ assertTrue(12 == mat4A.getM03());
+ assertTrue(1 == mat4A.getM10());
+ assertTrue(5 == mat4A.getM11());
+ assertTrue(9 == mat4A.getM12());
+ assertTrue(13 == mat4A.getM13());
+ assertTrue(2 == mat4A.getM20());
+ assertTrue(6 == mat4A.getM21());
+ assertTrue(10 == mat4A.getM22());
+ assertTrue(14 == mat4A.getM23());
+ assertTrue(3 == mat4A.getM30());
+ assertTrue(7 == mat4A.getM31());
+ assertTrue(11 == mat4A.getM32());
+ assertTrue(15 == mat4A.getM33());
+
+ final DoubleBuffer db = DoubleBuffer.allocate(16);
+ db.put(new double[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 });
+ db.flip();
+ // row major
+ mat4A.setIdentity();
+ mat4A.fromDoubleBuffer(db);
+ assertTrue(0 == mat4A.getM00());
+ assertTrue(1 == mat4A.getM01());
+ assertTrue(2 == mat4A.getM02());
+ assertTrue(3 == mat4A.getM03());
+ assertTrue(4 == mat4A.getM10());
+ assertTrue(5 == mat4A.getM11());
+ assertTrue(6 == mat4A.getM12());
+ assertTrue(7 == mat4A.getM13());
+ assertTrue(8 == mat4A.getM20());
+ assertTrue(9 == mat4A.getM21());
+ assertTrue(10 == mat4A.getM22());
+ assertTrue(11 == mat4A.getM23());
+ assertTrue(12 == mat4A.getM30());
+ assertTrue(13 == mat4A.getM31());
+ assertTrue(14 == mat4A.getM32());
+ assertTrue(15 == mat4A.getM33());
+
+ // column major
+ db.rewind();
+ mat4A.setIdentity();
+ mat4A.fromDoubleBuffer(db, false);
+ assertTrue(0 == mat4A.getM00());
+ assertTrue(4 == mat4A.getM01());
+ assertTrue(8 == mat4A.getM02());
+ assertTrue(12 == mat4A.getM03());
+ assertTrue(1 == mat4A.getM10());
+ assertTrue(5 == mat4A.getM11());
+ assertTrue(9 == mat4A.getM12());
+ assertTrue(13 == mat4A.getM13());
+ assertTrue(2 == mat4A.getM20());
+ assertTrue(6 == mat4A.getM21());
+ assertTrue(10 == mat4A.getM22());
+ assertTrue(14 == mat4A.getM23());
+ assertTrue(3 == mat4A.getM30());
+ assertTrue(7 == mat4A.getM31());
+ assertTrue(11 == mat4A.getM32());
+ assertTrue(15 == mat4A.getM33());
+ }
+
+ @Test
+ public void testToBuffer() {
+ final double[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+ final double[] colmajor = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 };
+
+ final Matrix4 mat4A = new Matrix4().fromArray(values);
+
+ // row major
+ final FloatBuffer fb = mat4A.toFloatBuffer(FloatBuffer.allocate(16));
+ fb.flip();
+ for (int i = 0; i < 16; i++) {
+ assertTrue(values[i] == fb.get());
+ }
+
+ // column major
+ fb.rewind();
+ mat4A.toFloatBuffer(fb, false);
+ fb.flip();
+ for (int i = 0; i < 16; i++) {
+ assertTrue(colmajor[i] == fb.get());
+ }
+
+ // row major
+ final DoubleBuffer db = mat4A.toDoubleBuffer(DoubleBuffer.allocate(16));
+ db.flip();
+ for (int i = 0; i < 16; i++) {
+ assertTrue(values[i] == db.get());
+ }
+
+ // column major
+ db.rewind();
+ mat4A.toDoubleBuffer(db, false);
+ db.flip();
+ for (int i = 0; i < 16; i++) {
+ assertTrue(colmajor[i] == db.get());
+ }
+ }
+
+ @Test
+ public void testFromArray() {
+ final double[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+ final Matrix4 mat4A = new Matrix4();
+
+ // row major
+ mat4A.setIdentity();
+ mat4A.fromArray(values);
+ assertTrue(0 == mat4A.getM00());
+ assertTrue(1 == mat4A.getM01());
+ assertTrue(2 == mat4A.getM02());
+ assertTrue(3 == mat4A.getM03());
+ assertTrue(4 == mat4A.getM10());
+ assertTrue(5 == mat4A.getM11());
+ assertTrue(6 == mat4A.getM12());
+ assertTrue(7 == mat4A.getM13());
+ assertTrue(8 == mat4A.getM20());
+ assertTrue(9 == mat4A.getM21());
+ assertTrue(10 == mat4A.getM22());
+ assertTrue(11 == mat4A.getM23());
+ assertTrue(12 == mat4A.getM30());
+ assertTrue(13 == mat4A.getM31());
+ assertTrue(14 == mat4A.getM32());
+ assertTrue(15 == mat4A.getM33());
+
+ // column major
+ mat4A.setIdentity();
+ mat4A.fromArray(values, false);
+ assertTrue(0 == mat4A.getM00());
+ assertTrue(4 == mat4A.getM01());
+ assertTrue(8 == mat4A.getM02());
+ assertTrue(12 == mat4A.getM03());
+ assertTrue(1 == mat4A.getM10());
+ assertTrue(5 == mat4A.getM11());
+ assertTrue(9 == mat4A.getM12());
+ assertTrue(13 == mat4A.getM13());
+ assertTrue(2 == mat4A.getM20());
+ assertTrue(6 == mat4A.getM21());
+ assertTrue(10 == mat4A.getM22());
+ assertTrue(14 == mat4A.getM23());
+ assertTrue(3 == mat4A.getM30());
+ assertTrue(7 == mat4A.getM31());
+ assertTrue(11 == mat4A.getM32());
+ assertTrue(15 == mat4A.getM33());
+ }
+
+ @Test
+ public void testToArray() {
+ final double[] values = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+ final Matrix4 mat4A = new Matrix4().fromArray(values);
+
+ // row major
+ final double[] dbls1 = mat4A.toArray(null);
+ for (int i = 0; i < 16; i++) {
+ assertTrue(values[i] == dbls1[i]);
+ }
+
+ // column major
+ final double[] colmajor = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 };
+ mat4A.toArray(dbls1, false);
+ for (int i = 0; i < 16; i++) {
+ assertTrue(colmajor[i] == dbls1[i]);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadArray() {
+ final Matrix4 mat4A = new Matrix4();
+ mat4A.toArray(new double[9]);
+ }
+
+ @Test
+ public void testAngleAxis() {
+ final Matrix4 mat4A = new Matrix4();
+ // rotate identity 90 degrees around X
+ final double angle = MathUtils.HALF_PI;
+ mat4A.fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X);
+ assertEquals(new Matrix4( //
+ 1, 0, 0, 0, //
+ 0, Math.cos(angle), -Math.sin(angle), 0, //
+ 0, Math.sin(angle), Math.cos(angle), 0, //
+ 0, 0, 0, 1), mat4A);
+ }
+
+ @Test
+ public void testRotations() {
+ final Vector4 rotated = new Vector4();
+ final Vector4 expected = new Vector4();
+ final Matrix4 worker = new Matrix4();
+
+ // test axis rotation methods against general purpose
+ // X AXIS
+ expected.set(1, 1, 1, 1);
+ rotated.set(1, 1, 1, 1);
+ worker.setIdentity().applyRotationX(MathUtils.QUARTER_PI).applyPost(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 1, 0, 0).applyPost(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Y AXIS
+ expected.set(1, 1, 1, 1);
+ rotated.set(1, 1, 1, 1);
+ worker.setIdentity().applyRotationY(MathUtils.QUARTER_PI).applyPost(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 0, 1, 0).applyPost(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Z AXIS
+ expected.set(1, 1, 1, 1);
+ rotated.set(1, 1, 1, 1);
+ worker.setIdentity().applyRotationZ(MathUtils.QUARTER_PI).applyPost(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 0, 0, 1).applyPost(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+ }
+
+ @Test
+ public void testTranslation() {
+ final Matrix4 src = new Matrix4();
+ src.applyRotation(MathUtils.QUARTER_PI, 1, 0, 0);
+
+ final Matrix4 trans = new Matrix4();
+ trans.setColumn(3, new Vector4(1, 2, 3, 1));
+ final Matrix4 transThenRotate = trans.multiply(src, null);
+ final Matrix4 rotateThenTrans = src.multiply(trans, null);
+
+ final Matrix4 pre1 = new Matrix4(src).applyTranslationPre(1, 2, 3);
+ final Matrix4 post1 = new Matrix4(src).applyTranslationPost(1, 2, 3);
+
+ assertEquals(transThenRotate, pre1);
+ assertEquals(rotateThenTrans, post1);
+ }
+
+ @Test
+ public void testMultiplyDiagonal() {
+ final Matrix4 mat4A = new Matrix4();
+ Matrix4 result = mat4A.multiplyDiagonalPost(new Vector4(2, 4, 6, 8), null);
+ assertEquals(new Matrix4( //
+ 2, 0, 0, 0, //
+ 0, 4, 0, 0, //
+ 0, 0, 6, 0, //
+ 0, 0, 0, 8), result);
+ mat4A.multiplyDiagonalPre(new Vector4(-2, -4, -6, -8), result);
+ assertEquals(new Matrix4( //
+ -2, 0, 0, 0, //
+ 0, -4, 0, 0, //
+ 0, 0, -6, 0, //
+ 0, 0, 0, -8), result);
+
+ final double a = MathUtils.HALF_PI;
+ mat4A.applyRotationY(a);
+ mat4A.multiplyDiagonalPost(new Vector4(2, 4, 6, 8), result);
+ assertEquals(new Matrix4( //
+ 2 * Math.cos(a), 4 * 0, 6 * Math.sin(a), 8 * 0, //
+ 2 * 0, 4 * 1, 6 * 0, 8 * 0, //
+ 2 * -Math.sin(a), 4 * 0, 6 * Math.cos(a), 8 * 0, //
+ 2 * 0, 4 * 0, 6 * 0, 8 * 1), result);
+ result = mat4A.multiplyDiagonalPre(new Vector4(-2, -4, -6, -8), null);
+ assertEquals(new Matrix4( //
+ -2 * Math.cos(a), -2 * 0, -2 * Math.sin(a), -2 * 0, //
+ -4 * 0, -4 * 1, -4 * 0, -4 * 0, //
+ -6 * -Math.sin(a), -6 * 0, -6 * Math.cos(a), -6 * 0, //
+ -8 * 0, -8 * 0, -8 * 0, -8 * 1), result);
+ }
+
+ @Test
+ public void testMultiply() {
+ final Matrix4 mat4A = new Matrix4( //
+ 0.01, 0.1, 0.2, 0.3, //
+ 1.0, 1.1, 1.2, 1.3, //
+ 2.0, 2.1, 2.2, 2.3, //
+ 3.0, 3.1, 3.2, 3.3);
+ mat4A.multiplyLocal(2);
+ assertEquals(new Matrix4( //
+ 0.02, 0.2, 0.4, 0.6, //
+ 2.0, 2.2, 2.4, 2.6, //
+ 4.0, 4.2, 4.4, 4.6, //
+ 6.0, 6.2, 6.4, 6.6), mat4A);
+
+ final Matrix4 mat4B = new Matrix4( //
+ 0.5, 1, 2, 3, //
+ 4, 5, 6, 7, //
+ 8, 9, 10, 11, //
+ 12, 13, 14, 15);
+ final Matrix4 result = mat4A.multiply(mat4B, null);
+ assertTrue(0.02 * 0.5 + 0.2 * 4 + 0.4 * 8 + 0.6 * 12 == result.getM00());
+ assertTrue(0.02 * 1 + 0.2 * 5 + 0.4 * 9 + 0.6 * 13 == result.getM01());
+ assertTrue(0.02 * 2 + 0.2 * 6 + 0.4 * 10 + 0.6 * 14 == result.getM02());
+ assertTrue(0.02 * 3 + 0.2 * 7 + 0.4 * 11 + 0.6 * 15 == result.getM03());
+ assertTrue(2.0 * 0.5 + 2.2 * 4 + 2.4 * 8 + 2.6 * 12 == result.getM10());
+ assertTrue(2.0 * 1 + 2.2 * 5 + 2.4 * 9 + 2.6 * 13 == result.getM11());
+ assertTrue(2.0 * 2 + 2.2 * 6 + 2.4 * 10 + 2.6 * 14 == result.getM12());
+ assertTrue(2.0 * 3 + 2.2 * 7 + 2.4 * 11 + 2.6 * 15 == result.getM13());
+ assertTrue(4.0 * 0.5 + 4.2 * 4 + 4.4 * 8 + 4.6 * 12 == result.getM20());
+ assertTrue(4.0 * 1 + 4.2 * 5 + 4.4 * 9 + 4.6 * 13 == result.getM21());
+ assertTrue(4.0 * 2 + 4.2 * 6 + 4.4 * 10 + 4.6 * 14 == result.getM22());
+ assertTrue(4.0 * 3 + 4.2 * 7 + 4.4 * 11 + 4.6 * 15 == result.getM23());
+ assertTrue(6.0 * 0.5 + 6.2 * 4 + 6.4 * 8 + 6.6 * 12 == result.getM30());
+ assertTrue(6.0 * 1 + 6.2 * 5 + 6.4 * 9 + 6.6 * 13 == result.getM31());
+ assertTrue(6.0 * 2 + 6.2 * 6 + 6.4 * 10 + 6.6 * 14 == result.getM32());
+ assertTrue(6.0 * 3 + 6.2 * 7 + 6.4 * 11 + 6.6 * 15 == result.getM33());
+ mat4A.multiplyLocal(mat4B);
+ assertTrue(0.02 * 0.5 + 0.2 * 4 + 0.4 * 8 + 0.6 * 12 == mat4A.getM00());
+ assertTrue(0.02 * 1 + 0.2 * 5 + 0.4 * 9 + 0.6 * 13 == mat4A.getM01());
+ assertTrue(0.02 * 2 + 0.2 * 6 + 0.4 * 10 + 0.6 * 14 == mat4A.getM02());
+ assertTrue(0.02 * 3 + 0.2 * 7 + 0.4 * 11 + 0.6 * 15 == mat4A.getM03());
+ assertTrue(2.0 * 0.5 + 2.2 * 4 + 2.4 * 8 + 2.6 * 12 == mat4A.getM10());
+ assertTrue(2.0 * 1 + 2.2 * 5 + 2.4 * 9 + 2.6 * 13 == mat4A.getM11());
+ assertTrue(2.0 * 2 + 2.2 * 6 + 2.4 * 10 + 2.6 * 14 == mat4A.getM12());
+ assertTrue(2.0 * 3 + 2.2 * 7 + 2.4 * 11 + 2.6 * 15 == mat4A.getM13());
+ assertTrue(4.0 * 0.5 + 4.2 * 4 + 4.4 * 8 + 4.6 * 12 == mat4A.getM20());
+ assertTrue(4.0 * 1 + 4.2 * 5 + 4.4 * 9 + 4.6 * 13 == mat4A.getM21());
+ assertTrue(4.0 * 2 + 4.2 * 6 + 4.4 * 10 + 4.6 * 14 == mat4A.getM22());
+ assertTrue(4.0 * 3 + 4.2 * 7 + 4.4 * 11 + 4.6 * 15 == mat4A.getM23());
+ assertTrue(6.0 * 0.5 + 6.2 * 4 + 6.4 * 8 + 6.6 * 12 == mat4A.getM30());
+ assertTrue(6.0 * 1 + 6.2 * 5 + 6.4 * 9 + 6.6 * 13 == mat4A.getM31());
+ assertTrue(6.0 * 2 + 6.2 * 6 + 6.4 * 10 + 6.6 * 14 == mat4A.getM32());
+ assertTrue(6.0 * 3 + 6.2 * 7 + 6.4 * 11 + 6.6 * 15 == mat4A.getM33());
+ }
+
+ @Test
+ public void testAddSubtract() {
+ final Matrix4 mat4A = new Matrix4( //
+ 0.0, 0.1, 0.2, 0.3, //
+ 1.0, 1.1, 1.2, 1.3, //
+ 2.0, 2.1, 2.2, 2.3, //
+ 3.0, 3.1, 3.2, 3.3);
+
+ final Matrix4 result1 = mat4A.add(new Matrix4(//
+ 1, 2, 3, 4,//
+ 5, 6, 7, 8,//
+ 9, 10, 11, 12,//
+ 13, 14, 15, 16), null);
+ assertEquals(new Matrix4( //
+ 1.0, 2.1, 3.2, 4.3, //
+ 6.0, 7.1, 8.2, 9.3, //
+ 11.0, 12.1, 13.2, 14.3, //
+ 16.0, 17.1, 18.2, 19.3), result1);
+
+ final Matrix4 result2 = result1.subtract(new Matrix4(//
+ 1, 2, 3, 4,//
+ 5, 6, 7, 8,//
+ 9, 10, 11, 12,//
+ 13, 14, 15, 16), null);
+ assertEquals(mat4A, result2);
+ result2.addLocal(Matrix4.IDENTITY);
+ assertEquals(new Matrix4( //
+ 1.0, 0.1, 0.2, 0.3, //
+ 1.0, 2.1, 1.2, 1.3, //
+ 2.0, 2.1, 3.2, 2.3, //
+ 3.0, 3.1, 3.2, 4.3), result2);
+
+ result1.subtractLocal(Matrix4.IDENTITY);
+ assertEquals(new Matrix4( //
+ 0.0, 2.1, 3.2, 4.3, //
+ 6.0, 6.1, 8.2, 9.3, //
+ 11.0, 12.1, 12.2, 14.3, //
+ 16.0, 17.1, 18.2, 18.3), result1);
+ }
+
+ @Test
+ public void testScale() {
+ final Matrix4 mat4A = new Matrix4( //
+ 0.01, 0.1, 0.2, 0.3, //
+ 1.0, 1.1, 1.2, 1.3, //
+ 2.0, 2.1, 2.2, 2.3, //
+ 3.0, 3.1, 3.2, 3.3);
+ final Matrix4 result = mat4A.scale(new Vector4(-1, 2, 3, 4), null);
+ assertEquals(new Matrix4( //
+ 0.01 * -1, 0.1 * 2, 0.2 * 3, 0.3 * 4, //
+ 1.0 * -1, 1.1 * 2, 1.2 * 3, 1.3 * 4, //
+ 2.0 * -1, 2.1 * 2, 2.2 * 3, 2.3 * 4, //
+ 3.0 * -1, 3.1 * 2, 3.2 * 3, 3.3 * 4), result);
+
+ result.scaleLocal(new Vector4(-1, 0.5, 1 / 3., .25));
+ assertEquals(mat4A, result);
+ }
+
+ @Test
+ public void testTranspose() {
+ final Matrix4 mat4A = new Matrix4( //
+ 0.01, 0.1, 0.2, 0.3, //
+ 1.0, 1.1, 1.2, 1.3, //
+ 2.0, 2.1, 2.2, 2.3, //
+ 3.0, 3.1, 3.2, 3.3);
+ final Matrix4 result = mat4A.transpose(null);
+ assertEquals(new Matrix4( //
+ 0.01, 1.0, 2.0, 3.0, //
+ 0.1, 1.1, 2.1, 3.1, //
+ 0.2, 1.2, 2.2, 3.2, //
+ 0.3, 1.3, 2.3, 3.3), result);
+ assertEquals(new Matrix4( //
+ 0.01, 0.1, 0.2, 0.3, //
+ 1.0, 1.1, 1.2, 1.3, //
+ 2.0, 2.1, 2.2, 2.3, //
+ 3.0, 3.1, 3.2, 3.3), result.transposeLocal());
+ // coverage
+ final Matrix4 result2 = result.transposeLocal().transpose(new Matrix4());
+ assertEquals(mat4A, result2);
+ }
+
+ @Test
+ public void testInvert() {
+ final Matrix4 mat4A = new Matrix4().applyRotationX(MathUtils.QUARTER_PI).applyTranslationPost(1, 2, 3);
+ final Matrix4 inverted = mat4A.invert(null);
+ assertEquals(Matrix4.IDENTITY, mat4A.multiply(inverted, null));
+ assertEquals(mat4A, inverted.invertLocal());
+ }
+
+ @Test(expected = ArithmeticException.class)
+ public void testBadInvert() {
+ final Matrix4 mat4A = new Matrix4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ mat4A.invertLocal();
+ }
+
+ @Test
+ public void testAdjugate() {
+ final double //
+ a = -3, b = 2, c = -5, d = 2, //
+ e = -1, f = 0, g = -2, h = 3, //
+ i = 1, j = -3, k = -4, l = 0, //
+ m = 4, n = 2, o = 3, p = 1;
+
+ final Matrix4 mat4A = new Matrix4( //
+ a, b, c, d,//
+ e, f, g, h, //
+ i, j, k, l, //
+ m, n, o, p);
+
+ // prepare sections
+ final Matrix3 m00 = new Matrix3(f, g, h, j, k, l, n, o, p);
+ final Matrix3 m01 = new Matrix3(b, c, d, j, k, l, n, o, p);
+ final Matrix3 m02 = new Matrix3(b, c, d, f, g, h, n, o, p);
+ final Matrix3 m03 = new Matrix3(b, c, d, f, g, h, j, k, l);
+ final Matrix3 m10 = new Matrix3(e, g, h, i, k, l, m, o, p);
+ final Matrix3 m11 = new Matrix3(a, c, d, i, k, l, m, o, p);
+ final Matrix3 m12 = new Matrix3(a, c, d, e, g, h, m, o, p);
+ final Matrix3 m13 = new Matrix3(a, c, d, e, g, h, i, k, l);
+ final Matrix3 m20 = new Matrix3(e, f, h, i, j, l, m, n, p);
+ final Matrix3 m21 = new Matrix3(a, b, d, i, j, l, m, n, p);
+ final Matrix3 m22 = new Matrix3(a, b, d, e, f, h, m, n, p);
+ final Matrix3 m23 = new Matrix3(a, b, d, e, f, h, i, j, l);
+ final Matrix3 m30 = new Matrix3(e, f, g, i, j, k, m, n, o);
+ final Matrix3 m31 = new Matrix3(a, b, c, i, j, k, m, n, o);
+ final Matrix3 m32 = new Matrix3(a, b, c, e, f, g, m, n, o);
+ final Matrix3 m33 = new Matrix3(a, b, c, e, f, g, i, j, k);
+
+ // generate adjugate
+ final Matrix4 testValue = new Matrix4( //
+ m00.determinant(), -m01.determinant(), m02.determinant(), -m03.determinant(), //
+ -m10.determinant(), m11.determinant(), -m12.determinant(), m13.determinant(), //
+ m20.determinant(), -m21.determinant(), m22.determinant(), -m23.determinant(), //
+ -m30.determinant(), m31.determinant(), -m32.determinant(), m33.determinant());
+
+ assertEquals(testValue, mat4A.adjugate(null));
+ assertEquals(testValue, mat4A.adjugateLocal());
+ }
+
+ @Test
+ public void testDeterminant() {
+ {
+ final double //
+ a = -3, b = 2, c = -5, d = 2, //
+ e = -1, f = 0, g = -2, h = 3, //
+ i = 1, j = -3, k = -4, l = 0, //
+ m = 4, n = 2, o = 3, p = 1;
+
+ final Matrix4 mat4A = new Matrix4( //
+ a, b, c, d,//
+ e, f, g, h, //
+ i, j, k, l, //
+ m, n, o, p);
+
+ // prepare sections
+ final double m00 = new Matrix3(f, g, h, j, k, l, n, o, p).determinant();
+ final double m01 = new Matrix3(e, g, h, i, k, l, m, o, p).determinant();
+ final double m02 = new Matrix3(e, f, h, i, j, l, m, n, p).determinant();
+ final double m03 = new Matrix3(e, f, g, i, j, k, m, n, o).determinant();
+ final double determinant = a * m00 - b * m01 + c * m02 - d * m03;
+
+ assertTrue(Math.abs(determinant - mat4A.determinant()) <= MathUtils.EPSILON);
+ }
+
+ {
+ final double //
+ a = -1.2, b = 4, c = -2.5, d = 1.7, //
+ e = 2, f = -3, g = -2, h = 3.2, //
+ i = 3.1, j = -1, k = 2, l = 1.15, //
+ m = 1, n = 2, o = 3.14, p = 1.4;
+
+ final Matrix4 mat4A = new Matrix4( //
+ a, b, c, d,//
+ e, f, g, h, //
+ i, j, k, l, //
+ m, n, o, p);
+
+ // prepare sections
+ final double m00 = new Matrix3(f, g, h, j, k, l, n, o, p).determinant();
+ final double m01 = new Matrix3(e, g, h, i, k, l, m, o, p).determinant();
+ final double m02 = new Matrix3(e, f, h, i, j, l, m, n, p).determinant();
+ final double m03 = new Matrix3(e, f, g, i, j, k, m, n, o).determinant();
+ final double determinant = a * m00 - b * m01 + c * m02 - d * m03;
+
+ assertTrue(Math.abs(determinant - mat4A.determinant()) <= MathUtils.EPSILON);
+ }
+ }
+
+ @Test
+ public void testClone() {
+ final Matrix4 mat1 = new Matrix4();
+ final Matrix4 mat2 = mat1.clone();
+ assertEquals(mat1, mat2);
+ assertNotSame(mat1, mat2);
+ }
+
+ @Test
+ public void testValid() {
+ final Matrix4 mat4 = new Matrix4();
+ assertTrue(Matrix4.isValid(mat4));
+ for (int i = 0; i < 16; i++) {
+ mat4.setIdentity();
+ mat4.setValue(i / 4, i % 4, Double.NaN);
+ assertFalse(Matrix4.isValid(mat4));
+ mat4.setIdentity();
+ mat4.setValue(i / 4, i % 4, Double.POSITIVE_INFINITY);
+ assertFalse(Matrix4.isValid(mat4));
+ }
+
+ mat4.setIdentity();
+ assertTrue(Matrix4.isValid(mat4));
+
+ assertFalse(Matrix4.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(mat4, mat4);
+ assertTrue(mat4.strictEquals(mat4));
+ assertFalse(mat4.equals(null));
+ assertFalse(mat4.strictEquals(null));
+ assertFalse(mat4.equals(new Vector2()));
+ assertFalse(mat4.strictEquals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Matrix4 matTemp = Matrix4.fetchTempInstance();
+ matTemp.set(mat4);
+ assertEquals(mat4, matTemp);
+ assertNotSame(mat4, matTemp);
+ Matrix4.releaseTempInstance(matTemp);
+
+ // cover more of equals
+ mat4.set(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
+ final Matrix4 comp = new Matrix4(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+ assertFalse(mat4.equals(comp));
+ assertFalse(mat4.strictEquals(comp));
+ for (int i = 0; i < 15; i++) {
+ comp.setValue(i / 4, i % 4, i);
+ assertFalse(mat4.equals(comp));
+ assertFalse(mat4.strictEquals(comp));
+ }
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Matrix4 mat1 = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+ final Matrix4 mat2 = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+ final Matrix4 mat3 = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0);
+
+ assertTrue(mat1.hashCode() == mat2.hashCode());
+ assertTrue(mat1.hashCode() != mat3.hashCode());
+ }
+
+ @Test
+ public void testOrthonormal() {
+ final Matrix4 mat4 = new Matrix4();
+ assertTrue(mat4.isOrthonormal());
+ // just rotation
+ mat4.set(new Matrix3().applyRotationX(MathUtils.QUARTER_PI));
+ assertTrue(mat4.isOrthonormal());
+ // non-uniform scale
+ mat4.set(new Matrix3().scaleLocal(new Vector3(1, 2, 3)).applyRotationX(MathUtils.QUARTER_PI));
+ assertFalse(mat4.isOrthonormal());
+ // uniform scale
+ mat4.set(new Matrix3().scaleLocal(new Vector3(2, 2, 2)).applyRotationX(MathUtils.QUARTER_PI));
+ assertFalse(mat4.isOrthonormal());
+ // uniform scale 1
+ mat4.set(new Matrix3().scaleLocal(new Vector3(1, 1, 1)).applyRotationX(MathUtils.QUARTER_PI));
+ assertTrue(mat4.isOrthonormal());
+ }
+
+ @Test
+ public void testApplyVector4() {
+ final Matrix4 mat4 = new Matrix4().applyRotationX(MathUtils.HALF_PI);
+ final Vector4 vec4 = new Vector4(0, 1, 0, 1);
+ final Vector4 result = mat4.applyPost(vec4, null);
+ assertTrue(Math.abs(new Vector4(0, 0, 1, 1).distance(result)) <= MathUtils.EPSILON);
+ vec4.set(0, 1, 1, 1);
+ mat4.applyPost(vec4, result);
+ assertTrue(Math.abs(new Vector4(0, -1, 1, 1).distance(result)) <= MathUtils.EPSILON);
+
+ vec4.set(0, 1, 1, 1);
+ mat4.applyPre(vec4, result);
+ assertTrue(Math.abs(new Vector4(0, 1, -1, 1).distance(result)) <= MathUtils.EPSILON);
+
+ vec4.set(1, 1, 1, 1);
+ assertTrue(Math.abs(new Vector4(1, 1, -1, 1).distance(mat4.applyPre(vec4, null))) <= MathUtils.EPSILON);
+ }
+
+ @Test
+ public void testApplyVector3() {
+ final Matrix4 mat4 = new Matrix4().applyRotationX(MathUtils.HALF_PI).applyTranslationPre(1, 2, 3);
+ final Vector3 vec3 = new Vector3(0, 1, 0);
+ final Vector3 result = mat4.applyPostPoint(vec3, null);
+ assertTrue(Math.abs(new Vector3(1, 2, 4).distance(result)) <= MathUtils.EPSILON);
+ vec3.set(0, 1, 1);
+ mat4.applyPostPoint(vec3, result);
+ assertTrue(Math.abs(new Vector3(1, 1, 4).distance(result)) <= MathUtils.EPSILON);
+
+ vec3.set(0, 1, 1);
+ mat4.applyPostVector(vec3, result);
+ assertTrue(Math.abs(new Vector3(0, -1, 1).distance(result)) <= MathUtils.EPSILON);
+
+ vec3.set(1, 1, 1);
+ assertTrue(Math.abs(new Vector3(1, -1, 1).distance(mat4.applyPostVector(vec3, null))) <= MathUtils.EPSILON);
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestObjectPool.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestObjectPool.java new file mode 100644 index 0000000..5f06c34 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestObjectPool.java @@ -0,0 +1,34 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import org.junit.Test;
+
+public class TestObjectPool {
+
+ @Test(expected = RuntimeException.class)
+ public void testPoolReleaseNullError() {
+ Vector2.releaseTempInstance(null);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testPoolBadClass() {
+ ObjectPool.create(Poolable.class, 10).fetch();
+ }
+
+ @Test
+ public void testPoolSize() {
+ final ObjectPool<Vector2> pool = ObjectPool.create(Vector2.class, 10);
+ for (int i = 0; i < 11; i++) {
+ pool.release(new Vector2());
+ }
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestPlane.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestPlane.java new file mode 100644 index 0000000..1b6b164 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestPlane.java @@ -0,0 +1,136 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.ardor3d.math.type.ReadOnlyPlane.Side;
+
+public class TestPlane {
+
+ @Test
+ public void testGetSet() {
+ final Plane plane = new Plane();
+ assertEquals(Vector3.UNIT_Y, plane.getNormal());
+ assertTrue(plane.getConstant() == 0.0);
+
+ plane.setNormal(Vector3.UNIT_X);
+ plane.setConstant(1.0);
+ assertEquals(Vector3.UNIT_X, plane.getNormal());
+ assertTrue(plane.getConstant() == 1.0);
+
+ final Plane plane2 = new Plane(plane);
+ assertEquals(Vector3.UNIT_X, plane2.getNormal());
+ assertTrue(plane.getConstant() == 1.0);
+
+ final Plane plane3 = new Plane(Vector3.NEG_UNIT_Z, 2.5);
+ assertEquals(Vector3.NEG_UNIT_Z, plane3.getNormal());
+ assertTrue(plane3.getConstant() == 2.5);
+
+ final Plane plane4 = new Plane().setPlanePoints(new Vector3(1, 1, 1), new Vector3(2, 1, 1),
+ new Vector3(2, 2, 1));
+ assertEquals(Vector3.UNIT_Z, plane4.getNormal());
+ assertTrue(plane4.getConstant() == 1.0);
+ }
+
+ @Test
+ public void testEquals() {
+ // couple of equals validity tests
+ final Plane plane1 = new Plane();
+ assertEquals(plane1, plane1);
+ assertFalse(plane1.equals(null));
+ assertFalse(plane1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Plane plane2 = Plane.fetchTempInstance();
+ plane2.set(plane1);
+ assertEquals(plane1, plane2);
+ assertNotSame(plane1, plane2);
+ Plane.releaseTempInstance(plane2);
+
+ // cover more of equals
+ assertFalse(plane1.equals(new Plane(Vector3.UNIT_X, 0)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Plane plane1 = new Plane(Vector3.UNIT_Y, 2);
+ final Plane plane2 = new Plane(Vector3.UNIT_Y, 2);
+ final Plane plane3 = new Plane(Vector3.UNIT_Z, 2);
+
+ assertTrue(plane1.hashCode() == plane2.hashCode());
+ assertTrue(plane1.hashCode() != plane3.hashCode());
+ }
+
+ @Test
+ public void testClone() {
+ final Plane plane1 = new Plane();
+ final Plane plane2 = plane1.clone();
+ assertEquals(plane1, plane2);
+ assertNotSame(plane1, plane2);
+ }
+
+ @Test
+ public void testValid() {
+ final Plane plane1 = new Plane();
+ final Plane plane2 = new Plane(new Vector3(Double.NaN, 0, 0), 0.5);
+ final Plane plane3 = new Plane(Vector3.UNIT_X, Double.NaN);
+ final Plane plane4 = new Plane(Vector3.UNIT_X, Double.POSITIVE_INFINITY);
+
+ assertTrue(Plane.isValid(plane1));
+ assertFalse(Plane.isValid(plane2));
+ assertFalse(Plane.isValid(plane3));
+ assertFalse(Plane.isValid(plane4));
+
+ plane4.setConstant(1);
+ assertTrue(Plane.isValid(plane4));
+
+ assertFalse(Plane.isValid(null));
+ }
+
+ @Test
+ public void testDistance() {
+ final Plane plane1 = new Plane(Vector3.UNIT_Y, 1.0);
+ final Vector3 point = new Vector3(0, 5, 0);
+ assertTrue(4.0 == plane1.pseudoDistance(point));
+ assertEquals(Side.Outside, plane1.whichSide(point));
+
+ point.set(0, -4, 0);
+ assertTrue(-5.0 == plane1.pseudoDistance(point));
+ assertEquals(Side.Inside, plane1.whichSide(point));
+
+ point.set(1, 1, 1);
+ assertTrue(0.0 == plane1.pseudoDistance(point));
+ assertEquals(Side.Neither, plane1.whichSide(point));
+ }
+
+ @Test
+ public void testReflect() {
+ final Plane plane1 = new Plane(Vector3.UNIT_X, 5.0);
+ assertEquals(new Vector3(), plane1.reflectVector(new Vector3(), new Vector3()));
+ assertEquals(new Vector3(-1, 0, 0), plane1.reflectVector(new Vector3(1, 0, 0), null));
+ assertEquals(new Vector3(-1, 1, 1).normalizeLocal(),
+ plane1.reflectVector(new Vector3(1, 1, 1).normalizeLocal(), null));
+ assertEquals(new Vector3(-3, 2, -1).normalizeLocal(),
+ plane1.reflectVector(new Vector3(3, 2, -1).normalizeLocal(), null));
+
+ final Plane plane2 = new Plane(Vector3.UNIT_Z, 1.0);
+ assertEquals(new Vector3(), plane2.reflectVector(new Vector3(), new Vector3()));
+ assertEquals(new Vector3(0, 0, -1), plane2.reflectVector(new Vector3(0, 0, 1), null));
+ assertEquals(new Vector3(1, 1, -1).normalizeLocal(),
+ plane2.reflectVector(new Vector3(1, 1, 1).normalizeLocal(), null));
+ assertEquals(new Vector3(3, 2, 1).normalizeLocal(),
+ plane2.reflectVector(new Vector3(3, 2, -1).normalizeLocal(), null));
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestQuaternion.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestQuaternion.java new file mode 100644 index 0000000..f7ee4f4 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestQuaternion.java @@ -0,0 +1,554 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+public class TestQuaternion {
+
+ @Test
+ public void testGetSet() {
+ final Quaternion quat1 = new Quaternion();
+ assertEquals(Quaternion.IDENTITY, quat1);
+ assertTrue(quat1.isIdentity());
+
+ quat1.setX(1);
+ assertTrue(quat1.getX() == 1.0);
+ quat1.setX(Double.POSITIVE_INFINITY);
+ assertTrue(quat1.getX() == Double.POSITIVE_INFINITY);
+ quat1.setX(Double.NEGATIVE_INFINITY);
+ assertTrue(quat1.getX() == Double.NEGATIVE_INFINITY);
+
+ quat1.setY(1);
+ assertTrue(quat1.getY() == 1.0);
+ quat1.setY(Double.POSITIVE_INFINITY);
+ assertTrue(quat1.getY() == Double.POSITIVE_INFINITY);
+ quat1.setY(Double.NEGATIVE_INFINITY);
+ assertTrue(quat1.getY() == Double.NEGATIVE_INFINITY);
+
+ quat1.setZ(1);
+ assertTrue(quat1.getZ() == 1.0);
+ quat1.setZ(Double.POSITIVE_INFINITY);
+ assertTrue(quat1.getZ() == Double.POSITIVE_INFINITY);
+ quat1.setZ(Double.NEGATIVE_INFINITY);
+ assertTrue(quat1.getZ() == Double.NEGATIVE_INFINITY);
+
+ quat1.setW(1);
+ assertTrue(quat1.getW() == 1.0);
+ quat1.setW(Double.POSITIVE_INFINITY);
+ assertTrue(quat1.getW() == Double.POSITIVE_INFINITY);
+ quat1.setW(Double.NEGATIVE_INFINITY);
+ assertTrue(quat1.getW() == Double.NEGATIVE_INFINITY);
+
+ quat1.set(Math.PI, Math.PI, Math.PI, Math.PI);
+ assertTrue(quat1.getXf() == (float) Math.PI);
+ assertTrue(quat1.getYf() == (float) Math.PI);
+ assertTrue(quat1.getZf() == (float) Math.PI);
+ assertTrue(quat1.getWf() == (float) Math.PI);
+
+ final Quaternion quat2 = new Quaternion();
+ quat2.set(quat1);
+ assertEquals(quat1, quat2);
+ }
+
+ @Test
+ public void testToArray() {
+ final Quaternion quat1 = new Quaternion();
+ quat1.set(Math.PI, Double.MAX_VALUE, 42, -1);
+ final double[] array = quat1.toArray(null);
+ final double[] array2 = quat1.toArray(new double[4]);
+ assertNotNull(array);
+ assertTrue(array.length == 4);
+ assertTrue(array[0] == Math.PI);
+ assertTrue(array[1] == Double.MAX_VALUE);
+ assertTrue(array[2] == 42);
+ assertTrue(array[3] == -1);
+ assertNotNull(array2);
+ assertTrue(array2.length == 4);
+ assertTrue(array2[0] == Math.PI);
+ assertTrue(array2[1] == Double.MAX_VALUE);
+ assertTrue(array2[2] == 42);
+ assertTrue(array2[3] == -1);
+ }
+
+ @Test(expected = ArrayIndexOutOfBoundsException.class)
+ public void testBadArray() {
+ final Quaternion quat = new Quaternion();
+ quat.toArray(new double[2]);
+ }
+
+ @Test(expected = ArrayIndexOutOfBoundsException.class)
+ public void testBadAxesArray() {
+ final Quaternion quat = new Quaternion();
+ quat.toAxes(new Vector3[2]);
+ }
+
+ @Test(expected = ArrayIndexOutOfBoundsException.class)
+ public void testBadEuler1() {
+ new Quaternion().fromEulerAngles(new double[2]);
+ }
+
+ @Test(expected = ArrayIndexOutOfBoundsException.class)
+ public void testBadEuler2() {
+ final Quaternion quat = new Quaternion();
+ quat.toEulerAngles(new double[2]);
+ }
+
+ @Test
+ public void testEulerAngles() {
+ final Quaternion quat = new Quaternion().fromEulerAngles(new double[] { MathUtils.HALF_PI, 0, 0 });
+ assertTrue(1.0 == quat.magnitude());
+ assertTrue(Math.abs(Vector3.NEG_UNIT_Z.distance(quat.apply(Vector3.UNIT_X, null))) <= MathUtils.EPSILON);
+
+ quat.fromEulerAngles(0, -MathUtils.HALF_PI, 0);
+ assertTrue(1.0 == quat.magnitude());
+ assertTrue(Math.abs(Vector3.NEG_UNIT_Y.distance(quat.apply(Vector3.UNIT_X, null))) <= MathUtils.EPSILON);
+
+ quat.fromEulerAngles(0, 0, MathUtils.HALF_PI);
+ assertTrue(1.0 == quat.magnitude());
+ assertTrue(Math.abs(Vector3.UNIT_Z.distance(quat.apply(Vector3.UNIT_Y, null))) <= MathUtils.EPSILON);
+
+ quat.fromEulerAngles(0, MathUtils.HALF_PI, 0);
+ double[] angles = quat.toEulerAngles(null);
+ final Quaternion quat2 = new Quaternion().fromEulerAngles(angles);
+ assertEquals(quat, quat2);
+ quat.fromEulerAngles(0, -MathUtils.HALF_PI, 0);
+ angles = quat.toEulerAngles(null);
+ quat2.fromEulerAngles(angles);
+ assertEquals(quat, quat2);
+ quat.fromEulerAngles(0, 0, MathUtils.HALF_PI);
+ angles = quat.toEulerAngles(null);
+ quat2.fromEulerAngles(angles);
+ assertEquals(quat, quat2);
+ }
+
+ @Test
+ public void testMatrix3() {
+ double a = MathUtils.HALF_PI;
+ final Quaternion quat = new Quaternion();
+ quat.fromRotationMatrix( //
+ 1, 0, 0, //
+ 0, Math.cos(a), -Math.sin(a), //
+ 0, Math.sin(a), Math.cos(a));
+
+ assertTrue(Math.abs(Vector3.UNIT_Z.distance(quat.apply(Vector3.UNIT_Y, null))) <= MathUtils.EPSILON);
+ final Matrix3 mat = quat.toRotationMatrix((Matrix3) null);
+ assertTrue(Math.abs(quat.apply(Vector3.NEG_ONE, null).distance(mat.applyPost(Vector3.NEG_ONE, null))) <= MathUtils.EPSILON);
+
+ a = MathUtils.PI;
+ quat.fromRotationMatrix( //
+ 1, 0, 0, //
+ 0, Math.cos(a), -Math.sin(a), //
+ 0, Math.sin(a), Math.cos(a));
+
+ assertTrue(Math.abs(Vector3.NEG_UNIT_Y.distance(quat.apply(Vector3.UNIT_Y, null))) <= MathUtils.EPSILON);
+ quat.toRotationMatrix(mat);
+ assertTrue(Math.abs(quat.apply(Vector3.ONE, null).distance(mat.applyPost(Vector3.ONE, null))) <= MathUtils.EPSILON);
+
+ quat.set(0, 0, 0, 0);
+ assertEquals(Matrix3.IDENTITY, quat.toRotationMatrix((Matrix3) null));
+
+ a = MathUtils.PI;
+ quat.fromRotationMatrix( //
+ Math.cos(a), 0, Math.sin(a), //
+ 0, 1, 0, //
+ -Math.sin(a), 0, Math.cos(a));
+
+ assertTrue(Math.abs(Vector3.NEG_UNIT_X.distance(quat.apply(Vector3.UNIT_X, null))) <= MathUtils.EPSILON);
+ final Matrix4 mat4 = quat.toRotationMatrix((Matrix4) null);
+ assertTrue(Math.abs(quat.apply(Vector3.NEG_ONE, null).distance(mat4.applyPostVector(Vector3.NEG_ONE, null))) <= MathUtils.EPSILON);
+
+ a = MathUtils.PI;
+ quat.fromRotationMatrix(new Matrix3(//
+ Math.cos(a), -Math.sin(a), 0, //
+ Math.sin(a), Math.cos(a), 0, //
+ 0, 0, 1));
+
+ assertTrue(Math.abs(Vector3.NEG_UNIT_X.distance(quat.apply(Vector3.UNIT_X, null))) <= MathUtils.EPSILON);
+ quat.toRotationMatrix(mat4);
+ assertTrue(Math.abs(quat.apply(Vector3.ONE, null).distance(mat4.applyPostVector(Vector3.ONE, null))) <= MathUtils.EPSILON);
+
+ quat.set(0, 0, 0, 0);
+ assertEquals(Matrix4.IDENTITY, quat.toRotationMatrix((Matrix4) null));
+ }
+
+ @Test
+ public void testRotations() {
+ final double a = MathUtils.QUARTER_PI;
+ final Quaternion quat = new Quaternion().fromRotationMatrix(new Matrix3(//
+ Math.cos(a), -Math.sin(a), 0, //
+ Math.sin(a), Math.cos(a), 0, //
+ 0, 0, 1));
+ final Vector3 column = quat.getRotationColumn(0, null);
+ assertTrue(Math.abs(new Vector3(Math.cos(a), Math.sin(a), 0).distance(column)) <= MathUtils.EPSILON);
+ quat.getRotationColumn(1, column);
+ assertTrue(Math.abs(new Vector3(-Math.sin(a), Math.sin(a), 0).distance(column)) <= MathUtils.EPSILON);
+ quat.getRotationColumn(2, column);
+ assertTrue(Math.abs(new Vector3(0, 0, 1).distance(column)) <= MathUtils.EPSILON);
+
+ quat.set(0, 0, 0, 0);
+ assertEquals(Vector3.UNIT_X, quat.getRotationColumn(0, null));
+
+ // Try a new way with new angles...
+ quat.fromEulerAngles(MathUtils.QUARTER_PI, MathUtils.PI, MathUtils.HALF_PI);
+ final Vector3 rotated = new Vector3(1, 1, 1);
+ quat.apply(rotated, rotated);
+
+ // expected
+ final Vector3 expected = new Vector3(1, 1, 1);
+ final Quaternion worker = new Quaternion();
+ // put together matrix, then apply to vector, so YZX
+ worker.applyRotationY(MathUtils.QUARTER_PI);
+ worker.applyRotationZ(MathUtils.PI);
+ worker.applyRotationX(MathUtils.HALF_PI);
+ worker.apply(expected, expected);
+
+ // test how close it came out
+ assertTrue(rotated.distance(expected) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // test axis rotation methods against general purpose
+ // X AXIS
+ expected.set(1, 1, 1);
+ rotated.set(1, 1, 1);
+ worker.setIdentity().applyRotationX(MathUtils.QUARTER_PI).apply(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 1, 0, 0).apply(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Y AXIS
+ expected.set(1, 1, 1);
+ rotated.set(1, 1, 1);
+ worker.setIdentity().applyRotationY(MathUtils.QUARTER_PI).apply(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 0, 1, 0).apply(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ // Z AXIS
+ expected.set(1, 1, 1);
+ rotated.set(1, 1, 1);
+ worker.setIdentity().applyRotationZ(MathUtils.QUARTER_PI).apply(expected, expected);
+ worker.setIdentity().applyRotation(MathUtils.QUARTER_PI, 0, 0, 1).apply(rotated, rotated);
+ assertTrue(rotated.distance(expected) <= MathUtils.EPSILON);
+
+ quat.set(worker);
+ worker.applyRotation(0, 0, 0, 0);
+ assertEquals(quat, worker);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadRotationColumn1() {
+ new Quaternion().getRotationColumn(-1, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testBadRotationColumn2() {
+ new Quaternion().getRotationColumn(4, null);
+ }
+
+ @Test
+ public void testAngleAxis() {
+ final Quaternion quat = new Quaternion().fromAngleAxis(MathUtils.HALF_PI, new Vector3(2, 0, 0));
+ final Quaternion quat2 = new Quaternion().fromAngleNormalAxis(MathUtils.HALF_PI, new Vector3(1, 0, 0));
+
+ assertEquals(quat2, quat);
+ assertTrue(1 - quat.magnitude() <= MathUtils.EPSILON);
+
+ assertEquals(quat.apply(Vector3.ONE, null), quat2.apply(Vector3.ONE, null));
+ assertTrue(Math.abs(new Vector3(0, -1, 0).distance(quat.apply(new Vector3(0, 0, 1), null))) <= MathUtils.EPSILON);
+
+ assertEquals(Quaternion.IDENTITY,
+ new Quaternion(1, 2, 3, 4).fromAngleAxis(MathUtils.HALF_PI, new Vector3(0, 0, 0)));
+
+ final Vector3 axisStore = new Vector3();
+ double angle = quat.toAngleAxis(axisStore);
+ assertEquals(quat, new Quaternion().fromAngleAxis(angle, axisStore));
+
+ quat.set(0, 0, 0, 0);
+ angle = quat.toAngleAxis(axisStore);
+ assertTrue(0.0 == angle);
+ assertEquals(Vector3.UNIT_X, axisStore);
+ }
+
+ @Test
+ public void testFromVectorToVector() {
+ final Quaternion quat = new Quaternion().fromVectorToVector(Vector3.UNIT_Z, Vector3.UNIT_X);
+ assertEquals(new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_Y), quat);
+
+ quat.fromVectorToVector(Vector3.UNIT_Z, Vector3.NEG_UNIT_Z);
+ assertTrue(Math.abs(new Vector3(0, 0, -1).distance(quat.apply(new Vector3(0, 0, 1), null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat.fromVectorToVector(Vector3.UNIT_X, Vector3.NEG_UNIT_X);
+ assertTrue(Math.abs(new Vector3(-1, 0, 0).distance(quat.apply(new Vector3(1, 0, 0), null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat.fromVectorToVector(Vector3.UNIT_Y, Vector3.NEG_UNIT_Y);
+ assertTrue(Math.abs(new Vector3(0, -1, 0).distance(quat.apply(new Vector3(0, 1, 0), null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat.fromVectorToVector(Vector3.ONE, Vector3.NEG_ONE);
+ assertTrue(Math.abs(new Vector3(-1, -1, -1).distance(quat.apply(new Vector3(1, 1, 1), null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat.fromVectorToVector(Vector3.ZERO, Vector3.ZERO);
+ assertEquals(Quaternion.IDENTITY, quat);
+ }
+
+ @Test
+ public void testNormalize() {
+ final Quaternion quat = new Quaternion(0, 1, 2, 3);
+ final Quaternion quat2 = quat.normalize(null);
+ assertEquals(quat2, quat.normalizeLocal());
+ assertTrue(Math.abs(1 - quat.magnitude()) <= MathUtils.EPSILON);
+ assertTrue(Math.abs(1 - quat2.magnitude()) <= MathUtils.EPSILON);
+ }
+
+ @Test
+ public void testApplyToZero() {
+ assertEquals(Vector3.ZERO, new Quaternion().apply(new Vector3(0, 0, 0), null));
+ }
+
+ @Test
+ public void testInvert() {
+ final Quaternion quat1 = new Quaternion(0, 1, 2, 3);
+ final Quaternion quat2 = quat1.invert(null);
+ assertEquals(Quaternion.IDENTITY, quat1.multiply(quat2, null));
+ assertEquals(quat1, quat2.invert(new Quaternion()));
+ assertEquals(quat1, quat2.invertLocal());
+
+ // normalized version
+ quat1.fromAngleAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Y);
+ quat1.invert(quat2);
+ assertEquals(Quaternion.IDENTITY, quat1.multiply(quat2, null));
+ assertEquals(quat1, quat2.invert(new Quaternion()));
+ assertEquals(quat1, quat2.invertLocal());
+
+ // conjugate check
+ assertEquals(new Quaternion(-1, -2, -3, 4), new Quaternion(1, 2, 3, 4).conjugate(null));
+ }
+
+ @Test
+ public void testAddSubtract() {
+ final Quaternion quat1 = new Quaternion(0, 1, 2, 3);
+ final Quaternion quat2 = new Quaternion(1, 1, 1, 1);
+ assertEquals(new Quaternion(1, 2, 3, 4), quat1.add(quat2, null));
+ assertEquals(new Quaternion(1, 2, 3, 4), quat1.add(quat2, new Quaternion()));
+ assertEquals(new Quaternion(1, 2, 3, 4), quat1.addLocal(quat2));
+
+ quat1.set(0, 1, 2, 3);
+ quat2.set(1, 1, 1, 1);
+ assertEquals(new Quaternion(-1, 0, 1, 2), quat1.subtract(quat2, null));
+ assertEquals(new Quaternion(-1, 0, 1, 2), quat1.subtract(quat2, new Quaternion()));
+ assertEquals(new Quaternion(-1, 0, 1, 2), quat1.subtractLocal(quat2));
+ }
+
+ @Test
+ public void testMultiply() {
+ final Quaternion quat1 = new Quaternion(0.5, 1, 2, 3);
+ final Quaternion quat2 = new Quaternion();
+ assertEquals(new Quaternion(1, 2, 4, 6), quat1.multiply(2, null));
+ assertEquals(new Quaternion(2, 4, 8, 12), quat1.multiply(4, quat2));
+ assertEquals(new Quaternion(1, 2, 4, 6), quat1.multiplyLocal(2));
+
+ quat1.fromAngleNormalAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Y);
+ quat1.multiply(quat1, quat2);
+
+ final ReadOnlyVector3 vec = Vector3.UNIT_Z;
+ assertTrue(Math.abs(Vector3.UNIT_X.distance(quat2.apply(vec, null))) <= Quaternion.ALLOWED_DEVIANCE);
+ quat1.multiplyLocal(quat1.getX(), quat1.getY(), quat1.getZ(), quat1.getW());
+ assertTrue(Math.abs(Vector3.UNIT_X.distance(quat1.apply(vec, null))) <= Quaternion.ALLOWED_DEVIANCE);
+ quat2.fromAngleNormalAxis(MathUtils.HALF_PI, Vector3.UNIT_Y);
+ quat1.multiplyLocal(quat2);
+ assertTrue(Math.abs(Vector3.NEG_UNIT_Z.distance(quat1.apply(vec, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat1.multiplyLocal(new Matrix3().applyRotationY(MathUtils.HALF_PI));
+ assertTrue(Math.abs(Vector3.NEG_UNIT_X.distance(quat1.apply(vec, null))) <= Quaternion.ALLOWED_DEVIANCE);
+ }
+
+ @Test
+ public void testAxes() {
+ final Matrix3 rot = new Matrix3().applyRotationX(MathUtils.QUARTER_PI).applyRotationY(MathUtils.HALF_PI);
+ final Quaternion quat1 = new Quaternion().fromAxes(rot.getColumn(0, null), rot.getColumn(1, null),
+ rot.getColumn(2, null));
+ final Quaternion quat2 = new Quaternion().fromRotationMatrix(rot);
+ assertEquals(quat2, quat1);
+
+ final Vector3[] axes = quat1.toAxes(new Vector3[3]);
+ quat1.fromAxes(axes[0], axes[1], axes[2]);
+ assertEquals(quat2, quat1);
+ }
+
+ @Test
+ public void testSlerp() {
+ final Quaternion quat = new Quaternion();
+ final Quaternion quat2 = new Quaternion().applyRotationY(MathUtils.HALF_PI);
+ final Quaternion store = quat.slerp(quat2, .5, null);
+ assertTrue(Math.abs(new Vector3(Math.sin(MathUtils.QUARTER_PI), 0, Math.sin(MathUtils.QUARTER_PI))
+ .distance(store.apply(Vector3.UNIT_Z, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // delta == 100%
+ quat2.setIdentity().applyRotationZ(MathUtils.PI);
+ quat.slerp(quat2, 1.0, store);
+ assertTrue(Math.abs(new Vector3(-1, 0, 0).distance(store.apply(Vector3.UNIT_X, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat2.setIdentity().applyRotationZ(MathUtils.PI);
+ quat.slerp(quat2, .5, store);
+ assertTrue(Math.abs(new Vector3(0, 1, 0).distance(store.apply(Vector3.UNIT_X, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // delta == 0%
+ quat2.setIdentity().applyRotationZ(MathUtils.PI);
+ quat.slerp(quat2, 0, store);
+ assertTrue(Math.abs(new Vector3(1, 0, 0).distance(store.apply(Vector3.UNIT_X, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // a==b
+ quat2.setIdentity();
+ quat.slerp(quat2, 0.25, store);
+ assertTrue(Math.abs(new Vector3(1, 0, 0).distance(store.apply(Vector3.UNIT_X, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // negative dot product
+ quat.setIdentity().applyRotationX(-2 * MathUtils.HALF_PI);
+ quat2.setIdentity().applyRotationX(MathUtils.HALF_PI);
+ quat.slerp(quat2, 0.5, store);
+ assertTrue(Math.abs(new Vector3(0, -Math.sin(MathUtils.QUARTER_PI), Math.sin(MathUtils.QUARTER_PI))
+ .distance(store.apply(Vector3.UNIT_Y, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // LOCAL
+ // delta == 100%
+ quat2.setIdentity().applyRotationX(MathUtils.PI);
+ quat.slerpLocal(quat2, 1.0);
+ assertTrue(Math.abs(new Vector3(0, -1, 0).distance(quat.apply(Vector3.UNIT_Y, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ quat.setIdentity();
+ quat.slerpLocal(quat2, .5);
+ assertTrue(Math.abs(new Vector3(0, 0, 1).distance(quat.apply(Vector3.UNIT_Y, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // delta == 0%
+ quat.setIdentity();
+ quat.slerpLocal(quat2, 0);
+ assertTrue(Math.abs(new Vector3(0, 1, 0).distance(quat.apply(Vector3.UNIT_Y, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // a==b
+ quat.setIdentity();
+ quat2.setIdentity();
+ quat.slerpLocal(quat2, 0.25);
+ assertTrue(Math.abs(new Vector3(0, 1, 0).distance(quat.apply(Vector3.UNIT_Y, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ // negative dot product
+ quat.setIdentity().applyRotationX(-2 * MathUtils.HALF_PI);
+ quat2.setIdentity().applyRotationX(MathUtils.HALF_PI);
+ quat.slerpLocal(quat2, 0.5);
+ assertTrue(Math.abs(new Vector3(0, -Math.sin(MathUtils.QUARTER_PI), Math.sin(MathUtils.QUARTER_PI))
+ .distance(quat.apply(Vector3.UNIT_Y, null))) <= Quaternion.ALLOWED_DEVIANCE);
+ }
+
+ @Test
+ public void testLookAt() {
+ final Vector3 direction = new Vector3(-1, 0, 0);
+ final Quaternion quat = new Quaternion().lookAt(direction, Vector3.UNIT_Y);
+ assertTrue(Math.abs(direction.distance(quat.apply(Vector3.UNIT_Z, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ direction.set(1, 1, 1).normalizeLocal();
+ quat.lookAt(direction, Vector3.UNIT_Y);
+ assertTrue(Math.abs(direction.distance(quat.apply(Vector3.UNIT_Z, null))) <= Quaternion.ALLOWED_DEVIANCE);
+
+ direction.set(-1, 2, -1).normalizeLocal();
+ quat.lookAt(direction, Vector3.UNIT_Y);
+ assertTrue(Math.abs(direction.distance(quat.apply(Vector3.UNIT_Z, null))) <= Quaternion.ALLOWED_DEVIANCE);
+ }
+
+ @Test
+ public void testDot() {
+ final Quaternion quat = new Quaternion(7, 2, 5, -1);
+ assertTrue(35.0 == quat.dot(3, 1, 2, -2));
+
+ assertTrue(-11.0 == quat.dot(new Quaternion(-1, 1, -1, 1)));
+ }
+
+ @Test
+ public void testClone() {
+ final Quaternion quat1 = new Quaternion();
+ final Quaternion quat2 = quat1.clone();
+ assertEquals(quat1, quat2);
+ assertNotSame(quat1, quat2);
+ }
+
+ @Test
+ public void testValid() {
+ final Quaternion quat = new Quaternion();
+ assertTrue(Quaternion.isValid(quat));
+
+ quat.set(Double.NaN, 0, 0, 0);
+ assertFalse(Quaternion.isValid(quat));
+ quat.set(0, Double.NaN, 0, 0);
+ assertFalse(Quaternion.isValid(quat));
+ quat.set(0, 0, Double.NaN, 0);
+ assertFalse(Quaternion.isValid(quat));
+ quat.set(0, 0, 0, Double.NaN);
+ assertFalse(Quaternion.isValid(quat));
+
+ quat.set(Double.NEGATIVE_INFINITY, 0, 0, 0);
+ assertFalse(Quaternion.isValid(quat));
+ quat.set(0, Double.NEGATIVE_INFINITY, 0, 0);
+ assertFalse(Quaternion.isValid(quat));
+ quat.set(0, 0, Double.NEGATIVE_INFINITY, 0);
+ assertFalse(Quaternion.isValid(quat));
+ quat.set(0, 0, 0, Double.NEGATIVE_INFINITY);
+ assertFalse(Quaternion.isValid(quat));
+
+ quat.setIdentity();
+ assertTrue(Quaternion.isValid(quat));
+
+ assertFalse(Quaternion.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(quat, quat);
+ assertTrue(quat.strictEquals(quat));
+ assertFalse(quat.equals(null));
+ assertFalse(quat.strictEquals(null));
+ assertFalse(quat.equals(new Vector2()));
+ assertFalse(quat.strictEquals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Quaternion quatTemp = Quaternion.fetchTempInstance();
+ quatTemp.set(quat);
+ assertEquals(quat, quatTemp);
+ assertNotSame(quat, quatTemp);
+ Quaternion.releaseTempInstance(quatTemp);
+
+ // cover more of equals
+ quat.set(0, 1, 2, 3);
+ final Quaternion comp = new Quaternion(-1, -1, -1, -1);
+ assertFalse(quat.equals(comp));
+ assertFalse(quat.strictEquals(comp));
+ comp.setX(0);
+ assertFalse(quat.equals(comp));
+ assertFalse(quat.strictEquals(comp));
+ comp.setY(1);
+ assertFalse(quat.equals(comp));
+ assertFalse(quat.strictEquals(comp));
+ comp.setZ(2);
+ assertFalse(quat.equals(comp));
+ assertFalse(quat.strictEquals(comp));
+ comp.setW(3);
+ assertEquals(quat, comp);
+ assertTrue(quat.strictEquals(comp));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Quaternion quat1 = new Quaternion(1, 2, 3, 4);
+ final Quaternion quat2 = new Quaternion(1, 2, 3, 4);
+ final Quaternion quat3 = new Quaternion(1, 2, 3, 0);
+
+ assertTrue(quat1.hashCode() == quat2.hashCode());
+ assertTrue(quat1.hashCode() != quat3.hashCode());
+ }
+
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestRay3.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestRay3.java new file mode 100644 index 0000000..f662700 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestRay3.java @@ -0,0 +1,230 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.ardor3d.math.type.ReadOnlyRay3;
+
+public class TestRay3 {
+ @Test
+ public void testData() {
+ final Ray3 ray = new Ray3();
+ assertEquals(Vector3.UNIT_Z, ray.getDirection());
+ assertEquals(Vector3.ZERO, ray.getOrigin());
+
+ ray.setDirection(Vector3.NEG_UNIT_X);
+ assertEquals(Vector3.NEG_UNIT_X, ray.getDirection());
+ ray.setOrigin(Vector3.ONE);
+ assertEquals(Vector3.ONE, ray.getOrigin());
+
+ final Ray3 ray2 = new Ray3(ray);
+ assertEquals(Vector3.NEG_UNIT_X, ray2.getDirection());
+ assertEquals(Vector3.ONE, ray2.getOrigin());
+
+ ray.set(new Ray3());
+ assertEquals(Vector3.UNIT_Z, ray.getDirection());
+ assertEquals(Vector3.ZERO, ray.getOrigin());
+ }
+
+ @Test
+ public void testValid() {
+ final Ray3 ray1 = new Ray3(new Vector3(0, 0, 0), new Vector3(0, 0, 1));
+ final Ray3 ray2 = new Ray3(new Vector3(Double.POSITIVE_INFINITY, 0, 0), new Vector3(0, 0, 1));
+ final Ray3 ray3 = new Ray3(new Vector3(0, 0, 0), new Vector3(Double.POSITIVE_INFINITY, 0, 1));
+
+ assertTrue(Ray3.isValid(ray1));
+ assertFalse(Ray3.isValid(ray2));
+ assertFalse(Ray3.isValid(ray3));
+
+ assertFalse(Ray3.isValid(null));
+
+ // couple if equals validity tests
+ assertEquals(ray1, ray1);
+ assertFalse(ray1.equals(null));
+ assertFalse(ray1.equals(new Vector3()));
+
+ // throw in a couple pool accesses for coverage
+ final Ray3 ray4 = Ray3.fetchTempInstance();
+ ray4.set(ray1);
+ assertEquals(ray1, ray4);
+ assertNotSame(ray1, ray4);
+ Ray3.releaseTempInstance(ray4);
+
+ // cover more of equals
+ assertFalse(ray1.equals(new Ray3(Vector3.ZERO, Vector3.NEG_UNIT_X)));
+ }
+
+ @Test
+ public void testClone() {
+ final Ray3 ray1 = new Ray3();
+ final Ray3 ray2 = ray1.clone();
+ assertEquals(ray1, ray2);
+ assertNotSame(ray1, ray2);
+ }
+
+ @Test
+ public void testDistance() {
+ final Ray3 ray1 = new Ray3();
+ assertTrue(25.0 == ray1.distanceSquared(new Vector3(0, 5, 3), null));
+
+ final Vector3 store = new Vector3();
+ assertTrue(9.0 == ray1.distanceSquared(new Vector3(0, 3, 3), store));
+ assertEquals(new Vector3(0, 0, 3), store);
+ assertTrue(18.0 == ray1.distanceSquared(new Vector3(0, 3, -3), store));
+ assertEquals(new Vector3(0, 0, 0), store);
+ }
+
+ @Test
+ public void testIntersectsTriangle() {
+ final Vector3 v0 = new Vector3(-1, -1, -1);
+ final Vector3 v1 = new Vector3(+1, -1, -1);
+ final Vector3 v2 = new Vector3(+1, +1, -1);
+
+ final Vector3 intersectionPoint = new Vector3();
+
+ // inside triangle
+ Ray3 pickRay = new Ray3(new Vector3(0.5, -0.5, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // horizontal edge
+ pickRay = new Ray3(new Vector3(0, -1, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // diagonal edge
+ pickRay = new Ray3(new Vector3(0, 0, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // vertical edge
+ pickRay = new Ray3(new Vector3(+1, 0, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // v0
+ pickRay = new Ray3(new Vector3(-1, -1, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // v1
+ pickRay = new Ray3(new Vector3(+1, -1, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // v2
+ pickRay = new Ray3(new Vector3(1, 1, 3), new Vector3(0, 0, -1));
+ assertTrue(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // outside horizontal edge
+ pickRay = new Ray3(new Vector3(0, -1.1, 3), new Vector3(0, 0, -1));
+ assertFalse(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // outside diagonal edge
+ pickRay = new Ray3(new Vector3(-0.1, 0.1, 3), new Vector3(0, 0, -1));
+ assertFalse(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // outside vertical edge
+ pickRay = new Ray3(new Vector3(+1.1, 0, 3), new Vector3(0, 0, -1));
+ assertFalse(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // inside triangle but ray pointing other way
+ pickRay = new Ray3(new Vector3(-0.5, -0.5, 3), new Vector3(0, 0, +1));
+ assertFalse(pickRay.intersectsTriangle(v0, v1, v2, intersectionPoint));
+
+ // test distance
+ pickRay = new Ray3(new Vector3(0.5, -0.5, 3), new Vector3(0, 0, -1));
+ assertTrue(4.0 == pickRay.getDistanceToPrimitive(new Vector3[] { v0, v1, v2 }));
+
+ // test intersect planar
+ assertTrue(pickRay.intersectsTrianglePlanar(v0, v1, v2, intersectionPoint));
+
+ }
+
+ @Test
+ public void testIntersectsPlane() {
+ final Vector3 intersectionPoint = new Vector3();
+
+ Plane plane = new Plane(new Vector3(0, 1, 0), 2);
+
+ Ray3 pickRay = new Ray3(new Vector3(0, 3, 0), new Vector3(0, 0, 1));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, 3, 0), new Vector3(0, 1, 0));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, 2, 0), new Vector3(0, 1, 0));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, 1, 0), new Vector3(0, 1, 0));
+ assertTrue(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, 0, 0), new Vector3(1, 0, 0));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, -3, 0), new Vector3(0, 0, 1));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, 3, 0), new Vector3(0, -1, 0));
+ assertTrue(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, -3, 0), new Vector3(1, 1, 1));
+ assertTrue(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, -3, 0), new Vector3(-1, -1, -1));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ plane = new Plane(new Vector3(1, 1, 1), -2);
+
+ pickRay = new Ray3(new Vector3(0, 0, 0), new Vector3(1, -1, 1));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, -1, 0), new Vector3(0, 1, 0));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, -2, 0), new Vector3(0, 1, 0));
+ assertFalse(pickRay.intersectsPlane(plane, intersectionPoint));
+
+ pickRay = new Ray3(new Vector3(0, -3, 0), new Vector3(0, 1, 0));
+ assertTrue(pickRay.intersectsPlane(plane, null));
+ }
+
+ @Test
+ public void testIntersectsQuad() {
+ final Vector3 v0 = new Vector3(0, 0, 0);
+ final Vector3 v1 = new Vector3(5, 0, 0);
+ final Vector3 v2 = new Vector3(5, 5, 0);
+ final Vector3 v3 = new Vector3(0, 5, 0);
+
+ Vector3 intersectionPoint = null;
+
+ // inside quad
+ final ReadOnlyRay3 pickRayA = new Ray3(new Vector3(2, 2, 10), new Vector3(0, 0, -1));
+ final ReadOnlyRay3 pickRayB = new Ray3(new Vector3(2, 4, 10), new Vector3(0, 0, -1));
+ assertTrue(pickRayA.intersectsQuad(v0, v1, v2, v3, intersectionPoint));
+ assertTrue(pickRayB.intersectsQuad(v0, v1, v2, v3, intersectionPoint));
+
+ // inside quad
+ final Ray3 pickRay2 = new Ray3(new Vector3(-1, 0, 10), new Vector3(0, 0, -1));
+ assertFalse(pickRay2.intersectsQuad(v0, v1, v2, v3, intersectionPoint));
+
+ // test distance
+ assertTrue(10.0 == pickRayA.getDistanceToPrimitive(new Vector3[] { v0, v1, v2, v3 }));
+ assertTrue(Double.POSITIVE_INFINITY == pickRay2.getDistanceToPrimitive(new Vector3[] { v0, v1, v2, v3 }));
+
+ // test unsupported pick
+ assertFalse(pickRay2.intersects(new Vector3[] { v0, v1 }, null));
+
+ // test intersect planar
+ assertFalse(new Ray3(new Vector3(0, 0, -1), Vector3.UNIT_Y).intersectsQuadPlanar(v0, v1, v2, v3,
+ intersectionPoint));
+ intersectionPoint = new Vector3();
+ assertTrue(pickRayA.intersectsQuadPlanar(v0, v1, v2, v3, intersectionPoint));
+ assertTrue(pickRayB.intersectsQuadPlanar(v0, v1, v2, v3, intersectionPoint));
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle2.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle2.java new file mode 100644 index 0000000..74cb987 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle2.java @@ -0,0 +1,120 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestRectangle2 {
+ @Test
+ public void testGetSet() {
+ final Rectangle2 rect1 = new Rectangle2();
+ assertTrue(0 == rect1.getX());
+ assertTrue(0 == rect1.getY());
+ assertTrue(0 == rect1.getWidth());
+ assertTrue(0 == rect1.getHeight());
+
+ final Rectangle2 rect2 = new Rectangle2(2, 3, 4, 5);
+ assertTrue(2 == rect2.getX());
+ assertTrue(3 == rect2.getY());
+ assertTrue(4 == rect2.getWidth());
+ assertTrue(5 == rect2.getHeight());
+
+ rect1.set(rect2);
+ assertTrue(2 == rect1.getX());
+ assertTrue(3 == rect1.getY());
+ assertTrue(4 == rect1.getWidth());
+ assertTrue(5 == rect1.getHeight());
+
+ final Rectangle2 rect3 = new Rectangle2(rect1);
+ assertTrue(2 == rect3.getX());
+ assertTrue(3 == rect3.getY());
+ assertTrue(4 == rect3.getWidth());
+ assertTrue(5 == rect3.getHeight());
+
+ rect1.set(5, 0, 10, 15);
+ assertTrue(5 == rect1.getX());
+ assertTrue(0 == rect1.getY());
+ assertTrue(10 == rect1.getWidth());
+ assertTrue(15 == rect1.getHeight());
+
+ rect1.setX(4);
+ assertTrue(4 == rect1.getX());
+ rect1.setY(42);
+ assertTrue(42 == rect1.getY());
+ rect1.setWidth(50);
+ assertTrue(50 == rect1.getWidth());
+ rect1.setHeight(100);
+ assertTrue(100 == rect1.getHeight());
+ }
+
+ @Test
+ public void testClone() {
+ final Rectangle2 rect1 = new Rectangle2();
+ final Rectangle2 rect2 = rect1.clone();
+ assertEquals(rect1, rect2);
+ assertNotSame(rect1, rect2);
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Rectangle2 rect1 = new Rectangle2(1, 2, 0, 0);
+ final Rectangle2 rect2 = new Rectangle2(1, 2, 0, 0);
+ final Rectangle2 rect3 = new Rectangle2(2, 2, 0, 0);
+
+ assertTrue(rect1.hashCode() == rect2.hashCode());
+ assertTrue(rect1.hashCode() != rect3.hashCode());
+ }
+
+ @Test
+ public void testEquals() {
+ final Rectangle2 rect1 = new Rectangle2(1, 2, 3, 4);
+ final Rectangle2 rect2 = new Rectangle2(1, 0, 0, 0);
+ final Rectangle2 rect3 = new Rectangle2(1, 2, 0, 0);
+ final Rectangle2 rect4 = new Rectangle2(1, 2, 3, 0);
+ final Rectangle2 rect5 = new Rectangle2(1, 2, 3, 4);
+
+ // couple of equals validity tests
+ assertEquals(rect1, rect1);
+ assertFalse(rect1.equals(null));
+ assertFalse(rect1.equals(new Vector2()));
+
+ assertFalse(rect1.equals(rect2));
+ assertFalse(rect1.equals(rect3));
+ assertFalse(rect1.equals(rect4));
+ assertTrue(rect1.equals(rect5));
+ }
+
+ @Test
+ public void testIntersect() {
+ final Rectangle2 rect1 = new Rectangle2(0, 0, 10, 10);
+ final Rectangle2 rect2 = new Rectangle2(5, 5, 10, 10);
+
+ Rectangle2 intersection = rect1.intersect(rect2, Rectangle2.fetchTempInstance());
+ assertTrue(5 == intersection.getX());
+ assertTrue(5 == intersection.getY());
+ assertTrue(5 == intersection.getWidth());
+ assertTrue(5 == intersection.getHeight());
+ Rectangle2.releaseTempInstance(intersection);
+ assertNotNull(rect1.intersect(rect2, null));
+
+ intersection = Rectangle2.intersect(rect1, rect2, Rectangle2.fetchTempInstance());
+ assertTrue(5 == intersection.getX());
+ assertTrue(5 == intersection.getY());
+ assertTrue(5 == intersection.getWidth());
+ assertTrue(5 == intersection.getHeight());
+ Rectangle2.releaseTempInstance(intersection);
+ assertNotNull(Rectangle2.intersect(rect1, rect2, null));
+
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle3.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle3.java new file mode 100644 index 0000000..0a98953 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle3.java @@ -0,0 +1,102 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestRectangle3 {
+
+ @Test
+ public void testGetSet() {
+ final Rectangle3 rect = new Rectangle3();
+ assertEquals(Vector3.ZERO, rect.getA());
+ assertEquals(Vector3.ZERO, rect.getB());
+ assertEquals(Vector3.ZERO, rect.getC());
+
+ rect.setA(Vector3.ONE);
+ rect.setB(Vector3.UNIT_X);
+ rect.setC(Vector3.UNIT_Z);
+ assertEquals(Vector3.ONE, rect.getA());
+ assertEquals(Vector3.UNIT_X, rect.getB());
+ assertEquals(Vector3.UNIT_Z, rect.getC());
+
+ final Rectangle3 rect2 = new Rectangle3(rect);
+ assertEquals(Vector3.ONE, rect2.getA());
+ assertEquals(Vector3.UNIT_X, rect2.getB());
+ assertEquals(Vector3.UNIT_Z, rect2.getC());
+
+ final Rectangle3 rect3 = new Rectangle3(Vector3.NEG_ONE, Vector3.UNIT_Z, Vector3.NEG_UNIT_Y);
+ assertEquals(Vector3.NEG_ONE, rect3.getA());
+ assertEquals(Vector3.UNIT_Z, rect3.getB());
+ assertEquals(Vector3.NEG_UNIT_Y, rect3.getC());
+ }
+
+ @Test
+ public void testEquals() {
+ // couple of equals validity tests
+ final Rectangle3 rect1 = new Rectangle3();
+ assertEquals(rect1, rect1);
+ assertFalse(rect1.equals(null));
+ assertFalse(rect1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Rectangle3 rect2 = Rectangle3.fetchTempInstance();
+ rect2.set(rect1);
+ assertEquals(rect1, rect2);
+ assertNotSame(rect1, rect2);
+ Rectangle3.releaseTempInstance(rect2);
+
+ // cover more of equals
+ assertTrue(rect1.equals(new Rectangle3(Vector3.ZERO, Vector3.ZERO, Vector3.ZERO)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.ZERO, Vector3.ZERO, Vector3.UNIT_X)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_X)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.ZERO, Vector3.UNIT_X, Vector3.ZERO)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.UNIT_X, Vector3.ZERO, Vector3.ZERO)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.UNIT_X, Vector3.ZERO, Vector3.UNIT_X)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.UNIT_X, Vector3.UNIT_X, Vector3.ZERO)));
+ assertFalse(rect1.equals(new Rectangle3(Vector3.UNIT_X, Vector3.UNIT_X, Vector3.UNIT_X)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Rectangle3 rect1 = new Rectangle3(Vector3.ZERO, Vector3.UNIT_Y, Vector3.UNIT_X);
+ final Rectangle3 rect2 = new Rectangle3(Vector3.ZERO, Vector3.UNIT_Y, Vector3.UNIT_X);
+ final Rectangle3 rect3 = new Rectangle3(Vector3.ZERO, Vector3.UNIT_Y, Vector3.UNIT_Z);
+
+ assertTrue(rect1.hashCode() == rect2.hashCode());
+ assertTrue(rect1.hashCode() != rect3.hashCode());
+ }
+
+ @Test
+ public void testClone() {
+ final Rectangle3 rect1 = new Rectangle3();
+ final Rectangle3 rect2 = rect1.clone();
+ assertEquals(rect1, rect2);
+ assertNotSame(rect1, rect2);
+ }
+
+ @Test
+ public void testRandom() {
+ MathUtils.setRandomSeed(0);
+ final Rectangle3 rect1 = new Rectangle3();
+ final Vector3 store = rect1.random(null);
+ assertEquals(new Vector3(0.0, 0.0, 0.0), store);
+
+ rect1.setA(new Vector3(1, 0, 0));
+ rect1.setB(new Vector3(1, 1, 0));
+ rect1.setC(new Vector3(0, 1, 0));
+ rect1.random(store);
+ assertEquals(new Vector3(0.39365482330322266, 0.8468815684318542, 0.0), store);
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestRing.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestRing.java new file mode 100644 index 0000000..b6120ab --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestRing.java @@ -0,0 +1,113 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestRing {
+
+ @Test
+ public void testGetSet() {
+ final Ring ring = new Ring();
+ assertEquals(Vector3.ZERO, ring.getCenter());
+ assertEquals(Vector3.UNIT_Y, ring.getUp());
+ assertTrue(ring.getInnerRadius() == 0.0);
+ assertTrue(ring.getOuterRadius() == 1.0);
+
+ ring.setCenter(Vector3.ONE);
+ ring.setUp(Vector3.UNIT_X);
+ ring.setInnerRadius(1.5);
+ ring.setOuterRadius(3.0);
+ assertEquals(Vector3.ONE, ring.getCenter());
+ assertEquals(Vector3.UNIT_X, ring.getUp());
+ assertTrue(ring.getInnerRadius() == 1.5);
+ assertTrue(ring.getOuterRadius() == 3.0);
+
+ final Ring ring2 = new Ring(ring);
+ assertEquals(Vector3.ONE, ring2.getCenter());
+ assertEquals(Vector3.UNIT_X, ring2.getUp());
+ assertTrue(ring2.getInnerRadius() == 1.5);
+ assertTrue(ring2.getOuterRadius() == 3.0);
+
+ final Ring ring3 = new Ring(Vector3.NEG_ONE, Vector3.UNIT_Z, 12.0, 42.0);
+ assertEquals(Vector3.NEG_ONE, ring3.getCenter());
+ assertEquals(Vector3.UNIT_Z, ring3.getUp());
+ assertTrue(ring3.getInnerRadius() == 12.0);
+ assertTrue(ring3.getOuterRadius() == 42.0);
+ }
+
+ @Test
+ public void testValid() {
+ assertTrue(Ring.isValid(new Ring()));
+ assertFalse(Ring.isValid(null));
+ assertFalse(Ring.isValid(new Ring(Vector3.ZERO, Vector3.UNIT_Y, 0.0, Double.NaN)));
+ assertFalse(Ring.isValid(new Ring(Vector3.ZERO, Vector3.UNIT_Y, 0.0, Double.POSITIVE_INFINITY)));
+ assertFalse(Ring.isValid(new Ring(Vector3.ZERO, Vector3.UNIT_Y, Double.NaN, 1.0)));
+ assertFalse(Ring.isValid(new Ring(Vector3.ZERO, Vector3.UNIT_Y, Double.NEGATIVE_INFINITY, 1.0)));
+ assertFalse(Ring.isValid(new Ring(Vector3.ZERO, new Vector3(Double.NaN, 0, 0), 0.0, 1.0)));
+ assertFalse(Ring.isValid(new Ring(Vector3.ZERO, new Vector3(Double.POSITIVE_INFINITY, 0, 0), 0.0, 1.0)));
+ assertFalse(Ring.isValid(new Ring(new Vector3(Double.NaN, 0, 0), Vector3.UNIT_Y, 0.0, 1.0)));
+ assertFalse(Ring.isValid(new Ring(new Vector3(Double.NEGATIVE_INFINITY, 0, 0), Vector3.UNIT_Y, 0.0, 1.0)));
+
+ // couple of equals validity tests
+ final Ring ring1 = new Ring();
+ assertEquals(ring1, ring1);
+ assertFalse(ring1.equals(null));
+ assertFalse(ring1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Ring ring2 = Ring.fetchTempInstance();
+ ring2.set(ring1);
+ assertEquals(ring1, ring2);
+ assertNotSame(ring1, ring2);
+ Ring.releaseTempInstance(ring2);
+
+ // cover more of equals
+ assertFalse(ring1.equals(new Ring(Vector3.UNIT_X, Vector3.UNIT_X, -1.0, 2.0)));
+ assertFalse(ring1.equals(new Ring(Vector3.UNIT_X, Vector3.UNIT_X, -1.0, 1.0)));
+ assertFalse(ring1.equals(new Ring(Vector3.UNIT_X, Vector3.UNIT_X, 0.0, 1.0)));
+ assertFalse(ring1.equals(new Ring(Vector3.UNIT_X, Vector3.UNIT_Y, 0.0, 1.0)));
+ assertTrue(ring1.equals(new Ring(Vector3.ZERO, Vector3.UNIT_Y, 0.0, 1.0)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Ring ring1 = new Ring(Vector3.ZERO, Vector3.UNIT_Y, 0.0, 2.0);
+ final Ring ring2 = new Ring(Vector3.ZERO, Vector3.UNIT_Y, 0.0, 2.0);
+ final Ring ring3 = new Ring(Vector3.ZERO, Vector3.UNIT_Y, 0.0, 3.0);
+
+ assertTrue(ring1.hashCode() == ring2.hashCode());
+ assertTrue(ring1.hashCode() != ring3.hashCode());
+ }
+
+ @Test
+ public void testClone() {
+ final Ring ring1 = new Ring();
+ final Ring ring2 = ring1.clone();
+ assertEquals(ring1, ring2);
+ assertNotSame(ring1, ring2);
+ }
+
+ @Test
+ public void testRandom() {
+ MathUtils.setRandomSeed(0);
+ final Ring ring1 = new Ring();
+ final Vector3 store = ring1.random(null);
+ assertEquals(new Vector3(0.7454530390475868, 0.0, -0.4186496466746111), store);
+
+ ring1.setUp(Vector3.UNIT_X);
+ ring1.random(store);
+ assertEquals(new Vector3(0.0, 0.30386186434027496, -0.3849731927481824), store);
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestTransform.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestTransform.java new file mode 100644 index 0000000..310e70f --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestTransform.java @@ -0,0 +1,424 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+
+import org.junit.Test;
+
+public class TestTransform {
+
+ @Test
+ public void testGetSet() {
+ final Transform trans = new Transform();
+ assertEquals(Transform.IDENTITY, trans);
+
+ final Transform immutable = new Transform(new Matrix3().applyRotationX(MathUtils.QUARTER_PI), new Vector3(0,
+ -1, -2), new Vector3(1, 2, 3), true, true, true);
+ assertTrue(true == immutable.isIdentity());
+ assertTrue(true == immutable.isRotationMatrix());
+ assertTrue(true == immutable.isUniformScale());
+ assertEquals(new Matrix3().applyRotationX(MathUtils.QUARTER_PI), immutable.getMatrix());
+ assertEquals(new Vector3(0, -1, -2), immutable.getScale());
+ assertEquals(new Vector3(1, 2, 3), immutable.getTranslation());
+
+ final Transform trans2 = new Transform(immutable);
+ assertEquals(immutable, trans2);
+ trans2.updateFlags(false);
+
+ trans.set(immutable);
+ assertEquals(Transform.IDENTITY, trans); // because of shortcut flags.
+
+ trans.set(trans2);
+ assertEquals(trans2, trans);
+
+ trans.setIdentity();
+ assertEquals(Transform.IDENTITY, trans);
+
+ final double a = MathUtils.QUARTER_PI;
+ trans.setRotation(new Quaternion().fromAngleAxis(a, Vector3.UNIT_Y));
+
+ assertEquals(new Matrix3( //
+ Math.cos(a), 0, Math.sin(a), //
+ 0, 1, 0, //
+ -Math.sin(a), 0, Math.cos(a)), trans.getMatrix());
+
+ trans2.setRotation(new Matrix3().fromAngleAxis(a, Vector3.UNIT_Y));
+ assertEquals(trans.getMatrix(), trans2.getMatrix());
+
+ trans.setScale(1.0);
+ assertEquals(Vector3.ONE, trans.getScale());
+
+ trans.setScale(new Vector3(1, 2, 3));
+ assertEquals(new Vector3(1, 2, 3), trans.getScale());
+
+ trans.setScale(-1, 5, -3);
+ assertEquals(new Vector3(-1, 5, -3), trans.getScale());
+
+ trans.setTranslation(new Vector3(10, 20, 30));
+ assertEquals(new Vector3(10, 20, 30), trans.getTranslation());
+
+ trans.setTranslation(-10, 50, -30);
+ assertEquals(new Vector3(-10, 50, -30), trans.getTranslation());
+
+ trans.setIdentity();
+ trans.setRotation(new Matrix3().fromAngleAxis(a, Vector3.UNIT_Y));
+ trans.setScale(2, 3, 4);
+ trans.setTranslation(5, 10, 15);
+
+ final Matrix4 mat4 = trans.getHomogeneousMatrix(null);
+ assertEquals(new Matrix4( //
+ 2 * Math.cos(a), 2 * 0, 2 * Math.sin(a), 5, //
+ 3 * 0, 3 * 1, 3 * 0, 10, //
+ 4 * -Math.sin(a), 4 * 0, 4 * Math.cos(a), 15, //
+ 0, 0, 0, 1), mat4);
+
+ trans2.fromHomogeneousMatrix(mat4);
+ trans2.getHomogeneousMatrix(mat4);
+ assertEquals(new Matrix4( //
+ 2 * Math.cos(a), 2 * 0, 2 * Math.sin(a), 5, //
+ 3 * 0, 3 * 1, 3 * 0, 10, //
+ 4 * -Math.sin(a), 4 * 0, 4 * Math.cos(a), 15, //
+ 0, 0, 0, 1), mat4);
+
+ trans.setIdentity();
+ trans.setRotation(new Matrix3(0, 1, 2, 3, 4, 5, 6, 7, 8));
+ trans.setTranslation(10, 11, 12);
+ trans.getHomogeneousMatrix(mat4);
+ assertEquals(new Matrix4( //
+ 0, 1, 2, 10, //
+ 3, 4, 5, 11, //
+ 6, 7, 8, 12, //
+ 0, 0, 0, 1), mat4);
+
+ }
+
+ @Test(expected = TransformException.class)
+ public void testFailScale1A() {
+ final Transform trans = new Transform(new Matrix3(), new Vector3(), new Vector3(), false, false, false);
+ trans.setScale(Vector3.ONE);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFailScale1B() {
+ final Transform trans = new Transform();
+ trans.setScale(Vector3.ZERO);
+ }
+
+ @Test(expected = TransformException.class)
+ public void testFailScale2A() {
+ final Transform trans = new Transform(new Matrix3(), new Vector3(), new Vector3(), false, false, false);
+ trans.setScale(1, 1, 1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFailScale2B() {
+ final Transform trans = new Transform();
+ trans.setScale(0, 0, 0);
+ }
+
+ @Test(expected = TransformException.class)
+ public void testFailScale3A() {
+ final Transform trans = new Transform(new Matrix3(), new Vector3(), new Vector3(), false, false, false);
+ trans.setScale(1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFailScale3B() {
+ final Transform trans = new Transform();
+ trans.setScale(0);
+ }
+
+ @Test
+ public void testTranslate() {
+ final Transform trans = new Transform();
+ trans.translate(1, 3, 5);
+ assertEquals(new Vector3(1, 3, 5), trans.getTranslation());
+ trans.translate(trans.getTranslation().negate(null));
+ assertEquals(Vector3.ZERO, trans.getTranslation());
+
+ trans.translate(new Vector3(1, 3, 5));
+ assertEquals(new Vector3(1, 3, 5), trans.getTranslation());
+ trans.translate(-1, -3, -5);
+ assertEquals(Vector3.ZERO, trans.getTranslation());
+ }
+
+ @Test
+ public void testApplyVector3() {
+ final Transform trans = new Transform().setRotation(new Matrix3().applyRotationX(MathUtils.HALF_PI)).translate(
+ 1, 2, 3);
+ final Vector3 vec3 = new Vector3(0, 1, 0);
+
+ final Vector3 result = trans.applyForward(vec3, null);
+ assertTrue(Math.abs(new Vector3(1, 2, 4).distance(result)) <= MathUtils.EPSILON);
+ trans.applyForward(vec3, result);
+ assertTrue(Math.abs(new Vector3(1, 2, 4).distance(result)) <= MathUtils.EPSILON);
+ trans.applyForward(vec3);
+ assertTrue(Math.abs(new Vector3(1, 2, 4).distance(vec3)) <= MathUtils.EPSILON);
+
+ vec3.set(0, 1, 1);
+ final Vector3 result2 = trans.applyForwardVector(vec3, null);
+ assertTrue(Math.abs(new Vector3(0, -1, 1).distance(result2)) <= MathUtils.EPSILON);
+ trans.applyForwardVector(vec3, result2);
+ assertTrue(Math.abs(new Vector3(0, -1, 1).distance(result2)) <= MathUtils.EPSILON);
+ trans.applyForwardVector(vec3);
+ assertTrue(Math.abs(new Vector3(0, -1, 1).distance(vec3)) <= MathUtils.EPSILON);
+
+ vec3.set(0, 1, 0);
+ final Vector3 result3 = trans.applyInverse(vec3, null);
+ assertTrue(Math.abs(new Vector3(-1, -3, 1).distance(result3)) <= MathUtils.EPSILON);
+ trans.applyInverse(vec3, result3);
+ assertTrue(Math.abs(new Vector3(-1, -3, 1).distance(result3)) <= MathUtils.EPSILON);
+ trans.applyInverse(vec3);
+ assertTrue(Math.abs(new Vector3(-1, -3, 1).distance(vec3)) <= MathUtils.EPSILON);
+
+ vec3.set(0, 1, 1);
+ final Vector3 result4 = trans.applyInverseVector(vec3, null);
+ assertTrue(Math.abs(new Vector3(0, 1, -1).distance(result4)) <= MathUtils.EPSILON);
+ trans.applyInverseVector(vec3, result4);
+ assertTrue(Math.abs(new Vector3(0, 1, -1).distance(result4)) <= MathUtils.EPSILON);
+ trans.applyInverseVector(vec3);
+ assertTrue(Math.abs(new Vector3(0, 1, -1).distance(vec3)) <= MathUtils.EPSILON);
+
+ trans.setRotation(new Matrix3().applyRotationY(MathUtils.PI)).translate(2, 3, -1);
+
+ vec3.set(1, 2, 3).normalizeLocal();
+ final Vector3 orig = new Vector3(vec3);
+ trans.applyForward(vec3);
+ trans.applyInverse(vec3);
+ assertTrue(Math.abs(orig.distance(vec3)) <= 10 * MathUtils.EPSILON); // accumulated error
+
+ vec3.set(orig);
+ trans.applyForwardVector(vec3);
+ trans.applyInverseVector(vec3);
+ assertTrue(Math.abs(orig.distance(vec3)) <= 10 * MathUtils.EPSILON); // accumulated error
+
+ vec3.set(orig);
+ trans.setIdentity();
+ trans.applyForward(vec3);
+ assertEquals(orig, vec3);
+ trans.applyForwardVector(vec3);
+ assertEquals(orig, vec3);
+ trans.applyInverse(vec3);
+ assertEquals(orig, vec3);
+ trans.applyInverseVector(vec3);
+ assertEquals(orig, vec3);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testApplyFail1() {
+ final Transform trans = new Transform();
+ trans.applyForward(null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testApplyFail2() {
+ final Transform trans = new Transform();
+ trans.applyForwardVector(null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testApplyFail3() {
+ final Transform trans = new Transform();
+ trans.applyInverse(null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testApplyFail4() {
+ final Transform trans = new Transform();
+ trans.applyInverseVector(null);
+ }
+
+ @Test
+ public void testMultiply() {
+ final Transform trans1 = new Transform();
+ final Transform trans2 = new Transform();
+ assertEquals(Transform.IDENTITY, trans1.multiply(trans2, null));
+
+ trans1.setTranslation(1, 2, 3);
+ final Transform trans3 = trans1.multiply(trans2, null);
+ assertEquals(trans1, trans3);
+
+ trans2.setTranslation(-1, -2, -3);
+ trans1.multiply(trans2, trans3);
+ assertEquals(Transform.IDENTITY, trans3);
+ assertTrue(trans3.isRotationMatrix());
+ assertTrue(trans3.isIdentity());
+ assertTrue(trans3.isUniformScale());
+
+ trans2.setScale(1, 2, 1);
+ trans1.multiply(trans2, trans3);
+ assertEquals(new Transform().setScale(1, 2, 1), trans3);
+ assertTrue(trans3.isRotationMatrix());
+ assertFalse(trans3.isIdentity());
+ assertFalse(trans3.isUniformScale());
+
+ trans1.setScale(1, 2, 1);
+ trans1.multiply(trans2, trans3);
+ assertEquals(new Transform().setRotation(new Matrix3(1, 0, 0, 0, 4, 0, 0, 0, 1)).setTranslation(0, -2, 0),
+ trans3);
+ assertFalse(trans3.isRotationMatrix());
+ assertFalse(trans3.isIdentity());
+ assertFalse(trans3.isUniformScale());
+ }
+
+ @Test
+ public void testInvert() {
+ final Transform trans1 = new Transform();
+ trans1.setRotation(new Matrix3().applyRotationZ(3 * MathUtils.QUARTER_PI));
+ final Transform trans2 = trans1.invert(null);
+ assertEquals(Transform.IDENTITY, trans1.multiply(trans2, null));
+
+ trans1.setIdentity().invert(trans1);
+ assertEquals(Transform.IDENTITY, trans1);
+ }
+
+ @Test
+ public void testClone() {
+ final Transform trans1 = new Transform();
+ final Transform trans2 = trans1.clone();
+ assertEquals(trans1, trans2);
+ assertNotSame(trans1, trans2);
+ }
+
+ @Test
+ public void testValid() {
+ final Transform trans = new Transform();
+ assertTrue(Transform.isValid(trans));
+ trans.setIdentity();
+ trans.setRotation(new Matrix3(Double.NaN, 0, 0, 0, 0, 0, 0, 0, 0));
+ assertFalse(Transform.isValid(trans));
+ trans.setIdentity();
+ trans.setScale(Double.NaN, 0, 0);
+ assertFalse(Transform.isValid(trans));
+ trans.setScale(Double.NaN);
+ assertFalse(Transform.isValid(trans));
+ trans.setIdentity();
+ trans.setTranslation(Double.NaN, 0, 0);
+ assertFalse(Transform.isValid(trans));
+
+ trans.setIdentity();
+ assertTrue(Transform.isValid(trans));
+
+ assertFalse(Transform.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(trans, trans);
+ assertTrue(trans.strictEquals(trans));
+ assertFalse(trans.equals(null));
+ assertFalse(trans.strictEquals(null));
+ assertFalse(trans.equals(new Vector2()));
+ assertFalse(trans.strictEquals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Transform transTemp = Transform.fetchTempInstance();
+ transTemp.set(trans);
+ assertEquals(trans, transTemp);
+ assertNotSame(trans, transTemp);
+ Transform.releaseTempInstance(transTemp);
+
+ // cover more of equals
+ trans.setScale(1, 2, 3);
+ trans.setRotation(new Matrix3(0, 1, 2, 3, 4, 5, 6, 7, 8));
+ trans.setTranslation(1, 2, 3);
+ final Transform comp = new Transform();
+ final Matrix3 mat3 = new Matrix3(-1, -1, -1, -1, -1, -1, -1, -1, -1);
+ comp.setScale(-1, -1, -1);
+ comp.setRotation(mat3);
+ comp.setTranslation(-1, -1, -1);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+ for (int i = 0; i < 8; i++) {
+ mat3.setValue(i / 3, i % 3, i);
+ comp.setRotation(mat3);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+ }
+ // test translation
+ trans.setRotation(Matrix3.IDENTITY);
+ comp.setRotation(Matrix3.IDENTITY);
+ comp.setTranslation(1, -1, -1);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+ comp.setTranslation(1, 2, -1);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+ comp.setTranslation(1, 2, 3);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+
+ // test scale
+ comp.setScale(1, -1, -1);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+ comp.setScale(1, 2, -1);
+ assertFalse(trans.equals(comp));
+ assertFalse(trans.strictEquals(comp));
+ comp.setScale(1, 2, 3);
+ assertTrue(trans.equals(comp));
+ assertTrue(trans.strictEquals(comp));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Transform trans1 = new Transform().setTranslation(1, 2, 3);
+ final Transform trans2 = new Transform().setTranslation(1, 2, 3);
+ final Transform trans3 = new Transform().setTranslation(1, 2, 0);
+
+ assertTrue(trans1.hashCode() == trans2.hashCode());
+ assertTrue(trans1.hashCode() != trans3.hashCode());
+ }
+
+ @Test
+ public void testGLApplyMatrix() {
+ final Transform trans = new Transform();
+
+ // non-rotational
+ trans.setRotation(new Matrix3(0, 1, 2, 3, 4, 5, 6, 7, 8));
+ trans.setTranslation(10, 11, 12);
+ final DoubleBuffer db = DoubleBuffer.allocate(16);
+ trans.getGLApplyMatrix(db);
+ for (final double val : new double[] { 0, 3, 6, 0, 1, 4, 7, 0, 2, 5, 8, 0, 10, 11, 12, 1 }) {
+ assertTrue(val == db.get());
+ }
+ final FloatBuffer fb = FloatBuffer.allocate(16);
+ trans.getGLApplyMatrix(fb);
+ for (final float val : new float[] { 0, 3, 6, 0, 1, 4, 7, 0, 2, 5, 8, 0, 10, 11, 12, 1 }) {
+ assertTrue(val == fb.get());
+ }
+
+ // rotational
+ final double a = MathUtils.QUARTER_PI;
+ trans.setRotation(new Matrix3().applyRotationY(a));
+ trans.setTranslation(10, 11, 12);
+ trans.setScale(2, 3, 4);
+ db.rewind();
+ trans.getGLApplyMatrix(db);
+ for (final double val : new double[] { 2 * Math.cos(a), 2 * 0, 2 * -Math.sin(a), 0, //
+ 3 * 0, 3 * 1, 3 * 0, 0, //
+ 4 * Math.sin(a), 4 * 0, 4 * Math.cos(a), 0, //
+ 10, 11, 12, 1 }) {
+ assertTrue(val == db.get());
+ }
+ fb.rewind();
+ trans.getGLApplyMatrix(fb);
+ for (final float val : new float[] { (float) (2 * Math.cos(a)), 2 * 0, (float) (2 * -Math.sin(a)), 0, //
+ 3 * 0, 3 * 1, 3 * 0, 0, //
+ (float) (4 * Math.sin(a)), 4 * 0, (float) (4 * Math.cos(a)), 0, //
+ 10, 11, 12, 1 }) {
+ assertTrue(val == fb.get());
+ }
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestTriangle.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestTriangle.java new file mode 100644 index 0000000..b59bc37 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestTriangle.java @@ -0,0 +1,166 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestTriangle {
+
+ @Test
+ public void testGetSet() {
+ final Triangle tri1 = new Triangle();
+ assertEquals(Vector3.ZERO, tri1.getA());
+ assertEquals(Vector3.ZERO, tri1.getB());
+ assertEquals(Vector3.ZERO, tri1.getC());
+ assertTrue(tri1.getIndex() == 0);
+
+ tri1.setA(Vector3.NEG_ONE);
+ tri1.setB(Vector3.UNIT_X);
+ tri1.setC(Vector3.UNIT_Z);
+ tri1.setIndex(1);
+ assertEquals(Vector3.NEG_ONE, tri1.getA());
+ assertEquals(Vector3.UNIT_X, tri1.getB());
+ assertEquals(Vector3.UNIT_Z, tri1.getC());
+ assertTrue(tri1.getIndex() == 1);
+
+ final Triangle tri2 = new Triangle(tri1);
+ assertEquals(Vector3.NEG_ONE, tri2.getA());
+ assertEquals(Vector3.UNIT_X, tri2.getB());
+ assertEquals(Vector3.UNIT_Z, tri2.getC());
+ assertTrue(tri2.getIndex() == 1);
+
+ final Triangle tri3 = new Triangle(Vector3.ONE, Vector3.UNIT_Y, Vector3.NEG_ONE);
+ assertEquals(Vector3.ONE, tri3.getA());
+ assertEquals(Vector3.UNIT_Y, tri3.getB());
+ assertEquals(Vector3.NEG_ONE, tri3.getC());
+ assertTrue(tri3.getIndex() == 0);
+
+ final Triangle tri4 = new Triangle(Vector3.ONE, Vector3.UNIT_Y, Vector3.NEG_ONE, 42);
+ assertEquals(Vector3.ONE, tri4.getA());
+ assertEquals(Vector3.UNIT_Y, tri4.getB());
+ assertEquals(Vector3.NEG_ONE, tri4.getC());
+ assertTrue(tri4.getIndex() == 42);
+
+ tri2.set(0, Vector3.UNIT_X);
+ tri2.set(1, Vector3.UNIT_Y);
+ tri2.set(2, Vector3.UNIT_Z);
+
+ // catch a few expected exceptions
+ try {
+ tri2.get(3);
+ fail("get(3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ tri2.get(-1);
+ fail("get(-1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ tri2.set(-1, Vector3.ZERO);
+ fail("set(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ tri2.set(3, Vector3.ZERO);
+ fail("set(3, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+
+ // shouldn't have changed
+ assertEquals(Vector3.UNIT_X, tri2.get(0));
+ assertEquals(Vector3.UNIT_Y, tri2.get(1));
+ assertEquals(Vector3.UNIT_Z, tri2.get(2));
+ }
+
+ @Test
+ public void testEquals() {
+ // couple of equals validity tests
+ final Triangle tri1 = new Triangle();
+ assertEquals(tri1, tri1);
+ assertFalse(tri1.equals(null));
+ assertFalse(tri1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Triangle tri2 = Triangle.fetchTempInstance();
+ tri2.set(tri1);
+ assertEquals(tri1, tri2);
+ assertNotSame(tri1, tri2);
+ Triangle.releaseTempInstance(tri2);
+
+ // cover more of equals
+ assertTrue(tri1.equals(new Triangle(Vector3.ZERO, Vector3.ZERO, Vector3.ZERO)));
+ assertFalse(tri1.equals(new Triangle(Vector3.ZERO, Vector3.ZERO, Vector3.UNIT_X)));
+ assertFalse(tri1.equals(new Triangle(Vector3.ZERO, Vector3.UNIT_X, Vector3.UNIT_X)));
+ assertFalse(tri1.equals(new Triangle(Vector3.ZERO, Vector3.UNIT_X, Vector3.ZERO)));
+ assertFalse(tri1.equals(new Triangle(Vector3.UNIT_X, Vector3.ZERO, Vector3.ZERO)));
+ assertFalse(tri1.equals(new Triangle(Vector3.UNIT_X, Vector3.ZERO, Vector3.UNIT_X)));
+ assertFalse(tri1.equals(new Triangle(Vector3.UNIT_X, Vector3.UNIT_X, Vector3.ZERO)));
+ assertFalse(tri1.equals(new Triangle(Vector3.UNIT_X, Vector3.UNIT_X, Vector3.UNIT_X)));
+ }
+
+ @Test
+ public void testValid() {
+ final Triangle vec1 = new Triangle();
+ final Triangle vec2 = new Triangle(new Vector3(0, 0, Double.NaN), Vector3.ZERO, Vector3.ZERO);
+ final Triangle vec3 = new Triangle(Vector3.ZERO, new Vector3(0, 0, Double.NaN), Vector3.ZERO);
+ final Triangle vec4 = new Triangle(Vector3.ZERO, Vector3.ZERO, new Vector3(0, 0, Double.NaN));
+
+ assertTrue(Triangle.isValid(vec1));
+ assertFalse(Triangle.isValid(vec2));
+ assertFalse(Triangle.isValid(vec3));
+ assertFalse(Triangle.isValid(vec4));
+
+ vec4.setC(Vector3.ZERO);
+ assertTrue(Triangle.isValid(vec4));
+
+ assertFalse(Triangle.isValid(null));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Triangle tri1 = new Triangle(Vector3.ZERO, Vector3.UNIT_Y, Vector3.NEG_ONE, 1);
+ final Triangle tri2 = new Triangle(Vector3.ZERO, Vector3.UNIT_Y, Vector3.NEG_ONE, 1);
+ final Triangle tri3 = new Triangle(Vector3.ZERO, Vector3.UNIT_Z, Vector3.NEG_ONE, 2);
+
+ assertTrue(tri1.hashCode() == tri2.hashCode());
+ assertTrue(tri1.hashCode() != tri3.hashCode());
+ }
+
+ @Test
+ public void testClone() {
+ final Triangle tri1 = new Triangle();
+ final Triangle tri2 = tri1.clone();
+ assertEquals(tri1, tri2);
+ assertNotSame(tri1, tri2);
+ }
+
+ @Test
+ public void testCenter() {
+ final Triangle tri1 = new Triangle(Vector3.ZERO, Vector3.UNIT_Y, Vector3.UNIT_X, 0);
+ assertEquals(new Vector3(1 / 3., 1 / 3., 0), tri1.getCenter()); // dirty
+ assertEquals(new Vector3(1 / 3., 1 / 3., 0), tri1.getCenter()); // clean
+ tri1.setA(Vector3.ONE);
+ assertEquals(new Vector3(2 / 3., 2 / 3., 1 / 3.), tri1.getCenter()); // dirty, but with existing center
+ }
+
+ @Test
+ public void testNormal() {
+ final Triangle tri1 = new Triangle(Vector3.ZERO, Vector3.UNIT_Y, Vector3.UNIT_X, 0);
+ assertEquals(new Vector3(0, 0, -1), tri1.getNormal()); // dirty
+ assertEquals(new Vector3(0, 0, -1), tri1.getNormal()); // clean
+ tri1.setB(Vector3.UNIT_Z);
+ assertEquals(new Vector3(0, 1, 0), tri1.getNormal()); // dirty, but with existing normal
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestValidatingTransform.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestValidatingTransform.java new file mode 100644 index 0000000..7dab55d --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestValidatingTransform.java @@ -0,0 +1,151 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestValidatingTransform {
+
+ @Test
+ public void testConstructor() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ assertEquals(Transform.IDENTITY, vt1);
+
+ vt1.translate(0, 1, 2);
+ vt1.setRotation(new Matrix3().fromAngleAxis(Math.PI, Vector3.UNIT_X));
+
+ final ValidatingTransform vt2 = new ValidatingTransform(vt1);
+ assertEquals(vt1, vt2);
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void failConstructor() {
+ final Transform bad = new Transform();
+ bad.translate(Double.NaN, 1, 2);
+ new ValidatingTransform(Transform.IDENTITY); // good
+ new ValidatingTransform(bad); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetRotationReadOnlyMatrix3() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ final Matrix3 rotation = new Matrix3();
+ vt1.setRotation(rotation); // good
+ rotation.setM00(Double.NaN);
+ vt1.setRotation(rotation); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetRotationReadOnlyQuaternion() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ final Quaternion rotation = new Quaternion();
+ vt1.setRotation(rotation); // good
+ rotation.setX(Double.NaN);
+ vt1.setRotation(rotation); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetTranslationReadOnlyVector3() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.setTranslation(new Vector3(0, 0, 1)); // good
+ vt1.setTranslation(new Vector3(0, 0, Double.NaN)); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetTranslationDoubleDoubleDouble() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.setTranslation(0, 0, 1); // good
+ vt1.setTranslation(0, 0, Double.NaN); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetScaleReadOnlyVector3() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.setScale(new Vector3(1, 1, 1)); // good
+ vt1.setScale(new Vector3(1, 1, Double.NaN)); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetScaleDoubleDoubleDouble() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.setScale(0, 0, 1); // good
+ vt1.setScale(0, 0, Double.NaN); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSetScaleDouble() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.setScale(1); // good
+ vt1.setScale(Double.NaN); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testSet() {
+ final Transform bad = new Transform();
+ bad.translate(Double.NaN, 1, 2);
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.set(Transform.IDENTITY); // good
+ vt1.set(bad); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testTranslateDoubleDoubleDouble() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.translate(1, 2, 3); // good
+ vt1.translate(0, 0, Double.NaN); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testTranslateReadOnlyVector3() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.translate(new Vector3(1, 2, 3)); // good
+ vt1.translate(new Vector3(0, 0, Double.NaN)); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testMultiply() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.multiply(Transform.IDENTITY, null); // good
+ final Transform bad = new Transform();
+ bad.translate(Double.NaN, 1, 2);
+ vt1.multiply(bad, null); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testInvert() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ vt1.setScale(2);
+ vt1.invert(null); // good
+ // a little chicanery to get around other checks.
+ ((Vector3) vt1.getScale()).setX(0);
+ vt1.invert(null); // bad
+ }
+
+ @Test(expected = InvalidTransformException.class)
+ public void testFromHomogeneousMatrix() {
+ final ValidatingTransform vt1 = new ValidatingTransform();
+ final Matrix4 matrix = new Matrix4();
+ vt1.fromHomogeneousMatrix(matrix); // good
+ matrix.setM00(Double.NaN);
+ vt1.fromHomogeneousMatrix(matrix); // bad
+ }
+
+ @Test
+ public void testClone() {
+ final ValidatingTransform trans1 = new ValidatingTransform();
+ final ValidatingTransform trans2 = trans1.clone();
+ assertEquals(trans1, trans2);
+ assertNotSame(trans1, trans2);
+ }
+
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestVector2.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestVector2.java new file mode 100644 index 0000000..6ba3b7c --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestVector2.java @@ -0,0 +1,376 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestVector2 {
+
+ @Test
+ public void testAdd() {
+ final Vector2 vec1 = new Vector2();
+ final Vector2 vec2 = new Vector2(Vector2.ONE);
+
+ vec1.addLocal(1, 2);
+ assertEquals(new Vector2(1, 2), vec1);
+ vec1.addLocal(-1, -2);
+ assertEquals(Vector2.ZERO, vec1);
+
+ vec1.zero();
+ vec1.addLocal(vec2);
+ assertEquals(Vector2.ONE, vec1);
+
+ vec1.zero();
+ final Vector2 vec3 = vec1.add(vec2, new Vector2());
+ assertEquals(Vector2.ZERO, vec1);
+ assertEquals(Vector2.ONE, vec3);
+
+ final Vector2 vec4 = vec1.add(1, 0, null);
+ assertEquals(Vector2.ZERO, vec1);
+ assertEquals(Vector2.UNIT_X, vec4);
+ }
+
+ @Test
+ public void testSubtract() {
+ final Vector2 vec1 = new Vector2();
+ final Vector2 vec2 = new Vector2(Vector2.ONE);
+
+ vec1.subtractLocal(1, 2);
+ assertEquals(new Vector2(-1, -2), vec1);
+ vec1.subtractLocal(-1, -2);
+ assertEquals(Vector2.ZERO, vec1);
+
+ vec1.zero();
+ vec1.subtractLocal(vec2);
+ assertEquals(Vector2.NEG_ONE, vec1);
+
+ vec1.zero();
+ final Vector2 vec3 = vec1.subtract(vec2, new Vector2());
+ assertEquals(Vector2.ZERO, vec1);
+ assertEquals(Vector2.NEG_ONE, vec3);
+
+ final Vector2 vec4 = vec1.subtract(1, 0, null);
+ assertEquals(Vector2.ZERO, vec1);
+ assertEquals(Vector2.NEG_UNIT_X, vec4);
+ }
+
+ @Test
+ public void testGetSet() {
+ final Vector2 vec1 = new Vector2();
+ vec1.setX(0);
+ assertTrue(vec1.getX() == 0.0);
+ vec1.setX(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getX() == Double.POSITIVE_INFINITY);
+ vec1.setX(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getX() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(0) == Double.NEGATIVE_INFINITY);
+
+ vec1.setY(0);
+ assertTrue(vec1.getY() == 0.0);
+ vec1.setY(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getY() == Double.POSITIVE_INFINITY);
+ vec1.setY(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getY() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(1) == Double.NEGATIVE_INFINITY);
+
+ vec1.set(Math.PI, Math.PI);
+ assertTrue(vec1.getXf() == (float) Math.PI);
+ assertTrue(vec1.getYf() == (float) Math.PI);
+
+ final Vector2 vec2 = new Vector2();
+ vec2.set(vec1);
+ assertEquals(vec1, vec2);
+
+ vec1.setValue(0, 0);
+ vec1.setValue(1, 0);
+ assertEquals(Vector2.ZERO, vec1);
+
+ // catch a few expected exceptions
+ try {
+ vec2.getValue(2);
+ fail("getValue(2) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.getValue(-1);
+ fail("getValue(-1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.setValue(-1, 0);
+ fail("setValue(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.setValue(2, 0);
+ fail("setValue(2, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ // above exceptions shouldn't have altered vec2
+ assertEquals(new Vector2(Math.PI, Math.PI), vec2);
+ }
+
+ @Test
+ public void testPolarAngle() {
+ final Vector2 vec1 = new Vector2();
+ assertTrue(0.0 == vec1.getPolarAngle());
+
+ vec1.set(1.0, 0.0); // 0
+ assertTrue(Math.abs(0 - vec1.getPolarAngle()) <= MathUtils.EPSILON);
+
+ vec1.set(0.0, 1.0); // -HALF_PI
+ assertTrue(Math.abs(-MathUtils.HALF_PI - vec1.getPolarAngle()) <= MathUtils.EPSILON);
+
+ vec1.set(-1.0, 0.0); // -PI
+ assertTrue(Math.abs(-MathUtils.PI - vec1.getPolarAngle()) <= MathUtils.EPSILON);
+
+ vec1.set(0, -1.0); // HALF_PI
+ assertTrue(Math.abs(MathUtils.HALF_PI - vec1.getPolarAngle()) <= MathUtils.EPSILON);
+ }
+
+ @Test
+ public void testToArray() {
+ final Vector2 vec1 = new Vector2();
+ vec1.set(Math.PI, Double.MAX_VALUE);
+ final double[] array = vec1.toArray(null);
+ final double[] array2 = vec1.toArray(new double[2]);
+ assertNotNull(array);
+ assertTrue(array.length == 2);
+ assertTrue(array[0] == Math.PI);
+ assertTrue(array[1] == Double.MAX_VALUE);
+ assertNotNull(array2);
+ assertTrue(array2.length == 2);
+ assertTrue(array2[0] == Math.PI);
+ assertTrue(array2[1] == Double.MAX_VALUE);
+
+ try {
+ vec1.toArray(new double[1]);
+ fail("toArray(d[1]) should have thrown ArrayIndexOutOfBoundsException.");
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testMultiply() {
+ final Vector2 vec1 = new Vector2(1, -1);
+ final Vector2 vec2 = vec1.multiply(2.0, null);
+ final Vector2 vec2B = vec1.multiply(2.0, new Vector2());
+ assertEquals(new Vector2(2.0, -2.0), vec2);
+ assertEquals(new Vector2(2.0, -2.0), vec2B);
+
+ vec2.multiplyLocal(0.5);
+ assertEquals(new Vector2(1.0, -1.0), vec2);
+
+ final Vector2 vec3 = vec1.multiply(vec2, null);
+ final Vector2 vec3B = vec1.multiply(vec2, new Vector2());
+ assertEquals(Vector2.ONE, vec3);
+ assertEquals(Vector2.ONE, vec3B);
+
+ final Vector2 vec4 = vec1.multiply(2, 3, null);
+ final Vector2 vec4B = vec1.multiply(2, 3, new Vector2());
+ assertEquals(new Vector2(2, -3), vec4);
+ assertEquals(new Vector2(2, -3), vec4B);
+
+ vec1.multiplyLocal(0.5, 0.5);
+ assertEquals(new Vector2(0.5, -0.5), vec1);
+
+ vec1.multiplyLocal(vec2);
+ assertEquals(new Vector2(0.5, 0.5), vec1);
+ }
+
+ @Test
+ public void testDivide() {
+ final Vector2 vec1 = new Vector2(1, -1);
+ final Vector2 vec2 = vec1.divide(2.0, null);
+ final Vector2 vec2B = vec1.divide(2.0, new Vector2());
+ assertEquals(new Vector2(0.5, -0.5), vec2);
+ assertEquals(new Vector2(0.5, -0.5), vec2B);
+
+ vec2.divideLocal(0.5);
+ assertEquals(new Vector2(1.0, -1.0), vec2);
+
+ final Vector2 vec3 = vec1.divide(vec2, null);
+ final Vector2 vec3B = vec1.divide(vec2, new Vector2());
+ assertEquals(Vector2.ONE, vec3);
+ assertEquals(Vector2.ONE, vec3B);
+
+ final Vector2 vec4 = vec1.divide(2, 3, null);
+ final Vector2 vec4B = vec1.divide(2, 3, new Vector2());
+ assertEquals(new Vector2(1 / 2., -1 / 3.), vec4);
+ assertEquals(new Vector2(1 / 2., -1 / 3.), vec4B);
+
+ vec1.divideLocal(0.5, 0.5);
+ assertEquals(new Vector2(2, -2), vec1);
+
+ vec1.divideLocal(vec2);
+ assertEquals(new Vector2(2, 2), vec1);
+ }
+
+ @Test
+ public void testScaleAdd() {
+ final Vector2 vec1 = new Vector2(1, 1);
+ final Vector2 vec2 = vec1.scaleAdd(2.0, new Vector2(1, 2), null);
+ final Vector2 vec2B = vec1.scaleAdd(2.0, new Vector2(1, 2), new Vector2());
+ assertEquals(new Vector2(3.0, 4.0), vec2);
+ assertEquals(new Vector2(3.0, 4.0), vec2B);
+
+ vec1.scaleAddLocal(2.0, new Vector2(1, 2));
+ assertEquals(vec2, vec1);
+ }
+
+ @Test
+ public void testNegate() {
+ final Vector2 vec1 = new Vector2(2, 1);
+ final Vector2 vec2 = vec1.negate(null);
+ assertEquals(new Vector2(-2, -1), vec2);
+
+ vec1.negateLocal();
+ assertEquals(vec2, vec1);
+ }
+
+ @Test
+ public void testNormalize() {
+ final Vector2 vec1 = new Vector2(2, 1);
+ assertTrue(vec1.length() == Math.sqrt(5));
+
+ final Vector2 vec2 = vec1.normalize(null);
+ final double invLength = MathUtils.inverseSqrt(2 * 2 + 1 * 1);
+ assertEquals(new Vector2(2 * invLength, 1 * invLength), vec2);
+
+ vec1.normalizeLocal();
+ assertEquals(new Vector2(2 * invLength, 1 * invLength), vec1);
+
+ vec1.zero();
+ vec1.normalize(vec2);
+ assertEquals(vec1, vec2);
+
+ // ensure no exception thrown
+ vec1.normalizeLocal();
+ vec1.normalize(null);
+ }
+
+ @Test
+ public void testDistance() {
+ final Vector2 vec1 = new Vector2(0, 0);
+ assertTrue(3.0 == vec1.distance(0, 3));
+ assertTrue(4.0 == vec1.distance(4, 0));
+
+ final Vector2 vec2 = new Vector2(1, 1);
+ assertTrue(Math.sqrt(2) == vec1.distance(vec2));
+ }
+
+ @Test
+ public void testLerp() {
+ final Vector2 vec1 = new Vector2(8, 3);
+ final Vector2 vec2 = new Vector2(2, 1);
+ assertEquals(new Vector2(5, 2), vec1.lerp(vec2, 0.5, null));
+ assertEquals(new Vector2(5, 2), vec1.lerp(vec2, 0.5, new Vector2()));
+ assertEquals(new Vector2(5, 2), Vector2.lerp(vec1, vec2, 0.5, null));
+ assertEquals(new Vector2(5, 2), Vector2.lerp(vec1, vec2, 0.5, new Vector2()));
+
+ vec1.set(14, 5);
+ vec1.lerpLocal(vec2, 0.25);
+ assertEquals(new Vector2(11, 4), vec1);
+
+ vec1.set(15, 7);
+ final Vector2 vec3 = new Vector2(-1, -1);
+ vec3.lerpLocal(vec1, vec2, 0.5);
+ assertEquals(new Vector2(8.5, 4.0), vec3);
+ }
+
+ @Test
+ public void testRotate() {
+ final Vector2 vec1 = new Vector2(1, 0);
+ final Vector2 vec2 = vec1.rotateAroundOrigin(MathUtils.HALF_PI, true, null);
+ final Vector2 vec2B = vec1.rotateAroundOrigin(MathUtils.HALF_PI, false, new Vector2());
+ assertEquals(new Vector2(0, -1), vec2);
+ assertEquals(new Vector2(0, 1), vec2B);
+ vec2.rotateAroundOriginLocal(MathUtils.HALF_PI, false);
+ assertEquals(new Vector2(1, 0), vec2);
+ vec2.rotateAroundOriginLocal(MathUtils.PI, true);
+ assertTrue(Math.abs(vec2.getX() - -1) <= MathUtils.EPSILON);
+ assertTrue(Math.abs(vec2.getY() - 0) <= MathUtils.EPSILON);
+ }
+
+ @Test
+ public void testAngle() {
+ final Vector2 vec1 = new Vector2(1, 0);
+ assertTrue(MathUtils.HALF_PI == vec1.angleBetween(new Vector2(0, 1)));
+ assertTrue(-MathUtils.HALF_PI == vec1.angleBetween(new Vector2(0, -1)));
+
+ assertTrue(MathUtils.HALF_PI == vec1.smallestAngleBetween(new Vector2(0, -1)));
+ }
+
+ @Test
+ public void testDot() {
+ final Vector2 vec1 = new Vector2(7, 2);
+ assertTrue(23.0 == vec1.dot(3, 1));
+
+ assertTrue(-5.0 == vec1.dot(new Vector2(-1, 1)));
+ }
+
+ @Test
+ public void testClone() {
+ final Vector2 vec1 = new Vector2(0, 0);
+ final Vector2 vec2 = vec1.clone();
+ assertEquals(vec1, vec2);
+ assertNotSame(vec1, vec2);
+ }
+
+ @Test
+ public void testValid() {
+ final Vector2 vec1 = new Vector2(0, 0);
+ final Vector2 vec2 = new Vector2(Double.POSITIVE_INFINITY, 0);
+ final Vector2 vec3 = new Vector2(0, Double.NEGATIVE_INFINITY);
+ final Vector2 vec4 = new Vector2(Double.NaN, 0);
+ final Vector2 vec5 = new Vector2(0, Double.NaN);
+
+ assertTrue(Vector2.isValid(vec1));
+ assertFalse(Vector2.isValid(vec2));
+ assertFalse(Vector2.isValid(vec3));
+ assertFalse(Vector2.isValid(vec4));
+ assertFalse(Vector2.isValid(vec5));
+
+ vec5.zero();
+ assertTrue(Vector2.isValid(vec5));
+
+ assertFalse(Vector2.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(vec1, vec1);
+ assertFalse(vec1.equals(null));
+ assertFalse(vec1.equals(new Vector3()));
+
+ // throw in a couple pool accesses for coverage
+ final Vector2 vec6 = Vector2.fetchTempInstance();
+ vec6.set(vec1);
+ assertEquals(vec1, vec6);
+ assertNotSame(vec1, vec6);
+ Vector2.releaseTempInstance(vec6);
+
+ // cover more of equals
+ vec1.set(0, 1);
+ assertFalse(vec1.equals(new Vector2(0, 2)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Vector2 vec1 = new Vector2(1, 2);
+ final Vector2 vec2 = new Vector2(1, 2);
+ final Vector2 vec3 = new Vector2(2, 2);
+
+ assertTrue(vec1.hashCode() == vec2.hashCode());
+ assertTrue(vec1.hashCode() != vec3.hashCode());
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestVector3.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestVector3.java new file mode 100644 index 0000000..8bf4bfa --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestVector3.java @@ -0,0 +1,405 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestVector3 {
+
+ @Test
+ public void testAdd() {
+ final Vector3 vec1 = new Vector3();
+ final Vector3 vec2 = new Vector3(Vector3.ONE);
+
+ vec1.addLocal(1, 2, 3);
+ assertEquals(new Vector3(1, 2, 3), vec1);
+ vec1.addLocal(-1, -2, -3);
+ assertEquals(Vector3.ZERO, vec1);
+
+ vec1.zero();
+ vec1.addLocal(vec2);
+ assertEquals(Vector3.ONE, vec1);
+
+ vec1.zero();
+ final Vector3 vec3 = vec1.add(vec2, new Vector3());
+ assertEquals(Vector3.ZERO, vec1);
+ assertEquals(Vector3.ONE, vec3);
+
+ final Vector3 vec4 = vec1.add(1, 0, 0, null);
+ assertEquals(Vector3.ZERO, vec1);
+ assertEquals(Vector3.UNIT_X, vec4);
+ }
+
+ @Test
+ public void testSubtract() {
+ final Vector3 vec1 = new Vector3();
+ final Vector3 vec2 = new Vector3(Vector3.ONE);
+
+ vec1.subtractLocal(1, 2, 3);
+ assertEquals(new Vector3(-1, -2, -3), vec1);
+ vec1.subtractLocal(-1, -2, -3);
+ assertEquals(Vector3.ZERO, vec1);
+
+ vec1.zero();
+ vec1.subtractLocal(vec2);
+ assertEquals(Vector3.NEG_ONE, vec1);
+
+ vec1.zero();
+ final Vector3 vec3 = vec1.subtract(vec2, new Vector3());
+ assertEquals(Vector3.ZERO, vec1);
+ assertEquals(Vector3.NEG_ONE, vec3);
+
+ final Vector3 vec4 = vec1.subtract(1, 0, 0, null);
+ assertEquals(Vector3.ZERO, vec1);
+ assertEquals(Vector3.NEG_UNIT_X, vec4);
+ }
+
+ @Test
+ public void testGetSet() {
+ final Vector3 vec1 = new Vector3();
+ vec1.setX(0);
+ assertTrue(vec1.getX() == 0.0);
+ vec1.setX(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getX() == Double.POSITIVE_INFINITY);
+ vec1.setX(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getX() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(0) == Double.NEGATIVE_INFINITY);
+
+ vec1.setY(0);
+ assertTrue(vec1.getY() == 0.0);
+ vec1.setY(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getY() == Double.POSITIVE_INFINITY);
+ vec1.setY(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getY() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(1) == Double.NEGATIVE_INFINITY);
+
+ vec1.setZ(0);
+ assertTrue(vec1.getZ() == 0.0);
+ vec1.setZ(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getZ() == Double.POSITIVE_INFINITY);
+ vec1.setZ(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getZ() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(2) == Double.NEGATIVE_INFINITY);
+
+ vec1.set(Math.PI, Math.PI, Math.PI);
+ assertTrue(vec1.getXf() == (float) Math.PI);
+ assertTrue(vec1.getYf() == (float) Math.PI);
+ assertTrue(vec1.getZf() == (float) Math.PI);
+
+ final Vector3 vec2 = new Vector3();
+ vec2.set(vec1);
+ assertEquals(vec1, vec2);
+
+ vec1.setValue(0, 0);
+ vec1.setValue(1, 0);
+ vec1.setValue(2, 0);
+ assertEquals(Vector3.ZERO, vec1);
+
+ // catch a few expected exceptions
+ try {
+ vec2.getValue(3);
+ fail("getValue(3) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.getValue(-1);
+ fail("getValue(-1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.setValue(-1, 0);
+ fail("setValue(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.setValue(3, 0);
+ fail("setValue(3, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ // above exceptions shouldn't have altered vec2
+ assertEquals(new Vector3(Math.PI, Math.PI, Math.PI), vec2);
+ }
+
+ @Test
+ public void testToArray() {
+ final Vector3 vec1 = new Vector3();
+ vec1.set(Math.PI, Double.MAX_VALUE, 42);
+ final double[] array = vec1.toArray(null);
+ final double[] array2 = vec1.toArray(new double[3]);
+ assertNotNull(array);
+ assertTrue(array.length == 3);
+ assertTrue(array[0] == Math.PI);
+ assertTrue(array[1] == Double.MAX_VALUE);
+ assertTrue(array[2] == 42);
+ assertNotNull(array2);
+ assertTrue(array2.length == 3);
+ assertTrue(array2[0] == Math.PI);
+ assertTrue(array2[1] == Double.MAX_VALUE);
+ assertTrue(array2[2] == 42);
+
+ try {
+ vec1.toArray(new double[1]);
+ fail("toArray(d[1]) should have thrown ArrayIndexOutOfBoundsException.");
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ }
+
+ final float[] farray = vec1.toFloatArray(null);
+ final float[] farray2 = vec1.toFloatArray(new float[3]);
+ assertNotNull(farray);
+ assertTrue(farray.length == 3);
+ assertTrue(farray[0] == (float) Math.PI);
+ assertTrue(farray[1] == (float) Double.MAX_VALUE);
+ assertTrue(farray[2] == 42f);
+ assertNotNull(farray2);
+ assertTrue(farray2.length == 3);
+ assertTrue(farray2[0] == (float) Math.PI);
+ assertTrue(farray2[1] == (float) Double.MAX_VALUE);
+ assertTrue(farray2[2] == 42f);
+
+ try {
+ vec1.toFloatArray(new float[1]);
+ fail("toFloatArray(d[1]) should have thrown ArrayIndexOutOfBoundsException.");
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testMultiply() {
+ final Vector3 vec1 = new Vector3(1, -1, 2);
+ final Vector3 vec2 = vec1.multiply(2.0, null);
+ final Vector3 vec2B = vec1.multiply(2.0, new Vector3());
+ assertEquals(new Vector3(2.0, -2.0, 4.0), vec2);
+ assertEquals(new Vector3(2.0, -2.0, 4.0), vec2B);
+
+ vec2.multiplyLocal(0.5);
+ assertEquals(new Vector3(1.0, -1.0, 2.0), vec2);
+
+ final Vector3 vec3 = vec1.multiply(vec2, null);
+ final Vector3 vec3B = vec1.multiply(vec2, new Vector3());
+ assertEquals(new Vector3(1, 1, 4), vec3);
+ assertEquals(new Vector3(1, 1, 4), vec3B);
+
+ final Vector3 vec4 = vec1.multiply(2, 3, 2, null);
+ final Vector3 vec4B = vec1.multiply(2, 3, 2, new Vector3());
+ assertEquals(new Vector3(2, -3, 4), vec4);
+ assertEquals(new Vector3(2, -3, 4), vec4B);
+
+ vec1.multiplyLocal(0.5, 0.5, 0.5);
+ assertEquals(new Vector3(0.5, -0.5, 1.0), vec1);
+
+ vec1.multiplyLocal(vec2);
+ assertEquals(new Vector3(0.5, 0.5, 2.0), vec1);
+ }
+
+ @Test
+ public void testDivide() {
+ final Vector3 vec1 = new Vector3(1, -1, 2);
+ final Vector3 vec2 = vec1.divide(2.0, null);
+ final Vector3 vec2B = vec1.divide(2.0, new Vector3());
+ assertEquals(new Vector3(0.5, -0.5, 1.0), vec2);
+ assertEquals(new Vector3(0.5, -0.5, 1.0), vec2B);
+
+ vec2.divideLocal(0.5);
+ assertEquals(new Vector3(1.0, -1.0, 2.0), vec2);
+
+ final Vector3 vec3 = vec1.divide(vec2, null);
+ final Vector3 vec3B = vec1.divide(vec2, new Vector3());
+ assertEquals(Vector3.ONE, vec3);
+ assertEquals(Vector3.ONE, vec3B);
+
+ final Vector3 vec4 = vec1.divide(2, 3, 4, null);
+ final Vector3 vec4B = vec1.divide(2, 3, 4, new Vector3());
+ assertEquals(new Vector3(0.5, -1 / 3., 0.5), vec4);
+ assertEquals(new Vector3(0.5, -1 / 3., 0.5), vec4B);
+
+ vec1.divideLocal(0.5, 0.5, 0.5);
+ assertEquals(new Vector3(2, -2, 4), vec1);
+
+ vec1.divideLocal(vec2);
+ assertEquals(new Vector3(2, 2, 2), vec1);
+ }
+
+ @Test
+ public void testScaleAdd() {
+ final Vector3 vec1 = new Vector3(1, 1, 1);
+ final Vector3 vec2 = vec1.scaleAdd(2.0, new Vector3(1, 2, 3), null);
+ final Vector3 vec2B = vec1.scaleAdd(2.0, new Vector3(1, 2, 3), new Vector3());
+ assertEquals(new Vector3(3.0, 4.0, 5.0), vec2);
+ assertEquals(new Vector3(3.0, 4.0, 5.0), vec2B);
+
+ vec1.scaleAddLocal(2.0, new Vector3(1, 2, 3));
+ assertEquals(vec2, vec1);
+ }
+
+ @Test
+ public void testNegate() {
+ final Vector3 vec1 = new Vector3(3, 2, -1);
+ final Vector3 vec2 = vec1.negate(null);
+ assertEquals(new Vector3(-3, -2, 1), vec2);
+
+ vec1.negateLocal();
+ assertEquals(vec2, vec1);
+ }
+
+ @Test
+ public void testNormalize() {
+ final Vector3 vec1 = new Vector3(2, 1, 3);
+ assertTrue(vec1.length() == Math.sqrt(14));
+
+ final Vector3 vec2 = vec1.normalize(null);
+ final double invLength = MathUtils.inverseSqrt(2 * 2 + 1 * 1 + 3 * 3);
+ assertEquals(new Vector3(2 * invLength, 1 * invLength, 3 * invLength), vec2);
+
+ vec1.normalizeLocal();
+ assertEquals(new Vector3(2 * invLength, 1 * invLength, 3 * invLength), vec1);
+
+ vec1.zero();
+ vec1.normalize(vec2);
+ assertEquals(vec1, vec2);
+
+ // ensure no exception thrown
+ vec1.normalizeLocal();
+ vec1.normalize(null);
+ }
+
+ @Test
+ public void testDistance() {
+ final Vector3 vec1 = new Vector3(0, 0, 0);
+ assertTrue(4.0 == vec1.distance(4, 0, 0));
+ assertTrue(3.0 == vec1.distance(0, 3, 0));
+ assertTrue(2.0 == vec1.distance(0, 0, 2));
+
+ final Vector3 vec2 = new Vector3(1, 1, 1);
+ assertTrue(Math.sqrt(3) == vec1.distance(vec2));
+ }
+
+ @Test
+ public void testLerp() {
+ final Vector3 vec1 = new Vector3(8, 3, -2);
+ final Vector3 vec2 = new Vector3(2, 1, 0);
+ assertEquals(new Vector3(5, 2, -1), vec1.lerp(vec2, 0.5, null));
+ assertEquals(new Vector3(5, 2, -1), vec1.lerp(vec2, 0.5, new Vector3()));
+ assertEquals(new Vector3(5, 2, -1), Vector3.lerp(vec1, vec2, 0.5, null));
+ assertEquals(new Vector3(5, 2, -1), Vector3.lerp(vec1, vec2, 0.5, new Vector3()));
+
+ vec1.set(14, 5, 4);
+ vec1.lerpLocal(vec2, 0.25);
+ assertEquals(new Vector3(11, 4, 3), vec1);
+
+ vec1.set(15, 7, 6);
+ final Vector3 vec3 = new Vector3(-1, -1, -1);
+ vec3.lerpLocal(vec1, vec2, 0.5);
+ assertEquals(new Vector3(8.5, 4.0, 3.0), vec3);
+
+ // coverage
+ assertEquals(vec1.lerp(vec1, .25, null), vec1);
+ assertEquals(vec2.lerpLocal(vec2, .25), vec2);
+ assertEquals(vec2.lerpLocal(vec2, vec2, .25), vec2);
+ assertEquals(Vector3.lerp(vec1, vec1, .25, null), vec1);
+ }
+
+ @Test
+ public void testCross() {
+ final Vector3 vec1 = new Vector3(1, 0, 0);
+ final Vector3 vec2 = new Vector3(0, 1, 0);
+ assertEquals(Vector3.UNIT_Z, vec1.cross(vec2, null));
+ assertEquals(Vector3.UNIT_Z, vec1.cross(vec2, new Vector3()));
+
+ assertEquals(Vector3.UNIT_Z, vec1.cross(0, 1, 0, null));
+ assertEquals(Vector3.UNIT_Z, vec1.cross(0, 1, 0, new Vector3()));
+
+ vec1.crossLocal(vec2);
+ assertEquals(Vector3.UNIT_Z, vec1);
+ vec2.crossLocal(1, 0, 0);
+ assertEquals(Vector3.NEG_UNIT_Z, vec2);
+ }
+
+ @Test
+ public void testAngle() {
+ final Vector3 vec1 = new Vector3(1, 0, 0);
+
+ assertTrue(MathUtils.HALF_PI == vec1.smallestAngleBetween(new Vector3(0, -1, 0)));
+ }
+
+ @Test
+ public void testDot() {
+ final Vector3 vec1 = new Vector3(7, 2, 5);
+ assertTrue(33.0 == vec1.dot(3, 1, 2));
+
+ assertTrue(-10.0 == vec1.dot(new Vector3(-1, 1, -1)));
+ }
+
+ @Test
+ public void testClone() {
+ final Vector3 vec1 = new Vector3(0, 0, 0);
+ final Vector3 vec2 = vec1.clone();
+ assertEquals(vec1, vec2);
+ assertNotSame(vec1, vec2);
+ }
+
+ @Test
+ public void testValid() {
+ final Vector3 vec1 = new Vector3(0, 0, 0);
+ final Vector3 vec2A = new Vector3(Double.POSITIVE_INFINITY, 0, 0);
+ final Vector3 vec2B = new Vector3(0, Double.NEGATIVE_INFINITY, 0);
+ final Vector3 vec2C = new Vector3(0, 0, Double.POSITIVE_INFINITY);
+ final Vector3 vec3A = new Vector3(Double.NaN, 0, 0);
+ final Vector3 vec3B = new Vector3(0, Double.NaN, 0);
+ final Vector3 vec3C = new Vector3(0, 0, Double.NaN);
+
+ assertTrue(Vector3.isValid(vec1));
+ assertFalse(Vector3.isValid(vec2A));
+ assertFalse(Vector3.isValid(vec2B));
+ assertFalse(Vector3.isValid(vec2C));
+ assertFalse(Vector3.isValid(vec3A));
+ assertFalse(Vector3.isValid(vec3B));
+ assertFalse(Vector3.isValid(vec3C));
+
+ assertFalse(Vector3.isInfinite(vec1));
+ assertTrue(Vector3.isInfinite(vec2A));
+
+ vec3C.zero();
+ assertTrue(Vector3.isValid(vec3C));
+
+ assertFalse(Vector3.isValid(null));
+ assertFalse(Vector3.isInfinite(null));
+
+ // couple of equals validity tests
+ assertEquals(vec1, vec1);
+ assertFalse(vec1.equals(null));
+ assertFalse(vec1.equals(new Vector4()));
+
+ // throw in a couple pool accesses for coverage
+ final Vector3 vec6 = Vector3.fetchTempInstance();
+ vec6.set(vec1);
+ assertEquals(vec1, vec6);
+ assertNotSame(vec1, vec6);
+ Vector3.releaseTempInstance(vec6);
+
+ // cover more of equals
+ vec1.set(0, 1, 2);
+ assertFalse(vec1.equals(new Vector3(0, 2, 3)));
+ assertFalse(vec1.equals(new Vector3(0, 1, 3)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Vector3 vec1 = new Vector3(1, 2, 3);
+ final Vector3 vec2 = new Vector3(1, 2, 3);
+ final Vector3 vec3 = new Vector3(2, 2, 2);
+
+ assertTrue(vec1.hashCode() == vec2.hashCode());
+ assertTrue(vec1.hashCode() != vec3.hashCode());
+ }
+}
diff --git a/ardor3d-math/src/test/java/com/ardor3d/math/TestVector4.java b/ardor3d-math/src/test/java/com/ardor3d/math/TestVector4.java new file mode 100644 index 0000000..21c1b42 --- /dev/null +++ b/ardor3d-math/src/test/java/com/ardor3d/math/TestVector4.java @@ -0,0 +1,377 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.math;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestVector4 {
+
+ @Test
+ public void testAdd() {
+ final Vector4 vec1 = new Vector4();
+ final Vector4 vec2 = new Vector4(Vector4.ONE);
+
+ vec1.addLocal(1, 2, 3, 4);
+ assertEquals(new Vector4(1, 2, 3, 4), vec1);
+ vec1.addLocal(-1, -2, -3, -4);
+ assertEquals(Vector4.ZERO, vec1);
+
+ vec1.zero();
+ vec1.addLocal(vec2);
+ assertEquals(Vector4.ONE, vec1);
+
+ vec1.zero();
+ final Vector4 vec3 = vec1.add(vec2, new Vector4());
+ assertEquals(Vector4.ZERO, vec1);
+ assertEquals(Vector4.ONE, vec3);
+
+ final Vector4 vec4 = vec1.add(0, 0, 0, 1, null);
+ assertEquals(Vector4.ZERO, vec1);
+ assertEquals(Vector4.UNIT_W, vec4);
+ }
+
+ @Test
+ public void testSubtract() {
+ final Vector4 vec1 = new Vector4();
+ final Vector4 vec2 = new Vector4(Vector4.ONE);
+
+ vec1.subtractLocal(1, 2, 3, 4);
+ assertEquals(new Vector4(-1, -2, -3, -4), vec1);
+ vec1.subtractLocal(-1, -2, -3, -4);
+ assertEquals(Vector4.ZERO, vec1);
+
+ vec1.zero();
+ vec1.subtractLocal(vec2);
+ assertEquals(Vector4.NEG_ONE, vec1);
+
+ vec1.zero();
+ final Vector4 vec3 = vec1.subtract(vec2, new Vector4());
+ assertEquals(Vector4.ZERO, vec1);
+ assertEquals(Vector4.NEG_ONE, vec3);
+
+ final Vector4 vec4 = vec1.subtract(0, 0, 0, 1, null);
+ assertEquals(Vector4.ZERO, vec1);
+ assertEquals(Vector4.NEG_UNIT_W, vec4);
+ }
+
+ @Test
+ public void testGetSet() {
+ final Vector4 vec1 = new Vector4();
+ vec1.setX(0);
+ assertTrue(vec1.getX() == 0.0);
+ vec1.setX(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getX() == Double.POSITIVE_INFINITY);
+ vec1.setX(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getX() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(0) == Double.NEGATIVE_INFINITY);
+
+ vec1.setY(0);
+ assertTrue(vec1.getY() == 0.0);
+ vec1.setY(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getY() == Double.POSITIVE_INFINITY);
+ vec1.setY(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getY() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(1) == Double.NEGATIVE_INFINITY);
+
+ vec1.setZ(0);
+ assertTrue(vec1.getZ() == 0.0);
+ vec1.setZ(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getZ() == Double.POSITIVE_INFINITY);
+ vec1.setZ(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getZ() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(2) == Double.NEGATIVE_INFINITY);
+
+ vec1.setW(0);
+ assertTrue(vec1.getW() == 0.0);
+ vec1.setW(Double.POSITIVE_INFINITY);
+ assertTrue(vec1.getW() == Double.POSITIVE_INFINITY);
+ vec1.setW(Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getW() == Double.NEGATIVE_INFINITY);
+ assertTrue(vec1.getValue(3) == Double.NEGATIVE_INFINITY);
+
+ vec1.set(Math.PI, Math.PI, Math.PI, Math.PI);
+ assertTrue(vec1.getXf() == (float) Math.PI);
+ assertTrue(vec1.getYf() == (float) Math.PI);
+ assertTrue(vec1.getZf() == (float) Math.PI);
+ assertTrue(vec1.getWf() == (float) Math.PI);
+
+ final Vector4 vec2 = new Vector4();
+ vec2.set(vec1);
+ assertEquals(vec1, vec2);
+
+ vec1.setValue(0, 0);
+ vec1.setValue(1, 0);
+ vec1.setValue(2, 0);
+ vec1.setValue(3, 0);
+ assertEquals(Vector4.ZERO, vec1);
+
+ // catch a few expected exceptions
+ try {
+ vec2.getValue(4);
+ fail("getValue(4) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.getValue(-1);
+ fail("getValue(-1) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.setValue(-1, 0);
+ fail("setValue(-1, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ try {
+ vec2.setValue(4, 0);
+ fail("setValue(4, 0) should have thrown IllegalArgumentException.");
+ } catch (final IllegalArgumentException e) {
+ }
+ // above exceptions shouldn't have altered vec2
+ assertEquals(new Vector4(Math.PI, Math.PI, Math.PI, Math.PI), vec2);
+ }
+
+ @Test
+ public void testToArray() {
+ final Vector4 vec1 = new Vector4();
+ vec1.set(Math.PI, Double.MAX_VALUE, 42, -1);
+ final double[] array = vec1.toArray(null);
+ final double[] array2 = vec1.toArray(new double[4]);
+ assertNotNull(array);
+ assertTrue(array.length == 4);
+ assertTrue(array[0] == Math.PI);
+ assertTrue(array[1] == Double.MAX_VALUE);
+ assertTrue(array[2] == 42);
+ assertTrue(array[3] == -1);
+ assertNotNull(array2);
+ assertTrue(array2.length == 4);
+ assertTrue(array2[0] == Math.PI);
+ assertTrue(array2[1] == Double.MAX_VALUE);
+ assertTrue(array2[2] == 42);
+ assertTrue(array2[3] == -1);
+
+ try {
+ vec1.toArray(new double[1]);
+ fail("toArray(d[1]) should have thrown ArrayIndexOutOfBoundsException.");
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testMultiply() {
+ final Vector4 vec1 = new Vector4(1, -1, 2, -2);
+ final Vector4 vec2 = vec1.multiply(2.0, null);
+ final Vector4 vec2B = vec1.multiply(2.0, new Vector4());
+ assertEquals(new Vector4(2.0, -2.0, 4.0, -4.0), vec2);
+ assertEquals(new Vector4(2.0, -2.0, 4.0, -4.0), vec2B);
+
+ vec2.multiplyLocal(0.5);
+ assertEquals(new Vector4(1.0, -1.0, 2.0, -2.0), vec2);
+
+ final Vector4 vec3 = vec1.multiply(vec2, null);
+ final Vector4 vec3B = vec1.multiply(vec2, new Vector4());
+ assertEquals(new Vector4(1, 1, 4, 4), vec3);
+ assertEquals(new Vector4(1, 1, 4, 4), vec3B);
+
+ final Vector4 vec4 = vec1.multiply(2, 3, 2, 3, null);
+ final Vector4 vec4B = vec1.multiply(2, 3, 2, 3, new Vector4());
+ assertEquals(new Vector4(2, -3, 4, -6), vec4);
+ assertEquals(new Vector4(2, -3, 4, -6), vec4B);
+
+ vec1.multiplyLocal(0.5, 0.5, 0.5, 0.5);
+ assertEquals(new Vector4(0.5, -0.5, 1.0, -1.0), vec1);
+
+ vec1.multiplyLocal(vec2);
+ assertEquals(new Vector4(0.5, 0.5, 2.0, 2.0), vec1);
+ }
+
+ @Test
+ public void testDivide() {
+ final Vector4 vec1 = new Vector4(1, -1, 2, -2);
+ final Vector4 vec2 = vec1.divide(2.0, null);
+ final Vector4 vec2B = vec1.divide(2.0, new Vector4());
+ assertEquals(new Vector4(0.5, -0.5, 1.0, -1.0), vec2);
+ assertEquals(new Vector4(0.5, -0.5, 1.0, -1.0), vec2B);
+
+ vec2.divideLocal(0.5);
+ assertEquals(new Vector4(1.0, -1.0, 2.0, -2.0), vec2);
+
+ final Vector4 vec3 = vec1.divide(vec2, null);
+ final Vector4 vec3B = vec1.divide(vec2, new Vector4());
+ assertEquals(Vector4.ONE, vec3);
+ assertEquals(Vector4.ONE, vec3B);
+
+ final Vector4 vec4 = vec1.divide(2, 3, 4, 5, null);
+ final Vector4 vec4B = vec1.divide(2, 3, 4, 5, new Vector4());
+ assertEquals(new Vector4(0.5, -1 / 3., 0.5, -0.4), vec4);
+ assertEquals(new Vector4(0.5, -1 / 3., 0.5, -0.4), vec4B);
+
+ vec1.divideLocal(0.5, 0.5, 0.5, 0.5);
+ assertEquals(new Vector4(2, -2, 4, -4), vec1);
+
+ vec1.divideLocal(vec2);
+ assertEquals(new Vector4(2, 2, 2, 2), vec1);
+ }
+
+ @Test
+ public void testScaleAdd() {
+ final Vector4 vec1 = new Vector4(1, 1, 1, 1);
+ final Vector4 vec2 = vec1.scaleAdd(2.0, new Vector4(1, 2, 3, 4), null);
+ final Vector4 vec2B = vec1.scaleAdd(2.0, new Vector4(1, 2, 3, 4), new Vector4());
+ assertEquals(new Vector4(3.0, 4.0, 5.0, 6.0), vec2);
+ assertEquals(new Vector4(3.0, 4.0, 5.0, 6.0), vec2B);
+
+ vec1.scaleAddLocal(2.0, new Vector4(1, 2, 3, 4));
+ assertEquals(vec2, vec1);
+ }
+
+ @Test
+ public void testNegate() {
+ final Vector4 vec1 = new Vector4(3, 2, -1, 1);
+ final Vector4 vec2 = vec1.negate(null);
+ assertEquals(new Vector4(-3, -2, 1, -1), vec2);
+
+ vec1.negateLocal();
+ assertEquals(vec2, vec1);
+ }
+
+ @Test
+ public void testNormalize() {
+ final Vector4 vec1 = new Vector4(2, 1, 3, -1);
+ assertTrue(vec1.length() == Math.sqrt(15));
+
+ final Vector4 vec2 = vec1.normalize(null);
+ final double invLength = MathUtils.inverseSqrt(2 * 2 + 1 * 1 + 3 * 3 + -1 * -1);
+ assertEquals(new Vector4(2 * invLength, 1 * invLength, 3 * invLength, -1 * invLength), vec2);
+
+ vec1.normalizeLocal();
+ assertEquals(new Vector4(2 * invLength, 1 * invLength, 3 * invLength, -1 * invLength), vec1);
+
+ vec1.zero();
+ vec1.normalize(vec2);
+ assertEquals(vec1, vec2);
+
+ // ensure no exception thrown
+ vec1.normalizeLocal();
+ vec1.normalize(null);
+ }
+
+ @Test
+ public void testDistance() {
+ final Vector4 vec1 = new Vector4(0, 0, 0, 0);
+ assertTrue(4.0 == vec1.distance(4, 0, 0, 0));
+ assertTrue(3.0 == vec1.distance(0, 3, 0, 0));
+ assertTrue(2.0 == vec1.distance(0, 0, 2, 0));
+ assertTrue(1.0 == vec1.distance(0, 0, 0, 1));
+
+ final Vector4 vec2 = new Vector4(1, 1, 1, 1);
+ assertTrue(Math.sqrt(4) == vec1.distance(vec2));
+ }
+
+ @Test
+ public void testLerp() {
+ final Vector4 vec1 = new Vector4(8, 3, -2, 2);
+ final Vector4 vec2 = new Vector4(2, 1, 0, -2);
+ assertEquals(new Vector4(5, 2, -1, 0), vec1.lerp(vec2, 0.5, null));
+ assertEquals(new Vector4(5, 2, -1, 0), vec1.lerp(vec2, 0.5, new Vector4()));
+ assertEquals(new Vector4(5, 2, -1, 0), Vector4.lerp(vec1, vec2, 0.5, null));
+ assertEquals(new Vector4(5, 2, -1, 0), Vector4.lerp(vec1, vec2, 0.5, new Vector4()));
+
+ vec1.set(14, 5, 4, 2);
+ vec1.lerpLocal(vec2, 0.25);
+ assertEquals(new Vector4(11, 4, 3, 1), vec1);
+
+ vec1.set(15, 7, 6, 8);
+ final Vector4 vec3 = new Vector4(-1, -1, -1, -1);
+ vec3.lerpLocal(vec1, vec2, 0.5);
+ assertEquals(new Vector4(8.5, 4.0, 3.0, 3.0), vec3);
+
+ // coverage
+ assertEquals(vec1.lerp(vec1, .25, null), vec1);
+ assertEquals(vec2.lerpLocal(vec2, .25), vec2);
+ assertEquals(vec2.lerpLocal(vec2, vec2, .25), vec2);
+ assertEquals(Vector4.lerp(vec1, vec1, .25, null), vec1);
+ }
+
+ @Test
+ public void testDot() {
+ final Vector4 vec1 = new Vector4(7, 2, 5, -1);
+ assertTrue(35.0 == vec1.dot(3, 1, 2, -2));
+
+ assertTrue(-11.0 == vec1.dot(new Vector4(-1, 1, -1, 1)));
+ }
+
+ @Test
+ public void testClone() {
+ final Vector4 vec1 = new Vector4(0, 0, 0, 0);
+ final Vector4 vec2 = vec1.clone();
+ assertEquals(vec1, vec2);
+ assertNotSame(vec1, vec2);
+ }
+
+ @Test
+ public void testValid() {
+ final Vector4 vec1 = new Vector4(0, 0, 0, 0);
+ final Vector4 vec2A = new Vector4(Double.POSITIVE_INFINITY, 0, 0, 0);
+ final Vector4 vec2B = new Vector4(0, Double.NEGATIVE_INFINITY, 0, 0);
+ final Vector4 vec2C = new Vector4(0, 0, Double.POSITIVE_INFINITY, 0);
+ final Vector4 vec2D = new Vector4(0, 0, 0, Double.POSITIVE_INFINITY);
+ final Vector4 vec3A = new Vector4(Double.NaN, 0, 0, 0);
+ final Vector4 vec3B = new Vector4(0, Double.NaN, 0, 0);
+ final Vector4 vec3C = new Vector4(0, 0, Double.NaN, 0);
+ final Vector4 vec3D = new Vector4(0, 0, 0, Double.NaN);
+
+ assertTrue(Vector4.isValid(vec1));
+ assertFalse(Vector4.isValid(vec2A));
+ assertFalse(Vector4.isValid(vec2B));
+ assertFalse(Vector4.isValid(vec2C));
+ assertFalse(Vector4.isValid(vec2D));
+ assertFalse(Vector4.isValid(vec3A));
+ assertFalse(Vector4.isValid(vec3B));
+ assertFalse(Vector4.isValid(vec3C));
+ assertFalse(Vector4.isValid(vec3D));
+
+ vec3C.zero();
+ assertTrue(Vector4.isValid(vec3C));
+
+ assertFalse(Vector4.isValid(null));
+
+ // couple of equals validity tests
+ assertEquals(vec1, vec1);
+ assertFalse(vec1.equals(null));
+ assertFalse(vec1.equals(new Vector2()));
+
+ // throw in a couple pool accesses for coverage
+ final Vector4 vec6 = Vector4.fetchTempInstance();
+ vec6.set(vec1);
+ assertEquals(vec1, vec6);
+ assertNotSame(vec1, vec6);
+ Vector4.releaseTempInstance(vec6);
+
+ // cover more of equals
+ vec1.set(0, 1, 2, 3);
+ assertFalse(vec1.equals(new Vector4(0, 4, 4, 4)));
+ assertFalse(vec1.equals(new Vector4(0, 1, 4, 4)));
+ assertFalse(vec1.equals(new Vector4(0, 1, 2, 4)));
+ }
+
+ @Test
+ public void testSimpleHash() {
+ // Just a simple sanity check.
+ final Vector4 vec1 = new Vector4(1, 2, 3, 4);
+ final Vector4 vec2 = new Vector4(1, 2, 3, 4);
+ final Vector4 vec3 = new Vector4(2, 2, 2, 2);
+
+ assertTrue(vec1.hashCode() == vec2.hashCode());
+ assertTrue(vec1.hashCode() != vec3.hashCode());
+ }
+}
|