From 9dd02f103042cb8a196f8a3ed2278da443e345bf Mon Sep 17 00:00:00 2001 From: neothemachine Date: Wed, 5 Dec 2012 17:03:16 +0100 Subject: move all files from trunk to root folder --- ardor3d-math/.classpath | 9 + ardor3d-math/.project | 17 + .../.settings/org.eclipse.core.resources.prefs | 2 + ardor3d-math/.settings/org.eclipse.jdt.core.prefs | 291 +++ ardor3d-math/.settings/org.eclipse.jdt.ui.prefs | 115 + ardor3d-math/lib/unittests/junit-4.5.jar | Bin 0 -> 198945 bytes ardor3d-math/pom.xml | 42 + .../src/main/java/com/ardor3d/math/ColorRGBA.java | 1040 +++++++++ .../src/main/java/com/ardor3d/math/FastMath.java | 93 + .../ardor3d/math/InvalidTransformException.java | 23 + .../src/main/java/com/ardor3d/math/Line3.java | 171 ++ .../src/main/java/com/ardor3d/math/Line3Base.java | 130 ++ .../main/java/com/ardor3d/math/LineSegment3.java | 334 +++ .../main/java/com/ardor3d/math/MathConstants.java | 45 + .../src/main/java/com/ardor3d/math/MathUtils.java | 570 +++++ .../src/main/java/com/ardor3d/math/Matrix3.java | 1959 ++++++++++++++++ .../src/main/java/com/ardor3d/math/Matrix4.java | 2440 ++++++++++++++++++++ .../src/main/java/com/ardor3d/math/ObjectPool.java | 67 + .../src/main/java/com/ardor3d/math/Plane.java | 338 +++ .../src/main/java/com/ardor3d/math/Poolable.java | 13 + .../src/main/java/com/ardor3d/math/Quaternion.java | 1560 +++++++++++++ .../src/main/java/com/ardor3d/math/Ray3.java | 395 ++++ .../src/main/java/com/ardor3d/math/Rectangle2.java | 295 +++ .../src/main/java/com/ardor3d/math/Rectangle3.java | 306 +++ .../src/main/java/com/ardor3d/math/Ring.java | 380 +++ .../src/main/java/com/ardor3d/math/Transform.java | 1061 +++++++++ .../java/com/ardor3d/math/TransformException.java | 23 + .../src/main/java/com/ardor3d/math/Triangle.java | 417 ++++ .../java/com/ardor3d/math/ValidatingTransform.java | 147 ++ .../src/main/java/com/ardor3d/math/Vector2.java | 1024 ++++++++ .../src/main/java/com/ardor3d/math/Vector3.java | 1135 +++++++++ .../src/main/java/com/ardor3d/math/Vector4.java | 1090 +++++++++ .../functions/ArchimedeanSpiralFunction3D.java | 83 + .../math/functions/BrickGridFunction3D.java | 113 + .../ardor3d/math/functions/CheckerFunction3D.java | 27 + .../ardor3d/math/functions/CloudsFunction3D.java | 32 + .../ardor3d/math/functions/CylinderFunction3D.java | 59 + .../com/ardor3d/math/functions/FbmFunction3D.java | 117 + .../com/ardor3d/math/functions/Function3D.java | 29 + .../java/com/ardor3d/math/functions/Functions.java | 241 ++ .../math/functions/GridPatternFunction3D.java | 80 + .../ardor3d/math/functions/HexGridFunction3D.java | 89 + .../math/functions/MandelbrotFunction3D.java | 54 + .../ardor3d/math/functions/MapperFunction3D.java | 145 ++ .../com/ardor3d/math/functions/MeshFunction3D.java | 45 + .../ardor3d/math/functions/RidgeFunction3D.java | 145 ++ .../com/ardor3d/math/functions/SimplexNoise.java | 423 ++++ .../math/functions/TurbulenceFunction3D.java | 90 + .../ardor3d/math/functions/VoroniFunction3D.java | 189 ++ .../com/ardor3d/math/type/ReadOnlyColorRGBA.java | 56 + .../java/com/ardor3d/math/type/ReadOnlyLine3.java | 19 + .../com/ardor3d/math/type/ReadOnlyLine3Base.java | 23 + .../ardor3d/math/type/ReadOnlyLineSegment3.java | 20 + .../com/ardor3d/math/type/ReadOnlyMatrix3.java | 90 + .../com/ardor3d/math/type/ReadOnlyMatrix4.java | 105 + .../java/com/ardor3d/math/type/ReadOnlyPlane.java | 46 + .../com/ardor3d/math/type/ReadOnlyQuaternion.java | 79 + .../java/com/ardor3d/math/type/ReadOnlyRay3.java | 37 + .../com/ardor3d/math/type/ReadOnlyRectangle2.java | 41 + .../com/ardor3d/math/type/ReadOnlyRectangle3.java | 27 + .../java/com/ardor3d/math/type/ReadOnlyRing.java | 29 + .../com/ardor3d/math/type/ReadOnlyTransform.java | 78 + .../com/ardor3d/math/type/ReadOnlyTriangle.java | 31 + .../com/ardor3d/math/type/ReadOnlyVector2.java | 82 + .../com/ardor3d/math/type/ReadOnlyVector3.java | 84 + .../com/ardor3d/math/type/ReadOnlyVector4.java | 82 + .../test/java/com/ardor3d/math/TestColorRGBA.java | 387 ++++ .../test/java/com/ardor3d/math/TestFastMath.java | 71 + .../src/test/java/com/ardor3d/math/TestLine3.java | 105 + .../java/com/ardor3d/math/TestLineSegment3.java | 140 ++ .../java/com/ardor3d/math/TestMathExceptions.java | 41 + .../test/java/com/ardor3d/math/TestMatrix3.java | 808 +++++++ .../test/java/com/ardor3d/math/TestMatrix4.java | 935 ++++++++ .../test/java/com/ardor3d/math/TestObjectPool.java | 34 + .../src/test/java/com/ardor3d/math/TestPlane.java | 136 ++ .../test/java/com/ardor3d/math/TestQuaternion.java | 554 +++++ .../src/test/java/com/ardor3d/math/TestRay3.java | 230 ++ .../test/java/com/ardor3d/math/TestRectangle2.java | 120 + .../test/java/com/ardor3d/math/TestRectangle3.java | 102 + .../src/test/java/com/ardor3d/math/TestRing.java | 113 + .../test/java/com/ardor3d/math/TestTransform.java | 424 ++++ .../test/java/com/ardor3d/math/TestTriangle.java | 166 ++ .../com/ardor3d/math/TestValidatingTransform.java | 151 ++ .../test/java/com/ardor3d/math/TestVector2.java | 376 +++ .../test/java/com/ardor3d/math/TestVector3.java | 405 ++++ .../test/java/com/ardor3d/math/TestVector4.java | 377 +++ 86 files changed, 24097 insertions(+) create mode 100644 ardor3d-math/.classpath create mode 100644 ardor3d-math/.project create mode 100644 ardor3d-math/.settings/org.eclipse.core.resources.prefs create mode 100644 ardor3d-math/.settings/org.eclipse.jdt.core.prefs create mode 100644 ardor3d-math/.settings/org.eclipse.jdt.ui.prefs create mode 100644 ardor3d-math/lib/unittests/junit-4.5.jar create mode 100644 ardor3d-math/pom.xml create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/ColorRGBA.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/FastMath.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/InvalidTransformException.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Line3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Line3Base.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/LineSegment3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/MathConstants.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/MathUtils.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Matrix3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Matrix4.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/ObjectPool.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Plane.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Poolable.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Quaternion.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Ray3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Rectangle2.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Rectangle3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Ring.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Transform.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/TransformException.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Triangle.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/ValidatingTransform.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Vector2.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Vector3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/Vector4.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/ArchimedeanSpiralFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/BrickGridFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/CheckerFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/CloudsFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/CylinderFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/FbmFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/Function3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/Functions.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/GridPatternFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/HexGridFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/MandelbrotFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/MapperFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/MeshFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/RidgeFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/SimplexNoise.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/TurbulenceFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/functions/VoroniFunction3D.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyColorRGBA.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLine3Base.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyLineSegment3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyMatrix4.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyPlane.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyQuaternion.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRay3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle2.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRectangle3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyRing.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTransform.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyTriangle.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector2.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector3.java create mode 100644 ardor3d-math/src/main/java/com/ardor3d/math/type/ReadOnlyVector4.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestColorRGBA.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestFastMath.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestLine3.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestLineSegment3.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestMathExceptions.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix3.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestMatrix4.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestObjectPool.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestPlane.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestQuaternion.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestRay3.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle2.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestRectangle3.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestRing.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestTransform.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestTriangle.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestValidatingTransform.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestVector2.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestVector3.java create mode 100644 ardor3d-math/src/test/java/com/ardor3d/math/TestVector4.java (limited to 'ardor3d-math') diff --git a/ardor3d-math/.classpath b/ardor3d-math/.classpath new file mode 100644 index 0000000..c45dc2c --- /dev/null +++ b/ardor3d-math/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ardor3d-math/.project b/ardor3d-math/.project new file mode 100644 index 0000000..8eea140 --- /dev/null +++ b/ardor3d-math/.project @@ -0,0 +1,17 @@ + + + ardor3d-math + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/ardor3d-math/.settings/org.eclipse.core.resources.prefs b/ardor3d-math/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..4824b80 --- /dev/null +++ b/ardor3d-math/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 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= +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/lib/unittests/junit-4.5.jar b/ardor3d-math/lib/unittests/junit-4.5.jar new file mode 100644 index 0000000..7339216 Binary files /dev/null and b/ardor3d-math/lib/unittests/junit-4.5.jar differ diff --git a/ardor3d-math/pom.xml b/ardor3d-math/pom.xml new file mode 100644 index 0000000..f21e2e1 --- /dev/null +++ b/ardor3d-math/pom.xml @@ -0,0 +1,42 @@ + + 4.0.0 + + com.ardor3d + ardor3d + 0.9-SNAPSHOT + ../pom.xml + + + ardor3d-math + bundle + Ardor 3D Math + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + ${project.build.sourceEncoding} + + + + + + + ${project.groupId} + ardor3d-savable + ${project.version} + + + org.easymock + easymockclassextension + + + + UTF-8 + + + 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 . + */ + +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 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 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. + *
    + *
  • chars: pattern - notes
  • + *
  • 1: V - RGB is parsed as val/15, A=1
  • + *
  • 2: VA - RGB is parsed as V/15, A as A/15
  • + *
  • 3: RGB - RGB is parsed as R/15, G/15, B/15, A=1
  • + *
  • 4: RGB - RGBA are parsed as R/15, G/15, B/15, A/15
  • + *
  • 6: RRGGBB - RGB is parsed as RR/255, GG/255, BB/255, A=1
  • + *
  • 8: RRGGBBAA - RGBA is parsed as RR/255, GG/255, BB/255, AA/255
  • + *
+ * + * @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 . + */ + +package com.ardor3d.math; + +/** + * A "close approximation" class for Math operations. + * + * References: + *
    + *
  • http://www.devmaster.net/forums/showthread.php?t=5784
  • + *
  • http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
  • + *
  • http://stackoverflow.com/questions/523531/fast-transcendent-trigonometric-functions-for-java
  • + *
  • http://www.lightsoft.co.uk/PD/stu/stuchat37.html
  • + *
  • http://wiki.java.net/bin/view/Games/JeffGems
  • + *
+ * + * 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 . + */ + +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_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 . + */ + +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 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 . + */ + +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 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 . + */ + +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 . + */ + +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 0.0 (inclusive) to 1.0 (exclusive). + */ + public static double nextRandomDouble() { + return MathUtils.rand.nextDouble(); + } + + /** + * Returns a random float between 0 and 1. + * + * @return A random float between 0.0f (inclusive) to 1.0f (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 min (inclusive) to max (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 . + */ + +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 MAT_POOL = ObjectPool.create(Matrix3.class, MathConstants.maxMathPoolSize); + + /** + *
+     * 1, 0, 0
+     * 0, 1, 0
+     * 0, 0, 1
+     * 
+ */ + 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 wikipedia.org-Transpose + */ + @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 wikipedia.org-Transpose + */ + 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 wikipedia.org-Adjugate_matrix + */ + @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 wikipedia.org-Adjugate_matrix + */ + public Matrix3 adjugateLocal() { + return adjugate(this); + } + + /** + * @return the determinate of this 3x3 matrix (aei+bfg+cdh-ceg-bdi-afh) + * @see wikipedia.org-Determinant + */ + @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 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 . + */ + +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 MAT_POOL = ObjectPool.create(Matrix4.class, MathConstants.maxMathPoolSize); + + /** + *
+     * 1, 0, 0, 0
+     * 0, 1, 0, 0
+     * 0, 0, 1, 0
+     * 0, 0, 0, 1
+     * 
+ */ + 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 wikipedia.org-Transpose + */ + @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 wikipedia.org-Transpose + */ + 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 wikipedia.org-Adjugate_matrix + */ + @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 wikipedia.org-Adjugate_matrix + */ + public Matrix4 adjugateLocal() { + return adjugate(this); + } + + /** + * @return the determinate of this matrix + * @see wikipedia.org-Determinant + */ + @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 wikipedia.org-Orthogonal matrix + */ + 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 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 . + */ + +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 + * the type. + */ +public abstract class ObjectPool { + private final ThreadLocal> _pool = new ThreadLocal>() { + @Override + protected List initialValue() { + return new ArrayList(_maxSize); + } + }; + + private final int _maxSize; + + protected ObjectPool(final int maxSize) { + _maxSize = maxSize; + } + + protected abstract T newInstance(); + + public final T fetch() { + final List 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 objects = _pool.get(); + if (objects.size() < _maxSize) { + objects.add(object); + } + } + + public static ObjectPool create(final Class clazz, final int maxSize) { + return new ObjectPool(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 . + */ + +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_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 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 . + */ + +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 . + */ + +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 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 euclideanspace.com-eulerToQuaternion + */ + 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 euclideanspace.com-quaternionToEuler + * @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 multiplicative inverse Q-1 of this quaternion Q such + * that QQ-1 = [0,0,0,1] (the identity quaternion). Note that for unit quaternions, a + * quaternion's inverse is equal to its (far easier to calculate) conjugate. + * + * @param store + * the Quaternion to store the result in. If null, 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 Q to its multiplicative inverse Q-1 such + * that QQ-1 = [0,0,0,1] (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 Quaternion 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 [-x, -y, -z, w] of this quaternion. + * + * @param store + * the Quaternion to store the result in. If null, 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 [-x, -y, -z, w]. + * + * @return this Quaternion 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 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 . + */ + +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 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 . + */ + +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; + +/** + *

+ * 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. + *

+ * 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 RECTANGLE_POOL = ObjectPool.create(Rectangle2.class, + MathConstants.maxMathPoolSize); + + private int _x; + private int _y; + private int _width; + private int _height; + + /** + * Constructor creates a new Rectangle2 with its origin at 0,0 and width/height of 0. + */ + public Rectangle2() {} + + /** + * Constructor creates a new Rectangle2 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 Rectangle2 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 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 . + */ + +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 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 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 . + */ + +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; + +/** + * Ring 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_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 Ring 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 Ring 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; + } + + /** + * getCenter returns the center of the ring. + * + * @return the center of the ring. + */ + @Override + public ReadOnlyVector3 getCenter() { + return _center; + } + + /** + * setCenter sets the center of the ring. + * + * @param center + * the center of the ring. + */ + public void setCenter(final ReadOnlyVector3 center) { + _center.set(center); + } + + /** + * getUp returns the ring's up vector. + * + * @return the ring's up vector. + */ + @Override + public ReadOnlyVector3 getUp() { + return _up; + } + + /** + * setUp sets the ring's up vector. + * + * @param up + * the ring's up vector. + */ + public void setUp(final ReadOnlyVector3 up) { + _up.set(up); + } + + /** + * getInnerRadius returns the ring's inner radius. + * + * @return the ring's inner radius. + */ + @Override + public double getInnerRadius() { + return _innerRadius; + } + + /** + * setInnerRadius sets the ring's inner radius. + * + * @param innerRadius + * the ring's inner radius. + */ + public void setInnerRadius(final double innerRadius) { + _innerRadius = innerRadius; + } + + /** + * getOuterRadius returns the ring's outer radius. + * + * @return the ring's outer radius. + */ + @Override + public double getOuterRadius() { + return _outerRadius; + } + + /** + * setOuterRadius sets the ring's outer radius. + * + * @param outerRadius + * the ring's outer radius. + */ + public void setOuterRadius(final double outerRadius) { + _outerRadius = outerRadius; + } + + /** + * + * random 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 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 . + */ + +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 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: + * + *
+     * R R R Tx
+     * R R R Ty
+     * R R R Tz
+     * 0 0 0 1
+     * 
+ */ + @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 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 . + */ + +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 . + */ + +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 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 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 . + */ + +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 . + */ + +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 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 (xy) to polar coordinates + * (r, theta). + */ + @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 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 . + */ + +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 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 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 . + */ + +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 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 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 . + */ + +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 wikipedia entry + */ +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 wikipedia entry + */ +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +package com.ardor3d.math.functions; + +/** + * A function that returns the famous Mandelbrot set. This is not really 3d... The z factor is ignored. + * + * @see wikipedia entry + */ +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 . + */ + +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 _entries = new ArrayList(); + 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 . + */ + +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 . + */ + +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 . + */ + +package com.ardor3d.math.functions; + +import java.util.BitSet; + +import com.ardor3d.math.MathUtils; + +/** + * Simplex noise in 2D, 3D and 4D + *

+ * 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 + *

+ */ +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) ? 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= 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 . + */ + +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 . + */ + +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 Voronoi graph 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 _points = new HashMap(); + + /** + * 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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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 . + */ + +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()); + } +} -- cgit v1.2.3