aboutsummaryrefslogtreecommitdiffstats
path: root/ardor3d-math
diff options
context:
space:
mode:
authorneothemachine <[email protected]>2012-12-05 19:19:31 +0100
committerneothemachine <[email protected]>2012-12-05 19:19:31 +0100
commit7ca002eb1d1510767cd524a800710cf3898ba699 (patch)
tree7b3bd1dec9f58e32d083b287f44569526761bb32 /ardor3d-math
parent105f890b5e750aaa5ec63e768e77eca2af8631a4 (diff)
parent9dd02f103042cb8a196f8a3ed2278da443e345bf (diff)
Merge branch 'kill_trunk' into dependencies
Diffstat (limited to 'ardor3d-math')
-rw-r--r--ardor3d-math/.settings/org.eclipse.jdt.core.prefs291
-rw-r--r--ardor3d-math/.settings/org.eclipse.jdt.ui.prefs115
-rw-r--r--ardor3d-math/pom.xml47
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/ColorRGBA.java1040
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/FastMath.java93
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/InvalidTransformException.java23
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Line3.java171
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Line3Base.java130
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/LineSegment3.java334
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/MathConstants.java45
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/MathUtils.java570
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Matrix3.java1959
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Matrix4.java2440
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/ObjectPool.java67
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Plane.java338
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Poolable.java13
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Quaternion.java1560
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Ray3.java395
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Rectangle2.java295
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Rectangle3.java306
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Ring.java380
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Transform.java1061
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/TransformException.java23
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Triangle.java417
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/ValidatingTransform.java147
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Vector2.java1024
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Vector3.java1135
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/Vector4.java1090
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/ArchimedeanSpiralFunction3D.java83
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/BrickGridFunction3D.java113
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/CheckerFunction3D.java27
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/CloudsFunction3D.java32
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/CylinderFunction3D.java59
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/FbmFunction3D.java117
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/Function3D.java29
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/Functions.java241
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/GridPatternFunction3D.java80
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/HexGridFunction3D.java89
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/MandelbrotFunction3D.java54
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/MapperFunction3D.java145
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/MeshFunction3D.java45
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/RidgeFunction3D.java145
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/SimplexNoise.java423
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/TurbulenceFunction3D.java90
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/functions/VoroniFunction3D.java189
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyColorRGBA.java56
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3.java19
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3Base.java23
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLineSegment3.java20
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix3.java90
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix4.java105
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyPlane.java46
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyQuaternion.java79
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRay3.java37
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle2.java41
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle3.java27
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRing.java29
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTransform.java78
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTriangle.java31
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector2.java82
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector3.java84
-rw-r--r--ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector4.java82
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestColorRGBA.java387
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestFastMath.java71
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestLine3.java105
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestLineSegment3.java140
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestMathExceptions.java41
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix3.java808
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix4.java935
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestObjectPool.java34
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestPlane.java136
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestQuaternion.java554
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestRay3.java230
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle2.java120
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle3.java102
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestRing.java113
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestTransform.java424
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestTriangle.java166
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestValidatingTransform.java151
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestVector2.java376
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestVector3.java405
-rw-r--r--ardor3d-math/src/test/java/com/ardor3d/math/TestVector4.java377
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>,&nbsp;<code>y</code>) to polar coordinates
+ * (r,&nbsp;<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());
+ }
+}