diff options
author | Renanse <[email protected]> | 2012-10-13 12:58:16 -0500 |
---|---|---|
committer | Renanse <[email protected]> | 2012-10-13 12:58:16 -0500 |
commit | e60afe7110d0d43cdc41f44aa631de19f312ec54 (patch) | |
tree | 99d94490c346b06186ebfbba79500a918b1f9ca1 /trunk/ardor3d-core | |
parent | 555475f5aa57fb5feffefe30fa9a088341cd6a92 (diff) |
moved to github
Diffstat (limited to 'trunk/ardor3d-core')
386 files changed, 69267 insertions, 0 deletions
diff --git a/trunk/ardor3d-core/.classpath b/trunk/ardor3d-core/.classpath new file mode 100644 index 0000000..b30d97b --- /dev/null +++ b/trunk/ardor3d-core/.classpath @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/test/java"/>
+ <classpathentry combineaccessrules="false" exported="true" kind="src" path="/ardor3d-savable"/>
+ <classpathentry combineaccessrules="false" exported="true" kind="src" path="/ardor3d-math"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/cglib-nodep-2.2.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/easymock.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/easymockclassextension.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/junit-4.5.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/google/guava-13.0.1.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/trunk/ardor3d-core/.project b/trunk/ardor3d-core/.project new file mode 100644 index 0000000..d426d9d --- /dev/null +++ b/trunk/ardor3d-core/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>ardor3d-core</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs b/trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..82a25eb --- /dev/null +++ b/trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Tue Jan 06 12:48:37 PST 2009 +eclipse.preferences.version=1 +encoding/src=UTF-8 diff --git a/trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs b/trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..10a0c88 --- /dev/null +++ b/trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,278 @@ +#Tue Apr 06 11:24:38 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.codeComplete.argumentPrefixes= +org.eclipse.jdt.core.codeComplete.argumentSuffixes= +org.eclipse.jdt.core.codeComplete.fieldPrefixes=_ +org.eclipse.jdt.core.codeComplete.fieldSuffixes= +org.eclipse.jdt.core.codeComplete.localPrefixes= +org.eclipse.jdt.core.codeComplete.localSuffixes= +org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= +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_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_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_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.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.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.format_guardian_clause_on_one_line=false +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_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=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_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_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_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_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_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_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.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_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs b/trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..8110121 --- /dev/null +++ b/trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,114 @@ +#Mon Jan 02 22:40:01 CST 2012 +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_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=11 +org.eclipse.jdt.ui.exception.name=ex +org.eclipse.jdt.ui.gettersetter.use.is=true +org.eclipse.jdt.ui.javadoc=false +org.eclipse.jdt.ui.keywordthis=false +org.eclipse.jdt.ui.overrideannotation=true +org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\n * \n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment"/><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="false" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">/**\n * Copyright (c) 2008-2012 Ardor Labs, Inc.\n *\n * This file is part of Ardor3D.\n *\n * Ardor3D is free software\: you can redistribute it and/or modify it \n * under the terms of its license which may be found in the accompanying\n * LICENSE file or at <http\://www.ardor3d.com/LICENSE>.\n */\n\n${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created JavaScript files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\n * \n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * @author ${user}\n *\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for vars" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Jsdoc)\n * ${see_to_overridden}\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created function stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated function stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates> +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_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=false +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=false +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/trunk/ardor3d-core/buildjar.jardesc b/trunk/ardor3d-core/buildjar.jardesc new file mode 100644 index 0000000..2ea7295 --- /dev/null +++ b/trunk/ardor3d-core/buildjar.jardesc @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
+<jardesc>
+ <jar path="C:/Users/Joshua Slack/Desktop/ardor3d-core.jar"/>
+ <options buildIfNeeded="true" compress="true" descriptionLocation="/ardor3d-core/buildjar.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="true" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
+ <storedRefactorings deprecationInfo="true" structuralOnly="false"/>
+ <selectedProjects/>
+ <manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
+ <sealing sealJar="false">
+ <packagesToSeal/>
+ <packagesToUnSeal/>
+ </sealing>
+ </manifest>
+ <selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false">
+ <javaElement handleIdentifier="=ardor3d-core/src\/main\/java"/>
+ <javaElement handleIdentifier="=ardor3d-core/src\/main\/resources"/>
+ </selectedElements>
+</jardesc>
diff --git a/trunk/ardor3d-core/lib/google/guava-13.0.1.jar b/trunk/ardor3d-core/lib/google/guava-13.0.1.jar Binary files differnew file mode 100644 index 0000000..09c5449 --- /dev/null +++ b/trunk/ardor3d-core/lib/google/guava-13.0.1.jar diff --git a/trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jar b/trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jar Binary files differnew file mode 100644 index 0000000..ed07cb5 --- /dev/null +++ b/trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jar diff --git a/trunk/ardor3d-core/lib/unittests/easymock.jar b/trunk/ardor3d-core/lib/unittests/easymock.jar Binary files differnew file mode 100644 index 0000000..de59168 --- /dev/null +++ b/trunk/ardor3d-core/lib/unittests/easymock.jar diff --git a/trunk/ardor3d-core/lib/unittests/easymockclassextension.jar b/trunk/ardor3d-core/lib/unittests/easymockclassextension.jar Binary files differnew file mode 100644 index 0000000..7d098a7 --- /dev/null +++ b/trunk/ardor3d-core/lib/unittests/easymockclassextension.jar diff --git a/trunk/ardor3d-core/lib/unittests/junit-4.5.jar b/trunk/ardor3d-core/lib/unittests/junit-4.5.jar Binary files differnew file mode 100644 index 0000000..7339216 --- /dev/null +++ b/trunk/ardor3d-core/lib/unittests/junit-4.5.jar diff --git a/trunk/ardor3d-core/pom.xml b/trunk/ardor3d-core/pom.xml new file mode 100644 index 0000000..9edd61b --- /dev/null +++ b/trunk/ardor3d-core/pom.xml @@ -0,0 +1,54 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.ardor3d</groupId> + <artifactId>ardor3d</artifactId> + <version>0.8-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>ardor3d-core</artifactId> + <packaging>bundle</packaging> + <name>Ardor 3D Core</name> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <source>1.6</source> + <target>1.6</target> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>2.6</version> + <configuration> + <encoding>${project.build.sourceEncoding}</encoding> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>ardor3d-math</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymockclassextension</artifactId> + </dependency> + </dependencies> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> +</project> + diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java new file mode 100644 index 0000000..636cc6b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java @@ -0,0 +1,25 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Documents which lock is used to guard a field, if the field is protected by a lock.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.FIELD)
+public @interface GuardedBy {
+ String value();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java new file mode 100644 index 0000000..e83a8ea --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java @@ -0,0 +1,24 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a class is, or at least is intended to be, immutable in a strict sense. See
+ * http://www.javaconcurrencyinpractice.com/ or the actual book for more information about immutability.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface Immutable {}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java new file mode 100644 index 0000000..22efacf --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java @@ -0,0 +1,35 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Methods flagged with this annotation should only be run in the main thread, that is, the thread that handles the
+ * OpenGL calls. It is possible, and good during development but possibly not during production, to use a method
+ * interceptor that enforces this constraint, but in any case, the presence of the annotation should help programmers
+ * when thinking about threading.
+ *
+ * This annotation should be used on any API method in the framework for which it is necessary to call it only from the
+ * main thread. If it adds to clarity, it may be a good idea to use it for internal methods as well.
+ *
+ * Note that this annotation, when present on an interface method, does not get inherited, and it is therefore there
+ * only for clarity purposes rather than as a way of getting method interception to work. It should always be added to
+ * any class directly implementing an interface method that uses it.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target( { ElementType.METHOD, ElementType.PARAMETER })
+@Inherited
+public @interface MainThread {}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java new file mode 100644 index 0000000..03e4c7f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.annotation; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Tells the Savable system to instantiate objects of this type using a specific static method. The method should take + * no arguments and return a new instance of the annotated class. + * + * It is recommended the method be named something indicating use for Savable system. + */ +@Target( { TYPE }) +@Retention(RUNTIME) +public @interface SavableFactory { + + /** + * @return the name of the static method to use to build this class. + */ + String factoryMethod(); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java new file mode 100644 index 0000000..912e005 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java @@ -0,0 +1,23 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a class is, or at least is intended to be, thread safe.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface ThreadSafe {}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java new file mode 100644 index 0000000..d488d81 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java @@ -0,0 +1,874 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.intersection.IntersectionRecord; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyPlane.Side; +import com.ardor3d.math.type.ReadOnlyRay3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>BoundingBox</code> defines an axis-aligned cube that defines a container for a group of vertices of a + * particular piece of geometry. This box defines a center and extents from that center along the x, y and z axis. <br> + * <br> + * A typical usage is to allow the class define the center and radius by calling either <code>containAABB</code> or + * <code>averagePoints</code>. A call to <code>computeFramePoint</code> in turn calls <code>containAABB</code>. + */ +public class BoundingBox extends BoundingVolume { + + private static final long serialVersionUID = 1L; + + private double _xExtent, _yExtent, _zExtent; + + /** + * Default constructor instantiates a new <code>BoundingBox</code> object. + */ + public BoundingBox() {} + + /** + * Constructor instantiates a new <code>BoundingBox</code> object with given values. + */ + public BoundingBox(final BoundingBox other) { + this(other.getCenter(), other.getXExtent(), other.getYExtent(), other.getZExtent()); + } + + /** + * Constructor instantiates a new <code>BoundingBox</code> object with given values. + */ + public BoundingBox(final ReadOnlyVector3 c, final double x, final double y, final double z) { + _center.set(c); + setXExtent(x); + setYExtent(y); + setZExtent(z); + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof BoundingBox)) { + return false; + } + final BoundingBox b = (BoundingBox) other; + return _center.equals(b._center) && _xExtent == b._xExtent && _yExtent == b._yExtent && _zExtent == b._zExtent; + } + + @Override + public Type getType() { + return Type.AABB; + } + + public void setXExtent(final double xExtent) { + _xExtent = xExtent; + } + + public double getXExtent() { + return _xExtent; + } + + public void setYExtent(final double yExtent) { + _yExtent = yExtent; + } + + public double getYExtent() { + return _yExtent; + } + + public void setZExtent(final double zExtent) { + _zExtent = zExtent; + } + + public double getZExtent() { + return _zExtent; + } + + @Override + public double getRadius() { + return MathUtils.sqrt(_xExtent * _xExtent + _yExtent * _yExtent + _zExtent * _zExtent); + } + + // Some transform matrices are not in decomposed form and in this + // situation we need to use a different, more robust, algorithm + // for computing the new bounding box. + @Override + public BoundingVolume transform(final ReadOnlyTransform transform, final BoundingVolume store) { + + if (transform.isRotationMatrix()) { + return transformRotational(transform, store); + } + + BoundingBox box; + if (store == null || store.getType() != Type.AABB) { + box = new BoundingBox(); + } else { + box = (BoundingBox) store; + } + + final Vector3[] corners = new Vector3[8]; + for (int i = 0; i < corners.length; i++) { + corners[i] = Vector3.fetchTempInstance(); + } + getCorners(corners); + + // Transform all of these points by the transform + for (int i = 0; i < corners.length; i++) { + transform.applyForward(corners[i]); + } + // Now compute based on these transformed points + double minX = corners[0].getX(); + double minY = corners[0].getY(); + double minZ = corners[0].getZ(); + double maxX = minX; + double maxY = minY; + double maxZ = minZ; + for (int i = 1; i < corners.length; i++) { + final double curX = corners[i].getX(); + final double curY = corners[i].getY(); + final double curZ = corners[i].getZ(); + minX = Math.min(minX, curX); + minY = Math.min(minY, curY); + minZ = Math.min(minZ, curZ); + maxX = Math.max(maxX, curX); + maxY = Math.max(maxY, curY); + maxZ = Math.max(maxZ, curZ); + } + + final double ctrX = (maxX + minX) * 0.5; + final double ctrY = (maxY + minY) * 0.5; + final double ctrZ = (maxZ + minZ) * 0.5; + + box._center.set(ctrX, ctrY, ctrZ); + box._xExtent = maxX - ctrX; + box._yExtent = maxY - ctrY; + box._zExtent = maxZ - ctrZ; + + for (int i = 0; i < corners.length; i++) { + Vector3.releaseTempInstance(corners[i]); + } + + return box; + } + + public BoundingVolume transformRotational(final ReadOnlyTransform transform, final BoundingVolume store) { + + final ReadOnlyMatrix3 rotate = transform.getMatrix(); + final ReadOnlyVector3 scale = transform.getScale(); + final ReadOnlyVector3 translate = transform.getTranslation(); + + BoundingBox box; + if (store == null || store.getType() != Type.AABB) { + box = new BoundingBox(); + } else { + box = (BoundingBox) store; + } + + _center.multiply(scale, box._center); + rotate.applyPost(box._center, box._center); + box._center.addLocal(translate); + + final Matrix3 transMatrix = Matrix3.fetchTempInstance(); + transMatrix.set(rotate); + // Make the rotation matrix all positive to get the maximum x/y/z extent + transMatrix.setValue(0, 0, Math.abs(transMatrix.getM00())); + transMatrix.setValue(0, 1, Math.abs(transMatrix.getM01())); + transMatrix.setValue(0, 2, Math.abs(transMatrix.getM02())); + transMatrix.setValue(1, 0, Math.abs(transMatrix.getM10())); + transMatrix.setValue(1, 1, Math.abs(transMatrix.getM11())); + transMatrix.setValue(1, 2, Math.abs(transMatrix.getM12())); + transMatrix.setValue(2, 0, Math.abs(transMatrix.getM20())); + transMatrix.setValue(2, 1, Math.abs(transMatrix.getM21())); + transMatrix.setValue(2, 2, Math.abs(transMatrix.getM22())); + + _compVect1.set(getXExtent() * scale.getX(), getYExtent() * scale.getY(), getZExtent() * scale.getZ()); + transMatrix.applyPost(_compVect1, _compVect1); + // Assign the biggest rotations after scales. + box.setXExtent(Math.abs(_compVect1.getX())); + box.setYExtent(Math.abs(_compVect1.getY())); + box.setZExtent(Math.abs(_compVect1.getZ())); + + Matrix3.releaseTempInstance(transMatrix); + + return box; + } + + /** + * <code>computeFromPoints</code> creates a new Bounding Box from a given set of points. It uses the + * <code>containAABB</code> method as default. + * + * @param points + * the points to contain. + */ + @Override + public void computeFromPoints(final FloatBuffer points) { + containAABB(points); + } + + @Override + public void computeFromPrimitives(final MeshData data, final int section, final int[] indices, final int start, + final int end) { + if (end - start <= 0) { + return; + } + + final Vector3 min = _compVect1 + .set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + final Vector3 max = _compVect2 + .set(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + + final int vertsPerPrimitive = data.getIndexMode(section).getVertexCount(); + Vector3[] store = new Vector3[vertsPerPrimitive]; + + for (int i = start; i < end; i++) { + store = data.getPrimitiveVertices(indices[i], section, store); + for (int j = 0; j < store.length; j++) { + checkMinMax(min, max, store[j]); + } + } + + _center.set(min.addLocal(max)); + _center.multiplyLocal(0.5); + + setXExtent(max.getX() - _center.getX()); + setYExtent(max.getY() - _center.getY()); + setZExtent(max.getZ() - _center.getZ()); + } + + private void checkMinMax(final Vector3 min, final Vector3 max, final ReadOnlyVector3 point) { + if (point.getX() < min.getX()) { + min.setX(point.getX()); + } + if (point.getX() > max.getX()) { + max.setX(point.getX()); + } + + if (point.getY() < min.getY()) { + min.setY(point.getY()); + } + if (point.getY() > max.getY()) { + max.setY(point.getY()); + } + + if (point.getZ() < min.getZ()) { + min.setZ(point.getZ()); + } + if (point.getZ() > max.getZ()) { + max.setZ(point.getZ()); + } + } + + /** + * <code>containAABB</code> creates a minimum-volume axis-aligned bounding box of the points, then selects the + * smallest enclosing sphere of the box with the sphere centered at the boxes center. + * + * @param points + * the list of points. + */ + public void containAABB(final FloatBuffer points) { + if (points == null) { + return; + } + + points.rewind(); + if (points.remaining() <= 2) { + return; + } + + BufferUtils.populateFromBuffer(_compVect1, points, 0); + double minX = _compVect1.getX(), minY = _compVect1.getY(), minZ = _compVect1.getZ(); + double maxX = _compVect1.getX(), maxY = _compVect1.getY(), maxZ = _compVect1.getZ(); + + for (int i = 1, len = points.remaining() / 3; i < len; i++) { + BufferUtils.populateFromBuffer(_compVect1, points, i); + + if (_compVect1.getX() < minX) { + minX = _compVect1.getX(); + } else if (_compVect1.getX() > maxX) { + maxX = _compVect1.getX(); + } + + if (_compVect1.getY() < minY) { + minY = _compVect1.getY(); + } else if (_compVect1.getY() > maxY) { + maxY = _compVect1.getY(); + } + + if (_compVect1.getZ() < minZ) { + minZ = _compVect1.getZ(); + } else if (_compVect1.getZ() > maxZ) { + maxZ = _compVect1.getZ(); + } + } + + _center.set(minX + maxX, minY + maxY, minZ + maxZ); + _center.multiplyLocal(0.5); + + setXExtent(maxX - _center.getX()); + setYExtent(maxY - _center.getY()); + setZExtent(maxZ - _center.getZ()); + } + + /** + * <code>whichSide</code> takes a plane (typically provided by a view frustum) to determine which side this bound is + * on. + * + * @param plane + * the plane to check against. + */ + @Override + public Side whichSide(final ReadOnlyPlane plane) { + final ReadOnlyVector3 normal = plane.getNormal(); + final double radius = Math.abs(getXExtent() * normal.getX()) + Math.abs(getYExtent() * normal.getY()) + + Math.abs(getZExtent() * normal.getZ()); + + final double distance = plane.pseudoDistance(_center); + + if (distance < -radius) { + return Plane.Side.Inside; + } else if (distance > radius) { + return Plane.Side.Outside; + } else { + return Plane.Side.Neither; + } + } + + /** + * <code>merge</code> combines this sphere with a second bounding sphere. This new sphere contains both bounding + * spheres and is returned. + * + * @param volume + * the sphere to combine with this sphere. + * @return the new sphere + */ + @Override + public BoundingVolume merge(final BoundingVolume volume) { + if (volume == null) { + return this; + } + + switch (volume.getType()) { + case AABB: { + final BoundingBox vBox = (BoundingBox) volume; + return merge(vBox._center, vBox.getXExtent(), vBox.getYExtent(), vBox.getZExtent(), new BoundingBox( + new Vector3(0, 0, 0), 0, 0, 0)); + } + + case Sphere: { + final BoundingSphere vSphere = (BoundingSphere) volume; + return merge(vSphere._center, vSphere.getRadius(), vSphere.getRadius(), vSphere.getRadius(), + new BoundingBox(new Vector3(0, 0, 0), 0, 0, 0)); + } + + case OBB: { + final OrientedBoundingBox box = (OrientedBoundingBox) volume; + final BoundingBox rVal = (BoundingBox) this.clone(null); + return rVal.mergeOBB(box); + } + + default: + return null; + } + } + + /** + * <code>mergeLocal</code> combines this sphere with a second bounding sphere locally. Altering this sphere to + * contain both the original and the additional sphere volumes; + * + * @param volume + * the sphere to combine with this sphere. + * @return this + */ + @Override + public BoundingVolume mergeLocal(final BoundingVolume volume) { + if (volume == null) { + return this; + } + + switch (volume.getType()) { + case AABB: { + final BoundingBox vBox = (BoundingBox) volume; + return merge(vBox._center, vBox.getXExtent(), vBox.getYExtent(), vBox.getZExtent(), this); + } + + case Sphere: { + final BoundingSphere vSphere = (BoundingSphere) volume; + return merge(vSphere._center, vSphere.getRadius(), vSphere.getRadius(), vSphere.getRadius(), this); + } + + case OBB: { + return mergeOBB((OrientedBoundingBox) volume); + } + + default: + return null; + } + } + + /** + * Merges this AABB with the given OBB. + * + * @param volume + * the OBB to merge this AABB with. + * @return This AABB extended to fit the given OBB. + */ + private BoundingBox mergeOBB(final OrientedBoundingBox volume) { + // check for infinite bounds to prevent NaN values + if (Double.isInfinite(getXExtent()) || Double.isInfinite(getYExtent()) || Double.isInfinite(getZExtent()) + || Vector3.isInfinite(volume.getExtent())) { + setCenter(Vector3.ZERO); + setXExtent(Double.POSITIVE_INFINITY); + setYExtent(Double.POSITIVE_INFINITY); + setZExtent(Double.POSITIVE_INFINITY); + return this; + } + + if (!volume.correctCorners) { + volume.computeCorners(); + } + + double minX, minY, minZ; + double maxX, maxY, maxZ; + + minX = _center.getX() - getXExtent(); + minY = _center.getY() - getYExtent(); + minZ = _center.getZ() - getZExtent(); + + maxX = _center.getX() + getXExtent(); + maxY = _center.getY() + getYExtent(); + maxZ = _center.getZ() + getZExtent(); + + for (int i = 1; i < volume._vectorStore.length; i++) { + final Vector3 temp = volume._vectorStore[i]; + if (temp.getX() < minX) { + minX = temp.getX(); + } else if (temp.getX() > maxX) { + maxX = temp.getX(); + } + + if (temp.getY() < minY) { + minY = temp.getY(); + } else if (temp.getY() > maxY) { + maxY = temp.getY(); + } + + if (temp.getZ() < minZ) { + minZ = temp.getZ(); + } else if (temp.getZ() > maxZ) { + maxZ = temp.getZ(); + } + } + + _center.set(minX + maxX, minY + maxY, minZ + maxZ); + _center.multiplyLocal(0.5); + + setXExtent(maxX - _center.getX()); + setYExtent(maxY - _center.getY()); + setZExtent(maxZ - _center.getZ()); + return this; + } + + /** + * <code>merge</code> combines this bounding box with another box which is defined by the center, x, y, z extents. + * + * @param boxCenter + * the center of the box to merge with + * @param boxX + * the x extent of the box to merge with. + * @param boxY + * the y extent of the box to merge with. + * @param boxZ + * the z extent of the box to merge with. + * @param store + * the box to store our results in. + * @return the resulting merged box. + */ + private BoundingBox merge(final Vector3 boxCenter, final double boxX, final double boxY, final double boxZ, + final BoundingBox store) { + // check for infinite bounds to prevent NaN values + if (Double.isInfinite(getXExtent()) || Double.isInfinite(getYExtent()) || Double.isInfinite(getZExtent()) + || Double.isInfinite(boxX) || Double.isInfinite(boxY) || Double.isInfinite(boxZ)) { + store.setCenter(Vector3.ZERO); + store.setXExtent(Double.POSITIVE_INFINITY); + store.setYExtent(Double.POSITIVE_INFINITY); + store.setZExtent(Double.POSITIVE_INFINITY); + return store; + } + + _compVect1.setX(_center.getX() - getXExtent()); + if (_compVect1.getX() > boxCenter.getX() - boxX) { + _compVect1.setX(boxCenter.getX() - boxX); + } + _compVect1.setY(_center.getY() - getYExtent()); + if (_compVect1.getY() > boxCenter.getY() - boxY) { + _compVect1.setY(boxCenter.getY() - boxY); + } + _compVect1.setZ(_center.getZ() - getZExtent()); + if (_compVect1.getZ() > boxCenter.getZ() - boxZ) { + _compVect1.setZ(boxCenter.getZ() - boxZ); + } + + _compVect2.setX(_center.getX() + getXExtent()); + if (_compVect2.getX() < boxCenter.getX() + boxX) { + _compVect2.setX(boxCenter.getX() + boxX); + } + _compVect2.setY(_center.getY() + getYExtent()); + if (_compVect2.getY() < boxCenter.getY() + boxY) { + _compVect2.setY(boxCenter.getY() + boxY); + } + _compVect2.setZ(_center.getZ() + getZExtent()); + if (_compVect2.getZ() < boxCenter.getZ() + boxZ) { + _compVect2.setZ(boxCenter.getZ() + boxZ); + } + + store._center.set(_compVect2).addLocal(_compVect1).multiplyLocal(0.5); + + store.setXExtent(_compVect2.getX() - store._center.getX()); + store.setYExtent(_compVect2.getY() - store._center.getY()); + store.setZExtent(_compVect2.getZ() - store._center.getZ()); + + return store; + } + + /** + * <code>clone</code> creates a new BoundingBox object containing the same data as this one. + * + * @param store + * where to store the cloned information. if null or wrong class, a new store is created. + * @return the new BoundingBox + */ + @Override + public BoundingVolume clone(final BoundingVolume store) { + if (store != null && store.getType() == Type.AABB) { + final BoundingBox rVal = (BoundingBox) store; + rVal._center.set(_center); + rVal.setXExtent(_xExtent); + rVal.setYExtent(_yExtent); + rVal.setZExtent(_zExtent); + rVal._checkPlane = _checkPlane; + return rVal; + } + + final BoundingBox rVal = new BoundingBox(_center, getXExtent(), getYExtent(), getZExtent()); + return rVal; + } + + /** + * <code>toString</code> returns the string representation of this object. The form is: + * "Radius: RRR.SSSS Center: <Vector>". + * + * @return the string representation of this. + */ + @Override + public String toString() { + return "com.ardor3d.scene.BoundingBox [Center: " + _center + " xExtent: " + getXExtent() + " yExtent: " + + getYExtent() + " zExtent: " + getZExtent() + "]"; + } + + @Override + public boolean intersects(final BoundingVolume bv) { + if (bv == null) { + return false; + } + + return bv.intersectsBoundingBox(this); + } + + @Override + public boolean intersectsSphere(final BoundingSphere bs) { + if (!Vector3.isValid(_center) || !Vector3.isValid(bs._center)) { + return false; + } + + if (Math.abs(_center.getX() - bs.getCenter().getX()) < bs.getRadius() + getXExtent() + && Math.abs(_center.getY() - bs.getCenter().getY()) < bs.getRadius() + getYExtent() + && Math.abs(_center.getZ() - bs.getCenter().getZ()) < bs.getRadius() + getZExtent()) { + return true; + } + + return false; + } + + @Override + public boolean intersectsBoundingBox(final BoundingBox bb) { + if (!Vector3.isValid(_center) || !Vector3.isValid(bb._center)) { + return false; + } + + if (_center.getX() + getXExtent() < bb._center.getX() - bb.getXExtent() + || _center.getX() - getXExtent() > bb._center.getX() + bb.getXExtent()) { + return false; + } else if (_center.getY() + getYExtent() < bb._center.getY() - bb.getYExtent() + || _center.getY() - getYExtent() > bb._center.getY() + bb.getYExtent()) { + return false; + } else if (_center.getZ() + getZExtent() < bb._center.getZ() - bb.getZExtent() + || _center.getZ() - getZExtent() > bb._center.getZ() + bb.getZExtent()) { + return false; + } else { + return true; + } + } + + @Override + public boolean intersectsOrientedBoundingBox(final OrientedBoundingBox obb) { + return obb.intersectsBoundingBox(this); + } + + @Override + public boolean intersects(final ReadOnlyRay3 ray) { + if (!Vector3.isValid(_center)) { + return false; + } + + final Vector3 diff = ray.getOrigin().subtract(_center, _compVect1); + + final ReadOnlyVector3 direction = ray.getDirection(); + + final double[] t = { 0.0, Double.POSITIVE_INFINITY }; + + // Check for degenerate cases and pad using zero tolerance. Should give close enough result. + double x = getXExtent(); + if (x < MathUtils.ZERO_TOLERANCE && x >= 0) { + x = MathUtils.ZERO_TOLERANCE; + } + double y = getYExtent(); + if (y < MathUtils.ZERO_TOLERANCE && y >= 0) { + y = MathUtils.ZERO_TOLERANCE; + } + double z = getZExtent(); + if (z < MathUtils.ZERO_TOLERANCE && z >= 0) { + z = MathUtils.ZERO_TOLERANCE; + } + + // Special case. + if (Double.isInfinite(x) && Double.isInfinite(y) && Double.isInfinite(z)) { + return true; + } + + final boolean notEntirelyClipped = clip(direction.getX(), -diff.getX() - x, t) + && clip(-direction.getX(), diff.getX() - x, t) && clip(direction.getY(), -diff.getY() - y, t) + && clip(-direction.getY(), diff.getY() - y, t) && clip(direction.getZ(), -diff.getZ() - z, t) + && clip(-direction.getZ(), diff.getZ() - z, t); + + return (notEntirelyClipped && (t[0] != 0.0 || t[1] != Double.POSITIVE_INFINITY)); + } + + @Override + public IntersectionRecord intersectsWhere(final ReadOnlyRay3 ray) { + if (!Vector3.isValid(_center)) { + return null; + } + + final Vector3 diff = ray.getOrigin().subtract(_center, _compVect1); + + final ReadOnlyVector3 direction = ray.getDirection(); + + final double[] t = { 0.0, Double.POSITIVE_INFINITY }; + + // Check for degenerate cases and pad using zero tolerance. Should give close enough result. + double x = getXExtent(); + if (x < MathUtils.ZERO_TOLERANCE && x >= 0) { + x = MathUtils.ZERO_TOLERANCE; + } + double y = getYExtent(); + if (y < MathUtils.ZERO_TOLERANCE && y >= 0) { + y = MathUtils.ZERO_TOLERANCE; + } + double z = getZExtent(); + if (z < MathUtils.ZERO_TOLERANCE && z >= 0) { + z = MathUtils.ZERO_TOLERANCE; + } + + final boolean notEntirelyClipped = clip(direction.getX(), -diff.getX() - x, t) + && clip(-direction.getX(), diff.getX() - x, t) && clip(direction.getY(), -diff.getY() - y, t) + && clip(-direction.getY(), diff.getY() - y, t) && clip(direction.getZ(), -diff.getZ() - z, t) + && clip(-direction.getZ(), diff.getZ() - z, t); + + if (notEntirelyClipped && (t[0] != 0.0 || t[1] != Double.POSITIVE_INFINITY)) { + if (t[1] > t[0]) { + final double[] distances = t; + final Vector3[] points = new Vector3[] { + new Vector3(ray.getDirection()).multiplyLocal(distances[0]).addLocal(ray.getOrigin()), + new Vector3(ray.getDirection()).multiplyLocal(distances[1]).addLocal(ray.getOrigin()) }; + return new IntersectionRecord(distances, points); + } + + final double[] distances = new double[] { t[0] }; + final Vector3[] points = new Vector3[] { new Vector3(ray.getDirection()).multiplyLocal(distances[0]) + .addLocal(ray.getOrigin()), }; + return new IntersectionRecord(distances, points); + } + + return null; + + } + + @Override + public boolean contains(final ReadOnlyVector3 point) { + return Math.abs(_center.getX() - point.getX()) < getXExtent() + && Math.abs(_center.getY() - point.getY()) < getYExtent() + && Math.abs(_center.getZ() - point.getZ()) < getZExtent(); + } + + @Override + public double distanceToEdge(final ReadOnlyVector3 point) { + // compute coordinates of point in box coordinate system + final Vector3 closest = point.subtract(_center, _compVect1); + + // project test point onto box + double sqrDistance = 0.0; + double delta; + + if (closest.getX() < -getXExtent()) { + delta = closest.getX() + getXExtent(); + sqrDistance += delta * delta; + closest.setX(-getXExtent()); + } else if (closest.getX() > getXExtent()) { + delta = closest.getX() - getXExtent(); + sqrDistance += delta * delta; + closest.setX(getXExtent()); + } + + if (closest.getY() < -getYExtent()) { + delta = closest.getY() + getYExtent(); + sqrDistance += delta * delta; + closest.setY(-getYExtent()); + } else if (closest.getY() > getYExtent()) { + delta = closest.getY() - getYExtent(); + sqrDistance += delta * delta; + closest.setY(getYExtent()); + } + + if (closest.getZ() < -getZExtent()) { + delta = closest.getZ() + getZExtent(); + sqrDistance += delta * delta; + closest.setZ(-getZExtent()); + } else if (closest.getZ() > getZExtent()) { + delta = closest.getZ() - getZExtent(); + sqrDistance += delta * delta; + closest.setZ(getZExtent()); + } + + return Math.sqrt(sqrDistance); + } + + /** + * Get our corners using the bounding center and extents. + * + * @param store + * An optional store. Must be at least length of 8. If null, one will be created for you. + * @return array filled with our corners. + * @throws ArrayIndexOutOfBoundsException + * if our store is length < 8. + */ + public Vector3[] getCorners(Vector3[] store) { + if (store == null) { + store = new Vector3[8]; + for (int i = 0; i < store.length; i++) { + store[i] = new Vector3(); + } + } + store[0].set(_center.getX() + _xExtent, _center.getY() + _yExtent, _center.getZ() + _zExtent); + store[1].set(_center.getX() + _xExtent, _center.getY() + _yExtent, _center.getZ() - _zExtent); + store[2].set(_center.getX() + _xExtent, _center.getY() - _yExtent, _center.getZ() + _zExtent); + store[3].set(_center.getX() + _xExtent, _center.getY() - _yExtent, _center.getZ() - _zExtent); + store[4].set(_center.getX() - _xExtent, _center.getY() + _yExtent, _center.getZ() + _zExtent); + store[5].set(_center.getX() - _xExtent, _center.getY() + _yExtent, _center.getZ() - _zExtent); + store[6].set(_center.getX() - _xExtent, _center.getY() - _yExtent, _center.getZ() + _zExtent); + store[7].set(_center.getX() - _xExtent, _center.getY() - _yExtent, _center.getZ() - _zExtent); + return store; + } + + /** + * <code>clip</code> determines if a line segment intersects the current test plane. + * + * @param denom + * the denominator of the line segment. + * @param numer + * the numerator of the line segment. + * @param t + * test values of the plane. + * @return true if the line segment intersects the plane, false otherwise. + */ + private boolean clip(final double denom, final double numer, final double[] t) { + // Return value is 'true' if line segment intersects the current test + // plane. Otherwise 'false' is returned in which case the line segment + // is entirely clipped. + if (denom > 0.0) { + if (numer > denom * t[1]) { + return false; + } + if (numer > denom * t[0]) { + t[0] = numer / denom; + } + return true; + } else if (denom < 0.0) { + if (numer > denom * t[0]) { + return false; + } + if (numer > denom * t[1]) { + t[1] = numer / denom; + } + return true; + } else { + return numer <= 0.0; + } + } + + /** + * Query extent. + * + * @param store + * where extent gets stored - null to return a new vector + * @return store / new vector + */ + public Vector3 getExtent(Vector3 store) { + if (store == null) { + store = new Vector3(); + } + store.set(getXExtent(), getYExtent(), getZExtent()); + return store; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(getXExtent(), "xExtent", 0); + capsule.write(getYExtent(), "yExtent", 0); + capsule.write(getZExtent(), "zExtent", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + setXExtent(capsule.readDouble("xExtent", 0)); + setYExtent(capsule.readDouble("yExtent", 0)); + setZExtent(capsule.readDouble("zExtent", 0)); + } + + @Override + public double getVolume() { + return (8 * getXExtent() * getYExtent() * getZExtent()); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java new file mode 100644 index 0000000..9bf81b8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java @@ -0,0 +1,729 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.intersection.IntersectionRecord; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyRay3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.math.type.ReadOnlyPlane.Side; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>BoundingSphere</code> defines a sphere that defines a container for a group of vertices of a particular piece + * of geometry. This sphere defines a radius and a center. <br> + * <br> + * A typical usage is to allow the class define the center and radius by calling either <code>containAABB</code> or + * <code>averagePoints</code>. A call to <code>computeFramePoint</code> in turn calls <code>containAABB</code>. + */ +public class BoundingSphere extends BoundingVolume { + private static final Logger logger = Logger.getLogger(BoundingSphere.class.getName()); + + private static final long serialVersionUID = 1L; + + private double _radius; + + static final private double radiusEpsilon = 1 + 0.00001; + + protected final Vector3 _compVect3 = new Vector3(); + protected final Vector3 _compVect4 = new Vector3(); + + /** + * Default constructor instantiates a new <code>BoundingSphere</code> object. + */ + public BoundingSphere() {} + + /** + * Constructor instantiates a new <code>BoundingSphere</code> object. + * + * @param r + * the radius of the sphere. + * @param c + * the center of the sphere. + */ + public BoundingSphere(final double r, final ReadOnlyVector3 c) { + _center.set(c); + setRadius(r); + } + + @Override + public Type getType() { + return Type.Sphere; + } + + @Override + public BoundingVolume transform(final ReadOnlyTransform transform, final BoundingVolume store) { + BoundingSphere sphere; + if (store == null || store.getType() != BoundingVolume.Type.Sphere) { + sphere = new BoundingSphere(1, new Vector3(0, 0, 0)); + } else { + sphere = (BoundingSphere) store; + } + + transform.applyForward(_center, sphere._center); + + if (!transform.isRotationMatrix()) { + final Vector3 scale = new Vector3(1, 1, 1); + transform.applyForwardVector(scale); + sphere.setRadius(Math.abs(maxAxis(scale) * getRadius()) + radiusEpsilon - 1); + } else { + final ReadOnlyVector3 scale = transform.getScale(); + sphere.setRadius(Math.abs(maxAxis(scale) * getRadius()) + radiusEpsilon - 1); + } + + return sphere; + } + + private double maxAxis(final ReadOnlyVector3 scale) { + return Math.max(Math.abs(scale.getX()), Math.max(Math.abs(scale.getY()), Math.abs(scale.getZ()))); + } + + /** + * <code>getRadius</code> returns the radius of the bounding sphere. + * + * @return the radius of the bounding sphere. + */ + public double getRadius() { + return _radius; + } + + /** + * <code>setRadius</code> sets the radius of this bounding sphere. + * + * @param radius + * the new radius of the bounding sphere. + */ + public void setRadius(final double radius) { + _radius = radius; + } + + /** + * <code>computeFromPoints</code> creates a new Bounding Sphere from a given set of points. It uses the + * <code>calcWelzl</code> method as default. + * + * @param points + * the points to contain. + */ + @Override + public void computeFromPoints(final FloatBuffer points) { + calcWelzl(points); + } + + @Override + public void computeFromPrimitives(final MeshData data, final int section, final int[] indices, final int start, + final int end) { + if (end - start <= 0) { + return; + } + + final int vertsPerPrimitive = data.getIndexMode(section).getVertexCount(); + final Vector3[] vertList = new Vector3[(end - start) * vertsPerPrimitive]; + Vector3[] store = new Vector3[vertsPerPrimitive]; + + int count = 0; + for (int i = start; i < end; i++) { + store = data.getPrimitiveVertices(indices[i], section, store); + for (int j = 0; j < vertsPerPrimitive; j++) { + vertList[count++] = Vector3.fetchTempInstance().set(store[0]); + } + } + + averagePoints(vertList); + for (int i = 0; i < vertList.length; i++) { + Vector3.releaseTempInstance(vertList[i]); + } + } + + /** + * Calculates a minimum bounding sphere for the set of points. The algorithm was originally found at + * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1 in C++ and + * translated to java by Cep21 + * + * @param points + * The points to calculate the minimum bounds from. + */ + public void calcWelzl(final FloatBuffer points) { + final float[] buf = new float[points.limit()]; + points.rewind(); + points.get(buf); + recurseMini(buf, buf.length / 3, 0, 0); + } + + /** + * Used from calcWelzl. This function recurses to calculate a minimum bounding sphere a few points at a time. + * + * @param points + * The array of points to look through. + * @param p + * The size of the list to be used. + * @param pnts + * The number of points currently considering to include with the sphere. + * @param ap + * A variable simulating pointer arithmetic from C++, and offset in <code>points</code>. + */ + private void recurseMini(final float[] points, final int p, final int pnts, final int ap) { + switch (pnts) { + case 0: + setRadius(0); + _center.set(0, 0, 0); + break; + case 1: + setRadius(1f - radiusEpsilon); + populateFromBuffer(_center, points, ap - 1); + break; + case 2: + populateFromBuffer(_compVect1, points, ap - 1); + populateFromBuffer(_compVect2, points, ap - 2); + setSphere(_compVect1, _compVect2); + break; + case 3: + populateFromBuffer(_compVect1, points, ap - 1); + populateFromBuffer(_compVect2, points, ap - 2); + populateFromBuffer(_compVect3, points, ap - 3); + setSphere(_compVect1, _compVect2, _compVect3); + break; + case 4: + populateFromBuffer(_compVect1, points, ap - 1); + populateFromBuffer(_compVect2, points, ap - 2); + populateFromBuffer(_compVect3, points, ap - 3); + populateFromBuffer(_compVect4, points, ap - 4); + setSphere(_compVect1, _compVect2, _compVect3, _compVect4); + return; + } + for (int i = 0; i < p; i++) { + populateFromBuffer(_compVect1, points, i + ap); + if (_compVect1.distanceSquared(_center) - (getRadius() * getRadius()) > radiusEpsilon - 1f) { + for (int j = i; j > 0; j--) { + populateFromBuffer(_compVect2, points, j + ap); + populateFromBuffer(_compVect3, points, j - 1 + ap); + setInBuffer(_compVect3, points, j + ap); + setInBuffer(_compVect2, points, j - 1 + ap); + } + recurseMini(points, i, pnts + 1, ap + 1); + } + } + } + + public static void populateFromBuffer(final Vector3 vector, final float[] buf, final int index) { + vector.setX(buf[index * 3]); + vector.setY(buf[index * 3 + 1]); + vector.setZ(buf[index * 3 + 2]); + } + + public static void setInBuffer(final ReadOnlyVector3 vector, final float[] buf, final int index) { + if (buf == null) { + return; + } + if (vector == null) { + buf[index * 3] = 0; + buf[(index * 3) + 1] = 0; + buf[(index * 3) + 2] = 0; + } else { + buf[index * 3] = vector.getXf(); + buf[(index * 3) + 1] = vector.getYf(); + buf[(index * 3) + 2] = vector.getZf(); + } + } + + /** + * Calculates the minimum bounding sphere of 4 points. Used in welzl's algorithm. + * + * @param O + * The 1st point inside the sphere. + * @param A + * The 2nd point inside the sphere. + * @param B + * The 3rd point inside the sphere. + * @param C + * The 4th point inside the sphere. + * @see #calcWelzl(java.nio.FloatBuffer) + */ + private void setSphere(final Vector3 O, final Vector3 A, final Vector3 B, final Vector3 C) { + final Vector3 a = A.subtract(O, null); + final Vector3 b = B.subtract(O, null); + final Vector3 c = C.subtract(O, null); + + final double Denominator = 2.0 * (a.getX() * (b.getY() * c.getZ() - c.getY() * b.getZ()) - b.getX() + * (a.getY() * c.getZ() - c.getY() * a.getZ()) + c.getX() * (a.getY() * b.getZ() - b.getY() * a.getZ())); + if (Denominator == 0) { + _center.set(0, 0, 0); + setRadius(0); + } else { + final Vector3 o = a.cross(b, null).multiplyLocal(c.lengthSquared()).addLocal( + c.cross(a, null).multiplyLocal(b.lengthSquared())).addLocal( + b.cross(c, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator); + + setRadius(o.length() * radiusEpsilon); + O.add(o, _center); + } + } + + /** + * Calculates the minimum bounding sphere of 3 points. Used in welzl's algorithm. + * + * @param O + * The 1st point inside the sphere. + * @param A + * The 2nd point inside the sphere. + * @param B + * The 3rd point inside the sphere. + * @see #calcWelzl(java.nio.FloatBuffer) + */ + private void setSphere(final Vector3 O, final Vector3 A, final Vector3 B) { + final Vector3 a = A.subtract(O, null); + final Vector3 b = B.subtract(O, null); + final Vector3 acrossB = a.cross(b, null); + + final double Denominator = 2.0 * acrossB.dot(acrossB); + + if (Denominator == 0) { + _center.set(0, 0, 0); + setRadius(0); + } else { + + final Vector3 o = acrossB.cross(a, null).multiplyLocal(b.lengthSquared()).addLocal( + b.cross(acrossB, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator); + setRadius(o.length() * radiusEpsilon); + O.add(o, _center); + } + } + + /** + * Calculates the minimum bounding sphere of 2 points. Used in welzl's algorithm. + * + * @param O + * The 1st point inside the sphere. + * @param A + * The 2nd point inside the sphere. + * @see #calcWelzl(java.nio.FloatBuffer) + */ + private void setSphere(final Vector3 O, final Vector3 A) { + setRadius(Math.sqrt(((A.getX() - O.getX()) * (A.getX() - O.getX()) + (A.getY() - O.getY()) + * (A.getY() - O.getY()) + (A.getZ() - O.getZ()) * (A.getZ() - O.getZ())) / 4f) + + radiusEpsilon - 1); + Vector3.lerp(O, A, .5, _center); + } + + /** + * <code>averagePoints</code> selects the sphere center to be the average of the points and the sphere radius to be + * the smallest value to enclose all points. + * + * @param points + * the list of points to contain. + */ + public void averagePoints(final Vector3[] points) { + _center.set(points[0]); + + for (int i = 1; i < points.length; i++) { + _center.addLocal(points[i]); + } + + final double quantity = 1.0 / points.length; + _center.multiplyLocal(quantity); + + double maxRadiusSqr = 0; + for (int i = 0; i < points.length; i++) { + final Vector3 diff = points[i].subtract(_center, _compVect1); + final double radiusSqr = diff.lengthSquared(); + if (radiusSqr > maxRadiusSqr) { + maxRadiusSqr = radiusSqr; + } + } + + setRadius(Math.sqrt(maxRadiusSqr) + radiusEpsilon - 1f); + + } + + /** + * <code>whichSide</code> takes a plane (typically provided by a view frustum) to determine which side this bound is + * on. + * + * @param plane + * the plane to check against. + * @return side + */ + @Override + public Side whichSide(final ReadOnlyPlane plane) { + final double distance = plane.pseudoDistance(_center); + + if (distance <= -getRadius()) { + return Plane.Side.Inside; + } else if (distance >= getRadius()) { + return Plane.Side.Outside; + } else { + return Plane.Side.Neither; + } + } + + /** + * <code>merge</code> combines this sphere with a second bounding sphere. This new sphere contains both bounding + * spheres and is returned. + * + * @param volume + * the sphere to combine with this sphere. + * @return a new sphere + */ + @Override + public BoundingVolume merge(final BoundingVolume volume) { + if (volume == null) { + return this; + } + + switch (volume.getType()) { + + case Sphere: { + final BoundingSphere sphere = (BoundingSphere) volume; + final double temp_radius = sphere.getRadius(); + final ReadOnlyVector3 tempCenter = sphere.getCenter(); + final BoundingSphere rVal = new BoundingSphere(); + return merge(temp_radius, tempCenter, rVal); + } + + case AABB: { + final BoundingBox box = (BoundingBox) volume; + final Vector3 radVect = new Vector3(box.getXExtent(), box.getYExtent(), box.getZExtent()); + final Vector3 tempCenter = box._center; + final BoundingSphere rVal = new BoundingSphere(); + return merge(radVect.length(), tempCenter, rVal); + } + + case OBB: { + final OrientedBoundingBox box = (OrientedBoundingBox) volume; + final BoundingSphere rVal = (BoundingSphere) this.clone(null); + return rVal.mergeLocalOBB(box); + } + + default: + return null; + + } + } + + /** + * <code>mergeLocal</code> combines this sphere with a second bounding sphere locally. Altering this sphere to + * contain both the original and the additional sphere volumes; + * + * @param volume + * the sphere to combine with this sphere. + * @return this + */ + @Override + public BoundingVolume mergeLocal(final BoundingVolume volume) { + if (volume == null) { + return this; + } + + switch (volume.getType()) { + + case Sphere: { + final BoundingSphere sphere = (BoundingSphere) volume; + final double temp_radius = sphere.getRadius(); + final ReadOnlyVector3 temp_center = sphere.getCenter(); + return merge(temp_radius, temp_center, this); + } + + case AABB: { + final BoundingBox box = (BoundingBox) volume; + final Vector3 temp_center = box._center; + _compVect1.set(box.getXExtent(), box.getYExtent(), box.getZExtent()); + final double radius = _compVect1.length(); + return merge(radius, temp_center, this); + } + + case OBB: { + return mergeLocalOBB((OrientedBoundingBox) volume); + } + + default: + return null; + } + } + + /** + * Merges this sphere with the given OBB. + * + * @param volume + * The OBB to merge. + * @return This sphere, after merging. + */ + private BoundingSphere mergeLocalOBB(final OrientedBoundingBox volume) { + // check for infinite bounds to prevent NaN values... is so, return infinite bounds with center at origin + if (Double.isInfinite(getRadius()) || Vector3.isInfinite(volume.getExtent())) { + setCenter(Vector3.ZERO); + setRadius(Double.POSITIVE_INFINITY); + return this; + } + + // compute edge points from the obb + if (!volume.correctCorners) { + volume.computeCorners(); + } + + final FloatBuffer mergeBuf = BufferUtils.createFloatBufferOnHeap(8 * 3); + + for (int i = 0; i < 8; i++) { + mergeBuf.put((float) volume._vectorStore[i].getX()); + mergeBuf.put((float) volume._vectorStore[i].getY()); + mergeBuf.put((float) volume._vectorStore[i].getZ()); + } + + // remember old radius and center + final double oldRadius = getRadius(); + final double oldCenterX = _center.getX(); + final double oldCenterY = _center.getY(); + final double oldCenterZ = _center.getZ(); + + // compute new radius and center from obb points + computeFromPoints(mergeBuf); + + final double newCenterX = _center.getX(); + final double newCenterY = _center.getY(); + final double newCenterZ = _center.getZ(); + final double newRadius = getRadius(); + + // restore old center and radius + _center.set(oldCenterX, oldCenterY, oldCenterZ); + setRadius(oldRadius); + + // merge obb points result + merge(newRadius, _compVect4.set(newCenterX, newCenterY, newCenterZ), this); + + return this; + } + + private BoundingVolume merge(final double otherRadius, final ReadOnlyVector3 otherCenter, final BoundingSphere store) { + // check for infinite bounds... is so, return infinite bounds with center at origin + if (Double.isInfinite(otherRadius) || Double.isInfinite(getRadius())) { + store.setCenter(Vector3.ZERO); + store.setRadius(Double.POSITIVE_INFINITY); + return store; + } + + final Vector3 diff = otherCenter.subtract(_center, _compVect1); + final double lengthSquared = diff.lengthSquared(); + final double radiusDiff = otherRadius - getRadius(); + final double radiusDiffSqr = radiusDiff * radiusDiff; + + // if one sphere wholly contains the other + if (radiusDiffSqr >= lengthSquared) { + // if we contain the other + if (radiusDiff <= 0.0) { + store.setCenter(_center); + store.setRadius(_radius); + return store; + } + // else the other contains us + else { + store.setCenter(otherCenter); + store.setRadius(otherRadius); + return store; + } + } + + // distance between sphere centers + final double length = Math.sqrt(lengthSquared); + + // init a center var using our center + final Vector3 rCenter = _compVect2; + rCenter.set(_center); + + // if our centers are at least a tiny amount apart from each other... + if (length > MathUtils.EPSILON) { + // place us between the two centers, weighted by radii + final double coeff = (length + radiusDiff) / (2.0 * length); + rCenter.addLocal(diff.multiplyLocal(coeff)); + } + + // set center on our resulting bounds + store.setCenter(rCenter); + + // Set radius + store.setRadius(0.5 * (length + getRadius() + otherRadius)); + return store; + } + + /** + * <code>clone</code> creates a new BoundingSphere object containing the same data as this one. + * + * @param store + * where to store the cloned information. if null or wrong class, a new store is created. + * @return the new BoundingSphere + */ + @Override + public BoundingVolume clone(final BoundingVolume store) { + if (store != null && store.getType() == Type.Sphere) { + final BoundingSphere rVal = (BoundingSphere) store; + rVal._center.set(_center); + rVal.setRadius(_radius); + rVal._checkPlane = _checkPlane; + return rVal; + } + + return new BoundingSphere(getRadius(), _center); + } + + @Override + public String toString() { + return "com.ardor3d.scene.BoundingSphere [Radius: " + getRadius() + " Center: " + _center + "]"; + } + + @Override + public boolean intersects(final BoundingVolume bv) { + if (bv == null) { + return false; + } + + return bv.intersectsSphere(this); + } + + @Override + public boolean intersectsSphere(final BoundingSphere bs) { + if (!Vector3.isValid(_center) || !Vector3.isValid(bs._center)) { + return false; + } + + final Vector3 diff = _compVect1.set(getCenter()).subtractLocal(bs.getCenter()); + final double rsum = getRadius() + bs.getRadius(); + return (diff.dot(diff) <= rsum * rsum); + } + + @Override + public boolean intersectsBoundingBox(final BoundingBox bb) { + if (!Vector3.isValid(_center) || !Vector3.isValid(bb._center)) { + return false; + } + + if (Math.abs(bb._center.getX() - getCenter().getX()) < getRadius() + bb.getXExtent() + && Math.abs(bb._center.getY() - getCenter().getY()) < getRadius() + bb.getYExtent() + && Math.abs(bb._center.getZ() - getCenter().getZ()) < getRadius() + bb.getZExtent()) { + return true; + } + + return false; + } + + @Override + public boolean intersectsOrientedBoundingBox(final OrientedBoundingBox obb) { + return obb.intersectsSphere(this); + } + + @Override + public boolean intersects(final ReadOnlyRay3 ray) { + if (!Vector3.isValid(_center)) { + return false; + } + + final Vector3 diff = ray.getOrigin().subtract(getCenter(), _compVect1); + final double radiusSquared = getRadius() * getRadius(); + final double a = diff.dot(diff) - radiusSquared; + if (a <= 0.0) { + // in sphere + return true; + } + + // outside sphere + final Vector3 dir = _compVect2.set(ray.getDirection()); + final double b = dir.dot(diff); + if (b >= 0.0) { + return false; + } + return b * b >= a; + } + + @Override + public IntersectionRecord intersectsWhere(final ReadOnlyRay3 ray) { + + final Vector3 diff = ray.getOrigin().subtract(getCenter(), _compVect1); + final double a = diff.dot(diff) - (getRadius() * getRadius()); + double a1, discr, root; + if (a <= 0.0) { + // inside sphere + a1 = ray.getDirection().dot(diff); + discr = (a1 * a1) - a; + root = Math.sqrt(discr); + final double[] distances = new double[] { root - a1 }; + final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()).addLocal( + ray.getOrigin()) }; + return new IntersectionRecord(distances, points); + } + + a1 = ray.getDirection().dot(diff); + if (a1 >= 0.0) { + // No intersection + return null; + } + + discr = a1 * a1 - a; + if (discr < 0.0) { + return null; + } else if (discr >= MathUtils.ZERO_TOLERANCE) { + root = Math.sqrt(discr); + final double[] distances = new double[] { -a1 - root, -a1 + root }; + final Vector3[] points = new Vector3[] { + ray.getDirection().multiply(distances[0], new Vector3()).addLocal(ray.getOrigin()), + ray.getDirection().multiply(distances[1], new Vector3()).addLocal(ray.getOrigin()) }; + final IntersectionRecord record = new IntersectionRecord(distances, points); + return record; + } + + final double[] distances = new double[] { -a1 }; + final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()).addLocal( + ray.getOrigin()) }; + return new IntersectionRecord(distances, points); + } + + @Override + public boolean contains(final ReadOnlyVector3 point) { + return getCenter().distanceSquared(point) < (getRadius() * getRadius()); + } + + @Override + public double distanceToEdge(final ReadOnlyVector3 point) { + return _center.distance(point) - getRadius(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + try { + capsule.write(getRadius(), "radius", 0); + } catch (final IOException ex) { + logger.logp(Level.SEVERE, this.getClass().toString(), "write(Ardor3DExporter)", "Exception", ex); + } + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + try { + setRadius(capsule.readDouble("radius", 0)); + } catch (final IOException ex) { + logger.logp(Level.SEVERE, this.getClass().toString(), "read(Ardor3DImporter)", "Exception", ex); + } + } + + @Override + public double getVolume() { + return 4 * MathUtils.ONE_THIRD * MathUtils.PI * getRadius() * getRadius() * getRadius(); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java new file mode 100644 index 0000000..5c0ba3d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.io.IOException; +import java.io.Serializable; +import java.nio.FloatBuffer; + +import com.ardor3d.intersection.IntersectionRecord; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyRay3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +public abstract class BoundingVolume implements Serializable, Savable { + private static final long serialVersionUID = 1L; + + public enum Type { + Sphere, AABB, OBB; + } + + protected int _checkPlane = 0; + + protected final Vector3 _center = new Vector3(); + + protected final Vector3 _compVect1 = new Vector3(); + protected final Vector3 _compVect2 = new Vector3(); + + public BoundingVolume() {} + + public BoundingVolume(final Vector3 center) { + _center.set(center); + } + + /** + * Grabs the checkplane we should check first. + * + */ + public int getCheckPlane() { + return _checkPlane; + } + + /** + * Sets the index of the plane that should be first checked during rendering. + * + * @param value + */ + public final void setCheckPlane(final int value) { + _checkPlane = value; + } + + /** + * getType returns the type of bounding volume this is. + */ + public abstract Type getType(); + + /** + * + * <code>transform</code> alters the location of the bounding volume by a transform. + * + * @param transform + * @param store + * @return + */ + public abstract BoundingVolume transform(final ReadOnlyTransform transform, final BoundingVolume store); + + /** + * + * <code>whichSide</code> returns the side on which the bounding volume lies on a plane. Possible values are + * POSITIVE_SIDE, NEGATIVE_SIDE, and NO_SIDE. + * + * @param plane + * the plane to check against this bounding volume. + * @return the side on which this bounding volume lies. + */ + public abstract ReadOnlyPlane.Side whichSide(ReadOnlyPlane plane); + + /** + * + * <code>computeFromPoints</code> generates a bounding volume that encompasses a collection of points. + * + * @param points + * the points to contain. + */ + public abstract void computeFromPoints(FloatBuffer points); + + /** + * <code>merge</code> combines two bounding volumes into a single bounding volume that contains both this bounding + * volume and the parameter volume. + * + * @param volume + * the volume to combine. + * @return the new merged bounding volume. + */ + public abstract BoundingVolume merge(BoundingVolume volume); + + /** + * <code>mergeLocal</code> combines two bounding volumes into a single bounding volume that contains both this + * bounding volume and the parameter volume. The result is stored locally. + * + * @param volume + * the volume to combine. + * @return this + */ + public abstract BoundingVolume mergeLocal(BoundingVolume volume); + + /** + * <code>clone</code> creates a new BoundingVolume object containing the same data as this one. + * + * @param store + * where to store the cloned information. if null or wrong class, a new store is created. + * @return the new BoundingVolume + */ + public abstract BoundingVolume clone(BoundingVolume store); + + /** + * @return the distance from the center of this bounding volume to its further edge/corner. Similar to converting + * this BoundingVolume to a sphere and asking for radius. + */ + public abstract double getRadius(); + + public final ReadOnlyVector3 getCenter() { + return _center; + } + + public final void setCenter(final ReadOnlyVector3 newCenter) { + _center.set(newCenter); + } + + public void setCenter(final double x, final double y, final double z) { + _center.set(x, y, z); + } + + /** + * Find the distance from the center of this Bounding Volume to the given point. + * + * @param point + * The point to get the distance to + * @return distance + */ + public final double distanceTo(final ReadOnlyVector3 point) { + return _center.distance(point); + } + + /** + * Find the squared distance from the center of this Bounding Volume to the given point. + * + * @param point + * The point to get the distance to + * @return distance + */ + public final double distanceSquaredTo(final ReadOnlyVector3 point) { + return _center.distanceSquared(point); + } + + /** + * Find the distance from the nearest edge of this Bounding Volume to the given point. + * + * @param point + * The point to get the distance to + * @return distance + */ + public abstract double distanceToEdge(ReadOnlyVector3 point); + + /** + * determines if this bounding volume and a second given volume are intersecting. Intersecting being: one volume + * contains another, one volume overlaps another or one volume touches another. + * + * @param bv + * the second volume to test against. + * @return true if this volume intersects the given volume. + */ + public abstract boolean intersects(BoundingVolume bv); + + /** + * determines if a ray intersects this bounding volume. + * + * @param ray + * the ray to test. + * @return true if this volume is intersected by a given ray. + */ + public abstract boolean intersects(ReadOnlyRay3 ray); + + /** + * determines if a ray intersects this bounding volume and if so, where. + * + * @param ray + * the ray to test. + * @return an IntersectionRecord containing information about any intersections made by the given Ray with this + * bounding + */ + public abstract IntersectionRecord intersectsWhere(ReadOnlyRay3 ray); + + /** + * determines if this bounding volume and a given bounding sphere are intersecting. + * + * @param bs + * the bounding sphere to test against. + * @return true if this volume intersects the given bounding sphere. + */ + public abstract boolean intersectsSphere(BoundingSphere bs); + + /** + * determines if this bounding volume and a given bounding box are intersecting. + * + * @param bb + * the bounding box to test against. + * @return true if this volume intersects the given bounding box. + */ + public abstract boolean intersectsBoundingBox(BoundingBox bb); + + /** + * determines if this bounding volume and a given bounding box are intersecting. + * + * @param bb + * the bounding box to test against. + * @return true if this volume intersects the given bounding box. + */ + public abstract boolean intersectsOrientedBoundingBox(OrientedBoundingBox bb); + + /** + * + * determines if a given point is contained within this bounding volume. + * + * @param point + * the point to check + * @return true if the point lies within this bounding volume. + */ + public abstract boolean contains(ReadOnlyVector3 point); + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + } + + public void read(final InputCapsule capsule) throws IOException { + _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO))); + } + + public Class<? extends BoundingVolume> getClassTag() { + return this.getClass(); + } + + public abstract void computeFromPrimitives(MeshData data, int section, final int[] indices, int start, int end); + + public abstract double getVolume(); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java new file mode 100644 index 0000000..b623327 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java @@ -0,0 +1,595 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.intersection.Intersection; +import com.ardor3d.intersection.PrimitiveKey; +import com.ardor3d.math.Ray3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; + +/** + * CollisionTree defines a well balanced red black tree used for triangle accurate collision detection. The + * CollisionTree supports three types: Oriented Bounding Box, Axis-Aligned Bounding Box and Sphere. The tree is composed + * of a hierarchy of nodes, all but leaf nodes have two children, a left and a right, where the children contain half of + * the triangles of the parent. This "half split" is executed down the tree until the node is maintaining a set maximum + * of triangles. This node is called the leaf node. Intersection checks are handled as follows:<br> + * 1. The bounds of the node is checked for intersection. If no intersection occurs here, no further processing is + * needed, the children (nodes or triangles) do not intersect.<br> + * 2a. If an intersection occurs and we have children left/right nodes, pass the intersection information to the + * children.<br> + * 2b. If an intersection occurs and we are a leaf node, pass each triangle individually for intersection checking.<br> + * <br> + * Optionally, during creation of the collision tree, sorting can be applied. Sorting will attempt to optimize the order + * of the triangles in such a way as to best split for left and right sub-trees. This function can lead to faster + * intersection tests, but increases the creation time for the tree. The number of triangles a leaf node is responsible + * for is defined in CollisionTreeManager. It is actually recommended to allow CollisionTreeManager to maintain the + * collision trees for a scene. + * + * @see com.ardor3d.bounding.CollisionTreeManager + */ +public class CollisionTree implements Serializable { + + private static final long serialVersionUID = 1L; + + public enum Type { + /** CollisionTree using Oriented Bounding Boxes. */ + OBB, + /** CollisionTree using Axis-Aligned Bounding Boxes. */ + AABB, + /** CollisionTree using Bounding Spheres. */ + Sphere; + } + + // Default tree is axis-aligned + protected Type _type = Type.AABB; + + // children trees + protected CollisionTree _left; + protected CollisionTree _right; + + // bounding volumes that contain the triangles that the node is handling + protected BoundingVolume _bounds; + protected BoundingVolume _worldBounds; + + /** + * the list of primitives indices that compose the tree. This list contains all the triangles of the mesh and is + * shared between all nodes of this tree. Stored here to allow for sorting. + */ + protected int[] _primitiveIndices; + + // Defines the pointers into the triIndex array that this node is directly responsible for. + protected int _start, _end; + + // Required Spatial information + protected transient WeakReference<Mesh> _mesh; + protected int _section; + + // Comparator used to sort triangle indices + protected transient final TreeComparator _comparator = new TreeComparator(); + + /** + * Constructor creates a new instance of CollisionTree. + * + * @param type + * the type of collision tree to make + * @see Type + */ + public CollisionTree(final Type type) { + _type = type; + } + + /** + * Recreate this Collision Tree for the given Node and child index. + * + * @param childIndex + * the index of the child to generate the tree for. + * @param parent + * The Node that this tree should represent. + * @param doSort + * true to sort primitives during creation, false otherwise + */ + public void construct(final int childIndex, final int section, final Node parent, final boolean doSort) { + final Spatial spat = parent.getChild(childIndex); + if (spat instanceof Mesh) { + _mesh = makeRef((Mesh) spat); + _primitiveIndices = new int[((Mesh) spat).getMeshData().getPrimitiveCount(section)]; + for (int i = 0; i < _primitiveIndices.length; i++) { + _primitiveIndices[i] = i; + } + createTree(section, 0, _primitiveIndices.length, doSort); + } + } + + /** + * Recreate this Collision Tree for the given mesh. + * + * @param mesh + * The mesh that this tree should represent. + * @param doSort + * true to sort primitives during creation, false otherwise + */ + public void construct(final Mesh mesh, final boolean doSort) { + _mesh = makeRef(mesh); + if (mesh.getMeshData().getSectionCount() == 1) { + _primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(0)]; + for (int i = 0; i < _primitiveIndices.length; i++) { + _primitiveIndices[i] = i; + } + createTree(0, 0, _primitiveIndices.length, doSort); + } else { + // divide up the sections into the tree by adding intermediate nodes as needed. + splitMesh(mesh, 0, mesh.getMeshData().getSectionCount(), doSort); + } + } + + protected void splitMesh(final Mesh mesh, final int sectionStart, final int sectionEnd, final boolean doSort) { + _mesh = makeRef(mesh); + + // Split range in half + final int rangeSize = sectionEnd - sectionStart; + final int halfRange = rangeSize / 2; // odd number will give +1 to right. + + // left half: + // if half size == 1, create as regular CollisionTree + if (halfRange == 1) { + // compute section + final int section = sectionStart; + + // create the left child + _left = new CollisionTree(_type); + + _left._primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(section)]; + for (int i = 0; i < _left._primitiveIndices.length; i++) { + _left._primitiveIndices[i] = i; + } + _left._mesh = _mesh; + _left.createTree(section, 0, _left._primitiveIndices.length, doSort); + } else { + // otherwise, make an empty collision tree and call split with new range + _left = new CollisionTree(_type); + _left.splitMesh(mesh, sectionStart, sectionStart + halfRange, doSort); + } + + // right half: + // if rangeSize - half size == 1, create as regular CollisionTree + if (rangeSize - halfRange == 1) { + // compute section + final int section = sectionStart + 1; + + // create the left child + _right = new CollisionTree(_type); + + _right._primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(section)]; + for (int i = 0; i < _right._primitiveIndices.length; i++) { + _right._primitiveIndices[i] = i; + } + _right._mesh = _mesh; + _right.createTree(section, 0, _right._primitiveIndices.length, doSort); + } else { + // otherwise, make an empty collision tree and call split with new range + _right = new CollisionTree(_type); + _right.splitMesh(mesh, sectionStart + halfRange, sectionEnd, doSort); + } + + // Ok, now since we technically have no primitives, we need our bounds to be the merging of our children bounds + // instead: + _bounds = _left._bounds.clone(_bounds); + _bounds.mergeLocal(_right._bounds); + _worldBounds = _bounds.clone(_worldBounds); + } + + /** + * Creates a Collision Tree by recursively creating children nodes, splitting the primitives this node is + * responsible for in half until the desired primitive count is reached. + * + * @param start + * The start index of the primitivesArray, inclusive. + * @param end + * The end index of the primitivesArray, exclusive. + * @param doSort + * True if the primitives should be sorted at each level, false otherwise. + */ + public void createTree(final int section, final int start, final int end, final boolean doSort) { + _section = section; + _start = start; + _end = end; + + if (_primitiveIndices == null) { + return; + } + + createBounds(); + + // the bounds at this level should contain all the primitives this level is responsible for. + _bounds.computeFromPrimitives(getMesh().getMeshData(), _section, _primitiveIndices, _start, _end); + + // check to see if we are a leaf, if the number of primitives we reference is less than or equal to the maximum + // defined by the CollisionTreeManager we are done. + if (_end - _start + 1 <= CollisionTreeManager.getInstance().getMaxPrimitivesPerLeaf()) { + return; + } + + // if doSort is set we need to attempt to optimize the referenced primitives. optimizing the sorting of the + // primitives will help group them spatially in the left/right children better. + if (doSort) { + sortPrimitives(); + } + + // create the left child + if (_left == null) { + _left = new CollisionTree(_type); + } + + _left._primitiveIndices = _primitiveIndices; + _left._mesh = _mesh; + _left.createTree(_section, _start, (_start + _end) / 2, doSort); + + // create the right child + if (_right == null) { + _right = new CollisionTree(_type); + } + _right._primitiveIndices = _primitiveIndices; + _right._mesh = _mesh; + _right.createTree(_section, (_start + _end) / 2, _end, doSort); + } + + /** + * Tests if the world bounds of the node at this level intersects a provided bounding volume. If an intersection + * occurs, true is returned, otherwise false is returned. If the provided volume is invalid, false is returned. + * + * @param volume + * the volume to intersect with. + * @return true if there is an intersect, false otherwise. + */ + public boolean intersectsBounding(final BoundingVolume volume) { + switch (volume.getType()) { + case AABB: + return _worldBounds.intersectsBoundingBox((BoundingBox) volume); + case OBB: + return _worldBounds.intersectsOrientedBoundingBox((OrientedBoundingBox) volume); + case Sphere: + return _worldBounds.intersectsSphere((BoundingSphere) volume); + default: + return false; + } + + } + + /** + * Determines if this Collision Tree intersects the given CollisionTree. If a collision occurs, true is returned, + * otherwise false is returned. If the provided collisionTree is invalid, false is returned. + * + * @param collisionTree + * The Tree to test. + * @return True if they intersect, false otherwise. + */ + public boolean intersect(final CollisionTree collisionTree) { + if (collisionTree == null) { + return false; + } + + collisionTree._worldBounds = collisionTree._bounds.transform(collisionTree.getMesh().getWorldTransform(), + collisionTree._worldBounds); + + // our two collision bounds do not intersect, therefore, our primitives + // must not intersect. Return false. + if (!intersectsBounding(collisionTree._worldBounds)) { + return false; + } + + // check children + if (_left != null) { // This is not a leaf + if (collisionTree.intersect(_left)) { + return true; + } + if (collisionTree.intersect(_right)) { + return true; + } + return false; + } + + // This is a leaf + if (collisionTree._left != null) { + // but collision isn't + if (intersect(collisionTree._left)) { + return true; + } + if (intersect(collisionTree._right)) { + return true; + } + return false; + } + + // both are leaves + final ReadOnlyTransform transformA = getMesh().getWorldTransform(); + final ReadOnlyTransform transformB = collisionTree.getMesh().getWorldTransform(); + + final MeshData dataA = getMesh().getMeshData(); + final MeshData dataB = collisionTree.getMesh().getMeshData(); + + Vector3[] storeA = null; + Vector3[] storeB = null; + + // for every primitive to compare, put them into world space and check for intersections + for (int i = _start; i < _end; i++) { + storeA = dataA.getPrimitiveVertices(_primitiveIndices[i], _section, storeA); + // to world space + for (int t = 0; t < storeA.length; t++) { + transformA.applyForward(storeA[t]); + } + for (int j = collisionTree._start; j < collisionTree._end; j++) { + storeB = dataB.getPrimitiveVertices(collisionTree._primitiveIndices[j], collisionTree._section, storeB); + // to world space + for (int t = 0; t < storeB.length; t++) { + transformB.applyForward(storeB[t]); + } + if (Intersection.intersection(storeA, storeB)) { + return true; + } + } + } + + return false; + } + + /** + * Determines if this Collision Tree intersects the given CollisionTree. If a collision occurs, true is returned, + * otherwise false is returned. If the provided collisionTree is invalid, false is returned. All collisions that + * occur are stored in lists as an integer index into the mesh's triangle buffer. where aList is the primitives for + * this mesh and bList is the primitives for the test tree. + * + * @param collisionTree + * The Tree to test. + * @param aList + * a list to contain the colliding primitives of this mesh. + * @param bList + * a list to contain the colliding primitives of the testing mesh. + * @return True if they intersect, false otherwise. + */ + public boolean intersect(final CollisionTree collisionTree, final List<PrimitiveKey> aList, + final List<PrimitiveKey> bList) { + + if (collisionTree == null) { + return false; + } + + collisionTree._worldBounds = collisionTree._bounds.transform(collisionTree.getMesh().getWorldTransform(), + collisionTree._worldBounds); + + // our two collision bounds do not intersect, therefore, our primitives + // must not intersect. Return false. + if (!intersectsBounding(collisionTree._worldBounds)) { + return false; + } + + // if our node is not a leaf send the children (both left and right) to + // the test tree. + if (_left != null) { // This is not a leaf + boolean test = collisionTree.intersect(_left, bList, aList); + test = collisionTree.intersect(_right, bList, aList) || test; + return test; + } + + // This node is a leaf, but the testing tree node is not. Therefore, + // continue processing the testing tree until we find its leaves. + if (collisionTree._left != null) { + boolean test = intersect(collisionTree._left, aList, bList); + test = intersect(collisionTree._right, aList, bList) || test; + return test; + } + + // both this node and the testing node are leaves. Therefore, we can + // switch to checking the contained primitives with each other. Any + // that are found to intersect are placed in the appropriate list. + final ReadOnlyTransform transformA = getMesh().getWorldTransform(); + final ReadOnlyTransform transformB = collisionTree.getMesh().getWorldTransform(); + + final MeshData dataA = getMesh().getMeshData(); + final MeshData dataB = collisionTree.getMesh().getMeshData(); + + Vector3[] storeA = null; + Vector3[] storeB = null; + + boolean test = false; + + for (int i = _start; i < _end; i++) { + storeA = dataA.getPrimitiveVertices(_primitiveIndices[i], _section, storeA); + // to world space + for (int t = 0; t < storeA.length; t++) { + transformA.applyForward(storeA[t]); + } + for (int j = collisionTree._start; j < collisionTree._end; j++) { + storeB = dataB.getPrimitiveVertices(collisionTree._primitiveIndices[j], collisionTree._section, storeB); + // to world space + for (int t = 0; t < storeB.length; t++) { + transformB.applyForward(storeB[t]); + } + if (Intersection.intersection(storeA, storeB)) { + test = true; + aList.add(new PrimitiveKey(_primitiveIndices[i], _section)); + bList.add(new PrimitiveKey(collisionTree._primitiveIndices[j], collisionTree._section)); + } + } + } + + return test; + } + + /** + * intersect checks for collisions between this collision tree and a provided Ray. Any collisions are stored in a + * provided list as primitive index values. The ray is assumed to have a normalized direction for accurate + * calculations. + * + * @param ray + * the ray to test for intersections. + * @param store + * a list to fill with the index values of the primitive hit. if null, a new List is created. + * @return the list. + */ + public List<PrimitiveKey> intersect(final Ray3 ray, final List<PrimitiveKey> store) { + List<PrimitiveKey> result = store; + if (result == null) { + result = new ArrayList<PrimitiveKey>(); + } + + // if our ray doesn't hit the bounds, then it must not hit a primitive. + if (!_worldBounds.intersects(ray)) { + return result; + } + + // This is not a leaf node, therefore, check each child (left/right) for intersection with the ray. + if (_left != null) { + _left._worldBounds = _left._bounds.transform(getMesh().getWorldTransform(), _left._worldBounds); + _left.intersect(ray, result); + } + + if (_right != null) { + _right._worldBounds = _right._bounds.transform(getMesh().getWorldTransform(), _right._worldBounds); + _right.intersect(ray, result); + } else if (_left == null) { + // This is a leaf node. We can therefore check each primitive this node contains. If an intersection occurs, + // place it in the list. + + final MeshData data = getMesh().getMeshData(); + final ReadOnlyTransform transform = getMesh().getWorldTransform(); + + Vector3[] points = null; + for (int i = _start; i < _end; i++) { + points = data.getPrimitiveVertices(_primitiveIndices[i], _section, points); + for (int t = 0; t < points.length; t++) { + transform.applyForward(points[t]); + } + if (ray.intersects(points, null)) { + result.add(new PrimitiveKey(_primitiveIndices[i], _section)); + } + } + } + return result; + } + + /** + * Returns the bounding volume for this tree node in local space. + * + * @return the bounding volume for this tree node in local space. + */ + public BoundingVolume getBounds() { + return _bounds; + } + + /** + * Returns the bounding volume for this tree node in world space. + * + * @return the bounding volume for this tree node in world space. + */ + public BoundingVolume getWorldBounds() { + return _worldBounds; + } + + /** + * creates the appropriate bounding volume based on the type set during construction. + */ + private void createBounds() { + switch (_type) { + case AABB: + _bounds = new BoundingBox(); + _worldBounds = new BoundingBox(); + break; + case OBB: + _bounds = new OrientedBoundingBox(); + _worldBounds = new OrientedBoundingBox(); + break; + case Sphere: + _bounds = new BoundingSphere(); + _worldBounds = new BoundingSphere(); + break; + default: + break; + } + } + + /** + * sortPrimitives attempts to optimize the ordering of the subsection of the array of primitives this node is + * responsible for. The sorting is based on the most efficient method along an axis. Using the TreeComparator and + * quick sort, the subsection of the array is sorted. + */ + public void sortPrimitives() { + switch (_type) { + case AABB: + // determine the longest length of the box, this axis will be best for sorting. + if (((BoundingBox) _bounds).getXExtent() > ((BoundingBox) _bounds).getYExtent()) { + if (((BoundingBox) _bounds).getXExtent() > ((BoundingBox) _bounds).getZExtent()) { + _comparator.setAxis(TreeComparator.Axis.X); + } else { + _comparator.setAxis(TreeComparator.Axis.Z); + } + } else { + if (((BoundingBox) _bounds).getYExtent() > ((BoundingBox) _bounds).getZExtent()) { + _comparator.setAxis(TreeComparator.Axis.Y); + } else { + _comparator.setAxis(TreeComparator.Axis.Z); + } + } + break; + case OBB: + // determine the longest length of the box, this axis will be best for sorting. + if (((OrientedBoundingBox) _bounds)._extent.getX() > ((OrientedBoundingBox) _bounds)._extent.getY()) { + if (((OrientedBoundingBox) _bounds)._extent.getX() > ((OrientedBoundingBox) _bounds)._extent.getZ()) { + _comparator.setAxis(TreeComparator.Axis.X); + } else { + _comparator.setAxis(TreeComparator.Axis.Z); + } + } else { + if (((OrientedBoundingBox) _bounds)._extent.getY() > ((OrientedBoundingBox) _bounds)._extent.getZ()) { + _comparator.setAxis(TreeComparator.Axis.Y); + } else { + _comparator.setAxis(TreeComparator.Axis.Z); + } + } + break; + case Sphere: + // sort any axis, X is fine. + _comparator.setAxis(TreeComparator.Axis.X); + break; + default: + break; + } + + _comparator.setMesh(getMesh()); + // TODO: broken atm + // SortUtil.qsort(_primitiveIndices, _start, _end - 1, _comparator); + } + + /** + * @return the Mesh referenced by _mesh + */ + private Mesh getMesh() { + return _mesh.get(); + } + + /** + * @param mesh + * Mesh object to reference. + * @return a new reference to the given mesh. + */ + private WeakReference<Mesh> makeRef(final Mesh mesh) { + return new WeakReference<Mesh>(mesh); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java new file mode 100644 index 0000000..840b3af --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.util.List; +import java.util.Map; + +import com.ardor3d.scenegraph.Mesh; + +/** + * CollisionTreeController defines an interface for determining which collision tree to remove from a supplied cache. + * The desired size is given for the controller to attempt to reduce the cache to, as well as a list of protected + * elements that should <b>not</b> be removed from the cache. + */ +public interface CollisionTreeController { + /** + * clean will reduce the size of cache to the provided desiredSize. The protectedList defines elements that should + * not be removed from the cache. + * + * @param cache + * the cache to reduce. + * @param protectedList + * the list of elements to not remove. + * @param desiredSize + * the desiredSize of the final cache. + */ + void clean(Map<Mesh, CollisionTree> cache, List<Mesh> protectedList, int desiredSize); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java new file mode 100644 index 0000000..bfe0c1d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java @@ -0,0 +1,410 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.MapMaker; + +/** + * CollisionTreeManager is an automated system for handling the creation and deletion of CollisionTrees. The manager + * maintains a cache map of currently generated collision trees. The collision system itself requests a collision tree + * from the manager via the <code>getCollisionTree</code> method. The cache is checked for the tree, and if it is + * available, sent to the caller. If the tree is not in the cache, and generateTrees is true, a new CollisionTree is + * generated on the fly and sent to the caller. When a new tree is created, the cache size is compared to the + * maxElements value. If the cache is larger than maxElements, the cache is sent to the CollisionTreeController for + * cleaning. + * <p> + * There are a number of settings that can be used to control how trees are generated. First, generateTrees denotes + * whether the manager should be creating trees at all. This is set to true by default. doSort defines if the + * CollisionTree primitive array should be sorted as it is built. This is false by default. Sorting is beneficial for + * model data that is not well ordered spatially. This occurrence is rare, and sorting slows creation time. It is, + * therefore, only to be used when model data requires it. maxPrimitivesPerLeaf defines the number of primitives a leaf + * node in the collision tree should maintain. The larger number of primitives maintained in a leaf node, the smaller + * the tree, but the larger the number of checks during a collision. By default, this value is set to 16. maxElements + * defines the maximum number of trees that will be maintained before clean-up is required. A collision tree is defined + * for each mesh that is being collided with. The user should determine the optimal number of trees to maintain (a + * memory/performance tradeoff), based on the number of meshes, their population density and their primitive size. By + * default, this value is set to 25. The type of trees that will be generated is defined by the treeType value, where + * valid options are define in CollisionTree as AABB_TREE, OBB_TREE and SPHERE_TREE. You can set the functionality of + * how trees are removed from the cache by providing the manager with a CollisionTreeController implementation. By + * default, the manager will use the UsageTreeController for removing trees, but any other CollisionTreeController is + * acceptable. You can create protected tree manually. These are collision trees that you request the manager to create + * and not allow them to be removed by the CollisionTreeController. + * + * @see com.ardor3d.bounding.CollisionTree + * @see com.ardor3d.bounding.CollisionTreeController + */ +public enum CollisionTreeManager { + INSTANCE; + + /** + * defines the default maximum number of trees to maintain. + */ + public static final int DEFAULT_MAX_ELEMENTS = 25; + /** + * defines the default maximum number of primitives in a tree leaf. + */ + public static final int DEFAULT_MAX_PRIMITIVES_PER_LEAF = 16; + + // the cache and protected list for storing trees. + private final Map<Mesh, CollisionTree> _cache; + private final List<Mesh> _protectedList; + + private boolean _generateTrees = true; + private boolean _doSort; + + private CollisionTree.Type _treeType = CollisionTree.Type.AABB; + + private int _maxPrimitivesPerLeaf = DEFAULT_MAX_PRIMITIVES_PER_LEAF; + private int _maxElements = DEFAULT_MAX_ELEMENTS; + + private CollisionTreeController _treeRemover; + + /** + * private constructor for the Singleton. Initializes the cache. + */ + private CollisionTreeManager() { + _cache = new MapMaker().weakKeys().makeMap(); + _protectedList = Collections.synchronizedList(new ArrayList<Mesh>(1)); + setCollisionTreeController(new UsageTreeController()); + } + + /** + * retrieves the singleton instance of the CollisionTreeManager. + * + * @return the singleton instance of the manager. + */ + public static CollisionTreeManager getInstance() { + return INSTANCE; + } + + private CollisionTree cacheGet(final Mesh mesh) { + return _cache.get(mesh); + } + + private void cacheRemove(final Mesh mesh) { + _cache.remove(mesh); + } + + private void cachePut(final Mesh mesh, final CollisionTree tree) { + _cache.put(mesh, tree); + } + + /** + * sets the CollisionTreeController used for cleaning the cache when the maximum number of elements is reached. + * + * @param treeRemover + * the controller used to clean the cache. + */ + public void setCollisionTreeController(final CollisionTreeController treeRemover) { + _treeRemover = treeRemover; + } + + /** + * getCollisionTree obtains a collision tree that is assigned to a supplied Mesh. The cache is checked for a + * pre-existing tree, if none is available and generateTrees is true, a new tree is created and returned. + * + * @param mesh + * the mesh to use as the key for the tree to obtain. + * @return the tree associated with a given mesh + */ + public synchronized CollisionTree getCollisionTree(final Mesh mesh) { + CollisionTree toReturn = null; + + toReturn = cacheGet(mesh); + + // we didn't have it in the cache, create it if possible. + if (toReturn == null) { + if (_generateTrees) { + return generateCollisionTree(_treeType, mesh, false); + } else { + return null; + } + } else { + // we had it in the cache, to keep the keyset in order, reinsert this element + cacheRemove(mesh); + cachePut(mesh, toReturn); + return toReturn; + } + } + + /** + * creates a new collision tree for the provided spatial. If the spatial is a node, it recursively calls + * generateCollisionTree for each child. If it is a Mesh, a call to generateCollisionTree is made for each mesh. If + * this tree(s) is to be protected, i.e. not deleted by the CollisionTreeController, set protect to true. + * + * @param type + * the type of collision tree to generate. + * @param object + * the Spatial to generate tree(s) for. + * @param protect + * true to keep these trees from being removed, false otherwise. + */ + public void generateCollisionTree(final CollisionTree.Type type, final Spatial object, final boolean protect) { + if (object instanceof Mesh) { + generateCollisionTree(type, (Mesh) object, protect); + } + if (object instanceof Node) { + if (((Node) object).getNumberOfChildren() > 0) { + for (final Spatial sp : ((Node) object).getChildren()) { + generateCollisionTree(type, sp, protect); + } + } + } + } + + /** + * generates a new tree for the associated mesh. The type is provided and a new tree is constructed of this type. + * The tree is placed in the cache. If the cache's size then becomes too large, the cache is sent to the + * CollisionTreeController for clean-up. If this tree is to be protected, i.e. protected from the + * CollisionTreeController, set protect to true. + * + * @param type + * the type of collision tree to generate. + * @param mesh + * the mesh to generate the tree for. + * @param protect + * true if this tree is to be protected, false otherwise. + * @return the new collision tree. + */ + public CollisionTree generateCollisionTree(final CollisionTree.Type type, final Mesh mesh, final boolean protect) { + if (mesh == null) { + return null; + } + + final CollisionTree tree = new CollisionTree(type); + + generateCollisionTree(tree, mesh, protect); + + return tree; + } + + /** + * generates a new tree for the associated mesh. It is provided with a pre-existing, non-null tree. The tree is + * placed in the cache. If the cache's size then becomes too large, the cache is sent to the CollisionTreeController + * for clean-up. If this tree is to be protected, i.e. protected from the CollisionTreeController, set protect to + * true. + * + * @param tree + * the tree to use for generation + * @param mesh + * the mesh to generate the tree for. + * @param protect + * true if this tree is to be protected, false otherwise. + */ + protected void generateCollisionTree(final CollisionTree tree, final Mesh mesh, final boolean protect) { + tree.construct(mesh, _doSort); + cachePut(mesh, tree); + // This mesh has been added by outside sources and labeled + // as protected. Therefore, put it in the protected list + // so it is not removed by a controller. + if (protect) { + setProtected(mesh); + } + + // Are we over our max? Test + if (_cache.size() > _maxElements && _treeRemover != null) { + _treeRemover.clean(_cache, _protectedList, _maxElements); + } + } + + /** + * removes a collision tree from the manager based on the mesh supplied. + * + * @param mesh + * the mesh to remove the corresponding collision tree. + */ + public void removeCollisionTree(final Mesh mesh) { + cacheRemove(mesh); + removeProtected(mesh); + } + + /** + * removes all collision trees associated with a Spatial object. + * + * @param object + * the spatial to remove all collision trees from. + */ + public void removeCollisionTree(final Spatial object) { + if (object instanceof Node) { + final Node n = (Node) object; + for (int i = n.getNumberOfChildren() - 1; i >= 0; i--) { + removeCollisionTree(n.getChild(i)); + } + } else if (object instanceof Mesh) { + removeCollisionTree((Mesh) object); + } + } + + /** + * updates the existing tree for a supplied mesh. If this tree does not exist, the tree is not updated. If the tree + * is not in the cache, no further operations are handled. + * + * @param mesh + * the mesh key for the tree to update. + */ + public void updateCollisionTree(final Mesh mesh) { + final CollisionTree ct = cacheGet(mesh); + if (ct != null) { + generateCollisionTree(ct, mesh, _protectedList != null && _protectedList.contains(mesh)); + } + } + + /** + * updates the existing tree(s) for a supplied spatial. If this tree does not exist, the tree is not updated. If the + * tree is not in the cache, no further operations are handled. + * + * @param object + * the object on which to update the tree. + */ + public void updateCollisionTree(final Spatial object) { + if (object instanceof Node) { + final Node n = (Node) object; + for (int i = n.getNumberOfChildren() - 1; i >= 0; i--) { + updateCollisionTree(n.getChild(i)); + } + } else if (object instanceof Mesh) { + updateCollisionTree((Mesh) object); + } + } + + /** + * returns true if the manager is set to sort new generated trees. False otherwise. + * + * @return true to sort tree, false otherwise. + */ + public boolean isDoSort() { + return _doSort; + } + + /** + * set if this manager should have newly generated trees sort primitives. + * + * @param doSort + * true to sort trees, false otherwise. + */ + public void setDoSort(final boolean doSort) { + _doSort = doSort; + } + + /** + * returns true if the manager will automatically generate new trees as needed, false otherwise. + * + * @return true if this manager is generating trees, false otherwise. + */ + public boolean isGenerateTrees() { + return _generateTrees; + } + + /** + * set if this manager should generate new trees as needed. + * + * @param generateTrees + * true to generate trees, false otherwise. + */ + public void setGenerateTrees(final boolean generateTrees) { + _generateTrees = generateTrees; + } + + /** + * @return the type of tree the manager will create. + * @see CollisionTree.Type + */ + public CollisionTree.Type getTreeType() { + return _treeType; + } + + /** + * @param treeType + * the type of tree to create. + * @see CollisionTree.Type + */ + public void setTreeType(final CollisionTree.Type treeType) { + _treeType = treeType; + } + + /** + * returns the maximum number of primitives a leaf of the collision tree may contain. + * + * @return the maximum number of primitives a leaf may contain. + */ + public int getMaxPrimitivesPerLeaf() { + return _maxPrimitivesPerLeaf; + } + + /** + * set the maximum number of primitives a leaf of the collision tree may contain. + * + * @param maxPrimitivesPerLeaf + * the maximum number of primitives a leaf may contain. + */ + public void setMaxPrimitivesPerLeaf(final int maxPrimitivesPerLeaf) { + _maxPrimitivesPerLeaf = maxPrimitivesPerLeaf; + } + + /** + * returns the maximum number of CollisionTree elements this manager will hold on to before starting to clear some. + * + * @return the maximum number of CollisionTree elements. + */ + public int getMaxElements() { + return _maxElements; + } + + /** + * set the maximum number of CollisionTree elements this manager will hold on to before starting to clear some. + * + * @param maxElements + * the maximum number of CollisionTree elements. + */ + public void setMaxElements(final int maxElements) { + _maxElements = maxElements; + } + + /** + * Add the given mesh to our "protected" list. This will signal to our cleanup operation that when deciding which + * trees to trim in an effort to keep our cache size to a certain desired size, do not trim the tree associated with + * this mesh. + * + * @param meshToProtect + * the mesh whose CollisionTree we want to protect. + */ + public void setProtected(final Mesh meshToProtect) { + if (!_protectedList.contains(meshToProtect)) { + _protectedList.add(meshToProtect); + } + } + + /** + * Removes the supplied mesh from the "protected" list. + * + * @param mesh + */ + public void removeProtected(final Mesh mesh) { + _protectedList.remove(mesh); + } + + /** + * + * @return an immutable copy of the list of protected meshes. + */ + public List<Mesh> getProtectedMeshes() { + return ImmutableList.copyOf(_protectedList); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java new file mode 100644 index 0000000..e3e77b0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java @@ -0,0 +1,1439 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.intersection.IntersectionRecord; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyPlane; +import com.ardor3d.math.type.ReadOnlyPlane.Side; +import com.ardor3d.math.type.ReadOnlyRay3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class OrientedBoundingBox extends BoundingVolume { + + private static final long serialVersionUID = 1L; + + /** X axis of the Oriented Box. */ + protected final Vector3 _xAxis = new Vector3(1, 0, 0); + + /** Y axis of the Oriented Box. */ + protected final Vector3 _yAxis = new Vector3(0, 1, 0); + + /** Z axis of the Oriented Box. */ + protected final Vector3 _zAxis = new Vector3(0, 0, 1); + + /** Extents of the box along the x,y,z axis. */ + protected final Vector3 _extent = new Vector3(0, 0, 0); + + /** Vector array used to store the array of 8 corners the box has. */ + protected final Vector3[] _vectorStore = new Vector3[8]; + + /** + * If true, the box's vectorStore array correctly represents the box's corners. + */ + public boolean correctCorners = false; + + protected final Vector3 _compVect3 = new Vector3(); + + public OrientedBoundingBox() { + for (int x = 0; x < 8; x++) { + _vectorStore[x] = new Vector3(); + } + } + + @Override + public Type getType() { + return Type.OBB; + } + + @Override + // XXX: HACK, revisit. + public BoundingVolume transform(final ReadOnlyTransform transform, BoundingVolume store) { + if (store == null || store.getType() != Type.OBB) { + store = new OrientedBoundingBox(); + } + final OrientedBoundingBox toReturn = (OrientedBoundingBox) store; + final Vector3 helper = new Vector3(); + helper.set(1, 0, 0); + final double scaleX = transform.applyForwardVector(helper).length(); + helper.set(0, 1, 0); + final double scaleY = transform.applyForwardVector(helper).length(); + helper.set(0, 0, 1); + final double scaleZ = transform.applyForwardVector(helper).length(); + toReturn._extent.set(Math.abs(_extent.getX() * scaleX), Math.abs(_extent.getY() * scaleY), + Math.abs(_extent.getZ() * scaleZ)); + + transform.getMatrix().applyPost(_xAxis, toReturn._xAxis); + transform.getMatrix().applyPost(_yAxis, toReturn._yAxis); + transform.getMatrix().applyPost(_zAxis, toReturn._zAxis); + if (!transform.isRotationMatrix()) { + toReturn._xAxis.normalizeLocal(); + toReturn._yAxis.normalizeLocal(); + toReturn._zAxis.normalizeLocal(); + } + + transform.applyForward(_center, toReturn._center); + toReturn.correctCorners = false; + toReturn.computeCorners(); + return toReturn; + } + + @Override + public Side whichSide(final ReadOnlyPlane plane) { + final ReadOnlyVector3 planeNormal = plane.getNormal(); + final double fRadius = Math.abs(_extent.getX() * (planeNormal.dot(_xAxis))) + + Math.abs(_extent.getY() * (planeNormal.dot(_yAxis))) + + Math.abs(_extent.getZ() * (planeNormal.dot(_zAxis))); + final double fDistance = plane.pseudoDistance(_center); + if (fDistance <= -fRadius) { + return Plane.Side.Inside; + } else if (fDistance >= fRadius) { + return Plane.Side.Outside; + } else { + return Plane.Side.Neither; + } + } + + @Override + public void computeFromPoints(final FloatBuffer points) { + containAABB(points); + } + + /** + * Calculates an AABB of the given point values for this OBB. + * + * @param points + * The points this OBB should contain. + */ + private void containAABB(final FloatBuffer points) { + if (points == null || points.limit() <= 2) { // we need at least a 3 + // double vector + return; + } + + BufferUtils.populateFromBuffer(_compVect1, points, 0); + double minX = _compVect1.getX(), minY = _compVect1.getY(), minZ = _compVect1.getZ(); + double maxX = _compVect1.getX(), maxY = _compVect1.getY(), maxZ = _compVect1.getZ(); + + for (int i = 1, len = points.limit() / 3; i < len; i++) { + BufferUtils.populateFromBuffer(_compVect1, points, i); + + minX = Math.min(_compVect1.getX(), minX); + maxX = Math.max(_compVect1.getX(), maxX); + + minY = Math.min(_compVect1.getY(), minY); + maxY = Math.max(_compVect1.getY(), maxY); + + minZ = Math.min(_compVect1.getZ(), minZ); + maxZ = Math.max(_compVect1.getZ(), maxZ); + } + + _center.set(minX + maxX, minY + maxY, minZ + maxZ); + _center.multiplyLocal(0.5); + + _extent.set(maxX - _center.getX(), maxY - _center.getY(), maxZ - _center.getZ()); + + _xAxis.set(1, 0, 0); + _yAxis.set(0, 1, 0); + _zAxis.set(0, 0, 1); + + correctCorners = false; + } + + @Override + public BoundingVolume merge(final BoundingVolume volume) { + // clone ourselves into a new bounding volume, then merge. + return clone(new OrientedBoundingBox()).mergeLocal(volume); + } + + @Override + public BoundingVolume mergeLocal(final BoundingVolume volume) { + if (volume == null) { + return this; + } + + switch (volume.getType()) { + + case OBB: { + return mergeOBB((OrientedBoundingBox) volume); + } + + case AABB: { + return mergeAABB((BoundingBox) volume); + } + + case Sphere: { + return mergeSphere((BoundingSphere) volume); + } + + default: + return null; + + } + } + + private BoundingVolume mergeSphere(final BoundingSphere volume) { + // check for infinite bounds to prevent NaN values + if (Vector3.isInfinite(getExtent()) || Double.isInfinite(volume.getRadius())) { + setCenter(Vector3.ZERO); + _extent.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + return this; + } + + final BoundingSphere mergeSphere = volume; + if (!correctCorners) { + computeCorners(); + } + + final FloatBuffer mergeBuf = BufferUtils.createFloatBufferOnHeap(16 * 3); + + mergeBuf.rewind(); + for (int i = 0; i < 8; i++) { + mergeBuf.put((float) _vectorStore[i].getX()); + mergeBuf.put((float) _vectorStore[i].getY()); + mergeBuf.put((float) _vectorStore[i].getZ()); + } + mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius())); + mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius())) + .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius())); + containAABB(mergeBuf); + correctCorners = false; + return this; + } + + private BoundingVolume mergeAABB(final BoundingBox volume) { + // check for infinite bounds to prevent NaN values + if (Vector3.isInfinite(getExtent()) || Double.isInfinite(volume.getXExtent()) + || Double.isInfinite(volume.getYExtent()) || Double.isInfinite(volume.getZExtent())) { + setCenter(Vector3.ZERO); + _extent.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + return this; + } + + final BoundingBox mergeBox = volume; + if (!correctCorners) { + computeCorners(); + } + + final FloatBuffer mergeBuf = BufferUtils.createFloatBufferOnHeap(16 * 3); + + mergeBuf.rewind(); + for (int i = 0; i < 8; i++) { + mergeBuf.put((float) _vectorStore[i].getX()); + mergeBuf.put((float) _vectorStore[i].getY()); + mergeBuf.put((float) _vectorStore[i].getZ()); + } + mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() + mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() + mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() - mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() + mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() - mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() + mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() - mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent())); + mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent())) + .put((float) (mergeBox._center.getY() - mergeBox.getYExtent())) + .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent())); + containAABB(mergeBuf); + correctCorners = false; + return this; + } + + private BoundingVolume mergeOBB(final OrientedBoundingBox volume) { + // check for infinite bounds to prevent NaN values + if (Vector3.isInfinite(getExtent()) || Vector3.isInfinite(volume.getExtent())) { + setCenter(Vector3.ZERO); + _extent.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + return this; + } + + // OrientedBoundingBox mergeBox=(OrientedBoundingBox) volume; + // if (!correctCorners) this.computeCorners(); + // if (!mergeBox.correctCorners) mergeBox.computeCorners(); + // Vector3[] mergeArray=new Vector3[16]; + // for (int i=0;i<vectorStore.length;i++){ + // mergeArray[i*2+0]=this .vectorStore[i]; + // mergeArray[i*2+1]=mergeBox.vectorStore[i]; + // } + // containAABB(mergeArray); + // correctCorners=false; + // return this; + // construct a box that contains the input boxes + // Box3<Real> kBox; + final OrientedBoundingBox rkBox0 = this; + final OrientedBoundingBox rkBox1 = volume; + + // The first guess at the box center. This value will be updated later + // after the input box vertices are projected onto axes determined by an + // average of box axes. + final Vector3 kBoxCenter = (rkBox0._center.add(rkBox1._center, Vector3.fetchTempInstance())).multiplyLocal(.5); + + // A box's axes, when viewed as the columns of a matrix, form a rotation + // matrix. The input box axes are converted to quaternions. The average + // quaternion is computed, then normalized to unit length. The result is + // the slerp of the two input quaternions with t-value of 1/2. The + // result is converted back to a rotation matrix and its columns are + // selected as the merged box axes. + final Quaternion kQ0 = Quaternion.fetchTempInstance(), kQ1 = Quaternion.fetchTempInstance(); + kQ0.fromAxes(rkBox0._xAxis, rkBox0._yAxis, rkBox0._zAxis); + kQ1.fromAxes(rkBox1._xAxis, rkBox1._yAxis, rkBox1._zAxis); + + if (kQ0.dot(kQ1) < 0.0) { + kQ1.multiplyLocal(-1.0); + } + + final Quaternion kQ = kQ0.addLocal(kQ1); + kQ.normalizeLocal(); + + final Matrix3 kBoxaxis = kQ.toRotationMatrix(Matrix3.fetchTempInstance()); + final Vector3 newXaxis = kBoxaxis.getColumn(0, Vector3.fetchTempInstance()); + final Vector3 newYaxis = kBoxaxis.getColumn(1, Vector3.fetchTempInstance()); + final Vector3 newZaxis = kBoxaxis.getColumn(2, Vector3.fetchTempInstance()); + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // pmin[i] and a maximum projected value pmax[i]. The corresponding end + // points on the axes are C+pmin[i]*D[i] and C+pmax[i]*D[i]. The point C + // is not necessarily the midpoint for any of the intervals. The actual + // box center will be adjusted from C to a point C' that is the midpoint + // of each interval, + // C' = C + sum_{i=0}^1 0.5*(pmin[i]+pmax[i])*D[i] + // The box extents are + // e[i] = 0.5*(pmax[i]-pmin[i]) + + int i; + double fDot; + final Vector3 kDiff = Vector3.fetchTempInstance(); + final Vector3 kMin = Vector3.fetchTempInstance(); + final Vector3 kMax = Vector3.fetchTempInstance(); + + if (!rkBox0.correctCorners) { + rkBox0.computeCorners(); + } + for (i = 0; i < 8; i++) { + rkBox0._vectorStore[i].subtract(kBoxCenter, kDiff); + + fDot = kDiff.dot(newXaxis); + if (fDot > kMax.getX()) { + kMax.setX(fDot); + } else if (fDot < kMin.getX()) { + kMin.setX(fDot); + } + + fDot = kDiff.dot(newYaxis); + if (fDot > kMax.getY()) { + kMax.setY(fDot); + } else if (fDot < kMin.getY()) { + kMin.setY(fDot); + } + + fDot = kDiff.dot(newZaxis); + if (fDot > kMax.getZ()) { + kMax.setZ(fDot); + } else if (fDot < kMin.getZ()) { + kMin.setZ(fDot); + } + + } + + if (!rkBox1.correctCorners) { + rkBox1.computeCorners(); + } + for (i = 0; i < 8; i++) { + rkBox1._vectorStore[i].subtract(kBoxCenter, kDiff); + + fDot = kDiff.dot(newXaxis); + if (fDot > kMax.getX()) { + kMax.setX(fDot); + } else if (fDot < kMin.getX()) { + kMin.setX(fDot); + } + + fDot = kDiff.dot(newYaxis); + if (fDot > kMax.getY()) { + kMax.setY(fDot); + } else if (fDot < kMin.getY()) { + kMin.setY(fDot); + } + + fDot = kDiff.dot(newZaxis); + if (fDot > kMax.getZ()) { + kMax.setZ(fDot); + } else if (fDot < kMin.getZ()) { + kMin.setZ(fDot); + } + } + + _xAxis.set(newXaxis); + _yAxis.set(newYaxis); + _zAxis.set(newZaxis); + + final Vector3 tempVec = Vector3.fetchTempInstance(); + _extent.setX(.5 * (kMax.getX() - kMin.getX())); + kBoxCenter.addLocal(_xAxis.multiply(.5 * (kMax.getX() + kMin.getX()), tempVec)); + + _extent.setY(.5 * (kMax.getY() - kMin.getY())); + kBoxCenter.addLocal(_yAxis.multiply(.5 * (kMax.getY() + kMin.getY()), tempVec)); + + _extent.setZ(.5 * (kMax.getZ() - kMin.getZ())); + kBoxCenter.addLocal(_zAxis.multiply(.5 * (kMax.getZ() + kMin.getZ()), tempVec)); + + _center.set(kBoxCenter); + + correctCorners = false; + + Quaternion.releaseTempInstance(kQ0); + Quaternion.releaseTempInstance(kQ1); + Matrix3.releaseTempInstance(kBoxaxis); + Vector3.releaseTempInstance(kBoxCenter); + Vector3.releaseTempInstance(newXaxis); + Vector3.releaseTempInstance(newYaxis); + Vector3.releaseTempInstance(newZaxis); + Vector3.releaseTempInstance(kDiff); + Vector3.releaseTempInstance(kMin); + Vector3.releaseTempInstance(kMax); + Vector3.releaseTempInstance(tempVec); + + return this; + } + + @Override + public BoundingVolume clone(final BoundingVolume store) { + OrientedBoundingBox toReturn; + if (store instanceof OrientedBoundingBox) { + toReturn = (OrientedBoundingBox) store; + } else { + toReturn = new OrientedBoundingBox(); + } + toReturn._extent.set(_extent); + toReturn._xAxis.set(_xAxis); + toReturn._yAxis.set(_yAxis); + toReturn._zAxis.set(_zAxis); + toReturn._center.set(_center); + toReturn._checkPlane = _checkPlane; + for (int x = _vectorStore.length; --x >= 0;) { + toReturn._vectorStore[x].set(_vectorStore[x]); + } + toReturn.correctCorners = correctCorners; + return toReturn; + } + + @Override + public double getRadius() { + double radius = 0.0; + radius = Math.max(radius, _xAxis.multiply(_extent.getX(), _compVect1).length()); + radius = Math.max(radius, _yAxis.multiply(_extent.getY(), _compVect1).length()); + radius = Math.max(radius, _zAxis.multiply(_extent.getZ(), _compVect1).length()); + + return radius; + } + + /** + * Sets the vectorStore information to the 8 corners of the box. + */ + public void computeCorners() { + final Vector3 tempAxis0 = _xAxis.multiply(_extent.getX(), _compVect1); + final Vector3 tempAxis1 = _yAxis.multiply(_extent.getY(), _compVect2); + final Vector3 tempAxis2 = _zAxis.multiply(_extent.getZ(), _compVect3); + + _vectorStore[0].set(_center).subtractLocal(tempAxis0).subtractLocal(tempAxis1).subtractLocal(tempAxis2); + _vectorStore[1].set(_center).addLocal(tempAxis0).subtractLocal(tempAxis1).subtractLocal(tempAxis2); + _vectorStore[2].set(_center).addLocal(tempAxis0).addLocal(tempAxis1).subtractLocal(tempAxis2); + _vectorStore[3].set(_center).subtractLocal(tempAxis0).addLocal(tempAxis1).subtractLocal(tempAxis2); + _vectorStore[4].set(_center).subtractLocal(tempAxis0).subtractLocal(tempAxis1).addLocal(tempAxis2); + _vectorStore[5].set(_center).addLocal(tempAxis0).subtractLocal(tempAxis1).addLocal(tempAxis2); + _vectorStore[6].set(_center).addLocal(tempAxis0).addLocal(tempAxis1).addLocal(tempAxis2); + _vectorStore[7].set(_center).subtractLocal(tempAxis0).addLocal(tempAxis1).addLocal(tempAxis2); + + correctCorners = true; + } + + @Override + public void computeFromPrimitives(final MeshData data, final int section, final int[] indices, final int start, + final int end) { + if (end - start <= 0) { + return; + } + + final int vertsPerPrimitive = data.getIndexMode(section).getVertexCount(); + Vector3[] store = new Vector3[vertsPerPrimitive]; + + final Vector3 min = _compVect1 + .set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + final Vector3 max = _compVect2 + .set(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + + Vector3 point; + for (int i = start; i < end; i++) { + store = data.getPrimitiveVertices(indices[i], section, store); + for (int j = 0; j < vertsPerPrimitive; j++) { + point = store[j]; + if (point.getX() < min.getX()) { + min.setX(point.getX()); + } else if (point.getX() > max.getX()) { + max.setX(point.getX()); + } + if (point.getY() < min.getY()) { + min.setY(point.getY()); + } else if (point.getY() > max.getY()) { + max.setY(point.getY()); + } + if (point.getZ() < min.getZ()) { + min.setZ(point.getZ()); + } else if (point.getZ() > max.getZ()) { + max.setZ(point.getZ()); + } + } + } + + _center.set(min.addLocal(max)); + _center.multiplyLocal(0.5); + + _extent.set(max.getX() - _center.getX(), max.getY() - _center.getY(), max.getZ() - _center.getZ()); + + _xAxis.set(1, 0, 0); + _yAxis.set(0, 1, 0); + _zAxis.set(0, 0, 1); + + correctCorners = false; + } + + public boolean intersection(final OrientedBoundingBox box1) { + // Cutoff for cosine of angles between box axes. This is used to catch the cases when at least one pair of axes + // are parallel. If this happens, there is no need to test for separation along the Cross(A[i],B[j]) directions. + final OrientedBoundingBox box0 = this; + final double cutoff = 0.999999; + boolean parallelPairExists = false; + int i; + + // convenience variables + final ReadOnlyVector3 akA[] = new ReadOnlyVector3[] { box0.getXAxis(), box0.getYAxis(), box0.getZAxis() }; + final ReadOnlyVector3[] akB = new ReadOnlyVector3[] { box1.getXAxis(), box1.getYAxis(), box1.getZAxis() }; + final ReadOnlyVector3 afEA = box0._extent; + final ReadOnlyVector3 afEB = box1._extent; + + // compute difference of box centers, D = C1-C0 + final Vector3 kD = box1._center.subtract(box0._center, _compVect1); + + final double[][] aafC = { new double[3], new double[3], new double[3] }; + + final double[][] aafAbsC = { new double[3], new double[3], new double[3] }; + + final double[] afAD = new double[3]; + double fR0, fR1, fR; // interval radii and distance between centers + double fR01; // = R0 + R1 + + // axis C0+t*A0 + for (i = 0; i < 3; i++) { + aafC[0][i] = akA[0].dot(akB[i]); + aafAbsC[0][i] = Math.abs(aafC[0][i]); + if (aafAbsC[0][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[0] = akA[0].dot(kD); + fR = Math.abs(afAD[0]); + fR1 = afEB.getX() * aafAbsC[0][0] + afEB.getY() * aafAbsC[0][1] + afEB.getZ() * aafAbsC[0][2]; + fR01 = afEA.getX() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1 + for (i = 0; i < 3; i++) { + aafC[1][i] = akA[1].dot(akB[i]); + aafAbsC[1][i] = Math.abs(aafC[1][i]); + if (aafAbsC[1][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[1] = akA[1].dot(kD); + fR = Math.abs(afAD[1]); + fR1 = afEB.getX() * aafAbsC[1][0] + afEB.getY() * aafAbsC[1][1] + afEB.getZ() * aafAbsC[1][2]; + fR01 = afEA.getY() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2 + for (i = 0; i < 3; i++) { + aafC[2][i] = akA[2].dot(akB[i]); + aafAbsC[2][i] = Math.abs(aafC[2][i]); + if (aafAbsC[2][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[2] = akA[2].dot(kD); + fR = Math.abs(afAD[2]); + fR1 = afEB.getX() * aafAbsC[2][0] + afEB.getY() * aafAbsC[2][1] + afEB.getZ() * aafAbsC[2][2]; + fR01 = afEA.getZ() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*B0 + fR = Math.abs(akB[0].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][0] + afEA.getY() * aafAbsC[1][0] + afEA.getZ() * aafAbsC[2][0]; + fR01 = fR0 + afEB.getX(); + if (fR > fR01) { + return false; + } + + // axis C0+t*B1 + fR = Math.abs(akB[1].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][1] + afEA.getY() * aafAbsC[1][1] + afEA.getZ() * aafAbsC[2][1]; + fR01 = fR0 + afEB.getY(); + if (fR > fR01) { + return false; + } + + // axis C0+t*B2 + fR = Math.abs(akB[2].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][2] + afEA.getY() * aafAbsC[1][2] + afEA.getZ() * aafAbsC[2][2]; + fR01 = fR0 + afEB.getZ(); + if (fR > fR01) { + return false; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D where checking the "edge" normals is sufficient for + // the separation of the boxes. + if (parallelPairExists) { + return true; + } + + // axis C0+t*A0xB0 + fR = Math.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]); + fR0 = afEA.getY() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[1][0]; + fR1 = afEB.getY() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A0xB1 + fR = Math.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]); + fR0 = afEA.getY() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[1][1]; + fR1 = afEB.getX() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A0xB2 + fR = Math.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]); + fR0 = afEA.getY() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[1][2]; + fR1 = afEB.getX() * aafAbsC[0][1] + afEB.getY() * aafAbsC[0][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB0 + fR = Math.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]); + fR0 = afEA.getX() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[0][0]; + fR1 = afEB.getY() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB1 + fR = Math.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]); + fR0 = afEA.getX() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[0][1]; + fR1 = afEB.getX() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB2 + fR = Math.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]); + fR0 = afEA.getX() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[0][2]; + fR1 = afEB.getX() * aafAbsC[1][1] + afEB.getY() * aafAbsC[1][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB0 + fR = Math.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]); + fR0 = afEA.getX() * aafAbsC[1][0] + afEA.getY() * aafAbsC[0][0]; + fR1 = afEB.getY() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB1 + fR = Math.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]); + fR0 = afEA.getX() * aafAbsC[1][1] + afEA.getY() * aafAbsC[0][1]; + fR1 = afEB.getX() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB2 + fR = Math.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]); + fR0 = afEA.getX() * aafAbsC[1][2] + afEA.getY() * aafAbsC[0][2]; + fR1 = afEB.getX() * aafAbsC[2][1] + afEB.getY() * aafAbsC[2][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + return true; + } + + @Override + public boolean intersects(final BoundingVolume bv) { + if (bv == null) { + return false; + } + + return bv.intersectsOrientedBoundingBox(this); + } + + @Override + public boolean intersectsSphere(final BoundingSphere bs) { + if (!Vector3.isValid(_center) || !Vector3.isValid(bs._center)) { + return false; + } + + _compVect1.set(bs.getCenter()).subtractLocal(_center); + final Matrix3 tempMa = Matrix3.fetchTempInstance().fromAxes(_xAxis, _yAxis, _zAxis); + + tempMa.applyPost(_compVect1, _compVect1); + + boolean result = false; + if (Math.abs(_compVect1.getX()) < bs.getRadius() + _extent.getX() + && Math.abs(_compVect1.getY()) < bs.getRadius() + _extent.getY() + && Math.abs(_compVect1.getZ()) < bs.getRadius() + _extent.getZ()) { + result = true; + } + + Matrix3.releaseTempInstance(tempMa); + return result; + } + + @Override + public boolean intersectsBoundingBox(final BoundingBox bb) { + if (!Vector3.isValid(_center) || !Vector3.isValid(bb._center)) { + return false; + } + + // Cutoff for cosine of angles between box axes. This is used to catch + // the cases when at least one pair of axes are parallel. If this + // happens, + // there is no need to test for separation along the Cross(A[i],B[j]) + // directions. + final double cutoff = 0.999999f; + boolean parallelPairExists = false; + int i; + + // convenience variables + final Vector3 akA[] = new Vector3[] { _xAxis, _yAxis, _zAxis }; + final Vector3[] akB = new Vector3[] { Vector3.fetchTempInstance(), Vector3.fetchTempInstance(), + Vector3.fetchTempInstance() }; + final Vector3 afEA = _extent; + final Vector3 afEB = Vector3.fetchTempInstance().set(bb.getXExtent(), bb.getYExtent(), bb.getZExtent()); + + // compute difference of box centers, D = C1-C0 + final Vector3 kD = bb.getCenter().subtract(_center, Vector3.fetchTempInstance()); + + final double[][] aafC = { new double[3], new double[3], new double[3] }; + + final double[][] aafAbsC = { new double[3], new double[3], new double[3] }; + + final double[] afAD = new double[3]; + double fR0, fR1, fR; // interval radii and distance between centers + double fR01; // = R0 + R1 + + try { + + // axis C0+t*A0 + for (i = 0; i < 3; i++) { + aafC[0][i] = akA[0].dot(akB[i]); + aafAbsC[0][i] = Math.abs(aafC[0][i]); + if (aafAbsC[0][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[0] = akA[0].dot(kD); + fR = Math.abs(afAD[0]); + fR1 = afEB.getX() * aafAbsC[0][0] + afEB.getY() * aafAbsC[0][1] + afEB.getZ() * aafAbsC[0][2]; + fR01 = afEA.getX() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1 + for (i = 0; i < 3; i++) { + aafC[1][i] = akA[1].dot(akB[i]); + aafAbsC[1][i] = Math.abs(aafC[1][i]); + if (aafAbsC[1][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[1] = akA[1].dot(kD); + fR = Math.abs(afAD[1]); + fR1 = afEB.getX() * aafAbsC[1][0] + afEB.getY() * aafAbsC[1][1] + afEB.getZ() * aafAbsC[1][2]; + fR01 = afEA.getY() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2 + for (i = 0; i < 3; i++) { + aafC[2][i] = akA[2].dot(akB[i]); + aafAbsC[2][i] = Math.abs(aafC[2][i]); + if (aafAbsC[2][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[2] = akA[2].dot(kD); + fR = Math.abs(afAD[2]); + fR1 = afEB.getX() * aafAbsC[2][0] + afEB.getY() * aafAbsC[2][1] + afEB.getZ() * aafAbsC[2][2]; + fR01 = afEA.getZ() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*B0 + fR = Math.abs(akB[0].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][0] + afEA.getY() * aafAbsC[1][0] + afEA.getZ() * aafAbsC[2][0]; + fR01 = fR0 + afEB.getX(); + if (fR > fR01) { + return false; + } + + // axis C0+t*B1 + fR = Math.abs(akB[1].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][1] + afEA.getY() * aafAbsC[1][1] + afEA.getZ() * aafAbsC[2][1]; + fR01 = fR0 + afEB.getY(); + if (fR > fR01) { + return false; + } + + // axis C0+t*B2 + fR = Math.abs(akB[2].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][2] + afEA.getY() * aafAbsC[1][2] + afEA.getZ() * aafAbsC[2][2]; + fR01 = fR0 + afEB.getZ(); + if (fR > fR01) { + return false; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D where checking the "edge" normals is sufficient for + // the separation of the boxes. + if (parallelPairExists) { + return true; + } + + // axis C0+t*A0xB0 + fR = Math.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]); + fR0 = afEA.getY() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[1][0]; + fR1 = afEB.getY() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A0xB1 + fR = Math.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]); + fR0 = afEA.getY() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[1][1]; + fR1 = afEB.getX() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A0xB2 + fR = Math.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]); + fR0 = afEA.getY() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[1][2]; + fR1 = afEB.getX() * aafAbsC[0][1] + afEB.getY() * aafAbsC[0][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB0 + fR = Math.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]); + fR0 = afEA.getX() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[0][0]; + fR1 = afEB.getY() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB1 + fR = Math.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]); + fR0 = afEA.getX() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[0][1]; + fR1 = afEB.getX() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB2 + fR = Math.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]); + fR0 = afEA.getX() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[0][2]; + fR1 = afEB.getX() * aafAbsC[1][1] + afEB.getY() * aafAbsC[1][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB0 + fR = Math.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]); + fR0 = afEA.getX() * aafAbsC[1][0] + afEA.getY() * aafAbsC[0][0]; + fR1 = afEB.getY() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB1 + fR = Math.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]); + fR0 = afEA.getX() * aafAbsC[1][1] + afEA.getY() * aafAbsC[0][1]; + fR1 = afEB.getX() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB2 + fR = Math.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]); + fR0 = afEA.getX() * aafAbsC[1][2] + afEA.getY() * aafAbsC[0][2]; + fR1 = afEB.getX() * aafAbsC[2][1] + afEB.getY() * aafAbsC[2][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + return true; + } finally { + // Make sure we release the temp vars + Vector3.releaseTempInstance(kD); + Vector3.releaseTempInstance(afEB); + for (final Vector3 vec : akB) { + Vector3.releaseTempInstance(vec); + } + + } + } + + @Override + public boolean intersectsOrientedBoundingBox(final OrientedBoundingBox obb) { + if (!Vector3.isValid(_center) || !Vector3.isValid(obb._center)) { + return false; + } + + // Cutoff for cosine of angles between box axes. This is used to catch + // the cases when at least one pair of axes are parallel. If this + // happens, + // there is no need to test for separation along the Cross(A[i],B[j]) + // directions. + final double cutoff = 0.999999f; + boolean parallelPairExists = false; + int i; + + // convenience variables + final Vector3 akA[] = new Vector3[] { _xAxis, _yAxis, _zAxis }; + final Vector3[] akB = new Vector3[] { obb._xAxis, obb._yAxis, obb._zAxis }; + final Vector3 afEA = _extent; + final Vector3 afEB = obb._extent; + + // compute difference of box centers, D = C1-C0 + final Vector3 kD = obb._center.subtract(_center, _compVect1); + + final double[][] aafC = { new double[3], new double[3], new double[3] }; + + final double[][] aafAbsC = { new double[3], new double[3], new double[3] }; + + final double[] afAD = new double[3]; + double fR0, fR1, fR; // interval radii and distance between centers + double fR01; // = R0 + R1 + + // axis C0+t*A0 + for (i = 0; i < 3; i++) { + aafC[0][i] = akA[0].dot(akB[i]); + aafAbsC[0][i] = Math.abs(aafC[0][i]); + if (aafAbsC[0][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[0] = akA[0].dot(kD); + fR = Math.abs(afAD[0]); + fR1 = afEB.getX() * aafAbsC[0][0] + afEB.getY() * aafAbsC[0][1] + afEB.getZ() * aafAbsC[0][2]; + fR01 = afEA.getX() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1 + for (i = 0; i < 3; i++) { + aafC[1][i] = akA[1].dot(akB[i]); + aafAbsC[1][i] = Math.abs(aafC[1][i]); + if (aafAbsC[1][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[1] = akA[1].dot(kD); + fR = Math.abs(afAD[1]); + fR1 = afEB.getX() * aafAbsC[1][0] + afEB.getY() * aafAbsC[1][1] + afEB.getZ() * aafAbsC[1][2]; + fR01 = afEA.getY() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2 + for (i = 0; i < 3; i++) { + aafC[2][i] = akA[2].dot(akB[i]); + aafAbsC[2][i] = Math.abs(aafC[2][i]); + if (aafAbsC[2][i] > cutoff) { + parallelPairExists = true; + } + } + afAD[2] = akA[2].dot(kD); + fR = Math.abs(afAD[2]); + fR1 = afEB.getX() * aafAbsC[2][0] + afEB.getY() * aafAbsC[2][1] + afEB.getZ() * aafAbsC[2][2]; + fR01 = afEA.getZ() + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*B0 + fR = Math.abs(akB[0].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][0] + afEA.getY() * aafAbsC[1][0] + afEA.getZ() * aafAbsC[2][0]; + fR01 = fR0 + afEB.getX(); + if (fR > fR01) { + return false; + } + + // axis C0+t*B1 + fR = Math.abs(akB[1].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][1] + afEA.getY() * aafAbsC[1][1] + afEA.getZ() * aafAbsC[2][1]; + fR01 = fR0 + afEB.getY(); + if (fR > fR01) { + return false; + } + + // axis C0+t*B2 + fR = Math.abs(akB[2].dot(kD)); + fR0 = afEA.getX() * aafAbsC[0][2] + afEA.getY() * aafAbsC[1][2] + afEA.getZ() * aafAbsC[2][2]; + fR01 = fR0 + afEB.getZ(); + if (fR > fR01) { + return false; + } + + // At least one pair of box axes was parallel, so the separation is + // effectively in 2D where checking the "edge" normals is sufficient for + // the separation of the boxes. + if (parallelPairExists) { + return true; + } + + // axis C0+t*A0xB0 + fR = Math.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]); + fR0 = afEA.getY() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[1][0]; + fR1 = afEB.getY() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A0xB1 + fR = Math.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]); + fR0 = afEA.getY() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[1][1]; + fR1 = afEB.getX() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A0xB2 + fR = Math.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]); + fR0 = afEA.getY() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[1][2]; + fR1 = afEB.getX() * aafAbsC[0][1] + afEB.getY() * aafAbsC[0][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB0 + fR = Math.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]); + fR0 = afEA.getX() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[0][0]; + fR1 = afEB.getY() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB1 + fR = Math.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]); + fR0 = afEA.getX() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[0][1]; + fR1 = afEB.getX() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A1xB2 + fR = Math.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]); + fR0 = afEA.getX() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[0][2]; + fR1 = afEB.getX() * aafAbsC[1][1] + afEB.getY() * aafAbsC[1][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB0 + fR = Math.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]); + fR0 = afEA.getX() * aafAbsC[1][0] + afEA.getY() * aafAbsC[0][0]; + fR1 = afEB.getY() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][1]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB1 + fR = Math.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]); + fR0 = afEA.getX() * aafAbsC[1][1] + afEA.getY() * aafAbsC[0][1]; + fR1 = afEB.getX() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + // axis C0+t*A2xB2 + fR = Math.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]); + fR0 = afEA.getX() * aafAbsC[1][2] + afEA.getY() * aafAbsC[0][2]; + fR1 = afEB.getX() * aafAbsC[2][1] + afEB.getY() * aafAbsC[2][0]; + fR01 = fR0 + fR1; + if (fR > fR01) { + return false; + } + + return true; + } + + @Override + public boolean intersects(final ReadOnlyRay3 ray) { + if (!Vector3.isValid(_center)) { + return false; + } + + double rhs; + final ReadOnlyVector3 rayDir = ray.getDirection(); + final Vector3 diff = _compVect1.set(ray.getOrigin()).subtractLocal(_center); + final Vector3 wCrossD = _compVect2; + + final double[] fWdU = new double[3]; + final double[] fAWdU = new double[3]; + final double[] fDdU = new double[3]; + final double[] fADdU = new double[3]; + final double[] fAWxDdU = new double[3]; + + fWdU[0] = rayDir.dot(_xAxis); + fAWdU[0] = Math.abs(fWdU[0]); + fDdU[0] = diff.dot(_xAxis); + fADdU[0] = Math.abs(fDdU[0]); + if (fADdU[0] > _extent.getX() && fDdU[0] * fWdU[0] >= 0.0) { + return false; + } + + fWdU[1] = rayDir.dot(_yAxis); + fAWdU[1] = Math.abs(fWdU[1]); + fDdU[1] = diff.dot(_yAxis); + fADdU[1] = Math.abs(fDdU[1]); + if (fADdU[1] > _extent.getY() && fDdU[1] * fWdU[1] >= 0.0) { + return false; + } + + fWdU[2] = rayDir.dot(_zAxis); + fAWdU[2] = Math.abs(fWdU[2]); + fDdU[2] = diff.dot(_zAxis); + fADdU[2] = Math.abs(fDdU[2]); + if (fADdU[2] > _extent.getZ() && fDdU[2] * fWdU[2] >= 0.0) { + return false; + } + + rayDir.cross(diff, wCrossD); + + fAWxDdU[0] = Math.abs(wCrossD.dot(_xAxis)); + rhs = _extent.getY() * fAWdU[2] + _extent.getZ() * fAWdU[1]; + if (fAWxDdU[0] > rhs) { + return false; + } + + fAWxDdU[1] = Math.abs(wCrossD.dot(_yAxis)); + rhs = _extent.getX() * fAWdU[2] + _extent.getZ() * fAWdU[0]; + if (fAWxDdU[1] > rhs) { + return false; + } + + fAWxDdU[2] = Math.abs(wCrossD.dot(_zAxis)); + rhs = _extent.getX() * fAWdU[1] + _extent.getY() * fAWdU[0]; + if (fAWxDdU[2] > rhs) { + return false; + + } + + return true; + } + + @Override + public IntersectionRecord intersectsWhere(final ReadOnlyRay3 ray) { + final ReadOnlyVector3 rayDir = ray.getDirection(); + final ReadOnlyVector3 rayOrigin = ray.getOrigin(); + + // convert ray to box coordinates + final Vector3 diff = rayOrigin.subtract(getCenter(), _compVect1); + diff.set(_xAxis.dot(diff), _yAxis.dot(diff), _zAxis.dot(diff)); + final Vector3 direction = _compVect2.set(_xAxis.dot(rayDir), _yAxis.dot(rayDir), _zAxis.dot(rayDir)); + + final double[] t = { 0, Double.POSITIVE_INFINITY }; + + final double saveT0 = t[0], saveT1 = t[1]; + final boolean notEntirelyClipped = clip(+direction.getX(), -diff.getX() - _extent.getX(), t) + && clip(-direction.getX(), +diff.getX() - _extent.getX(), t) + && clip(+direction.getY(), -diff.getY() - _extent.getY(), t) + && clip(-direction.getY(), +diff.getY() - _extent.getY(), t) + && clip(+direction.getZ(), -diff.getZ() - _extent.getZ(), t) + && clip(-direction.getZ(), +diff.getZ() - _extent.getZ(), t); + + if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) { + if (t[1] > t[0]) { + final double[] distances = t; + final Vector3[] points = new Vector3[] { + rayDir.multiply(distances[0], new Vector3()).addLocal(rayOrigin), + rayDir.multiply(distances[1], new Vector3()).addLocal(rayOrigin) }; + final IntersectionRecord record = new IntersectionRecord(distances, points); + return record; + } + + final double[] distances = new double[] { t[0] }; + final Vector3[] points = new Vector3[] { rayDir.multiply(distances[0], new Vector3()).addLocal(rayOrigin) }; + final IntersectionRecord record = new IntersectionRecord(distances, points); + return record; + } + + return null; + + } + + /** + * <code>clip</code> determines if a line segment intersects the current test plane. + * + * @param denom + * the denominator of the line segment. + * @param numer + * the numerator of the line segment. + * @param t + * test values of the plane. + * @return true if the line segment intersects the plane, false otherwise. + */ + private boolean clip(final double denom, final double numer, final double[] t) { + // Return value is 'true' if line segment intersects the current test + // plane. Otherwise 'false' is returned in which case the line segment + // is entirely clipped. + if (denom > 0.0) { + if (numer > denom * t[1]) { + return false; + } + if (numer > denom * t[0]) { + t[0] = numer / denom; + } + return true; + } else if (denom < 0.0) { + if (numer > denom * t[0]) { + return false; + } + if (numer > denom * t[1]) { + t[1] = numer / denom; + } + return true; + } else { + return numer <= 0.0; + } + } + + public void setXAxis(final ReadOnlyVector3 axis) { + _xAxis.set(axis); + correctCorners = false; + } + + public void setYAxis(final ReadOnlyVector3 axis) { + _yAxis.set(axis); + correctCorners = false; + } + + public void setZAxis(final ReadOnlyVector3 axis) { + _zAxis.set(axis); + correctCorners = false; + } + + public void setExtent(final ReadOnlyVector3 ext) { + _extent.set(ext); + correctCorners = false; + } + + public ReadOnlyVector3 getXAxis() { + return _xAxis; + } + + public ReadOnlyVector3 getYAxis() { + return _yAxis; + } + + public ReadOnlyVector3 getZAxis() { + return _zAxis; + } + + public ReadOnlyVector3 getExtent() { + return _extent; + } + + @Override + public boolean contains(final ReadOnlyVector3 point) { + _compVect1.set(point).subtractLocal(_center); + double coeff = _compVect1.dot(_xAxis); + if (Math.abs(coeff) > _extent.getX()) { + return false; + } + + coeff = _compVect1.dot(_yAxis); + if (Math.abs(coeff) > _extent.getY()) { + return false; + } + + coeff = _compVect1.dot(_zAxis); + if (Math.abs(coeff) > _extent.getZ()) { + return false; + } + + return true; + } + + @Override + public double distanceToEdge(final ReadOnlyVector3 point) { + // compute coordinates of point in box coordinate system + final Vector3 diff = point.subtract(_center, _compVect1); + final Vector3 closest = _compVect2.set(diff.dot(_xAxis), diff.dot(_yAxis), diff.dot(_zAxis)); + + // project test point onto box + double sqrDistance = 0.0; + double delta; + + if (closest.getX() < -_extent.getX()) { + delta = closest.getX() + _extent.getX(); + sqrDistance += delta * delta; + closest.setX(-_extent.getX()); + } else if (closest.getX() > _extent.getX()) { + delta = closest.getX() - _extent.getX(); + sqrDistance += delta * delta; + closest.setX(_extent.getX()); + } + + if (closest.getY() < -_extent.getY()) { + delta = closest.getY() + _extent.getY(); + sqrDistance += delta * delta; + closest.setY(-_extent.getY()); + } else if (closest.getY() > _extent.getY()) { + delta = closest.getY() - _extent.getY(); + sqrDistance += delta * delta; + closest.setY(_extent.getY()); + } + + if (closest.getZ() < -_extent.getZ()) { + delta = closest.getZ() + _extent.getZ(); + sqrDistance += delta * delta; + closest.setZ(-_extent.getZ()); + } else if (closest.getZ() > _extent.getZ()) { + delta = closest.getZ() - _extent.getZ(); + sqrDistance += delta * delta; + closest.setZ(_extent.getZ()); + } + + return Math.sqrt(sqrDistance); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_xAxis, "_xAxis", new Vector3(Vector3.UNIT_X)); + capsule.write(_yAxis, "yAxis", new Vector3(Vector3.UNIT_Y)); + capsule.write(_zAxis, "zAxis", new Vector3(Vector3.UNIT_Z)); + capsule.write(_extent, "extent", new Vector3(Vector3.ZERO)); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _xAxis.set((Vector3) capsule.readSavable("xAxis", new Vector3(Vector3.UNIT_X))); + _yAxis.set((Vector3) capsule.readSavable("yAxis", new Vector3(Vector3.UNIT_Y))); + _zAxis.set((Vector3) capsule.readSavable("zAxis", new Vector3(Vector3.UNIT_Z))); + _extent.set((Vector3) capsule.readSavable("extent", new Vector3(Vector3.ZERO))); + correctCorners = false; + } + + @Override + public double getVolume() { + return (8 * _extent.getX() * _extent.getY() * _extent.getZ()); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java new file mode 100644 index 0000000..9f4626e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.util.Comparator; + +import com.ardor3d.intersection.PrimitiveKey; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; + +public class TreeComparator implements Comparator<PrimitiveKey> { + enum Axis { + X, Y, Z; + } + + private Axis _axis; + + private Mesh _mesh; + + private Vector3[] _aCompare = null; + + private Vector3[] _bCompare = null; + + public void setAxis(final Axis axis) { + _axis = axis; + } + + public void setMesh(final Mesh mesh) { + _mesh = mesh; + } + + public int compare(final PrimitiveKey o1, final PrimitiveKey o2) { + + if (o1.equals(o2)) { + return 0; + } + + Vector3 centerA = null; + Vector3 centerB = null; + _aCompare = _mesh.getMeshData().getPrimitiveVertices(o1.getPrimitiveIndex(), o1.getSection(), _aCompare); + _bCompare = _mesh.getMeshData().getPrimitiveVertices(o2.getPrimitiveIndex(), o2.getSection(), _bCompare); + + for (int i = 1; i < _aCompare.length; i++) { + _aCompare[0].addLocal(_aCompare[i]); + } + for (int i = 1; i < _bCompare.length; i++) { + _bCompare[0].addLocal(_bCompare[i]); + } + if (_aCompare.length == _bCompare.length) { + // don't need average since lists are same size. (3X < 3Y ? X < Y) + centerA = _aCompare[0]; + centerB = _bCompare[0]; + } else { + // perform average since we have different size lists + centerA = _aCompare[0].divideLocal(_aCompare.length); + centerB = _bCompare[0].divideLocal(_bCompare.length); + } + + switch (_axis) { + case X: + if (centerA.getX() < centerB.getX()) { + return -1; + } + if (centerA.getX() > centerB.getX()) { + return 1; + } + return 0; + case Y: + if (centerA.getY() < centerB.getY()) { + return -1; + } + if (centerA.getY() > centerB.getY()) { + return 1; + } + return 0; + case Z: + if (centerA.getZ() < centerB.getZ()) { + return -1; + } + if (centerA.getZ() > centerB.getZ()) { + return 1; + } + return 0; + default: + return 0; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java new file mode 100644 index 0000000..ba4d285 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.bounding; + +import java.util.List; +import java.util.Map; + +import com.ardor3d.scenegraph.Mesh; + +/** + * UsageTreeController defines a CollisionTreeController implementation that removes cache elements based on the + * frequency of usage. By default, and implementation in the CollisionTreeManager, the cache's key set will be ordered + * with the first element being the oldest used. Therefore, UsageTreeController simply removes elements from the cache + * starting at the first key and working up until the desired size is reached or we run out of elements. + */ +public class UsageTreeController implements CollisionTreeController { + + /** + * removes elements from cache (that are not in the protectedList) until the desiredSize is reached. It removes + * elements from the keyset as they are ordered. + * + * @param cache + * the cache to clean. + * @param protectedList + * the list of elements to not remove. + * @param desiredSize + * the final size of the cache to attempt to reach. + */ + public void clean(final Map<Mesh, CollisionTree> cache, final List<Mesh> protectedList, final int desiredSize) { + + // get the ordered keyset (this will be ordered with oldest to newest). + final Object[] set = cache.keySet().toArray(); + int count = 0; + // go through the cache removing items that are not protected until the + // size of the cache is small enough to return. + while (cache.size() > desiredSize && count < set.length) { + if (protectedList == null || !protectedList.contains(set[count])) { + cache.remove(set[count]); + } + count++; + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java new file mode 100644 index 0000000..8bab195 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java @@ -0,0 +1,43 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import java.util.concurrent.CountDownLatch;
+
+import com.ardor3d.annotation.MainThread;
+
+/**
+ * This interface defines the View, and should maybe be called the ViewUpdater. It owns the rendering phase, and
+ * controls all interactions with the Renderer.
+ */
+public interface Canvas {
+
+ /**
+ * Do work to initialize this canvas, generally setting up the associated CanvasRenderer, etc.
+ */
+ @MainThread
+ void init();
+
+ /**
+ * Ask the canvas to render itself. Note that this may occur in another thread and therefore a latch is given so the
+ * caller may know when the draw has completed.
+ *
+ * @param latch
+ * a counter that should be decremented once drawing has completed.
+ */
+ @MainThread
+ void draw(CountDownLatch latch);
+
+ /**
+ * @return the CanvasRenderer associated with this Canvas.
+ */
+ CanvasRenderer getCanvasRenderer();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java new file mode 100644 index 0000000..5c5ba52 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java @@ -0,0 +1,100 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.util.Ardor3dException;
+
+/**
+ * Represents a class that knows how to render a scene using a specific Open GL implementation.
+ */
+public interface CanvasRenderer {
+ void init(DisplaySettings settings, boolean doSwap);
+
+ /**
+ * Draw the current state of the scene.
+ */
+ @MainThread
+ boolean draw();
+
+ /**
+ * Returns the camera being used by this canvas renderer. Modifying the returned {@link Camera} instance effects the
+ * view being rendered, so this method can be used to move the camera, etc.
+ *
+ * @return the camera used by this canvas renderer
+ */
+ Camera getCamera();
+
+ /**
+ * Replaces the camera being used by this canvas renderer.
+ *
+ * @param camera
+ * the camera to use
+ */
+ void setCamera(Camera camera);
+
+ /**
+ * Returns the scene being used by this canvas renderer.
+ *
+ * @return the camera used by this canvas renderer
+ */
+ Scene getScene();
+
+ /**
+ * Replaces the scene being used by this canvas renderer.
+ *
+ * @param scene
+ * the scene to use
+ */
+ void setScene(Scene scene);
+
+ /**
+ * Returns the renderer being used by this canvas renderer.
+ *
+ * @return the renderer used by this canvas renderer
+ */
+ Renderer getRenderer();
+
+ /**
+ * Have the CanvasRenderer claim the graphics context.
+ *
+ * @throws Ardor3dException
+ * if we can not claim the context.
+ */
+ void makeCurrentContext() throws Ardor3dException;
+
+ /**
+ * Have the CanvasRenderer release the graphics context.
+ */
+ void releaseCurrentContext();
+
+ /**
+ * @return the Ardor3D RenderContext associated with this CanvasRenderer.
+ */
+ RenderContext getRenderContext();
+
+ /**
+ * @return an int representing the buffers to clear at the start of each frame. Default is
+ * Renderer.BUFFER_COLOR_AND_DEPTH
+ */
+ int getFrameClear();
+
+ /**
+ * @param buffers
+ * an int representing the buffers to clear at the start of each frame. Default is
+ * Renderer.BUFFER_COLOR_AND_DEPTH
+ */
+ void setFrameClear(final int buffers);
+
+}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java new file mode 100644 index 0000000..19aed1c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java @@ -0,0 +1,266 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.framework; + +public class DisplaySettings { + private final int _width; + private final int _height; + private final int _colorDepth; + private final int _frequency; + private final int _alphaBits; + private final int _depthBits; + private final int _stencilBits; + private final int _samples; + private final boolean _fullScreen; + private final boolean _stereo; + private final CanvasRenderer _shareContext; + + /** + * Convenience method equivalent to <code>DisplaySettings(width, height, 0, 0, 0, 8, 0, 0, + * false, false, null)</code> + * + * @param width + * the canvas width + * @param height + * the canvas height + * @param depthBits + * the number of bits making up the z-buffer + * @param samples + * the number of samples used to anti-alias + * @see http://en.wikipedia.org/wiki/Z-buffering + * @see http://en.wikipedia.org/wiki/Multisample_anti-aliasing + */ + public DisplaySettings(final int width, final int height, final int depthBits, final int samples) { + _width = width; + _height = height; + _colorDepth = 0; + _frequency = 0; + _alphaBits = 0; + _depthBits = depthBits; + _stencilBits = 0; + _samples = samples; + _fullScreen = false; + _stereo = false; + _shareContext = null; + } + + /** + * Convenience method equivalent to <code>DisplaySettings(width, height, colorDepth, frequency, + * 0, 8, 0, 0, fullScreen, false, null)</code> + * + * @param width + * the canvas width + * @param height + * the canvas height + * @param colorDepth + * the number of color bits used to represent the color of a single pixel + * @param frequency + * the number of times per second to repaint the canvas + * @param fullScreen + * true if the canvas should assume exclusive access to the screen + * @see http://en.wikipedia.org/wiki/Refresh_rate + */ + public DisplaySettings(final int width, final int height, final int colorDepth, final int frequency, + final boolean fullScreen) { + _width = width; + _height = height; + _colorDepth = colorDepth; + _frequency = frequency; + _alphaBits = 0; + _depthBits = 8; + _stencilBits = 0; + _samples = 0; + _fullScreen = fullScreen; + _stereo = false; + _shareContext = null; + } + + /** + * Convenience method equivalent to <code>DisplaySettings(width, height, colorDepth, frequency, + * alphaBits, depthBits, stencilBits, samples, fullScreen, stereo, null)</code> + * + * @param width + * the canvas width + * @param height + * the canvas height + * @param colorDepth + * the number of color bits used to represent the color of a single pixel + * @param frequency + * the number of times per second to repaint the canvas + * @param alphaBits + * the numner of bits used to represent the translucency of a single pixel + * @param depthBits + * the number of bits making up the z-buffer + * @param stencilBits + * the number of bits making up the stencil buffer + * @param samples + * the number of samples used to anti-alias + * @param fullScreen + * true if the canvas should assume exclusive access to the screen + * @param stereo + * true if the canvas should be rendered stereoscopically (for 3D glasses) + * @see http://en.wikipedia.org/wiki/Refresh_rate + * @see http://en.wikipedia.org/wiki/Alpha_compositing + * @see http://en.wikipedia.org/wiki/Stencil_buffer + * @see http://en.wikipedia.org/wiki/Stereoscopy + */ + public DisplaySettings(final int width, final int height, final int colorDepth, final int frequency, + final int alphaBits, final int depthBits, final int stencilBits, final int samples, + final boolean fullScreen, final boolean stereo) { + _width = width; + _height = height; + _colorDepth = colorDepth; + _frequency = frequency; + _alphaBits = alphaBits; + _depthBits = depthBits; + _stencilBits = stencilBits; + _samples = samples; + _fullScreen = fullScreen; + _stereo = stereo; + _shareContext = null; + } + + /** + * Creates a new <code>DisplaySettings</code> object. + * + * @param width + * the canvas width + * @param height + * the canvas height + * @param colorDepth + * the number of color bits used to represent the color of a single pixel + * @param frequency + * the number of times per second to repaint the canvas + * @param alphaBits + * the numner of bits used to represent the translucency of a single pixel + * @param depthBits + * the number of bits making up the z-buffer + * @param stencilBits + * the number of bits making up the stencil buffer + * @param samples + * the number of samples used to anti-alias + * @param fullScreen + * true if the canvas should assume exclusive access to the screen + * @param stereo + * true if the canvas should be rendered stereoscopically (for 3D glasses) + * @param shareContext + * the renderer used to render the canvas (see "ardor3d.useMultipleContexts" property) + * @see http://en.wikipedia.org/wiki/Z-buffering + * @see http://en.wikipedia.org/wiki/Multisample_anti-aliasing + * @see http://en.wikipedia.org/wiki/Refresh_rate + * @see http://en.wikipedia.org/wiki/Alpha_compositing + * @see http://en.wikipedia.org/wiki/Stencil_buffer + * @see http://en.wikipedia.org/wiki/Stereoscopy + * @see http://www.ardor3d.com/forums/viewtopic.php?f=13&t=318&p=2311&hilit=ardor3d.useMultipleContexts#p2311 + */ + public DisplaySettings(final int width, final int height, final int colorDepth, final int frequency, + final int alphaBits, final int depthBits, final int stencilBits, final int samples, + final boolean fullScreen, final boolean stereo, final CanvasRenderer shareContext) { + _width = width; + _height = height; + _colorDepth = colorDepth; + _frequency = frequency; + _alphaBits = alphaBits; + _depthBits = depthBits; + _stencilBits = stencilBits; + _samples = samples; + _fullScreen = fullScreen; + _stereo = stereo; + _shareContext = shareContext; + } + + public CanvasRenderer getShareContext() { + return _shareContext; + } + + public int getWidth() { + return _width; + } + + public int getHeight() { + return _height; + } + + public int getColorDepth() { + return _colorDepth; + } + + public int getFrequency() { + return _frequency; + } + + public int getAlphaBits() { + return _alphaBits; + } + + public int getDepthBits() { + return _depthBits; + } + + public int getStencilBits() { + return _stencilBits; + } + + public int getSamples() { + return _samples; + } + + public boolean isFullScreen() { + return _fullScreen; + } + + public boolean isStereo() { + return _stereo; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final DisplaySettings that = (DisplaySettings) o; + + return _colorDepth == that._colorDepth + && _frequency == that._frequency + && _fullScreen != that._fullScreen + && _height != that._height + && _width != that._width + && _alphaBits != that._alphaBits + && _depthBits != that._depthBits + && _stencilBits != that._stencilBits + && _samples != that._samples + && _stereo != that._stereo + && ((_shareContext == that._shareContext) || (_shareContext != null && _shareContext + .equals(that._shareContext))); + } + + @Override + public int hashCode() { + int result; + result = 17; + result = 31 * result + _height; + result = 31 * result + _width; + result = 31 * result + _colorDepth; + result = 31 * result + _frequency; + result = 31 * result + _alphaBits; + result = 31 * result + _depthBits; + result = 31 * result + _stencilBits; + result = 31 * result + _samples; + result = 31 * result + (_fullScreen ? 1 : 0); + result = 31 * result + (_stereo ? 1 : 0); + result = 31 * result + (_shareContext != null ? _shareContext.hashCode() : 0); + return result; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java new file mode 100644 index 0000000..c5465cf --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.framework; + +import java.util.Iterator; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.annotation.GuardedBy; +import com.ardor3d.annotation.MainThread; +import com.ardor3d.util.Timer; + +/** + * Does the work needed in a given frame. + */ +public final class FrameHandler { + private static final Logger logger = Logger.getLogger(FrameHandler.class.toString()); + + /** + * Thread synchronization of the updaters list is delegated to the CopyOnWriteArrayList. + */ + private final CopyOnWriteArrayList<Updater> _updaters; + + /** + * Canvases is both protected by an intrinsic lock and by the fact that it is a CopyOnWriteArrayList. This is + * because it is necessary to check the size of the list and then fetch an iterator that is guaranteed to iterate + * over that number of elements. See {@link #updateFrame()} for the code that does this. + */ + @GuardedBy("this") + private final CopyOnWriteArrayList<Canvas> _canvases; + private final Timer _timer; + + /** + * Number of seconds we'll wait for a latch to count down to 0. Default is 5. + */ + private long _timeoutSeconds = 5; + + public FrameHandler(final Timer timer) { + _timer = timer; + _updaters = new CopyOnWriteArrayList<Updater>(); + _canvases = new CopyOnWriteArrayList<Canvas>(); + } + + @MainThread + public void updateFrame() { + // calculate tpf + // update updaters + // draw canvases + + _timer.update(); + + // using the CopyOnWriteArrayList synchronization here, since that means + // that we don't have to hold any locks while calling Updater.update(double), + // and also makes the code simple. An updater that is registered after the below + // loop has started will be updated at the next call to updateFrame(). + for (final Updater updater : _updaters) { + updater.update(_timer); + } + + int numCanvases; + Iterator<Canvas> iterator; + + // make sure that there is no race condition with addCanvas - getting the iterator and + // the number of canvases currently in the list in a synchronized section, and ensuring that + // the addCanvas() method is also synchronized on this, means that they will + // both remain valid outside the section later, when we call the probably long-running, alien + // draw() methods. Since 'canvases' is a CopyOnWriteArrayList, the iterator is guaranteed to + // be valid outside the synchronized section, and getting them both inside the synchronized section + // means that the number of canvases read will be the same as the number of elements the iterator + // will iterate over. + synchronized (this) { + numCanvases = _canvases.size(); + iterator = _canvases.iterator(); + } + + final CountDownLatch latch = new CountDownLatch(numCanvases); + + while (iterator.hasNext()) { + iterator.next().draw(latch); + } + + try { + // wait for all canvases to be drawn - the reason for using the latch is that + // in some cases (AWT, for instance), the thread that calls canvas.draw() is not the + // one that holds the OpenGL context, which means that drawing is simply queued. + // When the actual OpenGL rendering has been done, the OpenGL thread will countdown + // on the latch, and once all the canvases have finished rendering, this method + // will return. + final boolean success = latch.await(_timeoutSeconds, TimeUnit.SECONDS); + + if (!success) { + logger.logp(Level.SEVERE, FrameHandler.class.toString(), "updateFrame", + "Timeout while waiting for renderers"); + // FIXME: should probably reset update flag in canvases? + } + } catch (final InterruptedException e) { + // restore updated status + Thread.currentThread().interrupt(); + } + } + + /** + * Add an updater to the frame handler. + * <p> + * The frame handler calls the {@link Updater#update(com.ardor3d.util.ReadOnlyTimer) update} method of each updater + * that has been added to it once per frame, before rendering begins. + * <p> + * <strong>Note:</strong> that is the frame handler has already been initialized then the updater will <em>not</em> + * have it's {@code init} method called automatically, it is up to the client code to perform any initialization + * explicitly under this scenario. + * + * @param updater + * the updater to add. + */ + public void addUpdater(final Updater updater) { + _updaters.addIfAbsent(updater); + } + + /** + * Remove an updater from the frame handler. + * + * @param updater + * the updater to remove. + * @return {@code true} if the updater was removed, {@code false} otherwise (which will happen if, for example, the + * updater had not previously been added to the frame handler). + */ + public boolean removeUpdater(final Updater updater) { + return _updaters.remove(updater); + } + + /** + * Add a canvas to the frame handler. + * <p> + * The frame handler calls the {@link Canvas#draw(java.util.concurrent.CountDownLatch)} draw} method of each canvas + * that has been added to it once per frame, after updating is complete. + * <p> + * <strong>Note:</strong> that is the frame handler has already been initialized then the canvas will <em>not</em> + * have it's {@code init} method called automatically, it is up to the client code to perform any initialization + * explicitly under this scenario. + * + * @param canvas + * the canvas to add. + */ + public synchronized void addCanvas(final Canvas canvas) { + _canvases.addIfAbsent(canvas); + } + + /** + * Remove a canvas from the frame handler. + * + * @param canvas + * the canvas to remove. + * @return {@code true} if the canvas was removed, {@code false} otherwise (which will happen if, for example, the + * canvas had not previously been added to the frame handler). + */ + public synchronized boolean removeCanvas(final Canvas canvas) { + return _canvases.remove(canvas); + } + + public void init() { + // TODO: this can lead to problems with canvases and updaters added after init() has been called once... + for (final Canvas canvas : _canvases) { + canvas.init(); + } + + for (final Updater updater : _updaters) { + updater.init(); + } + } + + public long getTimeoutSeconds() { + return _timeoutSeconds; + } + + public void setTimeoutSeconds(final long timeoutSeconds) { + _timeoutSeconds = timeoutSeconds; + } + + public Timer getTimer() { + return _timer; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java new file mode 100644 index 0000000..5a0dc2d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java @@ -0,0 +1,82 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.image.Image;
+
+public interface NativeCanvas extends Canvas {
+
+ /**
+ * <code>close</code> shutdowns and destroys any window contexts.
+ */
+ void close();
+
+ /**
+ * <code>isActive</code> returns true if the display is active.
+ *
+ * @return whether the display system is active.
+ */
+ boolean isActive();
+
+ /**
+ * <code>isClosing</code> notifies if the window is currently closing. This could be caused via the application
+ * itself or external interrupts such as alt-f4 etc.
+ *
+ * @return true if the window is closing, false otherwise.
+ */
+ boolean isClosing();
+
+ /**
+ * <code>setVSyncEnabled</code> attempts to enable or disable monitor vertical synchronization. The method is a
+ * "best attempt" to change the monitor vertical refresh synchronization, and is <b>not </b> guaranteed to be
+ * successful. This is dependent on OS.
+ *
+ * @param enabled
+ * <code>true</code> to synchronize, <code>false</code> to ignore synchronization
+ */
+ void setVSyncEnabled(boolean enabled);
+
+ /**
+ * Sets the title of the display system. This is usually reflected by the renderer as text in the menu bar.
+ *
+ * @param title
+ * The new display title.
+ */
+ void setTitle(String title);
+
+ /**
+ * Sets one or more icons for the Canvas.
+ * <p>
+ * As a reference for usual platforms on number of icons and their sizes:
+ * <ul>
+ * <li>On Windows you should supply at least one 16x16 image and one 32x32.</li>
+ * <li>Linux (and similar platforms) expect one 32x32 image.</li>
+ * <li>Mac OS X should be supplied one 128x128 image.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Images should be in format RGBA8888. If they are not ardor3d will try to convert them using ImageUtils. If that
+ * fails a <code>Ardor3dException</code> could be thrown.
+ * </p>
+ *
+ * @param iconImages
+ * Array of Images to be used as icons.
+ */
+ void setIcon(Image[] iconImages);
+
+ /**
+ * If running in windowed mode, move the window's position to the given display coordinates.
+ *
+ * @param locX
+ * @param locY
+ */
+ void moveWindowTo(int locX, int locY);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java new file mode 100644 index 0000000..bc7bc68 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java @@ -0,0 +1,38 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.intersection.PickResults;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.renderer.Renderer;
+
+/**
+ * Owns all the data that is related to the scene. This class should not really know anything about rendering or the
+ * screen, it's just the scene data.
+ */
+public interface Scene {
+ /**
+ *
+ * @param renderer
+ * @return true if a render occurred and we should swap the buffers.
+ */
+ @MainThread
+ boolean renderUnto(Renderer renderer);
+
+ /**
+ * A scene should be able to handle a pick execution as it is the only thing that has a complete picture of the
+ * scenegraph(s).
+ *
+ * @param pickRay
+ */
+ PickResults doPick(Ray3 pickRay);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java new file mode 100644 index 0000000..c984f51 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java @@ -0,0 +1,25 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.util.ReadOnlyTimer;
+
+/**
+ * The purpose of this class is to own the update phase and separate update logic from the view.
+ */
+public interface Updater {
+ @MainThread
+ public void init();
+
+ @MainThread
+ public void update(final ReadOnlyTimer timer);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java new file mode 100644 index 0000000..28765af --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java @@ -0,0 +1,377 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +import java.io.IOException; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.google.common.collect.Lists; + +/** + * <code>Image</code> defines a data format for a graphical image. The image is defined by a format, a height and width, + * and the image data. The width and height must be greater than 0. The data is contained in a byte buffer, and should + * be packed before creation of the image object. + * + */ +public class Image implements Serializable, Savable { + + private static final long serialVersionUID = 1L; + + // image attributes + protected ImageDataFormat _format = ImageDataFormat.RGBA; + protected PixelDataType _type = PixelDataType.UnsignedByte; + protected int _width, _height, _depth; + protected int[] _mipMapSizes; + protected List<ByteBuffer> _data; + + /** + * Constructor instantiates a new <code>Image</code> object. All values are undefined. + */ + public Image() { + _data = new ArrayList<ByteBuffer>(1); + } + + /** + * Constructor instantiates a new <code>Image</code> object. The attributes of the image are defined during + * construction. + * + * @param format + * the data format of the image. Must not be null. + * @param type + * the data type of the image. Must not be null. + * @param width + * the width of the image. + * @param height + * the height of the image. + * @param data + * the image data. Must not be null. + * @param mipMapSizes + * the array of mipmap sizes, or null for no mipmaps. + */ + public Image(final ImageDataFormat format, final PixelDataType type, final int width, final int height, + final List<ByteBuffer> data, int[] mipMapSizes) { + + if (mipMapSizes != null && mipMapSizes.length <= 1) { + mipMapSizes = null; + } + + setDataFormat(format); + setDataType(type); + setData(data); + _width = width; + _height = height; + _depth = data.size(); + _mipMapSizes = mipMapSizes; + } + + /** + * Constructor instantiates a new <code>Image</code> object. The attributes of the image are defined during + * construction. + * + * @param format + * the data format of the image. Must not be null. + * @param type + * the data type of the image. Must not be null. + * @param width + * the width of the image. + * @param height + * the height of the image. + * @param data + * the image data. Must not be null. + * @param mipMapSizes + * the array of mipmap sizes, or null for no mipmaps. + */ + public Image(final ImageDataFormat format, final PixelDataType type, final int width, final int height, + final ByteBuffer data, final int[] mipMapSizes) { + this(format, type, width, height, Lists.newArrayList(data), mipMapSizes); + } + + /** + * <code>setData</code> sets the data that makes up the image. This data is packed into an array of + * <code>ByteBuffer</code> objects. + * + * @param data + * the data that contains the image information. Must not be null. + */ + public void setData(final List<ByteBuffer> data) { + if (data == null) { + throw new NullPointerException("data may not be null."); + } + _data = data; + } + + /** + * <code>setData</code> sets the data that makes up the image. This data is packed into a single + * <code>ByteBuffer</code>. + * + * @param data + * the data that contains the image information. + */ + public void setData(final ByteBuffer data) { + _data = Lists.newArrayList(data); + } + + /** + * Adds the given buffer onto the current list of image data + * + * @param data + * the data that contains the image information. + */ + public void addData(final ByteBuffer data) { + if (_data == null) { + _data = new ArrayList<ByteBuffer>(1); + } + _data.add(data); + } + + public void setData(final int index, final ByteBuffer data) { + if (index >= 0) { + while (_data.size() <= index) { + _data.add(null); + } + _data.set(index, data); + } else { + throw new IllegalArgumentException("index must be greater than or equal to 0."); + } + } + + /** + * Sets the mipmap data sizes stored in this image's data buffer. Mipmaps are stored sequentially, and the first + * mipmap is the main image data. To specify no mipmaps, pass null. + * + * @param mipMapSizes + * the mipmap sizes array, or null to indicate no mip maps. + */ + public void setMipMapByteSizes(int[] mipMapSizes) { + if (mipMapSizes != null && mipMapSizes.length <= 1) { + mipMapSizes = null; + } + + _mipMapSizes = mipMapSizes; + } + + /** + * <code>setHeight</code> sets the height value of the image. It is typically a good idea to try to keep this as a + * multiple of 2. + * + * @param height + * the height of the image. + */ + public void setHeight(final int height) { + _height = height; + } + + /** + * <code>setDepth</code> sets the depth value of the image. It is typically a good idea to try to keep this as a + * multiple of 2. This is used for 3d images. + * + * @param depth + * the depth of the image. + */ + public void setDepth(final int depth) { + _depth = depth; + } + + /** + * <code>setWidth</code> sets the width value of the image. It is typically a good idea to try to keep this as a + * multiple of 2. + * + * @param width + * the width of the image. + */ + public void setWidth(final int width) { + _width = width; + } + + /** + * @param format + * the image data format. + * @throws NullPointerException + * if format is null + * @see ImageDataFormat + */ + public void setDataFormat(final ImageDataFormat format) { + if (format == null) { + throw new NullPointerException("format may not be null."); + } + + _format = format; + } + + /** + * @return the image data format. + * @see ImageDataFormat + */ + public ImageDataFormat getDataFormat() { + return _format; + } + + /** + * @param type + * the image data type. + * @throws NullPointerException + * if type is null + * @see PixelDataType + */ + public void setDataType(final PixelDataType type) { + if (type == null) { + throw new NullPointerException("type may not be null."); + } + + _type = type; + } + + /** + * @return the image data type. + * @see PixelDataType + */ + public PixelDataType getDataType() { + return _type; + } + + /** + * @return the width of this image. + */ + public int getWidth() { + return _width; + } + + /** + * @return the height of this image. + */ + public int getHeight() { + return _height; + } + + /** + * @return the depth of this image (used for 3d textures and 2d texture arrays) + */ + public int getDepth() { + return _depth; + } + + /** + * <code>getData</code> returns the data for this image. If the data is undefined, null will be returned. + * + * @return the data for this image. + */ + public List<ByteBuffer> getData() { + return _data; + } + + /** + * @return the number of individual data buffers or slices in this Image. + */ + public int getDataSize() { + if (_data == null) { + return 0; + } else { + return _data.size(); + } + } + + /** + * <code>getData</code> returns the data for this image at a given index. If the data is undefined, null will be + * returned. + * + * @return the data for this image. + */ + public ByteBuffer getData(final int index) { + if (_data.size() > index) { + return _data.get(index); + } else { + return null; + } + } + + /** + * Returns whether the image data contains mipmaps. + * + * @return true if the image data contains mipmaps, false if not. + */ + public boolean hasMipmaps() { + return _mipMapSizes != null; + } + + /** + * Returns the mipmap sizes for this image. + * + * @return the mipmap sizes for this image. + */ + public int[] getMipMapByteSizes() { + return _mipMapSizes; + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Image)) { + return false; + } + final Image that = (Image) other; + if (getDataFormat() != that.getDataFormat()) { + return false; + } + if (getDataType() != that.getDataType()) { + return false; + } + if (getWidth() != that.getWidth()) { + return false; + } + if (getHeight() != that.getHeight()) { + return false; + } + if (!this.getData().equals(that.getData())) { + return false; + } + if (getMipMapByteSizes() != null && !Arrays.equals(getMipMapByteSizes(), that.getMipMapByteSizes())) { + return false; + } + if (getMipMapByteSizes() == null && that.getMipMapByteSizes() != null) { + return false; + } + + return true; + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_format, "dataformat", ImageDataFormat.RGBA); + capsule.write(_type, "datatype", PixelDataType.UnsignedByte); + capsule.write(_width, "width", 0); + capsule.write(_height, "height", 0); + capsule.write(_depth, "depth", 0); + capsule.write(_mipMapSizes, "mipMapSizes", null); + capsule.writeByteBufferList(_data, "data", null); + } + + public void read(final InputCapsule capsule) throws IOException { + _format = capsule.readEnum("dataformat", ImageDataFormat.class, ImageDataFormat.RGBA); + _type = capsule.readEnum("datatype", PixelDataType.class, PixelDataType.UnsignedByte); + _width = capsule.readInt("width", 0); + _height = capsule.readInt("height", 0); + _depth = capsule.readInt("depth", 0); + _mipMapSizes = capsule.readIntArray("mipMapSizes", null); + _data = capsule.readByteBufferList("data", null); + } + + public Class<? extends Image> getClassTag() { + return this.getClass(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java new file mode 100644 index 0000000..e1f8c7c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +public enum ImageDataFormat { + RG(2, false, false), // + RGB(3, false, false), // + RGBA(4, false, true), // + BGR(3, false, false), // + BGRA(4, false, true), // + Luminance(1, false, false), // + LuminanceAlpha(2, false, true), // + Alpha(1, false, true), // + Intensity(1, false, false), // + Red(1, false, false), // + Green(1, false, false), // + Blue(1, false, false), // + StencilIndex(1, false, false), // + ColorIndex(1, false, false), // + Depth(1, false, false), // + PrecompressedDXT1(1, true, false), // + PrecompressedDXT1A(1, true, true), // + PrecompressedDXT3(2, true, true), // + PrecompressedDXT5(2, true, true), // + PrecompressedLATC_L(1, true, true), // + PrecompressedLATC_LA(2, true, true); + + private final int _components; + private final boolean _compressed; + private final boolean _hasAlpha; + + ImageDataFormat(final int components, final boolean isCompressed, final boolean hasAlpha) { + _components = components; + _compressed = isCompressed; + _hasAlpha = hasAlpha; + } + + public int getComponents() { + return _components; + } + + public boolean isCompressed() { + return _compressed; + } + + public boolean hasAlpha() { + return _hasAlpha; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java new file mode 100644 index 0000000..c465d4b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +public enum PixelDataType { + UnsignedByte(1, null, null), // + Byte(1, null, null), // + UnsignedShort(2, null, null), // + Short(2, null, null), // + UnsignedInt(4, null, null), // + Int(4, null, null), // + Float(4, null, null), // + HalfFloat(2, null, null), // + UnsignedByte_3_3_2(null, 8, 3), // + UnsignedByte_2_3_3_Rev(null, 8, 3), // + UnsignedShort_5_6_5(null, 16, 3), // + UnsignedShort_5_6_5_Rev(null, 16, 3), // + UnsignedShort_4_4_4_4(null, 16, 4), // + UnsignedShort_4_4_4_4_Rev(null, 16, 4), // + UnsignedShort_5_5_5_1(null, 16, 4), // + UnsignedShort_1_5_5_5_Rev(null, 16, 4), // + UnsignedInt_8_8_8_8(null, 32, 4), // + UnsignedInt_8_8_8_8_Rev(null, 32, 4), // + UnsignedInt_10_10_10_2(null, 32, 4), // + UnsignedInt_2_10_10_10_Rev(null, 32, 4); + + final Integer _bytesPerComponent; + final Integer _bytesPerPixel; + final Integer _components; + + PixelDataType(final Integer bytesPerComponent, final Integer bytesPerPixel, final Integer components) { + _bytesPerComponent = bytesPerComponent; + _bytesPerPixel = bytesPerPixel; + _components = components; + } + + public int getBytesPerPixel(final int components) { + if (_components != null && components != _components.intValue()) { + throw new IllegalArgumentException("invalid number of components for " + name()); + } + + if (_bytesPerPixel != null) { + return _bytesPerPixel.intValue(); + } else { + return components * _bytesPerComponent; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java new file mode 100644 index 0000000..add9847 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java @@ -0,0 +1,1528 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Matrix4; +import com.ardor3d.math.Vector4; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyVector4; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.util.Constants; +import com.ardor3d.util.TextureKey; +import com.ardor3d.util.TextureManager; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * <code>Texture</code> defines a texture object to be used to display an image on a piece of geometry. The image to be + * displayed is defined by the <code>Image</code> class. All attributes required for texture mapping are contained + * within this class. This includes mipmapping if desired, magnificationFilter options, apply options and correction + * options. Default values are as follows: minificationFilter - NearestNeighborNoMipMaps, magnificationFilter - + * NearestNeighbor, wrap - EdgeClamp on S,T and R, apply - Modulate, environment - None. + * + * @see com.ardor3d.image.Image + */ +public abstract class Texture implements Savable { + + public static boolean DEFAULT_STORE_IMAGE = Constants.storeSavableImages; + + public enum Type { + /** + * One dimensional texture. (basically a line) + */ + OneDimensional, + /** + * Two dimensional texture (default). A rectangle. + */ + TwoDimensional, + /** + * Three dimensional texture. (A cube) + */ + ThreeDimensional, + /** + * A set of 6 TwoDimensional textures arranged as faces of a cube facing inwards. + */ + CubeMap, + /** + * A non-power of 2 texture. Does not support mipmapping. Only supports {@link WrapMode#Clamp}, + * {@link WrapMode#EdgeClamp}, and {@link WrapMode#BorderClamp} wrap modes. Texture coordinates are not + * normalized [0, 1] but rather [0,w] and [0,h] for u and v respectively. + */ + Rectangle; + } + + public enum MinificationFilter { + + /** + * Nearest neighbor interpolation is the fastest and crudest filtering method - it simply uses the color of the + * texel closest to the pixel center for the pixel color. While fast, this results in aliasing and shimmering + * during minification. (GL equivalent: GL_NEAREST) + */ + NearestNeighborNoMipMaps(false), + + /** + * In this method the four nearest texels to the pixel center are sampled (at texture level 0), and their colors + * are combined by weighted averages. Though smoother, without mipmaps it suffers the same aliasing and + * shimmering problems as nearest NearestNeighborNoMipMaps. (GL equivalent: GL_LINEAR) + */ + BilinearNoMipMaps(false), + + /** + * Same as NearestNeighborNoMipMaps except that instead of using samples from texture level 0, the closest + * mipmap level is chosen based on distance. This reduces the aliasing and shimmering significantly, but does + * not help with blockiness. (GL equivalent: GL_NEAREST_MIPMAP_NEAREST) + */ + NearestNeighborNearestMipMap(true), + + /** + * Same as BilinearNoMipMaps except that instead of using samples from texture level 0, the closest mipmap level + * is chosen based on distance. By using mipmapping we avoid the aliasing and shimmering problems of + * BilinearNoMipMaps. (GL equivalent: GL_LINEAR_MIPMAP_NEAREST) + */ + BilinearNearestMipMap(true), + + /** + * Similar to NearestNeighborNoMipMaps except that instead of using samples from texture level 0, a sample is + * chosen from each of the closest (by distance) two mipmap levels. A weighted average of these two samples is + * returned. (GL equivalent: GL_NEAREST_MIPMAP_LINEAR) + */ + NearestNeighborLinearMipMap(true), + + /** + * Trilinear filtering is a remedy to a common artifact seen in mipmapped bilinearly filtered images: an abrupt + * and very noticeable change in quality at boundaries where the renderer switches from one mipmap level to the + * next. Trilinear filtering solves this by doing a texture lookup and bilinear filtering on the two closest + * mipmap levels (one higher and one lower quality), and then linearly interpolating the results. This results + * in a smooth degradation of texture quality as distance from the viewer increases, rather than a series of + * sudden drops. Of course, closer than Level 0 there is only one mipmap level available, and the algorithm + * reverts to bilinear filtering (GL equivalent: GL_LINEAR_MIPMAP_LINEAR) + */ + Trilinear(true); + + private boolean _usesMipMapLevels; + + private MinificationFilter(final boolean usesMipMapLevels) { + _usesMipMapLevels = usesMipMapLevels; + } + + public boolean usesMipMapLevels() { + return _usesMipMapLevels; + } + } + + public enum MagnificationFilter { + + /** + * Nearest neighbor interpolation is the fastest and crudest filtering mode - it simply uses the color of the + * texel closest to the pixel center for the pixel color. While fast, this results in texture 'blockiness' + * during magnification. (GL equivalent: GL_NEAREST) + */ + NearestNeighbor, + + /** + * In this mode the four nearest texels to the pixel center are sampled (at the closest mipmap level), and their + * colors are combined by weighted average according to distance. This removes the 'blockiness' seen during + * magnification, as there is now a smooth gradient of color change from one texel to the next, instead of an + * abrupt jump as the pixel center crosses the texel boundary. (GL equivalent: GL_LINEAR) + */ + Bilinear; + + } + + public enum WrapMode { + /** + * Only the fractional portion of the coordinate is considered. + */ + Repeat, + /** + * Only the fractional portion of the coordinate is considered, but if the integer portion is odd, we'll use 1 - + * the fractional portion. (Introduced around OpenGL1.4) Falls back on Repeat if not supported. + */ + MirroredRepeat, + /** + * coordinate will be clamped to [0,1] + */ + Clamp, + /** + * mirrors and clamps the texture coordinate, where mirroring and clamping a value f computes: + * <code>mirrorClamp(f) = min(1, max(1/(2*N), + * abs(f)))</code> where N is the size of the one-, two-, or three-dimensional texture image in the direction of + * wrapping. (Introduced after OpenGL1.4) Falls back on Clamp if not supported. + */ + MirrorClamp, + /** + * coordinate will be clamped to the range [-1/(2N), 1 + 1/(2N)] where N is the size of the texture in the + * direction of clamping. Falls back on Clamp if not supported. + */ + BorderClamp, + /** + * Wrap mode MIRROR_CLAMP_TO_BORDER_EXT mirrors and clamps to border the texture coordinate, where mirroring and + * clamping to border a value f computes: + * <code>mirrorClampToBorder(f) = min(1+1/(2*N), max(1/(2*N), abs(f)))</code> where N is the size of the one-, + * two-, or three-dimensional texture image in the direction of wrapping." (Introduced after OpenGL1.4) Falls + * back on BorderClamp if not supported. + */ + MirrorBorderClamp, + /** + * coordinate will be clamped to the range [1/(2N), 1 - 1/(2N)] where N is the size of the texture in the + * direction of clamping. Falls back on Clamp if not supported. + */ + EdgeClamp, + /** + * mirrors and clamps to edge the texture coordinate, where mirroring and clamping to edge a value f computes: + * <code>mirrorClampToEdge(f) = min(1-1/(2*N), max(1/(2*N), abs(f)))</code> where N is the size of the one-, + * two-, or three-dimensional texture image in the direction of wrapping. (Introduced after OpenGL1.4) Falls + * back on EdgeClamp if not supported. + */ + MirrorEdgeClamp; + } + + public enum WrapAxis { + /** + * S wrapping (u or "horizontal" wrap) + */ + S, + /** + * T wrapping (v or "vertical" wrap) + */ + T, + /** + * R wrapping (w or "depth" wrap) + */ + R; + } + + public enum ApplyMode { + /** + * Apply modifier that replaces the previous pixel color with the texture color. + */ + Replace, + /** + * Apply modifier that replaces the color values of the pixel but makes use of the alpha values. + */ + Decal, + /** + * Apply modifier multiples the color of the pixel with the texture color. + */ + Modulate, + /** + * Apply modifier that interpolates the color of the pixel with a blend color using the texture color, such that + * the final color value is Cv = (1 - Ct) * Cf + BlendColor * Ct Where Ct is the color of the texture and Cf is + * the initial pixel color. + */ + Blend, + /** + * Apply modifier combines two textures based on the combine parameters set on this texture. + */ + Combine, + /** + * Apply modifier adds two textures. + */ + Add; + } + + /** + * Formula to use for texture coordinate generation + */ + public enum EnvironmentalMapMode { + /** + * Use texture coordinates as they are. (Do not do texture coordinate generation.) + */ + None, + /** + * TODO: add documentation + */ + EyeLinear, + /** + * TODO: add documentation + */ + ObjectLinear, + /** + * TODO: add documentation + */ + SphereMap, + /** + * TODO: add documentation + */ + NormalMap, + /** + * TODO: add documentation + */ + ReflectionMap; + } + + public enum CombinerFunctionRGB { + /** Arg0 */ + Replace, + /** Arg0 * Arg1 */ + Modulate, + /** Arg0 + Arg1 */ + Add, + /** Arg0 + Arg1 - 0.5 */ + AddSigned, + /** Arg0 * Arg2 + Arg1 * (1 - Arg2) */ + Interpolate, + /** Arg0 - Arg1 */ + Subtract, + /** + * 4 * ((Arg0r - 0.5) * (Arg1r - 0.5) + (Arg0g - 0.5) * (Arg1g - 0.5) + (Arg0b - 0.5) * (Arg1b - 0.5)) [ result + * placed in R,G,B ] + */ + Dot3RGB, + /** + * 4 * ((Arg0r - 0.5) * (Arg1r - 0.5) + (Arg0g - 0.5) * (Arg1g - 0.5) + (Arg0b - 0.5) * (Arg1b - 0.5)) [ result + * placed in R,G,B,A ] + */ + Dot3RGBA; + } + + public enum CombinerFunctionAlpha { + /** Arg0 */ + Replace, + /** Arg0 * Arg1 */ + Modulate, + /** Arg0 + Arg1 */ + Add, + /** Arg0 + Arg1 - 0.5 */ + AddSigned, + /** Arg0 * Arg2 + Arg1 * (1 - Arg2) */ + Interpolate, + /** Arg0 - Arg1 */ + Subtract; + } + + public enum CombinerSource { + /** + * The incoming fragment color from the previous texture unit. When used on texture unit 0, this is the same as + * using PrimaryColor. + */ + Previous, + /** The blend color set on this texture. */ + Constant, + /** The incoming fragment color before any texturing is applied. */ + PrimaryColor, + /** The current texture unit's bound texture. */ + CurrentTexture, + /** The texture bound on texture unit 0. */ + TextureUnit0, + /** The texture bound on texture unit 1. */ + TextureUnit1, + /** The texture bound on texture unit 2. */ + TextureUnit2, + /** The texture bound on texture unit 3. */ + TextureUnit3, + /** The texture bound on texture unit 4. */ + TextureUnit4, + /** The texture bound on texture unit 5. */ + TextureUnit5, + /** The texture bound on texture unit 6. */ + TextureUnit6, + /** The texture bound on texture unit 7. */ + TextureUnit7, + /** The texture bound on texture unit 8. */ + TextureUnit8, + /** The texture bound on texture unit 9. */ + TextureUnit9, + /** The texture bound on texture unit 10. */ + TextureUnit10, + /** The texture bound on texture unit 11. */ + TextureUnit11, + /** The texture bound on texture unit 12. */ + TextureUnit12, + /** The texture bound on texture unit 13. */ + TextureUnit13, + /** The texture bound on texture unit 14. */ + TextureUnit14, + /** The texture bound on texture unit 15. */ + TextureUnit15, + /** The texture bound on texture unit 16. */ + TextureUnit16, + /** The texture bound on texture unit 17. */ + TextureUnit17, + /** The texture bound on texture unit 18. */ + TextureUnit18, + /** The texture bound on texture unit 19. */ + TextureUnit19, + /** The texture bound on texture unit 20. */ + TextureUnit20, + /** The texture bound on texture unit 21. */ + TextureUnit21, + /** The texture bound on texture unit 22. */ + TextureUnit22, + /** The texture bound on texture unit 23. */ + TextureUnit23, + /** The texture bound on texture unit 24. */ + TextureUnit24, + /** The texture bound on texture unit 25. */ + TextureUnit25, + /** The texture bound on texture unit 26. */ + TextureUnit26, + /** The texture bound on texture unit 27. */ + TextureUnit27, + /** The texture bound on texture unit 28. */ + TextureUnit28, + /** The texture bound on texture unit 29. */ + TextureUnit29, + /** The texture bound on texture unit 30. */ + TextureUnit30, + /** The texture bound on texture unit 31. */ + TextureUnit31; + } + + public enum CombinerOperandRGB { + SourceColor, OneMinusSourceColor, SourceAlpha, OneMinusSourceAlpha; + } + + public enum CombinerOperandAlpha { + SourceAlpha, OneMinusSourceAlpha; + } + + public enum CombinerScale { + /** No scale (1.0x) */ + One(1.0f), + /** 2.0x */ + Two(2.0f), + /** 4.0x */ + Four(4.0f); + + private float scale; + + private CombinerScale(final float scale) { + this.scale = scale; + } + + public float floatValue() { + return scale; + } + } + + /** + * The shadowing texture compare mode + */ + public enum DepthTextureCompareMode { + /** Perform no shadow based comparsion */ + None, + /** Perform a comparison between source depth and texture depth */ + RtoTexture, + } + + /** + * The shadowing texture compare function + */ + public enum DepthTextureCompareFunc { + /** Outputs if the source depth is less than the texture depth */ + LessThanEqual, + /** Outputs if the source depth is greater than the texture depth */ + GreaterThanEqual + } + + /** + * The type of depth texture translation to output + */ + public enum DepthTextureMode { + /** Output luminance values based on the depth comparison */ + Luminance, + /** Output alpha values based on the depth comparison */ + Alpha, + /** Output intensity values based on the depth comparison */ + Intensity + } + + // texture attributes. + private Image _image = null; + private final ColorRGBA _constantColor = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA); + private final ColorRGBA _borderColor = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA); + + private final Matrix4 _texMatrix = new Matrix4(); + + private float _anisotropicFilterPercent = 0.0f; + private float _lodBias = 0.0f; + + private ApplyMode _apply = ApplyMode.Modulate; + private MinificationFilter _minificationFilter = MinificationFilter.NearestNeighborNoMipMaps; + private MagnificationFilter _magnificationFilter = MagnificationFilter.Bilinear; + + private EnvironmentalMapMode _envMapMode = EnvironmentalMapMode.None; + + private Vector4 _envPlaneS = null; + private Vector4 _envPlaneT = null; + private Vector4 _envPlaneR = null; + private Vector4 _envPlaneQ = null; + + private boolean _hasBorder = false; + + // The following will only used if apply is set to ApplyMode.Combine + private CombinerFunctionRGB _combineFuncRGB = CombinerFunctionRGB.Modulate; + private CombinerSource _combineSrc0RGB = CombinerSource.CurrentTexture; + private CombinerSource _combineSrc1RGB = CombinerSource.Previous; + private CombinerSource _combineSrc2RGB = CombinerSource.Constant; + private CombinerOperandRGB _combineOp0RGB = CombinerOperandRGB.SourceColor; + private CombinerOperandRGB _combineOp1RGB = CombinerOperandRGB.SourceColor; + private CombinerOperandRGB _combineOp2RGB = CombinerOperandRGB.SourceAlpha; + private CombinerScale _combineScaleRGB = CombinerScale.One; + + private CombinerFunctionAlpha _combineFuncAlpha = CombinerFunctionAlpha.Modulate; + private CombinerSource _combineSrc0Alpha = CombinerSource.CurrentTexture; + private CombinerSource _combineSrc1Alpha = CombinerSource.Previous; + private CombinerSource _combineSrc2Alpha = CombinerSource.Constant; + private CombinerOperandAlpha _combineOp0Alpha = CombinerOperandAlpha.SourceAlpha; + private CombinerOperandAlpha _combineOp1Alpha = CombinerOperandAlpha.SourceAlpha; + private CombinerOperandAlpha _combineOp2Alpha = CombinerOperandAlpha.SourceAlpha; + private CombinerScale _combineScaleAlpha = CombinerScale.One; + + private TextureKey _key = null; + private TextureStoreFormat _storeFormat = TextureStoreFormat.RGBA8; + private PixelDataType _rttPixelDataType = PixelDataType.UnsignedByte; + private transient boolean _storeImage = DEFAULT_STORE_IMAGE; + + private DepthTextureCompareMode _depthCompareMode = DepthTextureCompareMode.None; + private DepthTextureCompareFunc _depthCompareFunc = DepthTextureCompareFunc.GreaterThanEqual; + private DepthTextureMode _depthMode = DepthTextureMode.Intensity; + + private int _textureBaseLevel = 0; + private int _textureMaxLevel = -1; + + /** + * Constructor instantiates a new <code>Texture</code> object with default attributes. + */ + public Texture() {} + + /** + * sets a color that is used with CombinerSource.Constant + * + * @param color + * the new constant color (the default is {@link ColorRGBA#BLACK_NO_ALPHA}) + */ + public void setConstantColor(final ReadOnlyColorRGBA color) { + _constantColor.set(color); + } + + /** + * sets a color that is used with CombinerSource.Constant + * + * @param red + * @param green + * @param blue + * @param alpha + */ + public void setConstantColor(final float red, final float green, final float blue, final float alpha) { + _constantColor.set(red, green, blue, alpha); + } + + /** + * sets the color used when texture operations encounter the border of a texture. + * + * @param color + * the new border color (the default is {@link ColorRGBA#BLACK_NO_ALPHA}) + */ + public void setBorderColor(final ReadOnlyColorRGBA color) { + _borderColor.set(color); + } + + /** + * sets the color used when texture operations encounter the border of a texture. + * + * @param red + * @param green + * @param blue + * @param alpha + */ + public void setBorderColor(final float red, final float green, final float blue, final float alpha) { + _borderColor.set(red, green, blue, alpha); + } + + /** + * @return the MinificationFilterMode of this texture. + */ + public MinificationFilter getMinificationFilter() { + return _minificationFilter; + } + + /** + * @param minificationFilter + * the new MinificationFilterMode for this texture. + * @throws IllegalArgumentException + * if minificationFilter is null + */ + public void setMinificationFilter(final MinificationFilter minificationFilter) { + if (minificationFilter == null) { + throw new IllegalArgumentException("minificationFilter can not be null."); + } + _minificationFilter = minificationFilter; + } + + /** + * @return the MagnificationFilterMode of this texture. + */ + public MagnificationFilter getMagnificationFilter() { + return _magnificationFilter; + } + + /** + * @param magnificationFilter + * the new MagnificationFilter for this texture. + * @throws IllegalArgumentException + * if magnificationFilter is null + */ + public void setMagnificationFilter(final MagnificationFilter magnificationFilter) { + if (magnificationFilter == null) { + throw new IllegalArgumentException("magnificationFilter can not be null."); + } + _magnificationFilter = magnificationFilter; + } + + /** + * <code>setApply</code> sets the apply mode for this texture. + * + * @param apply + * the apply mode for this texture. + * @throws IllegalArgumentException + * if apply is null + */ + public void setApply(final ApplyMode apply) { + if (apply == null) { + throw new IllegalArgumentException("apply can not be null."); + } + _apply = apply; + } + + /** + * <code>setImage</code> sets the image object that defines the texture. + * + * @param image + * the image that defines the texture. + */ + public void setImage(final Image image) { + _image = image; + setDirty(); + } + + /** + * @param glContext + * the object representing the OpenGL context this texture belongs to. See + * {@link RenderContext#getGlContextRep()} + * @return the texture id of this texture in the given context. If the texture is not found in the given context, 0 + * is returned. + */ + public int getTextureIdForContext(final Object glContext) { + return _key.getTextureIdForContext(glContext); + } + + /** + * @param glContext + * the object representing the OpenGL context this texture belongs to. See + * {@link RenderContext#getGlContextRep()} + * @return the texture id of this texture in the given context as an Integer object. If the texture is not found in + * the given context, a 0 integer is returned. + */ + public Integer getTextureIdForContextAsInteger(final Object glContext) { + return _key.getTextureIdForContext(glContext); + } + + /** + * Sets the id for this texture in regards to the given OpenGL context. + * + * @param glContext + * the object representing the OpenGL context this texture belongs to. See + * {@link RenderContext#getGlContextRep()} + * @param textureId + * the texture id of this texture. To be valid, this must be greater than 0. + * @throws IllegalArgumentException + * if textureId is less than or equal to 0. + */ + public void setTextureIdForContext(final Object glContext, final int textureId) { + _key.setTextureIdForContext(glContext, textureId); + } + + /** + * <p> + * Removes any texture id for this texture for the given OpenGL context. + * </p> + * <p> + * Note: This does not remove the texture from the card and is provided for use by code that does remove textures + * from the card. + * </p> + * + * @param glContext + * the object representing the OpenGL context this texture belongs to. See + * {@link RenderContext#getGlContextRep()} + */ + public void removeFromIdCache(final Object glContext) { + _key.removeFromIdCache(glContext); + } + + /** + * @return the image data that makes up this texture. If no image data has been set, this will return null. + */ + public Image getImage() { + return _image; + } + + /** + * @return the apply mode of the texture. + */ + public ApplyMode getApply() { + return _apply; + } + + /** + * @return the color set to be used with CombinerSource.Constant for this texture (as applicable). (the default is + * {@link ColorRGBA#BLACK_NO_ALPHA}) + */ + public ReadOnlyColorRGBA getConstantColor() { + return _constantColor; + } + + /** + * @return the color to be used for border operations. (the default is {@link ColorRGBA#BLACK_NO_ALPHA}) + */ + public ReadOnlyColorRGBA getBorderColor() { + return _borderColor; + } + + /** + * Sets the wrap mode of this texture for a particular axis. + * + * @param axis + * the texture axis to define a wrapmode on. + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if axis or mode are null or invalid for this type of texture + */ + public abstract void setWrap(WrapAxis axis, WrapMode mode); + + /** + * Sets the wrap mode of this texture for all axis. + * + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if mode is null or invalid for this type of texture + */ + public abstract void setWrap(WrapMode mode); + + /** + * @param axis + * the axis to return for + * @return the wrap mode for the given coordinate axis on this texture. + * @throws IllegalArgumentException + * if axis is null or invalid for this type of texture + */ + public abstract WrapMode getWrap(WrapAxis axis); + + /** + * @return the {@link Type} enum value of this Texture object. + */ + public abstract Type getType(); + + /** + * @return the combineFuncRGB. + */ + public CombinerFunctionRGB getCombineFuncRGB() { + return _combineFuncRGB; + } + + /** + * @param combineFuncRGB + * The combineFuncRGB to set. + * @throws IllegalArgumentException + * if combineFuncRGB is null + */ + public void setCombineFuncRGB(final CombinerFunctionRGB combineFuncRGB) { + if (combineFuncRGB == null) { + throw new IllegalArgumentException("invalid CombinerFunctionRGB: null"); + } + _combineFuncRGB = combineFuncRGB; + } + + /** + * @return Returns the combineOp0Alpha. + */ + public CombinerOperandAlpha getCombineOp0Alpha() { + return _combineOp0Alpha; + } + + /** + * @param combineOp0Alpha + * The combineOp0Alpha to set. + * @throws IllegalArgumentException + * if combineOp0Alpha is null + */ + public void setCombineOp0Alpha(final CombinerOperandAlpha combineOp0Alpha) { + if (combineOp0Alpha == null) { + throw new IllegalArgumentException("invalid CombinerOperandAlpha: null"); + } + + _combineOp0Alpha = combineOp0Alpha; + } + + /** + * @return Returns the combineOp0RGB. + */ + public CombinerOperandRGB getCombineOp0RGB() { + return _combineOp0RGB; + } + + /** + * @param combineOp0RGB + * The combineOp0RGB to set. + * @throws IllegalArgumentException + * if combineOp0RGB is null + */ + public void setCombineOp0RGB(final CombinerOperandRGB combineOp0RGB) { + if (combineOp0RGB == null) { + throw new IllegalArgumentException("invalid CombinerOperandRGB: null"); + } + _combineOp0RGB = combineOp0RGB; + } + + /** + * @return Returns the combineOp1Alpha. + */ + public CombinerOperandAlpha getCombineOp1Alpha() { + return _combineOp1Alpha; + } + + /** + * @param combineOp1Alpha + * The combineOp1Alpha to set. + * @throws IllegalArgumentException + * if combineOp1Alpha is null + */ + public void setCombineOp1Alpha(final CombinerOperandAlpha combineOp1Alpha) { + if (combineOp1Alpha == null) { + throw new IllegalArgumentException("invalid CombinerOperandAlpha: null"); + } + _combineOp1Alpha = combineOp1Alpha; + } + + /** + * @return Returns the combineOp1RGB. + */ + public CombinerOperandRGB getCombineOp1RGB() { + return _combineOp1RGB; + } + + /** + * @param combineOp1RGB + * The combineOp1RGB to set. + * @throws IllegalArgumentException + * if combineOp1RGB is null + */ + public void setCombineOp1RGB(final CombinerOperandRGB combineOp1RGB) { + if (combineOp1RGB == null) { + throw new IllegalArgumentException("invalid CombinerOperandRGB: null"); + } + _combineOp1RGB = combineOp1RGB; + } + + /** + * @return Returns the combineOp2Alpha. + */ + public CombinerOperandAlpha getCombineOp2Alpha() { + return _combineOp2Alpha; + } + + /** + * @param combineOp2Alpha + * The combineOp2Alpha to set. + * @throws IllegalArgumentException + * if combineOp2Alpha is null + */ + public void setCombineOp2Alpha(final CombinerOperandAlpha combineOp2Alpha) { + if (combineOp2Alpha == null) { + throw new IllegalArgumentException("invalid CombinerOperandAlpha: null"); + } + _combineOp2Alpha = combineOp2Alpha; + } + + /** + * @return Returns the combineOp2RGB. + */ + public CombinerOperandRGB getCombineOp2RGB() { + return _combineOp2RGB; + } + + /** + * @param combineOp2RGB + * The combineOp2RGB to set. + * @throws IllegalArgumentException + * if combineOp2RGB is null + */ + public void setCombineOp2RGB(final CombinerOperandRGB combineOp2RGB) { + if (combineOp2RGB == null) { + throw new IllegalArgumentException("invalid CombinerOperandRGB: null"); + } + _combineOp2RGB = combineOp2RGB; + } + + /** + * @return Returns the combineScaleAlpha. + */ + public CombinerScale getCombineScaleAlpha() { + return _combineScaleAlpha; + } + + /** + * @param combineScaleAlpha + * The combineScaleAlpha to set. + * @throws IllegalArgumentException + * if combineScaleAlpha is null + */ + public void setCombineScaleAlpha(final CombinerScale combineScaleAlpha) { + if (combineScaleAlpha == null) { + throw new IllegalArgumentException("invalid CombinerScale: null"); + } + _combineScaleAlpha = combineScaleAlpha; + } + + /** + * @return Returns the combineScaleRGB. + */ + public CombinerScale getCombineScaleRGB() { + return _combineScaleRGB; + } + + /** + * @param combineScaleRGB + * The combineScaleRGB to set. + * @throws IllegalArgumentException + * if combineScaleRGB is null + */ + public void setCombineScaleRGB(final CombinerScale combineScaleRGB) { + if (combineScaleRGB == null) { + throw new IllegalArgumentException("invalid CombinerScale: null"); + } + _combineScaleRGB = combineScaleRGB; + } + + /** + * @return Returns the combineSrc0Alpha. + */ + public CombinerSource getCombineSrc0Alpha() { + return _combineSrc0Alpha; + } + + /** + * @param combineSrc0Alpha + * The combineSrc0Alpha to set. + * @throws IllegalArgumentException + * if combineSrc0Alpha is null + */ + public void setCombineSrc0Alpha(final CombinerSource combineSrc0Alpha) { + if (combineSrc0Alpha == null) { + throw new IllegalArgumentException("invalid CombinerSource: null"); + } + _combineSrc0Alpha = combineSrc0Alpha; + } + + /** + * @return Returns the combineSrc0RGB. + */ + public CombinerSource getCombineSrc0RGB() { + return _combineSrc0RGB; + } + + /** + * @param combineSrc0RGB + * The combineSrc0RGB to set. + * @throws IllegalArgumentException + * if combineSrc0RGB is null + */ + public void setCombineSrc0RGB(final CombinerSource combineSrc0RGB) { + if (combineSrc0RGB == null) { + throw new IllegalArgumentException("invalid CombinerSource: null"); + } + _combineSrc0RGB = combineSrc0RGB; + } + + /** + * @return Returns the combineSrc1Alpha. + */ + public CombinerSource getCombineSrc1Alpha() { + return _combineSrc1Alpha; + } + + /** + * @param combineSrc1Alpha + * The combineSrc1Alpha to set. + * @throws IllegalArgumentException + * if combineSrc1Alpha is null + */ + public void setCombineSrc1Alpha(final CombinerSource combineSrc1Alpha) { + if (combineSrc1Alpha == null) { + throw new IllegalArgumentException("invalid CombinerSource: null"); + } + _combineSrc1Alpha = combineSrc1Alpha; + } + + /** + * @return Returns the combineSrc1RGB. + */ + public CombinerSource getCombineSrc1RGB() { + return _combineSrc1RGB; + } + + /** + * @param combineSrc1RGB + * The combineSrc1RGB to set. + * @throws IllegalArgumentException + * if combineSrc1RGB is null + */ + public void setCombineSrc1RGB(final CombinerSource combineSrc1RGB) { + if (combineSrc1RGB == null) { + throw new IllegalArgumentException("invalid CombinerSource: null"); + } + _combineSrc1RGB = combineSrc1RGB; + } + + /** + * @return Returns the combineSrc2Alpha. + */ + public CombinerSource getCombineSrc2Alpha() { + return _combineSrc2Alpha; + } + + /** + * @param combineSrc2Alpha + * The combineSrc2Alpha to set. + * @throws IllegalArgumentException + * if combineSrc2Alpha is null + */ + public void setCombineSrc2Alpha(final CombinerSource combineSrc2Alpha) { + if (combineSrc2Alpha == null) { + throw new IllegalArgumentException("invalid CombinerSource: null"); + } + _combineSrc2Alpha = combineSrc2Alpha; + } + + /** + * @return Returns the combineSrc2RGB. + */ + public CombinerSource getCombineSrc2RGB() { + return _combineSrc2RGB; + } + + /** + * @param combineSrc2RGB + * The combineSrc2RGB to set. + * @throws IllegalArgumentException + * if combineSrc2RGB is null + */ + public void setCombineSrc2RGB(final CombinerSource combineSrc2RGB) { + if (combineSrc2RGB == null) { + throw new IllegalArgumentException("invalid CombinerSource: null"); + } + _combineSrc2RGB = combineSrc2RGB; + } + + /** + * @return Returns the combineFuncAlpha. + */ + public CombinerFunctionAlpha getCombineFuncAlpha() { + return _combineFuncAlpha; + } + + /** + * @param combineFuncAlpha + * The combineFuncAlpha to set. + * @throws IllegalArgumentException + * if combineFuncAlpha is null + */ + public void setCombineFuncAlpha(final CombinerFunctionAlpha combineFuncAlpha) { + if (combineFuncAlpha == null) { + throw new IllegalArgumentException("invalid CombinerFunctionAlpha: null"); + } + _combineFuncAlpha = combineFuncAlpha; + } + + /** + * @param envMapMode + * @throws IllegalArgumentException + * if envMapMode is null + */ + public void setEnvironmentalMapMode(final EnvironmentalMapMode envMapMode) { + if (envMapMode == null) { + throw new IllegalArgumentException("invalid EnvironmentalMapMode: null"); + } + _envMapMode = envMapMode; + } + + public EnvironmentalMapMode getEnvironmentalMapMode() { + return _envMapMode; + } + + public ReadOnlyVector4 getEnvPlaneS() { + return _envPlaneS; + } + + public void setEnvPlaneS(final ReadOnlyVector4 plane) { + if (plane == null) { + _envPlaneS = null; + return; + } else if (_envPlaneS == null) { + _envPlaneS = new Vector4(plane); + } else { + _envPlaneS.set(plane); + } + } + + public ReadOnlyVector4 getEnvPlaneT() { + return _envPlaneT; + } + + public void setEnvPlaneT(final ReadOnlyVector4 plane) { + if (plane == null) { + _envPlaneT = null; + return; + } else if (_envPlaneT == null) { + _envPlaneT = new Vector4(plane); + } else { + _envPlaneT.set(plane); + } + } + + public ReadOnlyVector4 getEnvPlaneR() { + return _envPlaneR; + } + + public void setEnvPlaneR(final ReadOnlyVector4 plane) { + if (plane == null) { + _envPlaneR = null; + return; + } else if (_envPlaneR == null) { + _envPlaneR = new Vector4(plane); + } else { + _envPlaneR.set(plane); + } + } + + public ReadOnlyVector4 getEnvPlaneQ() { + return _envPlaneQ; + } + + public void setEnvPlaneQ(final ReadOnlyVector4 plane) { + if (plane == null) { + _envPlaneQ = null; + return; + } else if (_envPlaneQ == null) { + _envPlaneQ = new Vector4(plane); + } else { + _envPlaneQ.set(plane); + } + } + + /** + * @return the anisotropic filtering level for this texture as a percentage (0.0 - 1.0) + */ + public float getAnisotropicFilterPercent() { + return _anisotropicFilterPercent; + } + + /** + * @param percent + * the anisotropic filtering level for this texture as a percentage (0.0 - 1.0) + */ + public void setAnisotropicFilterPercent(float percent) { + if (percent > 1.0f) { + percent = 1.0f; + } else if (percent < 0.0f) { + percent = 0.0f; + } + _anisotropicFilterPercent = percent; + } + + /** + * @return the lodBias for this texture + */ + public float getLodBias() { + return _lodBias; + } + + /** + * @param bias + * the lod bias for this texture. The default is 0. + */ + public void setLodBias(final float bias) { + _lodBias = bias; + } + + public void setTextureKey(final TextureKey tkey) { + _key = tkey; + } + + public TextureKey getTextureKey() { + return _key; + } + + public void setTextureStoreFormat(final TextureStoreFormat storeFormat) { + _storeFormat = storeFormat; + } + + public void setRenderedTexturePixelDataType(final PixelDataType type) { + _rttPixelDataType = type; + } + + public PixelDataType getRenderedTexturePixelDataType() { + return _rttPixelDataType; + } + + public TextureStoreFormat getTextureStoreFormat() { + return _storeFormat; + } + + public boolean isStoreImage() { + return _storeImage; + } + + public void setStoreImage(final boolean store) { + _storeImage = store; + } + + public boolean hasBorder() { + return _hasBorder; + } + + public void setHasBorder(final boolean hasBorder) { + _hasBorder = hasBorder; + } + + /** + * Get the depth texture compare function + * + * @return The depth texture compare function + */ + public DepthTextureCompareFunc getDepthCompareFunc() { + return _depthCompareFunc; + } + + /** + * Set the depth texture compare function + * + * param depthCompareFunc The depth texture compare function + */ + public void setDepthCompareFunc(final DepthTextureCompareFunc depthCompareFunc) { + _depthCompareFunc = depthCompareFunc; + } + + /** + * Get the depth texture apply mode + * + * @return The depth texture apply mode + */ + public DepthTextureMode getDepthMode() { + return _depthMode; + } + + /** + * Set the depth texture apply mode + * + * @param depthMode + * The depth texture apply mode + */ + public void setDepthMode(final DepthTextureMode depthMode) { + _depthMode = depthMode; + } + + /** + * Get the depth texture compare mode + * + * @return The depth texture compare mode + */ + public DepthTextureCompareMode getDepthCompareMode() { + return _depthCompareMode; + } + + /** + * Set the depth texture compare mode + * + * @param depthCompareMode + * The depth texture compare mode + */ + public void setDepthCompareMode(final DepthTextureCompareMode depthCompareMode) { + _depthCompareMode = depthCompareMode; + } + + public void setDirty() { + if (_key != null) { + _key.setDirty(); + } + } + + public boolean isDirty(final Object glContext) { + return _key.isDirty(glContext); + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof Texture)) { + return false; + } + + final Texture that = (Texture) other; + if (getImage() != null && !getImage().equals(that.getImage())) { + return false; + } + if (getImage() == null && that.getImage() != null) { + return false; + } + if (getAnisotropicFilterPercent() != that.getAnisotropicFilterPercent()) { + return false; + } + if (getApply() != that.getApply()) { + return false; + } + if (getCombineFuncAlpha() != that.getCombineFuncAlpha()) { + return false; + } + if (getCombineFuncRGB() != that.getCombineFuncRGB()) { + return false; + } + if (getCombineOp0Alpha() != that.getCombineOp0Alpha()) { + return false; + } + if (getCombineOp1RGB() != that.getCombineOp1RGB()) { + return false; + } + if (getCombineOp2Alpha() != that.getCombineOp2Alpha()) { + return false; + } + if (getCombineOp2RGB() != that.getCombineOp2RGB()) { + return false; + } + if (getCombineScaleAlpha() != that.getCombineScaleAlpha()) { + return false; + } + if (getCombineScaleRGB() != that.getCombineScaleRGB()) { + return false; + } + if (getCombineSrc0Alpha() != that.getCombineSrc0Alpha()) { + return false; + } + if (getCombineSrc0RGB() != that.getCombineSrc0RGB()) { + return false; + } + if (getCombineSrc1Alpha() != that.getCombineSrc1Alpha()) { + return false; + } + if (getCombineSrc1RGB() != that.getCombineSrc1RGB()) { + return false; + } + if (getCombineSrc2Alpha() != that.getCombineSrc2Alpha()) { + return false; + } + if (getCombineSrc2RGB() != that.getCombineSrc2RGB()) { + return false; + } + if (getEnvironmentalMapMode() != that.getEnvironmentalMapMode()) { + return false; + } + if (getMagnificationFilter() != that.getMagnificationFilter()) { + return false; + } + if (getMinificationFilter() != that.getMinificationFilter()) { + return false; + } + if (!_constantColor.equals(that._constantColor)) { + return false; + } + if (!_borderColor.equals(that._borderColor)) { + return false; + } + + return true; + } + + public abstract Texture createSimpleClone(); + + /** + * Retrieve a basic clone of this Texture (ie, clone everything but the image data, which is shared) + * + * @return Texture + */ + public Texture createSimpleClone(final Texture rVal) { + rVal.setAnisotropicFilterPercent(_anisotropicFilterPercent); + rVal.setApply(_apply); + rVal.setConstantColor(_constantColor); + rVal.setBorderColor(_borderColor); + rVal.setCombineFuncAlpha(_combineFuncAlpha); + rVal.setCombineFuncRGB(_combineFuncRGB); + rVal.setCombineOp0Alpha(_combineOp0Alpha); + rVal.setCombineOp0RGB(_combineOp0RGB); + rVal.setCombineOp1Alpha(_combineOp1Alpha); + rVal.setCombineOp1RGB(_combineOp1RGB); + rVal.setCombineOp2Alpha(_combineOp2Alpha); + rVal.setCombineOp2RGB(_combineOp2RGB); + rVal.setCombineScaleAlpha(_combineScaleAlpha); + rVal.setCombineScaleRGB(_combineScaleRGB); + rVal.setCombineSrc0Alpha(_combineSrc0Alpha); + rVal.setCombineSrc0RGB(_combineSrc0RGB); + rVal.setCombineSrc1Alpha(_combineSrc1Alpha); + rVal.setCombineSrc1RGB(_combineSrc1RGB); + rVal.setCombineSrc2Alpha(_combineSrc2Alpha); + rVal.setCombineSrc2RGB(_combineSrc2RGB); + rVal.setDepthCompareFunc(_depthCompareFunc); + rVal.setDepthCompareMode(_depthCompareMode); + rVal.setDepthMode(_depthMode); + rVal.setEnvironmentalMapMode(_envMapMode); + rVal.setEnvPlaneS(_envPlaneS); + rVal.setEnvPlaneT(_envPlaneT); + rVal.setEnvPlaneR(_envPlaneR); + rVal.setEnvPlaneQ(_envPlaneQ); + rVal.setHasBorder(_hasBorder); + rVal.setTextureStoreFormat(_storeFormat); + rVal.setRenderedTexturePixelDataType(_rttPixelDataType); + rVal.setImage(_image); // NOT CLONED. + rVal.setLodBias(_lodBias); + rVal.setMinificationFilter(_minificationFilter); + rVal.setMagnificationFilter(_magnificationFilter); + rVal.setStoreImage(_storeImage); + rVal.setTextureMatrix(_texMatrix); + if (getTextureKey() != null) { + rVal.setTextureKey(getTextureKey()); + } + return rVal; + } + + public ReadOnlyMatrix4 getTextureMatrix() { + return _texMatrix; + } + + public void setTextureMatrix(final ReadOnlyMatrix4 matrix) { + _texMatrix.set(matrix); + } + + public void write(final OutputCapsule capsule) throws IOException { + if (_storeImage) { + capsule.write(_image, "image", null); + } + capsule.write(_constantColor, "constantColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA)); + capsule.write(_borderColor, "borderColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA)); + capsule.write(_texMatrix, "texMatrix", new Matrix4(Matrix4.IDENTITY)); + capsule.write(_hasBorder, "hasBorder", false); + capsule.write(_anisotropicFilterPercent, "anisotropicFilterPercent", 0.0f); + capsule.write(_lodBias, "lodBias", 0.0f); + capsule.write(_minificationFilter, "minificationFilter", MinificationFilter.NearestNeighborNoMipMaps); + capsule.write(_magnificationFilter, "magnificationFilter", MagnificationFilter.Bilinear); + capsule.write(_apply, "apply", ApplyMode.Modulate); + capsule.write(_envMapMode, "envMapMode", EnvironmentalMapMode.None); + capsule.write(_envPlaneS, "envPlaneS", null); + capsule.write(_envPlaneT, "envPlaneT", null); + capsule.write(_envPlaneR, "envPlaneR", null); + capsule.write(_envPlaneQ, "envPlaneQ", null); + capsule.write(_combineFuncRGB, "combineFuncRGB", CombinerFunctionRGB.Replace); + capsule.write(_combineFuncAlpha, "combineFuncAlpha", CombinerFunctionAlpha.Replace); + capsule.write(_combineSrc0RGB, "combineSrc0RGB", CombinerSource.CurrentTexture); + capsule.write(_combineSrc1RGB, "combineSrc1RGB", CombinerSource.Previous); + capsule.write(_combineSrc2RGB, "combineSrc2RGB", CombinerSource.Constant); + capsule.write(_combineSrc0Alpha, "combineSrc0Alpha", CombinerSource.CurrentTexture); + capsule.write(_combineSrc1Alpha, "combineSrc1Alpha", CombinerSource.Previous); + capsule.write(_combineSrc2Alpha, "combineSrc2Alpha", CombinerSource.Constant); + capsule.write(_combineOp0RGB, "combineOp0RGB", CombinerOperandRGB.SourceColor); + capsule.write(_combineOp1RGB, "combineOp1RGB", CombinerOperandRGB.SourceColor); + capsule.write(_combineOp2RGB, "combineOp2RGB", CombinerOperandRGB.SourceAlpha); + capsule.write(_combineOp0Alpha, "combineOp0Alpha", CombinerOperandAlpha.SourceAlpha); + capsule.write(_combineOp1Alpha, "combineOp1Alpha", CombinerOperandAlpha.SourceAlpha); + capsule.write(_combineOp2Alpha, "combineOp2Alpha", CombinerOperandAlpha.SourceAlpha); + capsule.write(_combineScaleRGB, "combineScaleRGB", CombinerScale.One); + capsule.write(_combineScaleAlpha, "combineScaleAlpha", CombinerScale.One); + capsule.write(_storeFormat, "storeFormat", TextureStoreFormat.RGBA8); + capsule.write(_rttPixelDataType, "rttPixelDataType", PixelDataType.UnsignedByte); + capsule.write(_key, "textureKey", null); + } + + public void read(final InputCapsule capsule) throws IOException { + _minificationFilter = capsule.readEnum("minificationFilter", MinificationFilter.class, + MinificationFilter.NearestNeighborNoMipMaps); + _image = (Image) capsule.readSavable("image", null); + + // pull our key, if exists + final TextureKey key = (TextureKey) capsule.readSavable("textureKey", null); + if (key != null) { + _key = TextureKey.getKey(key.getSource(), key.isFlipped(), key.getFormat(), key.getId(), + key.getMinificationFilter()); + } else { + // none set, so pop in a generated key + _key = TextureKey.getRTTKey(_minificationFilter); + } + + // pull texture image from resource, if possible. + if (_image == null && _key != null && _key.getSource() != null) { + TextureManager.loadFromKey(_key, null, this); + } + + _constantColor.set((ColorRGBA) capsule.readSavable("constantColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA))); + _borderColor.set((ColorRGBA) capsule.readSavable("borderColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA))); + _texMatrix.set((Matrix4) capsule.readSavable("texMatrix", new Matrix4(Matrix4.IDENTITY))); + _hasBorder = capsule.readBoolean("hasBorder", false); + _anisotropicFilterPercent = capsule.readFloat("anisotropicFilterPercent", 0.0f); + _lodBias = capsule.readFloat("lodBias", 0.0f); + _magnificationFilter = capsule.readEnum("magnificationFilter", MagnificationFilter.class, + MagnificationFilter.Bilinear); + _apply = capsule.readEnum("apply", ApplyMode.class, ApplyMode.Modulate); + _envMapMode = capsule.readEnum("envMapMode", EnvironmentalMapMode.class, EnvironmentalMapMode.None); + _envPlaneS = (Vector4) capsule.readSavable("envPlaneS", null); + _envPlaneT = (Vector4) capsule.readSavable("envPlaneT", null); + _envPlaneR = (Vector4) capsule.readSavable("envPlaneR", null); + _envPlaneQ = (Vector4) capsule.readSavable("envPlaneQ", null); + _combineFuncRGB = capsule.readEnum("combineFuncRGB", CombinerFunctionRGB.class, CombinerFunctionRGB.Replace); + _combineFuncAlpha = capsule.readEnum("combineFuncAlpha", CombinerFunctionAlpha.class, + CombinerFunctionAlpha.Replace); + _combineSrc0RGB = capsule.readEnum("combineSrc0RGB", CombinerSource.class, CombinerSource.CurrentTexture); + _combineSrc1RGB = capsule.readEnum("combineSrc1RGB", CombinerSource.class, CombinerSource.Previous); + _combineSrc2RGB = capsule.readEnum("combineSrc2RGB", CombinerSource.class, CombinerSource.Constant); + _combineSrc0Alpha = capsule.readEnum("combineSrc0Alpha", CombinerSource.class, CombinerSource.CurrentTexture); + _combineSrc1Alpha = capsule.readEnum("combineSrc1Alpha", CombinerSource.class, CombinerSource.Previous); + _combineSrc2Alpha = capsule.readEnum("combineSrc2Alpha", CombinerSource.class, CombinerSource.Constant); + _combineOp0RGB = capsule.readEnum("combineOp0RGB", CombinerOperandRGB.class, CombinerOperandRGB.SourceColor); + _combineOp1RGB = capsule.readEnum("combineOp1RGB", CombinerOperandRGB.class, CombinerOperandRGB.SourceColor); + _combineOp2RGB = capsule.readEnum("combineOp2RGB", CombinerOperandRGB.class, CombinerOperandRGB.SourceAlpha); + _combineOp0Alpha = capsule.readEnum("combineOp0Alpha", CombinerOperandAlpha.class, + CombinerOperandAlpha.SourceAlpha); + _combineOp1Alpha = capsule.readEnum("combineOp1Alpha", CombinerOperandAlpha.class, + CombinerOperandAlpha.SourceAlpha); + _combineOp2Alpha = capsule.readEnum("combineOp2Alpha", CombinerOperandAlpha.class, + CombinerOperandAlpha.SourceAlpha); + _combineScaleRGB = capsule.readEnum("combineScaleRGB", CombinerScale.class, CombinerScale.One); + _combineScaleAlpha = capsule.readEnum("combineScaleAlpha", CombinerScale.class, CombinerScale.One); + _storeFormat = capsule.readEnum("storeFormat", TextureStoreFormat.class, TextureStoreFormat.RGBA8); + _rttPixelDataType = capsule.readEnum("rttPixelDataType", PixelDataType.class, PixelDataType.UnsignedByte); + } + + public Class<? extends Texture> getClassTag() { + return this.getClass(); + } + + public int getTextureBaseLevel() { + return _textureBaseLevel; + } + + public void setTextureBaseLevel(final int textureBaseLevel) { + _textureBaseLevel = textureBaseLevel; + } + + public int getTextureMaxLevel() { + return _textureMaxLevel; + } + + public void setTextureMaxLevel(final int textureMaxLevel) { + _textureMaxLevel = textureMaxLevel; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java new file mode 100644 index 0000000..5ba42cd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class Texture1D extends Texture { + + private WrapMode _wrapS = WrapMode.Repeat; + + @Override + public Texture createSimpleClone() { + return createSimpleClone(new Texture1D()); + } + + @Override + public Texture createSimpleClone(final Texture rVal) { + rVal.setWrap(WrapAxis.S, _wrapS); + return super.createSimpleClone(rVal); + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for a particular axis. + * + * @param axis + * the texture axis to define a wrapmode on. + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if axis or mode are null + */ + @Override + public void setWrap(final WrapAxis axis, final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } else if (axis == null) { + throw new IllegalArgumentException("axis can not be null."); + } + switch (axis) { + case S: + _wrapS = mode; + break; + } + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for all axis. + * + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if mode is null + */ + @Override + public void setWrap(final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + _wrapS = mode; + } + + /** + * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture. + * + * @param axis + * the axis to return for + * @return the wrap mode of the texture. + * @throws IllegalArgumentException + * if axis is null + */ + @Override + public WrapMode getWrap(final WrapAxis axis) { + switch (axis) { + case S: + return _wrapS; + } + throw new IllegalArgumentException("invalid WrapAxis: " + axis); + } + + @Override + public Type getType() { + return Type.OneDimensional; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof Texture1D)) { + return false; + } + final Texture1D that = (Texture1D) other; + if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) { + return false; + } + return super.equals(other); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java new file mode 100644 index 0000000..dc54a94 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class Texture2D extends Texture { + + private WrapMode _wrapS = WrapMode.Repeat; + private WrapMode _wrapT = WrapMode.Repeat; + + @Override + public Texture createSimpleClone() { + return createSimpleClone(new Texture2D()); + } + + @Override + public Texture createSimpleClone(final Texture rVal) { + rVal.setWrap(WrapAxis.S, _wrapS); + rVal.setWrap(WrapAxis.T, _wrapT); + return super.createSimpleClone(rVal); + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for a particular axis. + * + * @param axis + * the texture axis to define a wrapmode on. + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if axis or mode are null + */ + @Override + public void setWrap(final WrapAxis axis, final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } else if (axis == null) { + throw new IllegalArgumentException("axis can not be null."); + } + switch (axis) { + case S: + _wrapS = mode; + break; + case T: + _wrapT = mode; + break; + } + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for all axis. + * + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if mode is null + */ + @Override + public void setWrap(final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + _wrapS = mode; + _wrapT = mode; + } + + /** + * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture. + * + * @param axis + * the axis to return for + * @return the wrap mode of the texture. + * @throws IllegalArgumentException + * if axis is null + */ + @Override + public WrapMode getWrap(final WrapAxis axis) { + switch (axis) { + case S: + return _wrapS; + case T: + return _wrapT; + } + throw new IllegalArgumentException("invalid WrapAxis: " + axis); + } + + @Override + public Type getType() { + return Type.TwoDimensional; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof Texture2D)) { + return false; + } + final Texture2D that = (Texture2D) other; + if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) { + return false; + } + if (getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) { + return false; + } + return super.equals(other); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(_wrapT, "wrapT", WrapMode.EdgeClamp); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + _wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java new file mode 100644 index 0000000..8258e5e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class Texture3D extends Texture { + + private WrapMode _wrapS = WrapMode.Repeat; + private WrapMode _wrapT = WrapMode.Repeat; + private WrapMode _wrapR = WrapMode.Repeat; + + @Override + public Texture createSimpleClone() { + return createSimpleClone(new Texture3D()); + } + + @Override + public Texture createSimpleClone(final Texture rVal) { + rVal.setWrap(WrapAxis.S, _wrapS); + rVal.setWrap(WrapAxis.T, _wrapT); + rVal.setWrap(WrapAxis.R, _wrapR); + return super.createSimpleClone(rVal); + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for a particular axis. + * + * @param axis + * the texture axis to define a wrapmode on. + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if axis or mode are null + */ + @Override + public void setWrap(final WrapAxis axis, final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } else if (axis == null) { + throw new IllegalArgumentException("axis can not be null."); + } + switch (axis) { + case S: + _wrapS = mode; + break; + case T: + _wrapT = mode; + break; + case R: + _wrapR = mode; + break; + } + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for all axis. + * + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if mode is null + */ + @Override + public void setWrap(final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + _wrapS = mode; + _wrapT = mode; + _wrapR = mode; + } + + /** + * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture. + * + * @param axis + * the axis to return for + * @return the wrap mode of the texture. + * @throws IllegalArgumentException + * if axis is null + */ + @Override + public WrapMode getWrap(final WrapAxis axis) { + switch (axis) { + case S: + return _wrapS; + case T: + return _wrapT; + case R: + return _wrapR; + } + throw new IllegalArgumentException("invalid WrapAxis: " + axis); + } + + @Override + public Type getType() { + return Type.ThreeDimensional; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof Texture3D)) { + return false; + } + final Texture3D that = (Texture3D) other; + if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) { + return false; + } + if (getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) { + return false; + } + if (getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) { + return false; + } + return super.equals(other); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(_wrapT, "wrapT", WrapMode.EdgeClamp); + capsule.write(_wrapR, "wrapR", WrapMode.EdgeClamp); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + _wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + _wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java new file mode 100644 index 0000000..0253976 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class TextureCubeMap extends Texture { + + private WrapMode _wrapS = WrapMode.Repeat; + private WrapMode _wrapT = WrapMode.Repeat; + private WrapMode _wrapR = WrapMode.Repeat; + + private transient Face _currentRTTFace = Face.PositiveX; + + /** + * Face of the Cubemap as described by its directional offset from the origin. + */ + public enum Face { + PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ; + } + + @Override + public Texture createSimpleClone() { + return createSimpleClone(new TextureCubeMap()); + } + + @Override + public Texture createSimpleClone(final Texture rVal) { + rVal.setWrap(WrapAxis.S, _wrapS); + rVal.setWrap(WrapAxis.T, _wrapT); + rVal.setWrap(WrapAxis.R, _wrapR); + if (rVal instanceof TextureCubeMap) { + ((TextureCubeMap) rVal).setCurrentRTTFace(_currentRTTFace); + } + return super.createSimpleClone(rVal); + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for a particular axis. + * + * @param axis + * the texture axis to define a wrapmode on. + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if axis or mode are null + */ + @Override + public void setWrap(final WrapAxis axis, final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } else if (axis == null) { + throw new IllegalArgumentException("axis can not be null."); + } + switch (axis) { + case S: + _wrapS = mode; + break; + case T: + _wrapT = mode; + break; + case R: + _wrapR = mode; + break; + } + } + + /** + * <code>setWrap</code> sets the wrap mode of this texture for all axis. + * + * @param mode + * the wrap mode for the given axis of the texture. + * @throws IllegalArgumentException + * if mode is null + */ + @Override + public void setWrap(final WrapMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + _wrapS = mode; + _wrapT = mode; + _wrapR = mode; + } + + /** + * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture. + * + * @param axis + * the axis to return for + * @return the wrap mode of the texture. + * @throws IllegalArgumentException + * if axis is null + */ + @Override + public WrapMode getWrap(final WrapAxis axis) { + switch (axis) { + case S: + return _wrapS; + case T: + return _wrapT; + case R: + return _wrapR; + } + throw new IllegalArgumentException("invalid WrapAxis: " + axis); + } + + /** + * Set the cubemap Face to use for the next Render To Texture operation (when used with TextureRenderer.) NB: This + * field is transient - not saved by Savable. + * + * @param currentRTTFace + * the face to use + */ + public void setCurrentRTTFace(final Face currentRTTFace) { + _currentRTTFace = currentRTTFace; + } + + /** + * @return the cubemap Face to use for the next Render To Texture operation (when used with TextureRenderer.) + */ + public Face getCurrentRTTFace() { + return _currentRTTFace; + } + + @Override + public Type getType() { + return Type.CubeMap; + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof TextureCubeMap)) { + return false; + } + final TextureCubeMap that = (TextureCubeMap) other; + if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) { + return false; + } + if (getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) { + return false; + } + if (getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) { + return false; + } + return super.equals(other); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(_wrapT, "wrapT", WrapMode.EdgeClamp); + capsule.write(_wrapR, "wrapR", WrapMode.EdgeClamp); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + _wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + _wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java new file mode 100644 index 0000000..62580b0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java @@ -0,0 +1,366 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image; + +public enum TextureStoreFormat { + /** + * When used in texture loading, this indicates to convert the image's data properties to its closest + * TextureStoreFormat <strong>compressed</strong> format. + */ + GuessCompressedFormat, + /** + * When used in texture loading, this indicates to convert the image's data properties to its closest + * TextureStoreFormat format. + */ + GuessNoCompressedFormat, + /** + * 4 bit alpha only format - usually forced to 8bit by the card + */ + Alpha4, + /** + * 8 bit alpha only format + */ + Alpha8, + /** + * 12 bit alpha only format - often forced to 8bit or 16bit by the card + */ + Alpha12, + /** + * 16 bit alpha only format - older cards will often use 8bit instead. + */ + Alpha16, + /** + * 4 bit luminance only format - usually forced to 8bit by the card + */ + Luminance4, + /** + * 8 bit luminance only format + */ + Luminance8, + /** + * 12 bit luminance only format - often forced to 8bit or 16bit by the card + */ + Luminance12, + /** + * 16 bit luminance only format - older cards will often use 8bit instead. + */ + Luminance16, + /** + * 4 bit luminance, 4 bit alpha format + */ + Luminance4Alpha4, + /** + * 6 bit luminance, 2 bit alpha format + */ + Luminance6Alpha2, + /** + * 8 bit luminance, 8 bit alpha format + */ + Luminance8Alpha8, + /** + * 12 bit luminance, 4 bit alpha format + */ + Luminance12Alpha4, + /** + * 12 bit luminance, 12 bit alpha format + */ + Luminance12Alpha12, + /** + * 16 bit luminance, 16 bit alpha format + */ + Luminance16Alpha16, + /** + * 4 bit intensity only format - usually forced to 8bit by the card + */ + Intensity4, + /** + * 8 bit intensity only format + */ + Intensity8, + /** + * 12 bit intensity only format - often forced to 8bit or 16bit by the card + */ + Intensity12, + /** + * 16 bit intensity only format - older cards will often use 8bit instead. + */ + Intensity16, + /** + * 3 bit red, 3 bit green, 3 bit blue - often forced to 16 bit by the card + */ + R3G3B2, + /** + * 4 bits per red, green and blue + */ + RGB4, + /** + * 5 bits per red, green and blue + */ + RGB5, + /** + * 8 bits per red, green and blue + */ + RGB8, + /** + * 10 bits per red, green and blue - usually falls back to 8 bits on the card + */ + RGB10, + /** + * 12 bits per red, green and blue - usually falls back to 8 bits on the card + */ + RGB12, + /** + * 16 bits per red, green and blue - usually falls back to 8 bits on the card + */ + RGB16, + /** + * 2 bits per red, green, blue and alpha - often forced to RGBA4 by the card + */ + RGBA2, + /** + * 4 bits per red, green, blue and alpha + */ + RGBA4, + /** + * 5 bits per red, green and blue. 1 bit of alpha + */ + RGB5A1, + /** + * 8 bits per red, green, blue and alpha + */ + RGBA8, + /** + * 10 bits per red, green and blue. 2 bits of alpha - often forced to RGBA8 by the card + */ + RGB10A2, + /** + * 12 bits per red, green, blue and alpha - often forced to RGBA8 by the card + */ + RGBA12, + /** + * 16 bits per red, green, blue and alpha - often forced to RGBA8 by the card + */ + RGBA16, + /** + * RGB, potentially compressed and stored by the card. + */ + CompressedRed, + /** + * RGB, potentially compressed and stored by the card. + */ + CompressedRG, + /** + * RGB, potentially compressed and stored by the card. + */ + CompressedRGB, + /** + * RGBA, potentially compressed and stored by the card. + */ + CompressedRGBA, + /** + * Luminance, potentially compressed and stored by the card. + */ + CompressedLuminance, + /** + * LuminanceAlpha, potentially compressed and stored by the card. + */ + CompressedLuminanceAlpha, + /** + * Image data already in DXT1 format. + */ + NativeDXT1, + /** + * Image data already in DXT1 (with Alpha) format. + */ + NativeDXT1A, + /** + * Image data already in DXT3 format. + */ + NativeDXT3, + /** + * Image data already in DXT5 format. + */ + NativeDXT5, + /** + * Image data already in LATC format - Luminance only + */ + NativeLATC_L, + /** + * Image data already in LATC format - Luminance+Alpha + */ + NativeLATC_LA, + /** + * depth component format - let card choose bit size + */ + Depth, + /** + * 16 bit depth component format + */ + Depth16, + /** + * 24 bit depth component format + */ + Depth24, + /** + * 32 bit depth component format - often stored in Depth24 format by the card. + */ + Depth32, + /** + * Floating point depth format. + */ + Depth32F, + /** + * 16 bit float per red, green and blue + */ + RGB16F, + /** + * 32 bit float per red, green and blue + */ + RGB32F, + /** + * 16 bit float per red, green, blue and alpha + */ + RGBA16F, + /** + * 32 bit float per red, green, blue and alpha + */ + RGBA32F, + /** + * 16 bit float, alpha only format + */ + Alpha16F, + /** + * 16 bit float, alpha only format + */ + Alpha32F, + /** + * 16 bit float, luminance only format + */ + Luminance16F, + /** + * 32 bit float, luminance only format + */ + Luminance32F, + /** + * 16 bit float per luminance and alpha + */ + LuminanceAlpha16F, + /** + * 32 bit float per luminance and alpha + */ + LuminanceAlpha32F, + /** + * 16 bit float, intensity only format + */ + Intensity16F, + /** + * 32 bit float, intensity only format + */ + Intensity32F, + /** + * 8 bit, one-component format + */ + R8, + /** + * 8 bit integer, one-component format + */ + R8I, + /** + * 8 bit unsigned integer, one-component format + */ + R8UI, + /** + * 16 bit, one-component format + */ + R16, + /** + * 16 bit integer, one-component format + */ + R16I, + /** + * 16 bit unsigned integer, one-component format + */ + R16UI, + /** + * 16 bit float, one-component format + */ + R16F, + /** + * 32 bit integer, one-component format + */ + R32I, + /** + * 32 bit unsigned integer, one-component format + */ + R32UI, + /** + * 32 bit float, one-component format + */ + R32F, + /** + * 8 bit, two-component format + */ + RG8, + /** + * 8 bit integer, two-component format + */ + RG8I, + /** + * 8 bit unsigned integer, two-component format + */ + RG8UI, + /** + * 16 bit, two-component format + */ + RG16, + /** + * 16 bit integer, two-component format + */ + RG16I, + /** + * 16 bit unsigned integer, two-component format + */ + RG16UI, + /** + * 16 bit float, two-component format + */ + RG16F, + /** + * 32 bit integer, two-component format + */ + RG32I, + /** + * 32 bit unsigned integer, two-component format + */ + RG32UI, + /** + * 32 bit float, two-component format. + */ + RG32F; + + public boolean isDepthFormat() { + return this == Depth16 || this == Depth24 || this == Depth32; + } + + public boolean isCompressed() { + switch (this) { + case NativeDXT1: + case NativeDXT1A: + case NativeDXT3: + case NativeDXT5: + case NativeLATC_L: + case NativeLATC_LA: + return true; + default: + return false; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java new file mode 100644 index 0000000..b69ffa8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import java.io.IOException; +import java.io.InputStream; + +import com.ardor3d.image.Image; +import com.ardor3d.util.export.binary.BinaryImporter; + +/** + * Loads Image objects from binary Ardor3D format. + */ +public final class AbiLoader implements ImageLoader { + public AbiLoader() {} + + /** + * Load an image from Ardor3D binary format. + * + * @param is + * the input stream delivering the binary data. + * @param flip + * ignored... for now. + * @return the new loaded Image. + * @throws IOException + * if an error occurs during read. + */ + public Image load(final InputStream is, final boolean flip) throws IOException { + return (Image) new BinaryImporter().load(is); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java new file mode 100644 index 0000000..5035392 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import java.nio.ByteBuffer; + +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.geom.BufferUtils; + +/** + * + * <code>ColorMipMapGenerator</code> + * + */ +public abstract class ColorMipMapGenerator { + + /** + * Generates an ardor3d Image object containing a mipmapped Image. Each mipmap is a solid color. The first X mipmap + * colors are defined in topColors, any remaining mipmaps are a shade of default color. + * + * @param size + * dimensions of the texture (square) + * @param topColors + * initial colors to use for the mipmaps + * @param defaultColor + * color to use for remaining mipmaps, scaled darker for each successive mipmap + * @return generated Image object + */ + public static Image generateColorMipMap(final int size, final ColorRGBA[] topColors, final ColorRGBA defaultColor) { + + if (!MathUtils.isPowerOfTwo(size)) { + throw new Ardor3dException("size must be power of two!"); + } + + final int mips = (int) (MathUtils.log(size, 2)) + 1; + + int bufLength = size * size * 4; + final int[] mipLengths = new int[mips]; + mipLengths[0] = bufLength; + for (int x = 1; x < mips; x++) { + mipLengths[x] = mipLengths[x - 1] >> 1; + bufLength += (mipLengths[x]); + } + + final ByteBuffer bb = BufferUtils.createByteBuffer(bufLength); + + final int[] base = new int[] { (int) (defaultColor.getRed() * 255), (int) (defaultColor.getGreen() * 255), + (int) (defaultColor.getBlue() * 255) }; + + for (int x = 0; x < mips; x++) { + final int length = mipLengths[x] >> 2; + final float div = (float) (mips - x + topColors.length) / mips; + for (int i = 0; i < length; i++) { + if (x >= topColors.length) { + bb.put((byte) (base[0] * div)); + bb.put((byte) (base[1] * div)); + bb.put((byte) (base[2] * div)); + } else { + bb.put((byte) (topColors[x].getRed() * 255)); + bb.put((byte) (topColors[x].getGreen() * 255)); + bb.put((byte) (topColors[x].getBlue() * 255)); + } + bb.put((byte) 255); + } + } + bb.rewind(); + + return new Image(ImageDataFormat.RGBA, PixelDataType.UnsignedByte, size, size, bb, mipLengths); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java new file mode 100644 index 0000000..3591455 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.functions.Function3D; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.util.geom.BufferUtils; + +public abstract class GeneratedImageFactory { + + /** + * Creates a side x side sized image of a single solid color, of data type byte. + * + * @param color + * the color of our image + * @param useAlpha + * if true, the image will have an alpha component (whose value will come from the supplied color.) + * @param side + * the length of a side of our square image + * @return the new Image + */ + public static Image createSolidColorImage(final ReadOnlyColorRGBA color, final boolean useAlpha, final int side) { + final ByteBuffer data = BufferUtils.createByteBuffer(side * side * (useAlpha ? 4 : 3)); + final byte[] b = new byte[useAlpha ? 4 : 3]; + b[0] = (byte) (color.getRed() * 255); + b[1] = (byte) (color.getGreen() * 255); + b[2] = (byte) (color.getBlue() * 255); + if (useAlpha) { + b[3] = (byte) (color.getAlpha() * 255); + } + + for (int i = 0, max = side * side; i < max; i++) { + data.put(b); + } + data.rewind(); + final ImageDataFormat fmt = useAlpha ? ImageDataFormat.RGBA : ImageDataFormat.RGB; + return new Image(fmt, PixelDataType.UnsignedByte, side, side, data, null); + } + + /** + * Creates a one dimensional color image using each color given as a single pixel. + * + * @param useAlpha + * if true, the image will have an alpha component (whose value will come from each supplied color.) + * @param colors + * one or more colors + * @return a 1D image, width == colors.length + */ + public static Image create1DColorImage(final boolean useAlpha, final ReadOnlyColorRGBA... colors) { + final ByteBuffer data = BufferUtils.createByteBuffer(colors.length * (useAlpha ? 4 : 3)); + for (int i = 0; i < colors.length; i++) { + final ReadOnlyColorRGBA color = colors[i]; + data.put((byte) (color.getRed() * 255)); + data.put((byte) (color.getGreen() * 255)); + data.put((byte) (color.getBlue() * 255)); + + if (useAlpha) { + data.put((byte) (color.getAlpha() * 255)); + } + } + data.rewind(); + final ImageDataFormat fmt = (useAlpha) ? ImageDataFormat.RGBA : ImageDataFormat.RGB; + return new Image(fmt, PixelDataType.UnsignedByte, colors.length, 1, data, null); + } + + /** + * Creates an 8 bit luminance image using the given function as input. The domain of the image will be [-1, 1] on + * each axis unless that axis is size 0 (in which case the domain is [0, 0]. The expected range of the function + * result set is also [-1, 1] and this is remapped to [0, 255] for storage as a byte. + * + * @param source + * the source function to evaluate at each pixel of the image. + * @param width + * the width of the image + * @param height + * the height of the image + * @param depth + * the depth of the image (for 3D texture or texture array use) + * @return the resulting Image. + */ + public static Image createLuminance8Image(final Function3D source, final int width, final int height, + final int depth) { + // default range is [-1, 1] on each axis, unless that axis is size 1. + return createLuminance8Image(source, width, height, depth, // + width == 1 ? 0 : -1, width == 1 ? 0 : 1, // X + height == 1 ? 0 : -1, height == 1 ? 0 : 1, // Y + depth == 1 ? 0 : -1, depth == 1 ? 0 : 1, // Z + -1, 1); + } + + /** + * Creates an 8 bit luminance image using the given function as input. + * + * @param source + * the source function to evaluate at each pixel of the image. + * @param width + * the width of the image + * @param height + * the height of the image + * @param depth + * the depth of the image (for 3D texture or texture array use) + * @param startX + * the lowest domain value of the X axis. Or in other words, when a pixel on the leftmost side of the + * image is evaluated, this is the X value sent to the function. + * @param endX + * the highest domain value of the X axis. Or in other words, when a pixel on the rightmost side of the + * image is evaluated, this is the X value sent to the function. + * @param startY + * the lowest domain value of the Y axis. + * @param endY + * the highest domain value of the Y axis. + * @param startZ + * the lowest domain value of the Z axis. + * @param endZ + * the highest domain value of the Z axis. + * @param rangeStart + * the expected lowest value of the output from the source function. This is used to map the output to a + * byte. + * @param rangeEnd + * the expected highest value of the output from the source function. This is used to map the output to a + * byte. + * @return the resulting Image. + */ + public static Image createLuminance8Image(final Function3D source, final int width, final int height, + final int depth, final double startX, final double endX, final double startY, final double endY, + final double startZ, final double endZ, final double rangeStart, final double rangeEnd) { + double val; + final double rangeDiv = 1.0 / (rangeEnd - rangeStart); + // prepare list of image slices. + final List<ByteBuffer> dataList = new ArrayList<ByteBuffer>(depth); + + final byte[] data = new byte[width * height]; + for (double z = 0; z < depth; z++) { + // calc our z coordinate, using start and end Z and our current progress along depth + final double dz = (z / depth) * (endZ - startZ) + startZ; + int i = 0; + for (double y = 0; y < height; y++) { + // calc our y coordinate, using start and end Y and our current progress along height + final double dy = (y / height) * (endY - startY) + startY; + for (double x = 0; x < width; x++) { + // calc our x coordinate, using start and end X and our current progress along width + final double dx = (x / width) * (endX - startX) + startX; + + // Evaluate for dx, dy, dz + val = source.eval(dx, dy, dz); + + // Keep us in [rangeStart, rangeEnd] + val = MathUtils.clamp(val, rangeStart, rangeEnd); + + // Convert to [0, 255] + val = ((val - rangeStart) * rangeDiv) * 255.0; + data[i++] = (byte) val; + } + } + final ByteBuffer dataBuf = BufferUtils.createByteBuffer(data.length); + dataBuf.put(data); + dataList.add(dataBuf); + } + + return new Image(ImageDataFormat.Luminance, PixelDataType.UnsignedByte, width, height, dataList, null); + } + + /** + * Converts an 8 bit luminance Image to an RGB or RGBA color image by mapping the given values to a corresponding + * color value in the given colorTable. + * + * <p> + * XXX: perhaps replace the color array with some gradient class? + * </p> + * + * @param lumImage + * the Image to convert. + * @param useAlpha + * if true, the final image will use have an alpha channel, populated by the alpha values of the given + * colors. + * @param colorTable + * a set of colors, should be length 256. + * @return the new Image. + */ + public static Image createColorImageFromLuminance8(final Image lumImage, final boolean useAlpha, + final ReadOnlyColorRGBA... colorTable) { + assert (colorTable.length == 256) : "color table must be size 256."; + + final List<ByteBuffer> dataList = new ArrayList<ByteBuffer>(lumImage.getDepth()); + ReadOnlyColorRGBA c; + for (int i = 0; i < lumImage.getDepth(); i++) { + final ByteBuffer src = lumImage.getData(i); + final int size = src.capacity(); + final ByteBuffer out = BufferUtils.createByteBuffer(size * (useAlpha ? 4 : 3)); + final byte[] data = new byte[out.capacity()]; + int j = 0; + for (int x = 0; x < size; x++) { + c = colorTable[src.get(x) & 0xFF]; + data[j++] = (byte) (c.getRed() * 255); + data[j++] = (byte) (c.getGreen() * 255); + data[j++] = (byte) (c.getBlue() * 255); + if (useAlpha) { + data[j++] = (byte) (c.getAlpha() * 255); + } + } + out.put(data); + dataList.add(out); + } + + return new Image(useAlpha ? ImageDataFormat.RGBA : ImageDataFormat.RGB, PixelDataType.UnsignedByte, lumImage + .getWidth(), lumImage.getHeight(), dataList, null); + } + + /** + * Fill any empty spots in the given color array by linearly interpolating the non-empty values above and below it. + * + * @param colors + * the color table - must be length 256. + */ + public static void fillInColorTable(final ReadOnlyColorRGBA[] colors) { + assert (colors.length == 256) : "colors must be length 256"; + + // make sure we have a start color... + if (colors[0] == null) { + colors[0] = ColorRGBA.BLACK; + } + // make sure we have a end color... + if (colors[255] == null) { + colors[255] = ColorRGBA.WHITE; + } + + int begin = 0, end = findNonNull(1, colors); + // step through + for (int i = 1; i < 255; i++) { + if (colors[i] != null) { + begin = i; + end = findNonNull(begin + 1, colors); + if (end == -1) { + break; // done! + } + continue; + } + final float scalar = (float) (i - begin) / (end - begin); + colors[i] = ColorRGBA.lerp(colors[begin], colors[end], scalar, null); + } + } + + private static int findNonNull(final int start, final ReadOnlyColorRGBA[] colors) { + for (int i = start; i < colors.length; i++) { + if (colors[i] != null) { + return i; + } + } + return -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java new file mode 100644 index 0000000..4a5bd94 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import java.io.IOException; +import java.io.InputStream; + +import com.ardor3d.image.Image; + +/** + * Interface for image loaders. Implementing classes can be registered with the TextureManager to decode image formats + * with a certain file extension. + * + * @see com.ardor3d.util.TextureManager#addHandler(String, ImageLoader) + * + */ +public interface ImageLoader { + + /** + * Decodes image data from an InputStream. + * + * @param is + * The InputStream to create the image from. The inputstream should be closed before this method returns. + * @return The decoded Image. + * @throws IOException + */ + public Image load(InputStream is, boolean flipped) throws IOException; +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java new file mode 100644 index 0000000..b12475a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.image.Image; +import com.ardor3d.image.util.dds.DdsLoader; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.util.resource.ResourceSource; + +public abstract class ImageLoaderUtil { + private static final Logger logger = Logger.getLogger(ImageLoaderUtil.class.getName()); + + private static ImageLoader defaultLoader; + private static Map<String, ImageLoader> loaders = Collections.synchronizedMap(new HashMap<String, ImageLoader>()); + + static { + registerHandler(new DdsLoader(), ".DDS"); // dxt image + registerHandler(new TgaLoader(), ".TGA"); // targa image + registerHandler(new AbiLoader(), ".ABI"); // ardor3d binary image + } + + public static Image loadImage(final ResourceSource src, final boolean flipped) { + if (src == null) { + logger.warning("loadImage(ResourceSource, boolean): file is null, defaultTexture used."); + return TextureState.getDefaultTextureImage(); + } + + final String type = src.getType(); + if (type == null) { + logger.warning("loadImage(ResourceSource, boolean): type is null, defaultTexture used."); + return TextureState.getDefaultTextureImage(); + } + + InputStream is = null; + try { + is = src.openStream(); + logger.log(Level.FINER, "loadImage(ResourceSource, boolean) opened stream: {0}", src); + return loadImage(type, is, flipped); + } catch (final IOException e) { + logger.log(Level.WARNING, "loadImage(ResourceSource, boolean): defaultTexture used", e); + return TextureState.getDefaultTextureImage(); + } finally { + if (is != null) { + try { + is.close(); + } catch (final IOException ioe) { + } // ignore + } + } + } + + public static Image loadImage(final String type, final InputStream stream, final boolean flipped) { + + Image imageData = null; + try { + ImageLoader loader = loaders.get(type.toLowerCase()); + if (loader == null) { + loader = defaultLoader; + } + if (loader != null) { + imageData = loader.load(stream, flipped); + } else { + logger.log(Level.WARNING, "Unable to read image of type: {0}", type); + } + if (imageData == null) { + logger.warning("loadImage(String, InputStream, boolean): no imageData found. defaultTexture used."); + imageData = TextureState.getDefaultTextureImage(); + } + } catch (final IOException e) { + logger.log(Level.WARNING, "Could not load Image.", e); + imageData = TextureState.getDefaultTextureImage(); + } + return imageData; + } + + /** + * Register an ImageLoader to handle all files with a specific type. An ImageLoader can be registered to handle + * several formats without problems. + * + * @param handler + * the handler to use + * @param types + * The type or types for the format this ImageLoader will handle. This value is case insensitive. + * Examples include ".jpeg", ".gif", ".dds", etc. + */ + public static void registerHandler(final ImageLoader handler, final String... types) { + for (final String type : types) { + loaders.put(type.toLowerCase(), handler); + } + } + + public static void unregisterHandler(final String... types) { + for (final String type : types) { + loaders.remove(type.toLowerCase()); + } + } + + public static void registerDefaultHandler(final ImageLoader handler) { + defaultLoader = handler; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java new file mode 100644 index 0000000..f6064f5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.image.TextureStoreFormat; + +public abstract class ImageUtils { + + public static final int getPixelByteSize(final ImageDataFormat format, final PixelDataType type) { + return type.getBytesPerPixel(format.getComponents()); + } + + public static final TextureStoreFormat getTextureStoreFormat(final TextureStoreFormat format, final Image image) { + if (format != TextureStoreFormat.GuessCompressedFormat && format != TextureStoreFormat.GuessNoCompressedFormat) { + return format; + } + if (image == null) { + throw new Error("Unable to guess format type... Image is null."); + } + + final PixelDataType type = image.getDataType(); + final ImageDataFormat dataFormat = image.getDataFormat(); + switch (dataFormat) { + case ColorIndex: + case BGRA: + case RGBA: + if (format == TextureStoreFormat.GuessCompressedFormat) { + return TextureStoreFormat.CompressedRGBA; + } + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.RGBA8; + case Short: + case UnsignedShort: + case Int: + case UnsignedInt: + return TextureStoreFormat.RGBA16; + case HalfFloat: + return TextureStoreFormat.RGBA16F; + case Float: + return TextureStoreFormat.RGBA32F; + } + break; + case BGR: + case RGB: + if (format == TextureStoreFormat.GuessCompressedFormat) { + return TextureStoreFormat.CompressedRGB; + } + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.RGB8; + case Short: + case UnsignedShort: + case Int: + case UnsignedInt: + return TextureStoreFormat.RGB16; + case HalfFloat: + return TextureStoreFormat.RGB16F; + case Float: + return TextureStoreFormat.RGB32F; + } + break; + case RG: + if (format == TextureStoreFormat.GuessCompressedFormat) { + return TextureStoreFormat.CompressedRG; + } + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.RG8; + case Short: + case UnsignedShort: + return TextureStoreFormat.RG16; + case Int: + return TextureStoreFormat.RG16I; + case UnsignedInt: + return TextureStoreFormat.RG16UI; + case HalfFloat: + return TextureStoreFormat.RG16F; + case Float: + return TextureStoreFormat.RG32F; + } + break; + case Luminance: + if (format == TextureStoreFormat.GuessCompressedFormat) { + return TextureStoreFormat.CompressedLuminance; + } + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.Luminance8; + case Short: + case UnsignedShort: + case Int: + case UnsignedInt: + return TextureStoreFormat.Luminance16; + case HalfFloat: + return TextureStoreFormat.Luminance16F; + case Float: + return TextureStoreFormat.Luminance32F; + } + break; + case LuminanceAlpha: + if (format == TextureStoreFormat.GuessCompressedFormat) { + return TextureStoreFormat.CompressedLuminanceAlpha; + } + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.Luminance4Alpha4; + case Short: + case UnsignedShort: + return TextureStoreFormat.Luminance8Alpha8; + case Int: + case UnsignedInt: + return TextureStoreFormat.Luminance16Alpha16; + case HalfFloat: + return TextureStoreFormat.LuminanceAlpha16F; + case Float: + return TextureStoreFormat.LuminanceAlpha32F; + } + break; + case Alpha: + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.Alpha8; + case Short: + case UnsignedShort: + case Int: + case UnsignedInt: + return TextureStoreFormat.Alpha16; + case HalfFloat: + return TextureStoreFormat.Alpha16F; + case Float: + return TextureStoreFormat.Alpha32F; + } + break; + case Red: + if (format == TextureStoreFormat.GuessCompressedFormat) { + return TextureStoreFormat.CompressedRed; + } + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.R8; + case Short: + case UnsignedShort: + return TextureStoreFormat.R16; + case Int: + return TextureStoreFormat.R16I; + case UnsignedInt: + return TextureStoreFormat.R16UI; + case HalfFloat: + return TextureStoreFormat.R16F; + case Float: + return TextureStoreFormat.R32F; + } + break; + case Intensity: + case Green: + case Blue: + case StencilIndex: + switch (type) { + case Byte: + case UnsignedByte: + return TextureStoreFormat.Intensity8; + case Short: + case UnsignedShort: + case Int: + case UnsignedInt: + return TextureStoreFormat.Intensity16; + case HalfFloat: + return TextureStoreFormat.Intensity16F; + case Float: + return TextureStoreFormat.Intensity32F; + } + break; + case Depth: + // XXX: Should we actually switch here? Depth textures can be slightly fussy. + return TextureStoreFormat.Depth; + case PrecompressedDXT1: + return TextureStoreFormat.NativeDXT1; + case PrecompressedDXT1A: + return TextureStoreFormat.NativeDXT1A; + case PrecompressedDXT3: + return TextureStoreFormat.NativeDXT3; + case PrecompressedDXT5: + return TextureStoreFormat.NativeDXT5; + case PrecompressedLATC_L: + return TextureStoreFormat.NativeLATC_L; + case PrecompressedLATC_LA: + return TextureStoreFormat.NativeLATC_LA; + } + + throw new Error("Unhandled type / format combination: " + type + " / " + dataFormat); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java new file mode 100644 index 0000000..e39b826 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java @@ -0,0 +1,43 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.renderer.Camera;
+
+public class TextureProjector extends Camera {
+
+ private final static ReadOnlyMatrix4 BIAS = new Matrix4( //
+ 0.5, 0.0, 0.0, 0.0, //
+ 0.0, 0.5, 0.0, 0.0, //
+ 0.0, 0.0, 0.5, 0.0, //
+ 0.5, 0.5, 0.5, 1.0);
+
+ public TextureProjector() {
+ super(1, 1);
+ }
+
+ public void updateTextureMatrix(final Texture texture) {
+ final Matrix4 texMat = Matrix4.fetchTempInstance();
+ updateTextureMatrix(texMat);
+ texture.setTextureMatrix(texMat);
+ Matrix4.releaseTempInstance(texMat);
+ }
+
+ public void updateTextureMatrix(final Matrix4 matrixStore) {
+ update();
+ final ReadOnlyMatrix4 projectorView = getModelViewMatrix();
+ final ReadOnlyMatrix4 projectorProjection = getProjectionMatrix();
+ matrixStore.set(projectorView).multiplyLocal(projectorProjection).multiplyLocal(BIAS);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java new file mode 100644 index 0000000..8068501 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java @@ -0,0 +1,491 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Loads image files in the Targa format. Handles RLE Targa files. Does not handle Targa files in Black-and-White + * format. + */ +public final class TgaLoader implements ImageLoader { + + // 0 - no image data in file + public static final int TYPE_NO_IMAGE = 0; + + // 1 - uncompressed, color-mapped image + public static final int TYPE_COLORMAPPED = 1; + + // 2 - uncompressed, true-color image + public static final int TYPE_TRUECOLOR = 2; + + // 3 - uncompressed, black and white image + public static final int TYPE_BLACKANDWHITE = 3; + + // 9 - run-length encoded, color-mapped image + public static final int TYPE_COLORMAPPED_RLE = 9; + + // 10 - run-length encoded, true-color image + public static final int TYPE_TRUECOLOR_RLE = 10; + + // 11 - run-length encoded, black and white image + public static final int TYPE_BLACKANDWHITE_RLE = 11; + + public TgaLoader() {} + + /** + * Load an image from Targa format. + * + * @param is + * the input stream delivering the targa data. + * @param flip + * if true, we will flip the given targa image on the vertical axis. + * @return the new loaded Image. + * @throws IOException + * if an error occurs during read. + */ + public Image load(final InputStream is, boolean flip) throws IOException { + boolean flipH = false; + // open a stream to the file + final BufferedInputStream bis = new BufferedInputStream(is, 8192); + final DataInputStream dis = new DataInputStream(bis); + boolean createAlpha = false; + + // ---------- Start Reading the TGA header ---------- // + // length of the image id (1 byte) + final int idLength = dis.readUnsignedByte(); + + // Type of color map (if any) included with the image + // 0 - no color map data is included + // 1 - a color map is included + final int colorMapType = dis.readUnsignedByte(); + + // Type of image being read: + final int imageType = dis.readUnsignedByte(); + + // Read Color Map Specification (5 bytes) + // Index of first color map entry (if we want to use it, uncomment and remove extra read.) + // short cMapStart = flipEndian(dis.readShort()); + dis.readShort(); + // number of entries in the color map + final short cMapLength = flipEndian(dis.readShort()); + // number of bits per color map entry + final int cMapDepth = dis.readUnsignedByte(); + + // Read Image Specification (10 bytes) + // horizontal coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.) + // int xOffset = flipEndian(dis.readShort()); + dis.readShort(); + // vertical coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.) + // int yOffset = flipEndian(dis.readShort()); + dis.readShort(); + // width of image - in pixels + final int width = flipEndian(dis.readShort()); + // height of image - in pixels + final int height = flipEndian(dis.readShort()); + // bits per pixel in image. + final int pixelDepth = dis.readUnsignedByte(); + final int imageDescriptor = dis.readUnsignedByte(); + if ((imageDescriptor & 32) != 0) { + flip = !flip; + } + if ((imageDescriptor & 16) != 0) { + flipH = !flipH; + } + + // ---------- Done Reading the TGA header ---------- // + + // Skip image ID + if (idLength > 0) { + if (idLength != bis.skip(idLength)) { + throw new IOException("Unexpected number of bytes in file - too few."); + } + } + + ColorMapEntry[] cMapEntries = null; + if (colorMapType != 0) { + // read the color map. + final int bytesInColorMap = (cMapDepth * cMapLength) >> 3; + final int bitsPerColor = Math.min(cMapDepth / 3, 8); + + final byte[] cMapData = new byte[bytesInColorMap]; + if (-1 == bis.read(cMapData)) { + throw new EOFException(); + } + + // Only go to the trouble of constructing the color map + // table if this is declared a color mapped image. + if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) { + cMapEntries = new ColorMapEntry[cMapLength]; + final int alphaSize = cMapDepth - (3 * bitsPerColor); + final float scalar = 255f / (int) (Math.pow(2, bitsPerColor) - 1); + final float alphaScalar = 255f / (int) (Math.pow(2, alphaSize) - 1); + for (int i = 0; i < cMapLength; i++) { + final ColorMapEntry entry = new ColorMapEntry(); + final int offset = cMapDepth * i; + entry.red = (byte) (int) (getBitsAsByte(cMapData, offset, bitsPerColor) * scalar); + entry.green = (byte) (int) (getBitsAsByte(cMapData, offset + bitsPerColor, bitsPerColor) * scalar); + entry.blue = (byte) (int) (getBitsAsByte(cMapData, offset + (2 * bitsPerColor), bitsPerColor) * scalar); + if (alphaSize <= 0) { + entry.alpha = (byte) 255; + } else { + entry.alpha = (byte) (int) (getBitsAsByte(cMapData, offset + (3 * bitsPerColor), alphaSize) * alphaScalar); + } + + cMapEntries[i] = entry; + } + } + } + + // Allocate image data array + byte[] rawData = null; + int dl; + if ((pixelDepth == 32)) { + rawData = new byte[width * height * 4]; + dl = 4; + createAlpha = true; + } else { + rawData = new byte[width * height * 3]; + dl = 3; + } + int rawDataIndex = 0; + + if (imageType == TYPE_TRUECOLOR) { + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 0; + + // Faster than doing a 16-or-24-or-32 check on each individual pixel, + // just make a separate loop for each. + if (pixelDepth == 16) { + final byte[] data = new byte[2]; + final float scalar = 255f / 31f; + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + data[1] = dis.readByte(); + data[0] = dis.readByte(); + rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar); + rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar); + rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar); + if (createAlpha) { + alpha = getBitsAsByte(data, 0, 1); + if (alpha == 1) { + alpha = (byte) 255; + } + rawData[rawDataIndex++] = alpha; + } + } + } + } else if (pixelDepth == 24) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + if (createAlpha) { + rawData[rawDataIndex++] = (byte) 255; + } + + } + } + } else if (pixelDepth == 32) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } + } else { + throw new Ardor3dException("Unsupported TGA true color depth: " + pixelDepth); + } + } else if (imageType == TYPE_TRUECOLOR_RLE) { + byte red = 0; + byte green = 0; + byte blue = 0; + byte alpha = 0; + + // Faster than doing a 16-or-24-or-32 check on each individual pixel, + // just make a separate loop for each. + if (pixelDepth == 32) { + for (int i = 0; i <= (height - 1); ++i) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + + for (int j = 0; j < width; ++j) { + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if ((count & 0x80) != 0) { + // Its an RLE packed block - use the following 1 pixel for the next <count> pixels + count &= 0x07f; + j += count; + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + while (count-- >= 0) { + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } else { + // Its not RLE packed, but the next <count> pixels are raw. + j += count; + while (count-- >= 0) { + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + alpha = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + rawData[rawDataIndex++] = alpha; + } + } + } + } + + } else if (pixelDepth == 24) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; ++j) { + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if ((count & 0x80) != 0) { + // Its an RLE packed block - use the following 1 pixel for the next <count> pixels + count &= 0x07f; + j += count; + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + while (count-- >= 0) { + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + if (createAlpha) { + rawData[rawDataIndex++] = (byte) 255; + } + } + } else { + // Its not RLE packed, but the next <count> pixels are raw. + j += count; + while (count-- >= 0) { + blue = dis.readByte(); + green = dis.readByte(); + red = dis.readByte(); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + if (createAlpha) { + rawData[rawDataIndex++] = (byte) 255; + } + } + } + } + } + + } else if (pixelDepth == 16) { + final byte[] data = new byte[2]; + final float scalar = 255f / 31f; + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + // Get the number of pixels the next chunk covers (either packed or unpacked) + int count = dis.readByte(); + if ((count & 0x80) != 0) { + // Its an RLE packed block - use the following 1 pixel for the next <count> pixels + count &= 0x07f; + j += count; + data[1] = dis.readByte(); + data[0] = dis.readByte(); + blue = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar); + green = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar); + red = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar); + while (count-- >= 0) { + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + if (createAlpha) { + rawData[rawDataIndex++] = (byte) 255; + } + } + } else { + // Its not RLE packed, but the next <count> pixels are raw. + j += count; + while (count-- >= 0) { + data[1] = dis.readByte(); + data[0] = dis.readByte(); + blue = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar); + green = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar); + red = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar); + rawData[rawDataIndex++] = red; + rawData[rawDataIndex++] = green; + rawData[rawDataIndex++] = blue; + if (createAlpha) { + rawData[rawDataIndex++] = (byte) 255; + } + } + } + } + } + + } else { + throw new Ardor3dException("Unsupported TGA true color depth: " + pixelDepth); + } + + } else if (imageType == TYPE_COLORMAPPED) { + final int bytesPerIndex = pixelDepth / 8; + + if (bytesPerIndex == 1) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + final int index = dis.readUnsignedByte(); + if (index >= cMapEntries.length || index < 0) { + throw new Ardor3dException("TGA: Invalid color map entry referenced: " + index); + } + final ColorMapEntry entry = cMapEntries[index]; + rawData[rawDataIndex++] = entry.red; + rawData[rawDataIndex++] = entry.green; + rawData[rawDataIndex++] = entry.blue; + if (dl == 4) { + rawData[rawDataIndex++] = entry.alpha; + } + + } + } + } else if (bytesPerIndex == 2) { + for (int i = 0; i <= (height - 1); i++) { + if (!flip) { + rawDataIndex = (height - 1 - i) * width * dl; + } + for (int j = 0; j < width; j++) { + final int index = flipEndian(dis.readShort()); + if (index >= cMapEntries.length || index < 0) { + throw new Ardor3dException("TGA: Invalid color map entry referenced: " + index); + } + final ColorMapEntry entry = cMapEntries[index]; + rawData[rawDataIndex++] = entry.red; + rawData[rawDataIndex++] = entry.green; + rawData[rawDataIndex++] = entry.blue; + if (dl == 4) { + rawData[rawDataIndex++] = entry.alpha; + } + } + } + } else { + throw new Ardor3dException("TGA: unknown colormap indexing size used: " + bytesPerIndex); + } + } + + // Get a pointer to the image memory + final ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length); + scratch.clear(); + scratch.put(rawData); + scratch.rewind(); + // Create the ardor3d.image.Image object + final com.ardor3d.image.Image textureImage = new com.ardor3d.image.Image(); + if (dl == 4) { + textureImage.setDataFormat(ImageDataFormat.RGBA); + } else { + textureImage.setDataFormat(ImageDataFormat.RGB); + } + textureImage.setDataType(PixelDataType.UnsignedByte); + textureImage.setWidth(width); + textureImage.setHeight(height); + textureImage.setData(scratch); + return textureImage; + } + + private static byte getBitsAsByte(final byte[] data, final int offset, final int length) { + int offsetBytes = offset / 8; + int indexBits = offset % 8; + int rVal = 0; + + // start at data[offsetBytes]... spill into next byte as needed. + for (int i = length; --i >= 0;) { + final byte b = data[offsetBytes]; + final int test = indexBits == 7 ? 1 : 2 << (6 - indexBits); + if ((b & test) != 0) { + if (i == 0) { + rVal++; + } else { + rVal += (2 << i - 1); + } + } + indexBits++; + if (indexBits == 8) { + indexBits = 0; + offsetBytes++; + } + } + + return (byte) rVal; + } + + /** + * <code>flipEndian</code> is used to flip the endian bit of the header file. + * + * @param signedShort + * the bit to flip. + * @return the flipped bit. + */ + private static short flipEndian(final short signedShort) { + final int input = signedShort & 0xFFFF; + return (short) (input << 8 | (input & 0xFF00) >>> 8); + } + + private static class ColorMapEntry { + byte red, green, blue, alpha; + + @Override + public String toString() { + return "entry: " + red + "," + green + "," + blue + "," + alpha; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java new file mode 100644 index 0000000..b777d12 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +enum D3d10ResourceDimension { + D3D10_RESOURCE_DIMENSION_UNKNOWN(0), // + D3D10_RESOURCE_DIMENSION_BUFFER(1), // + D3D10_RESOURCE_DIMENSION_TEXTURE1D(2), // + D3D10_RESOURCE_DIMENSION_TEXTURE2D(3), // + D3D10_RESOURCE_DIMENSION_TEXTURE3D(4); // + + int _value; + + D3d10ResourceDimension(final int value) { + _value = value; + } + + static D3d10ResourceDimension forInt(final int value) { + for (final D3d10ResourceDimension dim : values()) { + if (dim._value == value) { + return dim; + } + } + throw new Error("unknown D3D10ResourceDimension: " + value); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java new file mode 100644 index 0000000..e624f2f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +import static com.ardor3d.image.util.dds.DdsUtils.isSet; + +import java.io.IOException; +import java.util.logging.Logger; + +import com.ardor3d.util.LittleEndianDataInput; + +class DdsHeader { + private static final Logger logger = Logger.getLogger(DdsHeader.class.getName()); + + // ---- VALUES USED IN dwFlags ---- + // Required caps flag. + final static int DDSD_CAPS = 0x1; + // Required caps flag. + final static int DDSD_HEIGHT = 0x2; + // Required caps flag. + final static int DDSD_WIDTH = 0x4; + // Required when pitch is provided for an uncompressed texture. + final static int DDSD_PITCH = 0x8; + // Required caps flag. + final static int DDSD_PIXELFORMAT = 0x1000; + // Required in a mipmapped texture. + final static int DDSD_MIPMAPCOUNT = 0x20000; + // Required when pitch is provided for a compressed texture. + final static int DDSD_LINEARSIZE = 0x80000; + // Required in a depth texture. + final static int DDSD_DEPTH = 0x800000; + // ---- /end VALUES USED IN dwFlags ---- + + // ---- VALUES USED IN dwCaps ---- + // Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or + // volume texture). + final static int DDSCAPS_COMPLEX = 0x8; + // Optional; should be used for a mipmap. + final static int DDSCAPS_MIPMAP = 0x400000; + // Required caps flag. + final static int DDSCAPS_TEXTURE = 0x1000; + // ---- /end VALUES USED IN dwCaps ---- + + // ---- VALUES USED IN dwCaps2 ---- + // Required for a cube map. + final static int DDSCAPS2_CUBEMAP = 0x200; + // Required when these surfaces are stored in a cube map. + final static int DDSCAPS2_CUBEMAP_POSITIVEX = 0x400; + // Required when these surfaces are stored in a cube map. + final static int DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800; + // Required when these surfaces are stored in a cube map. + final static int DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000; + // Required when these surfaces are stored in a cube map. + final static int DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000; + // Required when these surfaces are stored in a cube map. + final static int DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000; + // Required when these surfaces are stored in a cube map. + final static int DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; + // Required for a volume texture. + final static int DDSCAPS2_VOLUME = 0x200000; + // ---- /end VALUES USED IN dwCaps2 ---- + + int dwSize; + int dwFlags; + int dwHeight; + int dwWidth; + int dwLinearSize; + int dwDepth; + int dwMipMapCount; + int dwAlphaBitDepth; + int[] dwReserved1 = new int[10]; + DdsPixelFormat ddpf; + int dwCaps; + int dwCaps2; + int dwCaps3; + int dwCaps4; + int dwTextureStage; + + static DdsHeader read(final LittleEndianDataInput in) throws IOException { + final DdsHeader header = new DdsHeader(); + header.dwSize = in.readInt(); + if (header.dwSize != 124) { + throw new Error("invalid dds header size: " + header.dwSize); + } + header.dwFlags = in.readInt(); + header.dwHeight = in.readInt(); + header.dwWidth = in.readInt(); + header.dwLinearSize = in.readInt(); + header.dwDepth = in.readInt(); + header.dwMipMapCount = in.readInt(); + header.dwAlphaBitDepth = in.readInt(); + for (int i = 0; i < header.dwReserved1.length; i++) { + header.dwReserved1[i] = in.readInt(); + } + header.ddpf = DdsPixelFormat.read(in); + header.dwCaps = in.readInt(); + header.dwCaps2 = in.readInt(); + header.dwCaps3 = in.readInt(); + header.dwCaps4 = in.readInt(); + header.dwTextureStage = in.readInt(); + + final int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(header.dwHeight, header.dwWidth)) + / Math.log(2)); + + if (isSet(header.dwCaps, DDSCAPS_MIPMAP)) { + if (!isSet(header.dwFlags, DDSD_MIPMAPCOUNT)) { + header.dwMipMapCount = expectedMipmaps; + } else if (header.dwMipMapCount != expectedMipmaps) { + logger.fine("Got " + header.dwMipMapCount + " mipmaps, expected " + expectedMipmaps); + } + } else { + header.dwMipMapCount = 1; + } + + return header; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java new file mode 100644 index 0000000..7ef5c62 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +import java.io.IOException; + +import com.ardor3d.util.LittleEndianDataInput; + +class DdsHeaderDX10 { + final static int D3D10_RESOURCE_MISC_GENERATE_MIPS = 0x1; + final static int D3D10_RESOURCE_MISC_SHARED = 0x2; + final static int D3D10_RESOURCE_MISC_TEXTURECUBE = 0x4; + final static int D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX = 0x10; + final static int D3D10_RESOURCE_MISC_GDI_COMPATIBLE = 0x20; + + DxgiFormat dxgiFormat; + D3d10ResourceDimension resourceDimension; + int miscFlag; + int arraySize; + int reserved; + + static DdsHeaderDX10 read(final LittleEndianDataInput in) throws IOException { + final DdsHeaderDX10 header = new DdsHeaderDX10(); + header.dxgiFormat = DxgiFormat.forInt(in.readInt()); + header.resourceDimension = D3d10ResourceDimension.forInt(in.readInt()); + header.miscFlag = in.readInt(); + header.arraySize = in.readInt(); + header.reserved = in.readInt(); + return header; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java new file mode 100644 index 0000000..8d551ac --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java @@ -0,0 +1,385 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +import static com.ardor3d.image.util.dds.DdsUtils.flipDXT; +import static com.ardor3d.image.util.dds.DdsUtils.getInt; +import static com.ardor3d.image.util.dds.DdsUtils.isSet; +import static com.ardor3d.image.util.dds.DdsUtils.shiftCount; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.logging.Logger; + +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.image.util.ImageLoader; +import com.ardor3d.image.util.ImageUtils; +import com.ardor3d.util.LittleEndianDataInput; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Lists; + +/** + * <p> + * <code>DdsLoader</code> is an image loader that reads in a DirectX DDS file. + * </p> + * Supports 2D images, volume images and cubemaps in the following formats:<br> + * Compressed:<br> + * <ul> + * <li>DXT1A</li> + * <li>DXT3</li> + * <li>DXT5</li> + * <li>LATC</li> + * </ul> + * Uncompressed:<br> + * <ul> + * <li>RGB</li> + * <li>RGBA</li> + * <li>Luminance</li> + * <li>LuminanceAlpha</li> + * <li>Alpha</li> + * </ul> + * Note that Cubemaps must have all 6 faces defined to load properly. FIXME: Needs a software inflater for compressed + * formats in cases where support is not present? Maybe JSquish? + */ +public class DdsLoader implements ImageLoader { + private static final Logger logger = Logger.getLogger(DdsLoader.class.getName()); + + public Image load(final InputStream is, final boolean flipVertically) throws IOException { + final LittleEndianDataInput in = new LittleEndianDataInput(is); + + // Read and check magic word... + final int dwMagic = in.readInt(); + if (dwMagic != getInt("DDS ")) { + throw new Error("Not a dds file."); + } + logger.finest("Reading DDS file."); + + // Create our data store; + final DdsImageInfo info = new DdsImageInfo(); + + info.flipVertically = flipVertically; + + // Read standard dds header + info.header = DdsHeader.read(in); + + // if applicable, read DX10 header + info.headerDX10 = info.header.ddpf.dwFourCC == getInt("DX10") ? DdsHeaderDX10.read(in) : null; + + // Create our new image + final Image image = new Image(); + image.setWidth(info.header.dwWidth); + image.setHeight(info.header.dwHeight); + + // update depth based on flags / header + updateDepth(image, info); + + // add our format and image data. + populateImage(image, info, in); + + // return the loaded image + return image; + } + + private static final void updateDepth(final Image image, final DdsImageInfo info) { + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP)) { + int depth = 0; + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEX)) { + depth++; + } + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEX)) { + depth++; + } + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEY)) { + depth++; + } + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEY)) { + depth++; + } + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEZ)) { + depth++; + } + if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEZ)) { + depth++; + } + + if (depth != 6) { + throw new Error("Cubemaps without all faces defined are not currently supported."); + } + + image.setDepth(depth); + } else { + // make sure we have at least depth of 1. + image.setDepth(info.header.dwDepth > 0 ? info.header.dwDepth : 1); + } + } + + private static final void populateImage(final Image image, final DdsImageInfo info, final LittleEndianDataInput in) + throws IOException { + final int flags = info.header.ddpf.dwFlags; + + final boolean compressedFormat = isSet(flags, DdsPixelFormat.DDPF_FOURCC); + final boolean rgb = isSet(flags, DdsPixelFormat.DDPF_RGB); + final boolean alphaPixels = isSet(flags, DdsPixelFormat.DDPF_ALPHAPIXELS); + final boolean lum = isSet(flags, DdsPixelFormat.DDPF_LUMINANCE); + final boolean alpha = isSet(flags, DdsPixelFormat.DDPF_ALPHA); + + if (compressedFormat) { + final int fourCC = info.header.ddpf.dwFourCC; + // DXT1 format + if (fourCC == getInt("DXT1")) { + info.bpp = 4; + // if (isSet(flags, DdsPixelFormat.DDPF_ALPHAPIXELS)) { + // XXX: many authoring tools do not set alphapixels, so we'll error on the side of alpha + logger.finest("DDS format: DXT1A"); + image.setDataFormat(ImageDataFormat.PrecompressedDXT1A); + // } else { + // logger.finest("DDS format: DXT1"); + // image.setDataFormat(ImageDataFormat.PrecompressedDXT1); + // } + } + + // DXT3 format + else if (fourCC == getInt("DXT3")) { + logger.finest("DDS format: DXT3"); + info.bpp = 8; + image.setDataFormat(ImageDataFormat.PrecompressedDXT3); + } + + // DXT5 format + else if (fourCC == getInt("DXT5")) { + logger.finest("DDS format: DXT5"); + info.bpp = 8; + image.setDataFormat(ImageDataFormat.PrecompressedDXT5); + } + + // DXT10 info present... + else if (fourCC == getInt("DX10")) { + switch (info.headerDX10.dxgiFormat) { + case DXGI_FORMAT_BC4_UNORM: + logger.finest("DXGI format: BC4_UNORM"); + info.bpp = 4; + image.setDataFormat(ImageDataFormat.PrecompressedLATC_L); + break; + case DXGI_FORMAT_BC5_UNORM: + logger.finest("DXGI format: BC5_UNORM"); + info.bpp = 8; + image.setDataFormat(ImageDataFormat.PrecompressedLATC_LA); + break; + default: + throw new Error("dxgiFormat not supported: " + info.headerDX10.dxgiFormat); + } + } + + // DXT2 format - unsupported + else if (fourCC == getInt("DXT2")) { + logger.finest("DDS format: DXT2"); + throw new Error("DXT2 is not supported."); + } + + // DXT4 format - unsupported + else if (fourCC == getInt("DXT4")) { + logger.finest("DDS format: DXT4"); + throw new Error("DXT4 is not supported."); + } + + // Unsupported compressed type. + else { + throw new Error("unsupported compressed dds format found (" + fourCC + ")"); + } + } + + // not a compressed format + else { + // TODO: more use of bit masks? + // TODO: Use bit size instead of hardcoded 8 bytes? (need to also implement in readUncompressed) + image.setDataType(PixelDataType.UnsignedByte); + + info.bpp = info.header.ddpf.dwRGBBitCount; + + // One of the RGB formats? + if (rgb) { + if (alphaPixels) { + logger.finest("DDS format: uncompressed rgba"); + image.setDataFormat(ImageDataFormat.RGBA); + } else { + logger.finest("DDS format: uncompressed rgb "); + image.setDataFormat(ImageDataFormat.RGB); + } + } + + // A luminance or alpha format + else if (lum || alphaPixels) { + if (lum && alphaPixels) { + logger.finest("DDS format: uncompressed LumAlpha"); + image.setDataFormat(ImageDataFormat.LuminanceAlpha); + } + + else if (lum) { + logger.finest("DDS format: uncompressed Lum"); + image.setDataFormat(ImageDataFormat.Luminance); + } + + else if (alpha) { + logger.finest("DDS format: uncompressed Alpha"); + image.setDataFormat(ImageDataFormat.Alpha); + } + } // end luminance/alpha type + + // Unsupported type. + else { + throw new Error("unsupported uncompressed dds format found."); + } + } + + info.calcMipmapSizes(compressedFormat); + image.setMipMapByteSizes(info.mipmapByteSizes); + + // Add up total byte size of single depth layer + int totalSize = 0; + for (final int size : info.mipmapByteSizes) { + totalSize += size; + } + + // Go through and load in image data + final List<ByteBuffer> imageData = Lists.newArrayList(); + for (int i = 0; i < image.getDepth(); i++) { + // read in compressed data + if (compressedFormat) { + imageData.add(readDXT(in, totalSize, info, image)); + } + + // read in uncompressed data + else if (rgb || lum || alpha) { + imageData.add(readUncompressed(in, totalSize, rgb, lum, alpha, alphaPixels, info, image)); + } + } + + // set on image + image.setData(imageData); + } + + static final ByteBuffer readDXT(final LittleEndianDataInput in, final int totalSize, final DdsImageInfo info, + final Image image) throws IOException { + int mipWidth = info.header.dwWidth; + int mipHeight = info.header.dwHeight; + + final ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize); + for (int mip = 0; mip < info.header.dwMipMapCount; mip++) { + final byte[] data = new byte[info.mipmapByteSizes[mip]]; + in.readFully(data); + if (!info.flipVertically) { + buffer.put(data); + } else { + final byte[] flipped = flipDXT(data, mipWidth, mipHeight, image.getDataFormat()); + buffer.put(flipped); + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + } + buffer.rewind(); + return buffer; + } + + private static ByteBuffer readUncompressed(final LittleEndianDataInput in, final int totalSize, + final boolean useRgb, final boolean useLum, final boolean useAlpha, final boolean useAlphaPixels, + final DdsImageInfo info, final Image image) throws IOException { + final int redLumShift = shiftCount(info.header.ddpf.dwRBitMask); + final int greenShift = shiftCount(info.header.ddpf.dwGBitMask); + final int blueShift = shiftCount(info.header.ddpf.dwBBitMask); + final int alphaShift = shiftCount(info.header.ddpf.dwABitMask); + + final int sourcebytesPP = info.header.ddpf.dwRGBBitCount / 8; + final int targetBytesPP = ImageUtils.getPixelByteSize(image.getDataFormat(), image.getDataType()); + + final ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize); + + int mipWidth = info.header.dwWidth; + int mipHeight = info.header.dwHeight; + int offset = 0; + + for (int mip = 0; mip < info.header.dwMipMapCount; mip++) { + for (int y = 0; y < mipHeight; y++) { + for (int x = 0; x < mipWidth; x++) { + final byte[] b = new byte[sourcebytesPP]; + in.readFully(b); + + final int i = getInt(b); + + final byte redLum = (byte) (((i & info.header.ddpf.dwRBitMask) >> redLumShift)); + final byte green = (byte) (((i & info.header.ddpf.dwGBitMask) >> greenShift)); + final byte blue = (byte) (((i & info.header.ddpf.dwBBitMask) >> blueShift)); + final byte alpha = (byte) (((i & info.header.ddpf.dwABitMask) >> alphaShift)); + + if (info.flipVertically) { + dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP); + } + + if (useAlpha) { + dataBuffer.put(alpha); + } else if (useLum) { + if (useAlphaPixels) { + dataBuffer.put(redLum).put(alpha); + } else { + dataBuffer.put(redLum); + } + } else if (useRgb) { + if (useAlphaPixels) { + dataBuffer.put(redLum).put(green).put(blue).put(alpha); + } else { + dataBuffer.put(redLum).put(green).put(blue); + } + } + } + } + + offset += mipWidth * mipHeight * targetBytesPP; + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + + return dataBuffer; + } + + private final static class DdsImageInfo { + boolean flipVertically; + int bpp = 0; + DdsHeader header; + DdsHeaderDX10 headerDX10; + int mipmapByteSizes[]; + + void calcMipmapSizes(final boolean compressed) { + int width = header.dwWidth; + int height = header.dwHeight; + int size = 0; + + mipmapByteSizes = new int[header.dwMipMapCount]; + + for (int i = 0; i < header.dwMipMapCount; i++) { + if (compressed) { + size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2; + } else { + size = width * height * bpp / 8; + } + + mipmapByteSizes[i] = ((size + 3) / 4) * 4; + + width = Math.max(width / 2, 1); + height = Math.max(height / 2, 1); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java new file mode 100644 index 0000000..71ee4b0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +import java.io.IOException; + +import com.ardor3d.util.LittleEndianDataInput; + +class DdsPixelFormat { + + // ---- VALUES USED IN dwFlags ---- + // Texture contains alpha data; dwABitMask contains valid data. + final static int DDPF_ALPHAPIXELS = 0x1; + // Used in some older DDS files for alpha channel only uncompressed data (dwRGBBitCount contains the alpha channel + // bitcount; dwABitMask contains valid data) + final static int DDPF_ALPHA = 0x2; + // Texture contains compressed RGB data; dwFourCC contains valid data. + final static int DDPF_FOURCC = 0x4; + // Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks (dwRBitMask, dwGBitMask, dwBBitMask) + // contain valid data. + final static int DDPF_RGB = 0x40; + // Used in some older DDS files for YUV uncompressed data (dwRGBBitCount contains the YUV bit count; dwRBitMask + // contains the Y mask, dwGBitMask contains the U mask, dwBBitMask contains the V mask) + final static int DDPF_YUV = 0x200; + // Used in some older DDS files for single channel color uncompressed data (dwRGBBitCount contains the luminance + // channel bit count; dwRBitMask contains the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel + // DDS file. + final static int DDPF_LUMINANCE = 0x20000; + // ---- /end VALUES USED IN dwFlags ---- + + int dwSize; + int dwFlags; + int dwFourCC; + int dwRGBBitCount; + int dwRBitMask; + int dwGBitMask; + int dwBBitMask; + int dwABitMask; + + static DdsPixelFormat read(final LittleEndianDataInput in) throws IOException { + final DdsPixelFormat format = new DdsPixelFormat(); + format.dwSize = in.readInt(); + if (format.dwSize != 32) { + throw new Error("invalid pixel format size: " + format.dwSize); + } + format.dwFlags = in.readInt(); + format.dwFourCC = in.readInt(); + format.dwRGBBitCount = in.readInt(); + format.dwRBitMask = in.readInt(); + format.dwGBitMask = in.readInt(); + format.dwBBitMask = in.readInt(); + format.dwABitMask = in.readInt(); + return format; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java new file mode 100644 index 0000000..ffcde82 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +import com.ardor3d.image.ImageDataFormat; + +public class DdsUtils { + + /** + * Get the necessary bit shifts needed to align mask with 0. + * + * @param mask + * the bit mask to test + * @return number of bits to shift to the right to align mask with 0. + */ + static final int shiftCount(int mask) { + if (mask == 0) { + return 0; + } + + int i = 0; + while ((mask & 0x1) == 0) { + mask = mask >> 1; + i++; + if (i > 32) { + throw new Error(Integer.toHexString(mask)); + } + } + + return i; + } + + /** + * Check a value against a bit mask to see if it is set. + * + * @param value + * the value to check + * @param bitMask + * our mask + * @return true if the mask passes + */ + static final boolean isSet(final int value, final int bitMask) { + return (value & bitMask) == bitMask; + } + + /** + * Get the string as a dword int value. + * + * @param string + * our string... should only be 1-4 chars long. + * @return the int value + */ + static final int getInt(final String string) { + return getInt(string.getBytes()); + } + + /** + * Get the byte array as a dword int value. + * + * @param bytes + * our array... should only be 1-4 bytes long. + * @return the int value + */ + static final int getInt(final byte[] bytes) { + int rVal = 0; + rVal |= ((bytes[0] & 0xff) << 0); + if (bytes.length > 1) { + rVal |= ((bytes[1] & 0xff) << 8); + } + if (bytes.length > 2) { + rVal |= ((bytes[2] & 0xff) << 16); + } + if (bytes.length > 3) { + rVal |= ((bytes[3] & 0xff) << 24); + } + return rVal; + } + + /** + * Flip a dxt mipmap/image. Inspired by similar code in opentk and the nvidia sdk. + * + * @param rawData + * our unflipped image as raw bytes + * @param width + * our image's width + * @param height + * our image's height + * @param format + * our image's format + * @return the flipped image as raw bytes. + */ + public static byte[] flipDXT(final byte[] rawData, final int width, final int height, final ImageDataFormat format) { + final byte[] returnData = new byte[rawData.length]; + + final int blocksPerColumn = (width + 3) >> 2; + final int blocksPerRow = (height + 3) >> 2; + final int bytesPerBlock = format.getComponents() * 8; + + for (int sourceRow = 0; sourceRow < blocksPerRow; sourceRow++) { + final int targetRow = blocksPerRow - sourceRow - 1; + for (int column = 0; column < blocksPerColumn; column++) { + final int target = (targetRow * blocksPerColumn + column) * bytesPerBlock; + final int source = (sourceRow * blocksPerColumn + column) * bytesPerBlock; + switch (format) { + case PrecompressedDXT1: + case PrecompressedDXT1A: + case PrecompressedLATC_L: + System.arraycopy(rawData, source, returnData, target, 4); + returnData[target + 4] = rawData[source + 7]; + returnData[target + 5] = rawData[source + 6]; + returnData[target + 6] = rawData[source + 5]; + returnData[target + 7] = rawData[source + 4]; + break; + case PrecompressedDXT3: + // Alpha + returnData[target + 0] = rawData[source + 6]; + returnData[target + 1] = rawData[source + 7]; + returnData[target + 2] = rawData[source + 4]; + returnData[target + 3] = rawData[source + 5]; + returnData[target + 4] = rawData[source + 2]; + returnData[target + 5] = rawData[source + 3]; + returnData[target + 6] = rawData[source + 0]; + returnData[target + 7] = rawData[source + 1]; + + // Color + System.arraycopy(rawData, source + 8, returnData, target + 8, 4); + returnData[target + 12] = rawData[source + 15]; + returnData[target + 13] = rawData[source + 14]; + returnData[target + 14] = rawData[source + 13]; + returnData[target + 15] = rawData[source + 12]; + break; + case PrecompressedDXT5: + // Alpha, the first 2 bytes remain + returnData[target + 0] = rawData[source + 0]; + returnData[target + 1] = rawData[source + 1]; + + // extract 3 bits each and flip them + getBytesFromUInt24(returnData, target + 5, flipUInt24(getUInt24(rawData, source + 2))); + getBytesFromUInt24(returnData, target + 2, flipUInt24(getUInt24(rawData, source + 5))); + + // Color + System.arraycopy(rawData, source + 8, returnData, target + 8, 4); + returnData[target + 12] = rawData[source + 15]; + returnData[target + 13] = rawData[source + 14]; + returnData[target + 14] = rawData[source + 13]; + returnData[target + 15] = rawData[source + 12]; + break; + case PrecompressedLATC_LA: + // alpha + System.arraycopy(rawData, source, returnData, target, 4); + returnData[target + 4] = rawData[source + 7]; + returnData[target + 5] = rawData[source + 6]; + returnData[target + 6] = rawData[source + 5]; + returnData[target + 7] = rawData[source + 4]; + + // Color + System.arraycopy(rawData, source + 8, returnData, target + 8, 4); + returnData[target + 12] = rawData[source + 15]; + returnData[target + 13] = rawData[source + 14]; + returnData[target + 14] = rawData[source + 13]; + returnData[target + 15] = rawData[source + 12]; + break; + } + } + } + return returnData; + } + + // DXT5 Alpha block flipping, inspired by code from Evan Hart (nVidia SDK) + private static int getUInt24(final byte[] input, final int offset) { + int result = 0; + result |= (input[offset + 0] & 0xff) << 0; + result |= (input[offset + 1] & 0xff) << 8; + result |= (input[offset + 2] & 0xff) << 16; + return result; + } + + private static void getBytesFromUInt24(final byte[] input, final int offset, final int uint24) { + input[offset + 0] = (byte) (uint24 & 0x000000ff); + input[offset + 1] = (byte) ((uint24 & 0x0000ff00) >> 8); + input[offset + 2] = (byte) ((uint24 & 0x00ff0000) >> 16); + return; + } + + private static final int ThreeBitMask = 0x7; + + private static int flipUInt24(int uint24) { + final byte[][] threeBits = new byte[2][]; + for (int i = 0; i < 2; i++) { + threeBits[i] = new byte[4]; + } + + // extract 3 bits each into the array + threeBits[0][0] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[0][1] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[0][2] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[0][3] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[1][0] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[1][1] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[1][2] = (byte) (uint24 & ThreeBitMask); + uint24 >>= 3; + threeBits[1][3] = (byte) (uint24 & ThreeBitMask); + + // stuff 8x 3bits into 3 bytes + int result = 0; + result = result | (threeBits[1][0] << 0); + result = result | (threeBits[1][1] << 3); + result = result | (threeBits[1][2] << 6); + result = result | (threeBits[1][3] << 9); + result = result | (threeBits[0][0] << 12); + result = result | (threeBits[0][1] << 15); + result = result | (threeBits[0][2] << 18); + result = result | (threeBits[0][3] << 21); + return result; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java new file mode 100644 index 0000000..3c99126 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.image.util.dds; + +enum DxgiFormat { + + DXGI_FORMAT_UNKNOWN(0), // + DXGI_FORMAT_R32G32B32A32_TYPELESS(1), // + DXGI_FORMAT_R32G32B32A32_FLOAT(2), // + DXGI_FORMAT_R32G32B32A32_UINT(3), // + DXGI_FORMAT_R32G32B32A32_SINT(4), // + DXGI_FORMAT_R32G32B32_TYPELESS(5), // + DXGI_FORMAT_R32G32B32_FLOAT(6), // + DXGI_FORMAT_R32G32B32_UINT(7), // + DXGI_FORMAT_R32G32B32_SINT(8), // + DXGI_FORMAT_R16G16B16A16_TYPELESS(9), // + DXGI_FORMAT_R16G16B16A16_FLOAT(10), // + DXGI_FORMAT_R16G16B16A16_UNORM(11), // + DXGI_FORMAT_R16G16B16A16_UINT(12), // + DXGI_FORMAT_R16G16B16A16_SNORM(13), // + DXGI_FORMAT_R16G16B16A16_SINT(14), // + DXGI_FORMAT_R32G32_TYPELESS(15), // + DXGI_FORMAT_R32G32_FLOAT(16), // + DXGI_FORMAT_R32G32_UINT(17), // + DXGI_FORMAT_R32G32_SINT(18), // + DXGI_FORMAT_R32G8X24_TYPELESS(19), // + DXGI_FORMAT_D32_FLOAT_S8X24_UINT(20), // + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS(21), // + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT(22), // + DXGI_FORMAT_R10G10B10A2_TYPELESS(23), // + DXGI_FORMAT_R10G10B10A2_UNORM(24), // + DXGI_FORMAT_R10G10B10A2_UINT(25), // + DXGI_FORMAT_R11G11B10_FLOAT(26), // + DXGI_FORMAT_R8G8B8A8_TYPELESS(27), // + DXGI_FORMAT_R8G8B8A8_UNORM(28), // + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB(29), // + DXGI_FORMAT_R8G8B8A8_UINT(30), // + DXGI_FORMAT_R8G8B8A8_SNORM(31), // + DXGI_FORMAT_R8G8B8A8_SINT(32), // + DXGI_FORMAT_R16G16_TYPELESS(33), // + DXGI_FORMAT_R16G16_FLOAT(34), // + DXGI_FORMAT_R16G16_UNORM(35), // + DXGI_FORMAT_R16G16_UINT(36), // + DXGI_FORMAT_R16G16_SNORM(37), // + DXGI_FORMAT_R16G16_SINT(38), // + DXGI_FORMAT_R32_TYPELESS(39), // + DXGI_FORMAT_D32_FLOAT(40), // + DXGI_FORMAT_R32_FLOAT(41), // + DXGI_FORMAT_R32_UINT(42), // + DXGI_FORMAT_R32_SINT(43), // + DXGI_FORMAT_R24G8_TYPELESS(44), // + DXGI_FORMAT_D24_UNORM_S8_UINT(45), // + DXGI_FORMAT_R24_UNORM_X8_TYPELESS(46), // + DXGI_FORMAT_X24_TYPELESS_G8_UINT(47), // + DXGI_FORMAT_R8G8_TYPELESS(48), // + DXGI_FORMAT_R8G8_UNORM(49), // + DXGI_FORMAT_R8G8_UINT(50), // + DXGI_FORMAT_R8G8_SNORM(51), // + DXGI_FORMAT_R8G8_SINT(52), // + DXGI_FORMAT_R16_TYPELESS(53), // + DXGI_FORMAT_R16_FLOAT(54), // + DXGI_FORMAT_D16_UNORM(55), // + DXGI_FORMAT_R16_UNORM(56), // + DXGI_FORMAT_R16_UINT(57), // + DXGI_FORMAT_R16_SNORM(58), // + DXGI_FORMAT_R16_SINT(59), // + DXGI_FORMAT_R8_TYPELESS(60), // + DXGI_FORMAT_R8_UNORM(61), // + DXGI_FORMAT_R8_UINT(62), // + DXGI_FORMAT_R8_SNORM(63), // + DXGI_FORMAT_R8_SINT(64), // + DXGI_FORMAT_A8_UNORM(65), // + DXGI_FORMAT_R1_UNORM(66), // + DXGI_FORMAT_R9G9B9E5_SHAREDEXP(67), // + DXGI_FORMAT_R8G8_B8G8_UNORM(68), // + DXGI_FORMAT_G8R8_G8B8_UNORM(69), // + DXGI_FORMAT_BC1_TYPELESS(70), // + DXGI_FORMAT_BC1_UNORM(71), // + DXGI_FORMAT_BC1_UNORM_SRGB(72), // + DXGI_FORMAT_BC2_TYPELESS(73), // + DXGI_FORMAT_BC2_UNORM(74), // + DXGI_FORMAT_BC2_UNORM_SRGB(75), // + DXGI_FORMAT_BC3_TYPELESS(76), // + DXGI_FORMAT_BC3_UNORM(77), // + DXGI_FORMAT_BC3_UNORM_SRGB(78), // + DXGI_FORMAT_BC4_TYPELESS(79), // + DXGI_FORMAT_BC4_UNORM(80), // + DXGI_FORMAT_BC4_SNORM(81), // + DXGI_FORMAT_BC5_TYPELESS(82), // + DXGI_FORMAT_BC5_UNORM(83), // + DXGI_FORMAT_BC5_SNORM(84), // + DXGI_FORMAT_B5G6R5_UNORM(85), // + DXGI_FORMAT_B5G5R5A1_UNORM(86), // + DXGI_FORMAT_B8G8R8A8_UNORM(87), // + DXGI_FORMAT_B8G8R8X8_UNORM(88), // + DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM(89), // + DXGI_FORMAT_B8G8R8A8_TYPELESS(90), // + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB(91), // + DXGI_FORMAT_B8G8R8X8_TYPELESS(92), // + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB(93), // + DXGI_FORMAT_BC6H_TYPELESS(94), // + DXGI_FORMAT_BC6H_UF16(95), // + DXGI_FORMAT_BC6H_SF16(96), // + DXGI_FORMAT_BC7_TYPELESS(97), // + DXGI_FORMAT_BC7_UNORM(98), // + DXGI_FORMAT_BC7_UNORM_SRGB(99), // + DXGI_FORMAT_FORCE_UINT(0xffffffff); // + + int _value; + + DxgiFormat(final int value) { + _value = value; + } + + static DxgiFormat forInt(final int value) { + for (final DxgiFormat dim : values()) { + if (dim._value == value) { + return dim; + } + } + throw new Error("unknown DXGIFormat: " + value); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java new file mode 100644 index 0000000..65150fc --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java @@ -0,0 +1,18 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Enumerates the different states a mouse button can be in.
+ */
+public enum ButtonState {
+ UP, DOWN, UNDEFINED
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java new file mode 100644 index 0000000..0f7c895 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java @@ -0,0 +1,47 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+public class ControllerEvent {
+
+ private final long nanos;
+ private final String controllerName;
+ private final String componentName;
+ private final float value;
+
+ public ControllerEvent(final long nanos, final String controllerName, final String componentName, final float value) {
+ this.nanos = nanos;
+ this.controllerName = controllerName;
+ this.componentName = componentName;
+ this.value = value;
+ }
+
+ public long getNanos() {
+ return nanos;
+ }
+
+ public String getControllerName() {
+ return controllerName;
+ }
+
+ public String getComponentName() {
+ return componentName;
+ }
+
+ public float getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "ControllerEvent: " + controllerName + ", " + componentName + ", " + value + ", " + nanos;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java new file mode 100644 index 0000000..a65032d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java @@ -0,0 +1,114 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.ardor3d.annotation.Immutable;
+import com.google.common.collect.Maps;
+
+@Immutable
+public class ControllerState {
+
+ public static final ControllerState NOTHING = new ControllerState();
+
+ private final Map<String, Map<String, Float>> controllerStates = new LinkedHashMap<String, Map<String, Float>>();
+ private final List<ControllerEvent> eventsSinceLastState = new ArrayList<ControllerEvent>();
+
+ /**
+ * Sets a components state
+ */
+ public void set(final String controllerName, final String componentName, final float value) {
+ Map<String, Float> controllerState = null;
+ if (controllerStates.containsKey(controllerName)) {
+ controllerState = controllerStates.get(controllerName);
+ } else {
+ controllerState = new LinkedHashMap<String, Float>();
+ controllerStates.put(controllerName, controllerState);
+ }
+
+ controllerState.put(componentName, value);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof ControllerState) {
+ final ControllerState other = (ControllerState) obj;
+ return other.controllerStates.equals(controllerStates);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder stateString = new StringBuilder("ControllerState: ");
+
+ for (final String controllerStateKey : controllerStates.keySet()) {
+ final Map<String, Float> state = controllerStates.get(controllerStateKey);
+ stateString.append("[").append(controllerStateKey);
+ for (final String stateKey : state.keySet()) {
+ stateString.append("[").append(stateKey).append(":").append(state.get(stateKey)).append("]");
+ }
+ stateString.append("]");
+ }
+
+ return stateString.toString();
+ }
+
+ public ControllerState snapshot() {
+ final ControllerState snapshot = new ControllerState();
+ duplicateStates(snapshot.controllerStates);
+ snapshot.eventsSinceLastState.addAll(eventsSinceLastState);
+
+ return snapshot;
+ }
+
+ private void duplicateStates(final Map<String, Map<String, Float>> store) {
+ store.clear();
+ for (final Entry<String, Map<String, Float>> entry : controllerStates.entrySet()) {
+ store.put(entry.getKey(), Maps.newLinkedHashMap(entry.getValue()));
+ }
+ }
+
+ public void addEvent(final ControllerEvent event) {
+ eventsSinceLastState.add(event);
+ set(event.getControllerName(), event.getComponentName(), event.getValue());
+ }
+
+ public List<ControllerEvent> getEvents() {
+ Collections.sort(eventsSinceLastState, new Comparator<ControllerEvent>() {
+ public int compare(final ControllerEvent o1, final ControllerEvent o2) {
+ return (int) (o2.getNanos() - o1.getNanos());
+ }
+ });
+
+ return Collections.unmodifiableList(eventsSinceLastState);
+ }
+
+ public void clearEvents() {
+ eventsSinceLastState.clear();
+ }
+
+ public List<String> getControllerNames() {
+ return new ArrayList<String>(controllerStates.keySet());
+ }
+
+ public List<String> getControllerComponentNames(final String controller) {
+ return new ArrayList<String>(controllerStates.get(controller).keySet());
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java new file mode 100644 index 0000000..f718c1b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java @@ -0,0 +1,30 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.google.common.collect.PeekingIterator;
+
+public interface ControllerWrapper {
+ /**
+ * Allows the keyboard wrapper implementation to initialise itself.
+ */
+ public void init();
+
+ /**
+ * Returns a peeking iterator that allows the client to loop through all keyboard events that have not yet been
+ * handled.
+ *
+ * @return an iterator that allows the client to check which events have still not been handled
+ */
+ public PeekingIterator<ControllerEvent> getEvents();
+
+ public ControllerState getBlankState();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java new file mode 100644 index 0000000..6ac7b40 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java @@ -0,0 +1,20 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Describes the interface to implement to keep track of focus changes.
+ */
+public interface FocusWrapper {
+ public boolean getAndClearFocusLost();
+
+ void init();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java new file mode 100644 index 0000000..9a3716b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +/** + * Enumeration of possible states of 'grabbedness' for a mouse. + * + */ +public enum GrabbedState { + GRABBED, NOT_GRABBED +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java new file mode 100644 index 0000000..7cfb58a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java @@ -0,0 +1,75 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.ardor3d.annotation.Immutable;
+
+/**
+ * The total input state of the devices that are being handled.
+ */
+@Immutable
+public class InputState {
+ public static final InputState LOST_FOCUS = new InputState(KeyboardState.NOTHING, MouseState.NOTHING,
+ ControllerState.NOTHING);
+ public static final InputState EMPTY = new InputState(KeyboardState.NOTHING, MouseState.NOTHING,
+ ControllerState.NOTHING);
+
+ private final KeyboardState keyboardState;
+ private final MouseState mouseState;
+ private final ControllerState controllerState;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param keyboardState
+ * a non-null KeyboardState instance
+ * @param mouseState
+ * a non-null MouseState instance
+ * @throws NullPointerException
+ * if either parameter is null
+ */
+ public InputState(final KeyboardState keyboardState, final MouseState mouseState,
+ final ControllerState controllerState) {
+ if (keyboardState == null) {
+ throw new NullPointerException("Keyboard state");
+ }
+
+ if (mouseState == null) {
+ throw new NullPointerException("Mouse state");
+ }
+
+ if (controllerState == null) {
+ throw new NullPointerException("Controller state");
+ }
+
+ this.keyboardState = keyboardState;
+ this.mouseState = mouseState;
+ this.controllerState = controllerState;
+ }
+
+ public KeyboardState getKeyboardState() {
+ return keyboardState;
+ }
+
+ public MouseState getMouseState() {
+ return mouseState;
+ }
+
+ public ControllerState getControllerState() {
+ return controllerState;
+ }
+
+ @Override
+ public String toString() {
+ return "InputState{" + "keyboardState=" + keyboardState + ", mouseState=" + mouseState + ", controllerState="
+ + controllerState + '}';
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java new file mode 100644 index 0000000..d9cad0d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java @@ -0,0 +1,761 @@ +/**
+ * Copyright 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Keys supported by Ardor3D platforms. Note that all keys are not likely supported by any one platform.
+ */
+public enum Key {
+ /**
+ * Returned when a key character is not supported.
+ */
+ UNKNOWN,
+
+ /**
+ * escape key.
+ */
+ ESCAPE,
+
+ /**
+ * 1 key.
+ */
+ ONE,
+
+ /**
+ * 2 key.
+ */
+ TWO,
+
+ /**
+ * 3 key.
+ */
+ THREE,
+
+ /**
+ * 4 key.
+ */
+ FOUR,
+
+ /**
+ * 5 key.
+ */
+ FIVE,
+
+ /**
+ * 6 key.
+ */
+ SIX,
+
+ /**
+ * 7 key.
+ */
+ SEVEN,
+
+ /**
+ * 8 key.
+ */
+ EIGHT,
+
+ /**
+ * 9 key.
+ */
+ NINE,
+
+ /**
+ * 0 key.
+ */
+ ZERO,
+
+ /**
+ * - key.
+ */
+ MINUS,
+
+ /**
+ * = key.
+ */
+ EQUALS,
+
+ /**
+ * back key.
+ */
+ BACK,
+
+ /**
+ * tab key.
+ */
+ TAB,
+
+ /**
+ * q key.
+ */
+ Q,
+
+ /**
+ * w key.
+ */
+ W,
+
+ /**
+ * e key.
+ */
+ E,
+
+ /**
+ * r key.
+ */
+ R,
+
+ /**
+ * t key.
+ */
+ T,
+
+ /**
+ * y key.
+ */
+ Y,
+
+ /**
+ * u key.
+ */
+ U,
+
+ /**
+ * i key.
+ */
+ I,
+
+ /**
+ * o key.
+ */
+ O,
+
+ /**
+ * p key.
+ */
+ P,
+
+ /**
+ * [ key.
+ */
+ LBRACKET,
+
+ /**
+ * ] key.
+ */
+ RBRACKET,
+
+ /**
+ * enter key.
+ */
+ RETURN,
+
+ /**
+ * left control key.
+ */
+ LCONTROL,
+
+ /**
+ * a key.
+ */
+ A,
+
+ /**
+ * s key.
+ */
+ S,
+
+ /**
+ * d key.
+ */
+ D,
+
+ /**
+ * f key.
+ */
+ F,
+
+ /**
+ * g key.
+ */
+ G,
+
+ /**
+ * h key.
+ */
+ H,
+
+ /**
+ * j key.
+ */
+ J,
+
+ /**
+ * k key.
+ */
+ K,
+
+ /**
+ * l key.
+ */
+ L,
+
+ /**
+ * ; key.
+ */
+ SEMICOLON,
+
+ /**
+ * ' key.
+ */
+ APOSTROPHE,
+
+ /**
+ * Applications key.
+ */
+ APPS,
+
+ /**
+ * ` key.
+ */
+ GRAVE,
+
+ /**
+ * left shift key.
+ */
+ LSHIFT,
+
+ /**
+ * \ key.
+ */
+ BACKSLASH,
+
+ /**
+ * z key.
+ */
+ Z,
+
+ /**
+ * x key.
+ */
+ X,
+
+ /**
+ * c key.
+ */
+ C,
+
+ /**
+ * v key.
+ */
+ V,
+
+ /**
+ * b key.
+ */
+ B,
+
+ /**
+ * n key.
+ */
+ N,
+
+ /**
+ * m key.
+ */
+ M,
+
+ /**
+ * , key.
+ */
+ COMMA,
+
+ /**
+ * . key .
+ */
+ PERIOD,
+
+ /**
+ * / key .
+ */
+ SLASH,
+
+ /**
+ * right shift key.
+ */
+ RSHIFT,
+
+ /**
+ * * key .
+ */
+ MULTIPLY,
+
+ /**
+ * left alt key.
+ */
+ LMENU,
+
+ /**
+ * space key.
+ */
+ SPACE,
+
+ /**
+ * caps lock key.
+ */
+ CAPITAL,
+
+ /**
+ * F1 key.
+ */
+ F1,
+
+ /**
+ * F2 key.
+ */
+ F2,
+
+ /**
+ * F3 key.
+ */
+ F3,
+
+ /**
+ * F4 key.
+ */
+ F4,
+
+ /**
+ * F5 key.
+ */
+ F5,
+
+ /**
+ * F6 key.
+ */
+ F6,
+
+ /**
+ * F7 key.
+ */
+ F7,
+
+ /**
+ * F8 key.
+ */
+ F8,
+
+ /**
+ * F9 key.
+ */
+ F9,
+
+ /**
+ * F10 key.
+ */
+ F10,
+
+ /**
+ * NumLK key.
+ */
+ NUMLOCK,
+
+ /**
+ * Scroll lock key.
+ */
+ SCROLL,
+
+ /**
+ * 7 key .
+ */
+ NUMPAD7,
+
+ /**
+ * 8 key .
+ */
+ NUMPAD8,
+
+ /**
+ * 9 key .
+ */
+ NUMPAD9,
+
+ /**
+ * - key .
+ */
+ NUMPADSUBTRACT,
+
+ /**
+ * 4 key .
+ */
+ NUMPAD4,
+
+ /**
+ * 5 key .
+ */
+ NUMPAD5,
+
+ /**
+ * 6 key .
+ */
+ NUMPAD6,
+
+ /**
+ * + key .
+ */
+ NUMPADADD,
+
+ /**
+ * 1 key .
+ */
+ NUMPAD1,
+
+ /**
+ * 2 key .
+ */
+ NUMPAD2,
+
+ /**
+ * 3 key .
+ */
+ NUMPAD3,
+
+ /**
+ * 0 key .
+ */
+ NUMPAD0,
+
+ /**
+ * . key .
+ */
+ DECIMAL,
+
+ /**
+ * F11 key.
+ */
+ F11,
+
+ /**
+ * F12 key.
+ */
+ F12,
+
+ /**
+ * F13 key.
+ */
+ F13,
+
+ /**
+ * F14 key.
+ */
+ F14,
+
+ /**
+ * F15 key.
+ */
+ F15,
+
+ /**
+ * kana key .
+ */
+ KANA,
+
+ /**
+ * convert key .
+ */
+ CONVERT,
+
+ /**
+ * noconvert key .
+ */
+ NOCONVERT,
+
+ /**
+ * yen key .
+ */
+ YEN,
+
+ /**
+ * = on num pad .
+ */
+ NUMPADEQUALS,
+
+ /**
+ * circum flex key .
+ */
+ CIRCUMFLEX,
+
+ /**
+ * @ key .
+ */
+ AT,
+
+ /**
+ * : key
+ */
+ COLON,
+
+ /**
+ * _ key .
+ */
+ UNDERLINE,
+
+ /**
+ * kanji key .
+ */
+ KANJI,
+
+ /**
+ * stop key .
+ */
+ STOP,
+
+ /**
+ * ax key .
+ */
+ AX,
+
+ /**
+ * .
+ */
+ UNLABELED,
+
+ /**
+ * Enter key .
+ */
+ NUMPADENTER,
+
+ /**
+ * right control key.
+ */
+ RCONTROL,
+
+ /**
+ * , key on num pad .
+ */
+ NUMPADCOMMA,
+
+ /**
+ * / key .
+ */
+ DIVIDE,
+
+ /**
+ * SysRq key.
+ */
+ SYSRQ,
+
+ /**
+ * right alt key.
+ */
+ RMENU,
+
+ /**
+ * pause key.
+ */
+ PAUSE,
+
+ /**
+ * home key.
+ */
+ HOME,
+
+ /**
+ * up arrow key.
+ */
+ UP,
+
+ /**
+ * PageUp/Prior key.
+ */
+ PAGEUP_PRIOR,
+
+ /**
+ * left arrow key.
+ */
+ LEFT,
+
+ /**
+ * right arrow key.
+ */
+ RIGHT,
+
+ /**
+ * end key.
+ */
+ END,
+
+ /**
+ * down arrow key.
+ */
+ DOWN,
+
+ /**
+ * PageDown/Next key.
+ */
+ PAGEDOWN_NEXT,
+
+ /**
+ * insert key.
+ */
+ INSERT,
+
+ /**
+ * delete key.
+ */
+ DELETE,
+
+ /**
+ * Left Windows/Option key
+ */
+ LMETA,
+
+ /**
+ * Right Windows/Option key
+ */
+ RMETA,
+
+ /**
+ * power key.
+ */
+ POWER,
+
+ /**
+ * sleep key.
+ */
+ SLEEP,
+
+ /**
+ * mobile call button
+ */
+ CALL,
+
+ /**
+ * mobile camera button
+ */
+ CAMERA,
+
+ /**
+ * mobile clear button
+ */
+ CLEAR,
+
+ /**
+ * dpad center button
+ */
+ CENTER,
+
+ /**
+ * mobile end call button
+ */
+ ENDCALL,
+
+ /**
+ * mobile envelope button
+ */
+ ENVELOPE,
+
+ /**
+ * mobile explorer button
+ */
+ EXPLORER,
+
+ /**
+ * mobile focus button
+ */
+ FOCUS,
+
+ /**
+ * mobile headsethook button
+ */
+ HEADSETHOOK,
+
+ /**
+ * mobile fast fwd button
+ */
+ MEDIA_FAST_FORWARD,
+
+ /**
+ * mobile next button
+ */
+ MEDIA_NEXT,
+
+ /**
+ * mobile play/pause button
+ */
+ PLAY_PAUSE,
+
+ /**
+ * mobile previous button
+ */
+ MEDIA_PREVIOUS,
+
+ /**
+ * mobile rewind button
+ */
+ MEDIA_REWIND,
+
+ /**
+ * mobile stop button
+ */
+ MEDIA_STOP,
+
+ /**
+ * mobile menu button
+ */
+ MENU,
+
+ /**
+ * mobile mute button
+ */
+ MUTE,
+
+ /**
+ * mobile notification button
+ */
+ NOTIFICATION,
+
+ /**
+ * plus key
+ */
+ PLUS,
+
+ /**
+ * pound key
+ */
+ POUND,
+
+ /**
+ * mobile call button
+ */
+ SEARCH,
+
+ /**
+ * mobile star button
+ */
+ STAR,
+
+ /**
+ * mobile # button
+ */
+ SYM,
+
+ /**
+ * volume down button
+ */
+ VOLUME_DOWN,
+
+ /**
+ * volume up button
+ */
+ VOLUME_UP;
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java new file mode 100644 index 0000000..3a13675 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +import com.ardor3d.annotation.Immutable; + +/** + * Describes the state of a key - either it has been pressed or it has been released. Also keeps track of which + * character the event corresponds to - the difference between a key and a character is that a key corresponds to a + * physical key on the keyboard, whereas the character is the character that a keypress combination represents. Keys are + * universal and mapped into the Key enum, whereas characters can be any char value. Some examples of the differences: + * <ul> + * <li>On almost any keyboard, pressing {@link Key#EIGHT} results in the character '8'.</li> + * <li>On an English keyboard, pressing {@link Key#EIGHT} when the {@link Key#LSHIFT} is down leads to the character + * '*'.</li> + * <li>On a Swedish keyboard, pressing {@link Key#EIGHT} when the {@link Key#LSHIFT} is down leads to the character '('. + * </li> + * </ul> + */ +@Immutable +public class KeyEvent { + public static final KeyEvent NOTHING = new KeyEvent(Key.UNKNOWN, KeyState.UP, (char) 0); + + private final Key _key; + private final KeyState _state; + private final char _keyChar; + + public KeyEvent(final Key key, final KeyState state, final char keyChar) { + _key = key; + _state = state; + _keyChar = keyChar; + } + + public Key getKey() { + return _key; + } + + public KeyState getState() { + return _state; + } + + public char getKeyChar() { + return _keyChar; + } + + @Override + public String toString() { + return "KeyEvent{" + "_key=" + _key + ", _state=" + _state + ", _keyChar=" + _keyChar + '}'; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java new file mode 100644 index 0000000..4ca5df1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java @@ -0,0 +1,23 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Thrown when an attempt at fetching a {@link Key} instance for an invalid/unknown key code is made.
+ */
+public class KeyNotFoundException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public KeyNotFoundException(final int keyCode) {
+ super("No Key enum value found for code: " + keyCode);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java new file mode 100644 index 0000000..770e2f3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java @@ -0,0 +1,18 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Describes whether a key is down or up.
+ */
+public enum KeyState {
+ DOWN, UP
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java new file mode 100644 index 0000000..d5f73bb --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +import com.ardor3d.annotation.Immutable; + +/** + * A keyboard state at some point in time. Contains an EnumSet of the keys that are down, as well as a KeyEvent that + * describes the latest event (a key being pressed or released). + */ +@Immutable +public class KeyboardState { + public static final KeyboardState NOTHING = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING); + + private final EnumSet<Key> _keysDown; + private final Set<Key> _keysDownView; + private final KeyEvent _keyEvent; + + public KeyboardState(final EnumSet<Key> keysDown, final KeyEvent keyEvent) { + // keeping the keysDown as an EnumSet rather than as an unmodifiableSet in order to get + // the performance benefit of working with the fast implementations of contains(), + // removeAll(), etc., in EnumSet. The intention is that the keysDown set should never change. + // The reason why the performance benefits are lost when using an unmodifiableSet is that + // methods like containsAll(), etc., are not symmetrical in the EnumSet implementations. + // So typically, unmodifiableSet.containsAll(EnumSet) will be faster than + // enumSet.containsAll(unmodifiableSet). + _keysDown = keysDown; + _keyEvent = keyEvent; + _keysDownView = Collections.unmodifiableSet(keysDown); + } + + public boolean isDown(final Key key) { + return _keysDown.contains(key); + } + + public boolean isAllDown(final Key... keys) { + for (final Key key : keys) { + if (!_keysDown.contains(key)) { + return false; + } + } + return true; + } + + public boolean isAtLeastOneDown(final Key... keys) { + for (final Key key : keys) { + if (_keysDown.contains(key)) { + return true; + } + } + return false; + } + + public Set<Key> getKeysDown() { + return _keysDownView; + } + + public KeyEvent getKeyEvent() { + return _keyEvent; + } + + public EnumSet<Key> getKeysReleasedSince(final KeyboardState previous) { + final EnumSet<Key> result = EnumSet.copyOf(previous._keysDown); + + result.removeAll(_keysDown); + + return result; + } + + public EnumSet<Key> getKeysPressedSince(final KeyboardState previous) { + final EnumSet<Key> result = EnumSet.copyOf(_keysDown); + + result.removeAll(previous._keysDown); + + return result; + + } + + public EnumSet<Key> getKeysHeldSince(final KeyboardState previous) { + final EnumSet<Key> result = EnumSet.copyOf(_keysDown); + + result.retainAll(previous._keysDown); + + return result; + + } + + @Override + public String toString() { + return "KeyboardState{_keysDown=" + _keysDown + ", _keyEvent=" + _keyEvent + '}'; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java new file mode 100644 index 0000000..106db04 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java @@ -0,0 +1,31 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * Defines the API for keyboard wrappers.
+ */
+public interface KeyboardWrapper {
+ /**
+ * Allows the keyboard wrapper implementation to initialise itself.
+ */
+ public void init();
+
+ /**
+ * Returns a peeking iterator that allows the client to loop through all keyboard events that have not yet been
+ * handled.
+ *
+ * @return an iterator that allows the client to check which events have still not been handled
+ */
+ public PeekingIterator<KeyEvent> getEvents();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java new file mode 100644 index 0000000..7c088a7 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java @@ -0,0 +1,37 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.EnumMap;
+
+import com.google.common.collect.Maps;
+
+public enum MouseButton {
+ LEFT, RIGHT, MIDDLE;
+
+ public static EnumMap<MouseButton, ButtonState> makeMap(final ButtonState left, final ButtonState right,
+ final ButtonState middle) {
+ if (left == null) {
+ throw new NullPointerException("left");
+ }
+ if (right == null) {
+ throw new NullPointerException("right");
+ }
+ if (middle == null) {
+ throw new NullPointerException("middle");
+ }
+ final EnumMap<MouseButton, ButtonState> map = Maps.newEnumMap(MouseButton.class);
+ map.put(LEFT, left);
+ map.put(RIGHT, right);
+ map.put(MIDDLE, middle);
+ return map;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java new file mode 100644 index 0000000..e24e4ce --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.ardor3d.annotation.Immutable; +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.util.geom.BufferUtils; + +/** + * An immutable representation of a mouse cursor. A mouse cursor consists of an image and a hotspot where clicking is + * done. + * + */ +@Immutable +public class MouseCursor { + /** + * This constant is used to identify that the native operating system's default cursor should be used. It is not a + * valid mouse cursor in itself. + */ + public static final MouseCursor SYSTEM_DEFAULT = new MouseCursor("system default", new Image(ImageDataFormat.RGBA, + PixelDataType.UnsignedByte, 1, 1, BufferUtils.createByteBuffer(4), null), 0, 0); + + private final String _name; + private final Image _image; + private final int _hotspotX; + private final int _hotspotY; + + /** + * Instantiates a MouseCursor. + * + * @param name + * the name of this cursor, for debugging purposes. + * @param image + * the image that will be shown when this cursor is active. + * @param hotspotX + * the X coordinate of the clicking hotspot, 0 = left side + * @param hotspotY + * the Y coordinate of the clicking hotspot, 0 = bottom + */ + public MouseCursor(final String name, final Image image, final int hotspotX, final int hotspotY) { + _name = name; + _image = image; + _hotspotX = hotspotX; + _hotspotY = hotspotY; + + checkArgument(hotspotX >= 0 && hotspotX < image.getWidth(), "hotspot X is out of bounds: 0 <= %s < " + + image.getWidth(), hotspotX); + checkArgument(hotspotY >= 0 && hotspotY < image.getHeight(), "hotspot Y is out of bounds: 0 <= %s < " + + image.getHeight(), hotspotY); + } + + public String getName() { + return _name; + } + + public Image getImage() { + return _image; + } + + public int getWidth() { + return _image.getWidth(); + } + + public int getHeight() { + return _image.getHeight(); + } + + public int getHotspotX() { + return _hotspotX; + } + + public int getHotspotY() { + return _hotspotY; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final MouseCursor that = (MouseCursor) o; + + if (_hotspotX != that._hotspotX) { + return false; + } + if (_hotspotY != that._hotspotY) { + return false; + } + if (_image != null ? !_image.equals(that._image) : that._image != null) { + return false; + } + // noinspection RedundantIfStatement + if (_name != null ? !_name.equals(that._name) : that._name != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = _name != null ? _name.hashCode() : 0; + result = 31 * result + (_image != null ? _image.hashCode() : 0); + result = 31 * result + _hotspotX; + result = 31 * result + _hotspotY; + return result; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java new file mode 100644 index 0000000..641df06 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +/** + * Defines the contract for managing the native mouse. + */ +public interface MouseManager { + /** + * Change the mouse cursor presently used. This is a mandatory operation that all implementing classes must support. + * + * @param cursor + * the cursor to use + */ + public void setCursor(MouseCursor cursor); + + /** + * Optional method for changing the mouse cursor position to the specified coordinates. A client can confirm whether + * or not this method is support by calling {@link #isSetPositionSupported()}. + * + * @param x + * x position within the current canvas, 0 = left + * @param y + * y position within the current canvas, 0 = bottom + */ + public void setPosition(int x, int y); + + /** + * Optional method for changing the mouse to behave as if it is grabbed or not. A client can confirm whether or not + * this method is support by calling {@link #isSetGrabbedSupported()}. + * + * @param grabbedState + * the value determines which grabbed state is selected + */ + public void setGrabbed(GrabbedState grabbedState); + + /** + * @return current grabbed state of the mouse. + */ + public GrabbedState getGrabbed(); + + /** + * Indicates to clients whether or not it is safe to call the {@link #setPosition(int, int)} method. Note that if + * this method returns false, a runtime exception may be thrown by the {@link #setPosition(int, int)} method. + * + * @return true if the mouse's position can be changed by this implementation, false otherwise. + */ + public boolean isSetPositionSupported(); + + /** + * Indicates to clients whether or not it is safe to call the {@link #setGrabbed(GrabbedState)} method. Note that if + * this method returns false, a runtime exception may be thrown by the {@link #setGrabbed(GrabbedState)} method. + * + * @return true if the mouse's grabbed state can be changed by this implementation, false otherwise. + */ + public boolean isSetGrabbedSupported(); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java new file mode 100644 index 0000000..8ee44ce --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java @@ -0,0 +1,264 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +import java.util.EnumMap; +import java.util.EnumSet; + +import com.ardor3d.annotation.Immutable; +import com.google.common.collect.EnumMultiset; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.Maps; +import com.google.common.collect.Multiset; +import com.google.common.collect.ImmutableMultiset.Builder; + +/** + * Describes the mouse state at some point in time. + */ +@Immutable +public class MouseState { + public static final MouseState NOTHING = new MouseState(0, 0, 0, 0, 0, null, null); + public static long CLICK_TIME_MS = 500; + + private final int _x; + private final int _y; + private final int _dx; + private final int _dy; + private final int _dwheel; + private final ImmutableMap<MouseButton, ButtonState> _buttonStates; + private final ImmutableMultiset<MouseButton> _clickCounts; + + /** + * Constructs a new MouseState instance. + * + * @param x + * the mouse's x position + * @param y + * the mouse's y position + * @param dx + * the delta in the mouse's x position since the last update + * @param dy + * the delta in the mouse's y position since the last update + * @param dwheel + * the delta in the mouse's wheel movement since the last update + * @param buttonStates + * the states of the various given buttons. + * @param clicks + * the number of times each button has been clicked + */ + public MouseState(final int x, final int y, final int dx, final int dy, final int dwheel, + final EnumMap<MouseButton, ButtonState> buttonStates, final Multiset<MouseButton> clicks) { + _x = x; + _y = y; + _dx = dx; + _dy = dy; + _dwheel = dwheel; + if (buttonStates != null) { + final com.google.common.collect.ImmutableMap.Builder<MouseButton, ButtonState> builder = ImmutableMap + .builder(); + _buttonStates = builder.putAll(buttonStates).build(); + } else { + _buttonStates = ImmutableMap.of(); + } + if (clicks != null) { + final Builder<MouseButton> builder = ImmutableMultiset.builder(); + _clickCounts = builder.addAll(clicks).build(); + } else { + _clickCounts = ImmutableMultiset.of(); + } + } + + public int getX() { + return _x; + } + + public int getY() { + return _y; + } + + public int getDx() { + return _dx; + } + + public int getDy() { + return _dy; + } + + public int getDwheel() { + return _dwheel; + } + + /** + * + * @param state + * the button state to look for + * @return true if at least one mouse button is in the given button state. + */ + public boolean hasButtonState(final ButtonState state) { + return _buttonStates.containsValue(state); + } + + /** + * + * @param state + * the button to look for + * @return true if the given mouse button is currently mapped to a state. + */ + public boolean hasButtonState(final MouseButton button) { + return _buttonStates.containsKey(button); + } + + /** + * Returns all the buttons' states. It could be easier for most classes to use the + * {@link #getButtonState(MouseButton)} methods, and that also results in less object creation. + * + * @return a defensive copy of the states of all the buttons at this point in time. + */ + public EnumMap<MouseButton, ButtonState> getButtonStates() { + return getButtonStates(null); + } + + /** + * Returns all the buttons' states. It could be easier for most classes to use the + * {@link #getButtonState(MouseButton)} methods, and that also results in less object creation. + * + * @param store + * a map to store the states in... any values in store are cleared first. If store is null, a new map is + * created. + * @return a defensive copy of the states of all the buttons at this point in time. + */ + public EnumMap<MouseButton, ButtonState> getButtonStates(final EnumMap<MouseButton, ButtonState> store) { + EnumMap<MouseButton, ButtonState> rVal = store; + if (store == null) { + rVal = Maps.newEnumMap(MouseButton.class); + } + rVal.clear(); + rVal.putAll(_buttonStates); + return rVal; + } + + /** + * Returns the current state for the supplied button, or UP if no state for that button is registered. + * + * @param button + * the mouse button to check + * @return the button's state, or {@link ButtonState#UP} if no button state registered. + */ + public ButtonState getButtonState(final MouseButton button) { + if (_buttonStates.containsKey(button)) { + return _buttonStates.get(button); + } + + return ButtonState.UP; + } + + public EnumSet<MouseButton> getButtonsReleasedSince(final MouseState previous) { + final EnumSet<MouseButton> result = EnumSet.noneOf(MouseButton.class); + for (final MouseButton button : MouseButton.values()) { + if (previous.getButtonState(button) == ButtonState.DOWN) { + if (getButtonState(button) != ButtonState.DOWN) { + result.add(button); + } + } + } + + return result; + } + + public EnumSet<MouseButton> getButtonsPressedSince(final MouseState previous) { + final EnumSet<MouseButton> result = EnumSet.noneOf(MouseButton.class); + for (final MouseButton button : MouseButton.values()) { + if (getButtonState(button) == ButtonState.DOWN) { + if (previous.getButtonState(button) != ButtonState.DOWN) { + result.add(button); + } + } + } + + return result; + } + + /** + * Returns all the buttons' states. It could be easier for most classes to use the + * {@link #getClickCount(MouseButton)} method, and that also results in less object creation. + * + * @return a defensive copy of the click counts of all the buttons at this point in time. + */ + public Multiset<MouseButton> getClickCounts() { + if (_clickCounts.isEmpty()) { + return EnumMultiset.create(MouseButton.class); + } else { + return EnumMultiset.create(_clickCounts); + } + } + + public Multiset<MouseButton> getClickCounts(final EnumMultiset<MouseButton> store) { + final EnumMultiset<MouseButton> rVal = store; + if (store == null) { + if (_clickCounts.isEmpty()) { + return EnumMultiset.create(MouseButton.class); + } else { + return EnumMultiset.create(_clickCounts); + } + } + rVal.clear(); + rVal.addAll(_clickCounts); + return rVal; + } + + /** + * Returns the click count of a mouse button as of this frame. Click counts are non-zero only for frames when the + * mouse button is released. A double-click sequence, for instance, could show up like this: + * <nl> + * <li>Frame 1, mouse button pressed - click count == 0</li> + * <li>Frame 2, mouse button down - click count == 0</li> + * <li>Frame 3, mouse button released - click count == 1</li> + * <li>Frame 4, mouse button up - click count == 0</li> + * <li>Frame 5, mouse button pressed - click count == 0</li> + * <li>Frame 6, mouse button down - click count == 0</li> + * <li>Frame 7, mouse button released - click count == 2</li> + * </nl> + * + * Whether or not a mouse press/release sequence counts as a click (or double-click) depends on the time passed + * between them. See {@link #CLICK_TIME_MS}. + * + * + * @param button + * the button to check for clicks + * @return the click count in this frame + */ + public int getClickCount(final MouseButton button) { + return _clickCounts.count(button); + } + + /** + * Returns a new EnumSet of all buttons that were clicked this frame. + * + * @return every mouse button whose click count this frame is > 0 + */ + public EnumSet<MouseButton> getButtonsClicked() { + final EnumSet<MouseButton> result = EnumSet.noneOf(MouseButton.class); + for (final MouseButton button : MouseButton.values()) { + if (getClickCount(button) != 0) { + result.add(button); + } + } + + return result; + } + + @Override + public String toString() { + return "MouseState{" + "x=" + _x + ", y=" + _y + ", dx=" + _dx + ", dy=" + _dy + ", dwheel=" + _dwheel + + ", buttonStates=" + _buttonStates.toString() + ", clickCounts=" + _clickCounts.toString() + '}'; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java new file mode 100644 index 0000000..c44ef28 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java @@ -0,0 +1,31 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * Defines the API for mouse wrappers.
+ */
+public interface MouseWrapper {
+ /**
+ * Allows the mouse wrapper implementation to initialize itself.
+ */
+ public void init();
+
+ /**
+ * Returns a peeking iterator that allows the client to loop through all mouse events that have not yet been
+ * handled.
+ *
+ * @return an iterator that allows the client to check which events have still not been handled
+ */
+ public PeekingIterator<MouseState> getEvents();
+}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java new file mode 100644 index 0000000..fa125f9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input; + +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import com.ardor3d.input.logical.DummyControllerWrapper; +import com.ardor3d.input.logical.DummyFocusWrapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.PeekingIterator; + +/** + * Provides access to the physical layer of the input system. This is done via one method that polls the input system, + * causing it to track which states it has been in {@link #readState()}, and one method that fetches the list of states + * that are new {@link #drainAvailableStates()}. + */ +public class PhysicalLayer { + + private static final Logger logger = Logger.getLogger(PhysicalLayer.class.getName()); + + private final BlockingQueue<InputState> _stateQueue; + private final KeyboardWrapper _keyboardWrapper; + private final MouseWrapper _mouseWrapper; + private final FocusWrapper _focusWrapper; + private final ControllerWrapper _controllerWrapper; + + private KeyboardState _currentKeyboardState; + private MouseState _currentMouseState; + private ControllerState _currentControllerState; + + private boolean _inited = false; + + private static final long MAX_INPUT_POLL_TIME = TimeUnit.SECONDS.toNanos(2); + private static final List<InputState> EMPTY_LIST = ImmutableList.of(); + + public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper) { + this(keyboardWrapper, mouseWrapper, DummyControllerWrapper.INSTANCE, DummyFocusWrapper.INSTANCE); + } + + public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper, + final FocusWrapper focusWrapper) { + this(keyboardWrapper, mouseWrapper, DummyControllerWrapper.INSTANCE, focusWrapper); + } + + public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper, + final ControllerWrapper controllerWrapper) { + this(keyboardWrapper, mouseWrapper, controllerWrapper, DummyFocusWrapper.INSTANCE); + } + + public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper, + final ControllerWrapper controllerWrapper, final FocusWrapper focusWrapper) { + _keyboardWrapper = keyboardWrapper; + _mouseWrapper = mouseWrapper; + _focusWrapper = focusWrapper; + _controllerWrapper = controllerWrapper; + _stateQueue = new LinkedBlockingQueue<InputState>(); + + _currentKeyboardState = KeyboardState.NOTHING; + _currentMouseState = MouseState.NOTHING; + } + + /** + * Causes a poll of the input devices to happen, making any updates to input states available via the + * {@link #drainAvailableStates()} method. + * + * @throws IllegalStateException + * if too many state changes have happened since the last call to this method + */ + public void readState() { + if (!_inited) { + init(); + } + + KeyboardState oldKeyState = _currentKeyboardState; + if (_currentMouseState.getDwheel() == 0 && _currentMouseState.getDx() == 0 && _currentMouseState.getDy() == 0) { + // we can reuse - do nothing + } else { + // we can't reuse + _currentMouseState = new MouseState(_currentMouseState.getX(), _currentMouseState.getY(), 0, 0, 0, + _currentMouseState.getButtonStates(), _currentMouseState.getClickCounts()); + } + MouseState oldMouseState = _currentMouseState; + ControllerState oldControllerState = _currentControllerState; + + final long loopExitTime = System.nanoTime() + MAX_INPUT_POLL_TIME; + + while (true) { + readKeyboardState(); + readMouseState(); + readControllerState(); + + // if there is no new input, exit the loop. Otherwise, add a new input state to the queue, and + // see if there is even more input to read. + if (oldKeyState.equals(_currentKeyboardState) && oldMouseState.equals(_currentMouseState) + && oldControllerState.equals(_currentControllerState)) { + break; + } + + _stateQueue.add(new InputState(_currentKeyboardState, _currentMouseState, _currentControllerState)); + + oldKeyState = _currentKeyboardState; + oldMouseState = _currentMouseState; + oldControllerState = _currentControllerState; + + if (System.nanoTime() > loopExitTime) { + logger.severe("Spent too long collecting input data, this is probably an input system bug"); + break; + } + } + + if (_focusWrapper.getAndClearFocusLost()) { + lostFocus(); + } + } + + private void readControllerState() { + final PeekingIterator<ControllerEvent> eventIterator = _controllerWrapper.getEvents(); + + if (eventIterator.hasNext()) { + _currentControllerState = new ControllerState(); + while (eventIterator.hasNext()) { + final ControllerEvent event = eventIterator.next(); + _currentControllerState.addEvent(event); + } + } + } + + private void readMouseState() { + final PeekingIterator<MouseState> eventIterator = _mouseWrapper.getEvents(); + + if (eventIterator.hasNext()) { + _currentMouseState = eventIterator.next(); + } + } + + private void readKeyboardState() { + final PeekingIterator<KeyEvent> eventIterator = _keyboardWrapper.getEvents(); + + // if no new events, just leave the current state as is + if (!eventIterator.hasNext()) { + return; + } + + final KeyEvent keyEvent = eventIterator.next(); + + // EnumSet.copyOf fails if the collection is empty, since it needs at least one object to + // figure out which type of enum to deal with. Hence the check below. + final EnumSet<Key> keysDown = _currentKeyboardState.getKeysDown().isEmpty() ? EnumSet.noneOf(Key.class) + : EnumSet.copyOf(_currentKeyboardState.getKeysDown()); + + if (keyEvent.getState() == KeyState.DOWN) { + keysDown.add(keyEvent.getKey()); + } else { + // ignore the fact that this removal might fail - for instance, at startup, the + // set of keys tracked as down will be empty even if somebody presses a key when the + // app starts. + keysDown.remove(keyEvent.getKey()); + } + + _currentKeyboardState = new KeyboardState(keysDown, keyEvent); + } + + /** + * Fetches any new <code>InputState</code>s since the last call to this method. If no input system changes have been + * made since the last call (no mouse movements, no keys pressed or released), an empty list is returned. + * + * @return the list of new <code>InputState</code>, or an empty list if there have been no changes in input + */ + public List<InputState> drainAvailableStates() { + // returning a reusable empty list to avoid object creation if there is no new + // input available. There is a race condition here (input might become available right after + // the check of isEmpty()) but that's OK, it won't do any harm if that is picked up next frame. + if (_stateQueue.isEmpty()) { + return EMPTY_LIST; + } + + final LinkedList<InputState> result = new LinkedList<InputState>(); + + _stateQueue.drainTo(result); + + return result; + } + + private void lostFocus() { + _stateQueue.add(InputState.LOST_FOCUS); + _currentKeyboardState = KeyboardState.NOTHING; + _currentMouseState = MouseState.NOTHING; + _currentControllerState = _controllerWrapper.getBlankState(); + } + + private void init() { + _inited = true; + + _keyboardWrapper.init(); + _mouseWrapper.init(); + _focusWrapper.init(); + _controllerWrapper.init(); + + _currentControllerState = _controllerWrapper.getBlankState(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java new file mode 100644 index 0000000..d93f9c0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java @@ -0,0 +1,322 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.control; + +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.Key; +import com.ardor3d.input.KeyboardState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +public class FirstPersonControl { + + protected final Vector3 _upAxis = new Vector3(); + protected double _mouseRotateSpeed = .005; + protected double _moveSpeed = 50; + protected double _keyRotateSpeed = 2.25; + protected final Matrix3 _workerMatrix = new Matrix3(); + protected final Vector3 _workerStoreA = new Vector3(); + protected InputTrigger _mouseTrigger; + protected InputTrigger _keyTrigger; + + protected boolean _clampVerticalAngle = false; + protected double _minVerticalAngle = -60 * MathUtils.DEG_TO_RAD; + protected double _maxVerticalAngle = 60 * MathUtils.DEG_TO_RAD; + + public FirstPersonControl(final ReadOnlyVector3 upAxis) { + _upAxis.set(upAxis); + } + + public ReadOnlyVector3 getUpAxis() { + return _upAxis; + } + + public void setUpAxis(final ReadOnlyVector3 upAxis) { + _upAxis.set(upAxis); + } + + public double getMouseRotateSpeed() { + return _mouseRotateSpeed; + } + + public void setMouseRotateSpeed(final double speed) { + _mouseRotateSpeed = speed; + } + + public double getMoveSpeed() { + return _moveSpeed; + } + + public void setMoveSpeed(final double speed) { + _moveSpeed = speed; + } + + public double getKeyRotateSpeed() { + return _keyRotateSpeed; + } + + public void setKeyRotateSpeed(final double speed) { + _keyRotateSpeed = speed; + } + + protected void move(final Camera camera, final KeyboardState kb, final double tpf) { + // MOVEMENT + int moveFB = 0, strafeLR = 0; + if (kb.isDown(Key.W)) { + moveFB += 1; + } + if (kb.isDown(Key.S)) { + moveFB -= 1; + } + if (kb.isDown(Key.A)) { + strafeLR += 1; + } + if (kb.isDown(Key.D)) { + strafeLR -= 1; + } + + if (moveFB != 0 || strafeLR != 0) { + final Vector3 loc = _workerStoreA.zero(); + if (moveFB == 1) { + loc.addLocal(camera.getDirection()); + } else if (moveFB == -1) { + loc.subtractLocal(camera.getDirection()); + } + if (strafeLR == 1) { + loc.addLocal(camera.getLeft()); + } else if (strafeLR == -1) { + loc.subtractLocal(camera.getLeft()); + } + loc.normalizeLocal().multiplyLocal(_moveSpeed * tpf).addLocal(camera.getLocation()); + camera.setLocation(loc); + } + + // ROTATION + int rotX = 0, rotY = 0; + if (kb.isDown(Key.UP)) { + rotY -= 1; + } + if (kb.isDown(Key.DOWN)) { + rotY += 1; + } + if (kb.isDown(Key.LEFT)) { + rotX += 1; + } + if (kb.isDown(Key.RIGHT)) { + rotX -= 1; + } + if (rotX != 0 || rotY != 0) { + rotate(camera, rotX * (_keyRotateSpeed / _mouseRotateSpeed) * tpf, rotY + * (_keyRotateSpeed / _mouseRotateSpeed) * tpf); + } + } + + protected void rotate(final Camera camera, final double dx, final double dy) { + if (dx != 0) { + applyDx(dx, camera); + } + + if (dy != 0) { + applyDY(dy, camera); + } + + if (dx != 0 || dy != 0) { + camera.normalize(); + } + } + + private void applyDx(final double dx, final Camera camera) { + _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx, _upAxis); + _workerMatrix.applyPost(camera.getLeft(), _workerStoreA); + camera.setLeft(_workerStoreA); + _workerMatrix.applyPost(camera.getDirection(), _workerStoreA); + camera.setDirection(_workerStoreA); + _workerMatrix.applyPost(camera.getUp(), _workerStoreA); + camera.setUp(_workerStoreA); + } + + private void applyDY(final double dy, final Camera camera) { + // apply dy angle change to direction vector + _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy, camera.getLeft()); + _workerMatrix.applyPost(camera.getDirection(), _workerStoreA); + camera.setDirection(_workerStoreA); + + // do we want to constrain our vertical angle? + if (isClampVerticalAngle()) { + // check if we went out of bounds and back up + final double angleV = MathUtils.HALF_PI - _workerStoreA.smallestAngleBetween(_upAxis); + if (angleV > getMaxVerticalAngle() || angleV < getMinVerticalAngle()) { + // clamp the angle to our range + final double newAngle = MathUtils.clamp(angleV, getMinVerticalAngle(), getMaxVerticalAngle()); + // take the difference in angles and back up the direction vector + _workerMatrix.fromAngleNormalAxis(-(newAngle - angleV), camera.getLeft()); + _workerMatrix.applyPost(camera.getDirection(), _workerStoreA); + camera.setDirection(_workerStoreA); + // figure out new up vector by crossing direction and left. + camera.getDirection().cross(camera.getLeft(), _workerStoreA); + camera.setUp(_workerStoreA); + return; + } + } + + // just apply to up vector + _workerMatrix.applyPost(camera.getUp(), _workerStoreA); + camera.setUp(_workerStoreA); + } + + /** + * @param layer + * the logical layer to register with + * @param upAxis + * the up axis of the camera + * @param dragOnly + * if true, mouse input will only rotate the camera if one of the mouse buttons (left, center or right) + * is down. + * @return a new FirstPersonControl object + */ + public static FirstPersonControl setupTriggers(final LogicalLayer layer, final ReadOnlyVector3 upAxis, + final boolean dragOnly) { + + final FirstPersonControl control = new FirstPersonControl(upAxis); + control.setupKeyboardTriggers(layer); + control.setupMouseTriggers(layer, dragOnly); + return control; + } + + /** + * Deregister the triggers of the given FirstPersonControl from the given LogicalLayer. + * + * @param layer + * @param control + */ + public static void removeTriggers(final LogicalLayer layer, final FirstPersonControl control) { + if (control._mouseTrigger != null) { + layer.deregisterTrigger(control._mouseTrigger); + } + if (control._keyTrigger != null) { + layer.deregisterTrigger(control._keyTrigger); + } + } + + public void setupMouseTriggers(final LogicalLayer layer, final boolean dragOnly) { + // Mouse look + final Predicate<TwoInputStates> someMouseDown = Predicates.or(TriggerConditions.leftButtonDown(), + Predicates.or(TriggerConditions.rightButtonDown(), TriggerConditions.middleButtonDown())); + final Predicate<TwoInputStates> dragged = Predicates.and(TriggerConditions.mouseMoved(), someMouseDown); + final TriggerAction dragAction = new TriggerAction() { + + // Test boolean to allow us to ignore first mouse event. First event can wildly vary based on platform. + private boolean firstPing = true; + + public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { + final MouseState mouse = inputStates.getCurrent().getMouseState(); + if (mouse.getDx() != 0 || mouse.getDy() != 0) { + if (!firstPing) { + FirstPersonControl.this.rotate(source.getCanvasRenderer().getCamera(), -mouse.getDx(), + -mouse.getDy()); + } else { + firstPing = false; + } + } + } + }; + + _mouseTrigger = new InputTrigger(dragOnly ? dragged : TriggerConditions.mouseMoved(), dragAction); + layer.registerTrigger(_mouseTrigger); + } + + public Predicate<TwoInputStates> setupKeyboardTriggers(final LogicalLayer layer) { + // WASD control + final Predicate<TwoInputStates> keysHeld = new Predicate<TwoInputStates>() { + Key[] keys = new Key[] { Key.W, Key.A, Key.S, Key.D, Key.LEFT, Key.RIGHT, Key.UP, Key.DOWN }; + + public boolean apply(final TwoInputStates states) { + for (final Key k : keys) { + if (states.getCurrent() != null && states.getCurrent().getKeyboardState().isDown(k)) { + return true; + } + } + return false; + } + }; + + final TriggerAction moveAction = new TriggerAction() { + public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { + FirstPersonControl.this.move(source.getCanvasRenderer().getCamera(), inputStates.getCurrent() + .getKeyboardState(), tpf); + } + }; + _keyTrigger = new InputTrigger(keysHeld, moveAction); + layer.registerTrigger(_keyTrigger); + return keysHeld; + } + + public InputTrigger getKeyTrigger() { + return _keyTrigger; + } + + public InputTrigger getMouseTrigger() { + return _mouseTrigger; + } + + public boolean isClampVerticalAngle() { + return _clampVerticalAngle; + } + + /** + * @param clampVerticalAngle + * if true, the vertical angle of the camera is locked between the minimum and maximum angles (default is + * [-60, 60]) + */ + public void setClampVerticalAngle(final boolean clampVerticalAngle) { + _clampVerticalAngle = clampVerticalAngle; + } + + public double getMinVerticalAngle() { + return _minVerticalAngle; + } + + /** + * @param minVerticalAngle + * the new minimum angle, in radians, to clamp our vertical angle to. Defaults to -60 degrees (in + * radians). Must be less than the max angle. Has no effect unless clampVerticalAngle is true. + * @see #setClampVerticalAngle(boolean) + */ + public void setMinVerticalAngle(final double minVerticalAngle) { + _minVerticalAngle = minVerticalAngle; + } + + public double getMaxVerticalAngle() { + return _maxVerticalAngle; + } + + /** + * + * @param maxVerticalAngle + * the new maximum angle, in radians, to clamp our vertical angle to. Defaults to +60 degrees (in + * radians). Must be less than the max angle. Has no effect unless clampVerticalAngle is true. + * @see #setClampVerticalAngle(boolean) + */ + public void setMaxVerticalAngle(final double maxVerticalAngle) { + _maxVerticalAngle = maxVerticalAngle; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java new file mode 100644 index 0000000..2be9c8b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java @@ -0,0 +1,382 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.control; + +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.MouseWheelMovedCondition; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.scenegraph.Spatial; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +/** + * <p> + * Orbital type Camera controller. Basically, this class references a camera and provides methods for moving that camera + * around a target position or spatial using spherical polar coordinates. + * </p> + * <p> + * To use, create a new instance of OrbitCamController. Update the controller in your update loop. + * </p> + * <p> + * Example: Creates a new control, adds mouse triggers to a Logical Layer, and sets the default location (15 units away, + * 0 degrees ascent, 0 degrees azimuth). + * </p> + * + * <pre> + * // ... in init + * control = new OrbitCamControl(myCamera, targetLocation); + * control.setupMouseTriggers(myLogicalLayer, true); + * control.setSphereCoords(15, 0, 0); + * + * // ...in update loop + * control.update(timer.getTimePerFrame()); + * </pre> + */ +public class OrbitCamControl { + + /** + * Our absolute min/max ascent (pitch) angle, in radians. This is set at 89.95 degrees to prevent the camera's + * direction from becoming parallel to the world up vector. + */ + public static final double ABSOLUTE_MAXASCENT = 89.95 * MathUtils.DEG_TO_RAD; + + /** + * The camera we are modifying. + */ + protected Camera _camera; + protected Vector3 _worldUpVec = new Vector3(Vector3.UNIT_Y); + + protected Vector3 _sphereCoords = new Vector3(); + protected Vector3 _camPosition = new Vector3(); + + protected Vector3 _lookAtPoint = new Vector3(); + protected Spatial _lookAtSpatial = null; + protected TargetType _targetType; + + protected boolean _invertedX = false; + protected boolean _invertedY = false; + protected boolean _invertedWheel = true; + + protected double _zoomSpeed = 0.01; + protected double _baseDistance = 15; + protected double _minZoomDistance = 1; + protected double _maxZoomDistance = 100; + + protected double _minAscent = -ABSOLUTE_MAXASCENT; + protected double _maxAscent = ABSOLUTE_MAXASCENT; + protected double _xSpeed = 0.01; + protected double _ySpeed = 0.01; + + protected boolean _dirty = true; + + protected InputTrigger _mouseTrigger; + + public enum TargetType { + Point, Spatial + } + + /** + * Construct a new orbit controller + * + * @param cam + * the camera to control + * @param target + * a world location to lock our sights on. + */ + public OrbitCamControl(final Camera cam, final ReadOnlyVector3 target) { + _camera = cam; + _targetType = TargetType.Point; + _lookAtPoint.set(target); + } + + /** + * Construct a new orbit controller + * + * @param cam + * the camera to control + * @param target + * a spatial whose world location we'll lock our sights on. + */ + public OrbitCamControl(final Camera cam, final Spatial target) { + _camera = cam; + _targetType = TargetType.Spatial; + _lookAtSpatial = target; + } + + public Camera getCamera() { + return _camera; + } + + public void setCamera(final Camera camera) { + _camera = camera; + } + + public ReadOnlyVector3 getWorldUpVec() { + return _worldUpVec; + } + + public void setWorldUpVec(final ReadOnlyVector3 worldUpVec) { + _worldUpVec.set(worldUpVec); + _dirty = true; + } + + public void setInvertedWheel(final boolean invertedWheel) { + _invertedWheel = invertedWheel; + } + + public boolean isInvertedWheel() { + return _invertedWheel; + } + + public void setInvertedX(final boolean invertedX) { + _invertedX = invertedX; + } + + public boolean isInvertedX() { + return _invertedX; + } + + public void setInvertedY(final boolean invertedY) { + _invertedY = invertedY; + } + + public boolean isInvertedY() { + return _invertedY; + } + + public Vector3 getLookAtPoint() { + return _lookAtPoint; + } + + /** + * Sets a specific world location for the camera to point at and circle around. + * + * @param point + */ + public void setLookAtPoint(final Vector3 point) { + _dirty = !point.equals(_lookAtPoint); + _lookAtPoint = point; + _targetType = TargetType.Point; + } + + public Spatial getLookAtSpatial() { + return _lookAtSpatial; + } + + /** + * Sets a spatial to look at. We'll use the world transform of the spatial, so its transform needs to be up to date. + * + * @param spatial + */ + public void setLookAtSpatial(final Spatial spatial) { + _dirty = spatial != _lookAtSpatial; // identity equality + _lookAtSpatial = spatial; + _targetType = TargetType.Spatial; + } + + public TargetType getTargetType() { + return _targetType; + } + + public double getZoomSpeed() { + return _zoomSpeed; + } + + public void setZoomSpeed(final double zoomSpeed) { + _zoomSpeed = zoomSpeed; + } + + public double getBaseDistance() { + return _baseDistance; + } + + public void setBaseDistance(final double baseDistance) { + _baseDistance = baseDistance; + zoom(0); + } + + public double getMaxAscent() { + return _maxAscent; + } + + public void setMaxAscent(final double maxAscent) { + _maxAscent = Math.min(maxAscent, ABSOLUTE_MAXASCENT); + move(0, 0); + } + + public double getMinAscent() { + return _minAscent; + } + + public void setMinAscent(final double minAscent) { + _minAscent = Math.max(minAscent, -ABSOLUTE_MAXASCENT); + move(0, 0); + } + + public double getMaxZoomDistance() { + return _maxZoomDistance; + } + + public void setMaxZoomDistance(final double maxZoomDistance) { + _maxZoomDistance = maxZoomDistance; + zoom(0); + } + + public double getMinZoomDistance() { + return _minZoomDistance; + } + + public void setMinZoomDistance(final double minZoomDistance) { + _minZoomDistance = minZoomDistance; + zoom(0); + } + + public double getXSpeed() { + return _xSpeed; + } + + public void setXSpeed(final double speed) { + _xSpeed = speed; + } + + public double getYSpeed() { + return _ySpeed; + } + + public void setYSpeed(final double speed) { + _ySpeed = speed; + } + + public void setSphereCoords(final ReadOnlyVector3 sphereCoords) { + _sphereCoords.set(sphereCoords); + makeDirty(); + } + + public void setSphereCoords(final double x, final double y, final double z) { + _sphereCoords.set(x, y, z); + makeDirty(); + } + + protected void updateTargetPos() { + if (_targetType == TargetType.Spatial) { + final double x = _lookAtPoint.getX(); + final double y = _lookAtPoint.getY(); + final double z = _lookAtPoint.getZ(); + _lookAtSpatial.getWorldTransform().applyForward(Vector3.ZERO, _lookAtPoint); + if (x != _lookAtPoint.getX() || y != _lookAtPoint.getY() || z != _lookAtPoint.getZ()) { + makeDirty(); + } + } + } + + public void makeDirty() { + _dirty = true; + } + + /** + * Zoom camera in/out from the target point. + * + * @param percent + * a value applied to the baseDistance to determine how far in/out to zoom. Inverted if + * {@link #isInvertedWheel()} is true. + */ + public void zoom(final double percent) { + final double amount = (_invertedWheel ? -1 : 1) * percent * _baseDistance; + _sphereCoords.setX(MathUtils.clamp(_sphereCoords.getX() + amount, _minZoomDistance, _maxZoomDistance)); + makeDirty(); + } + + /** + * + * @param xDif + * a value applied to the azimuth value of our spherical coordinates. Inverted if {@link #isInvertedX()} + * is true. + * @param yDif + * a value applied to the theta value of our spherical coordinates. Inverted if {@link #isInvertedY()} is + * true. + */ + public void move(final double xDif, final double yDif) { + final double azimuthAccel = _invertedX ? -xDif : xDif; + final double thetaAccel = _invertedY ? -yDif : yDif; + + // update our master spherical coords, using x and y movement + _sphereCoords.setY(MathUtils.moduloPositive(_sphereCoords.getY() - azimuthAccel, MathUtils.TWO_PI)); + _sphereCoords.setZ(MathUtils.clamp(_sphereCoords.getZ() + thetaAccel, _minAscent, _maxAscent)); + makeDirty(); + } + + /** + * Update the position of the Camera controlled by this object. + * + * @param time + * a delta time, in seconds. Not used currently, but might be useful for doing "ease-in" of camera + * movements. + */ + public void update(final double time) { + updateTargetPos(); + + if (!_dirty) { + return; + } + if (_worldUpVec.getY() == 1) { + MathUtils.sphericalToCartesian(_sphereCoords, _camPosition); + } else if (_worldUpVec.getZ() == 1) { + MathUtils.sphericalToCartesianZ(_sphereCoords, _camPosition); + } + + _camera.setLocation(_camPosition.addLocal(_lookAtPoint)); + + _camera.lookAt(_lookAtPoint, _worldUpVec); + _dirty = false; + } + + public void setupMouseTriggers(final LogicalLayer layer, final boolean dragOnly) { + // Mouse look + final Predicate<TwoInputStates> someMouseDown = Predicates.or(TriggerConditions.leftButtonDown(), Predicates + .or(TriggerConditions.rightButtonDown(), TriggerConditions.middleButtonDown())); + final Predicate<TwoInputStates> scrollWheelMoved = new MouseWheelMovedCondition(); + final Predicate<TwoInputStates> dragged = Predicates.and(TriggerConditions.mouseMoved(), someMouseDown); + final TriggerAction mouseAction = new TriggerAction() { + + // Test boolean to allow us to ignore first mouse event. First event can wildly vary based on platform. + private boolean firstPing = true; + + public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) { + final MouseState mouse = inputStates.getCurrent().getMouseState(); + if (mouse.getDx() != 0 || mouse.getDy() != 0) { + if (!firstPing) { + move(_xSpeed * mouse.getDx(), _ySpeed * mouse.getDy()); + } else { + firstPing = false; + } + } + + if (mouse.getDwheel() != 0) { + zoom(_zoomSpeed * mouse.getDwheel()); + } + } + }; + + final Predicate<TwoInputStates> predicate = Predicates.or(scrollWheelMoved, dragOnly ? dragged + : TriggerConditions.mouseMoved()); + _mouseTrigger = new InputTrigger(predicate, mouseAction); + layer.registerTrigger(_mouseTrigger); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java new file mode 100644 index 0000000..ae3dcc1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java @@ -0,0 +1,26 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.ControllerState;
+import com.google.common.base.Predicate;
+
+public final class AnyControllerCondition implements Predicate<TwoInputStates> {
+
+ public boolean apply(final TwoInputStates states) {
+ final ControllerState oldState = states.getPrevious().getControllerState();
+ final ControllerState currentState = states.getCurrent().getControllerState();
+
+ final boolean apply = !oldState.equals(currentState);
+ return apply;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java new file mode 100644 index 0000000..5ab407f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import com.ardor3d.input.InputState; +import com.google.common.base.Predicate; + +/** + * Applicable whenever 'any' key has been pressed. + */ +public class AnyKeyCondition implements Predicate<TwoInputStates> { + public boolean apply(final TwoInputStates twoInputStates) { + final InputState currentState = twoInputStates.getCurrent(); + final InputState previousState = twoInputStates.getPrevious(); + + return !currentState.getKeyboardState().getKeysPressedSince(previousState.getKeyboardState()).isEmpty(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java new file mode 100644 index 0000000..5b77235 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import java.util.Set; + +import com.ardor3d.framework.Canvas; + +public class BasicTriggersApplier implements LogicalTriggersApplier { + + public void checkAndPerformTriggers(final Set<InputTrigger> triggers, final Canvas source, + final TwoInputStates states, final double tpf) { + for (final InputTrigger trigger : triggers) { + trigger.performIfValid(source, states, tpf); + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java new file mode 100644 index 0000000..9f76c6a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java @@ -0,0 +1,60 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.List;
+
+import com.ardor3d.input.ControllerEvent;
+import com.ardor3d.input.ControllerState;
+import com.google.common.base.Predicate;
+
+public final class ControllerComponentCondition implements Predicate<TwoInputStates> {
+
+ private int controllerIndex = -1;
+ private int componentIndex = -1;
+ private String controllerName = null;
+ private String componentName = null;
+
+ public ControllerComponentCondition(final int controller, final int component) {
+ controllerIndex = controller;
+ componentIndex = component;
+ }
+
+ public ControllerComponentCondition(final String controller, final String component) {
+ controllerName = controller;
+ componentName = component;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ boolean apply = false;
+ final ControllerState currentState = states.getCurrent().getControllerState();
+ final ControllerState previousState = states.getPrevious().getControllerState();
+
+ if (!previousState.equals(currentState)) {
+
+ if (controllerName == null) {
+ controllerName = currentState.getControllerNames().get(controllerIndex);
+ }
+ if (componentName == null) {
+ componentName = currentState.getControllerComponentNames(controllerName).get(componentIndex);
+ }
+
+ final List<ControllerEvent> events = currentState.getEvents();
+ for (final ControllerEvent event : events) {
+ if (event.getControllerName().equals(controllerName) && event.getComponentName().equals(componentName)) {
+ apply = true;
+ }
+ }
+ }
+ return apply;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java new file mode 100644 index 0000000..79a4a02 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java @@ -0,0 +1,51 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.List;
+
+import com.ardor3d.input.ControllerEvent;
+import com.ardor3d.input.ControllerState;
+import com.google.common.base.Predicate;
+
+public final class ControllerCondition implements Predicate<TwoInputStates> {
+
+ private int controllerIndex = -1;
+ private String controllerName = null;
+
+ public ControllerCondition(final int controller) {
+ controllerIndex = controller;
+ }
+
+ public ControllerCondition(final String controller) {
+ controllerName = controller;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ boolean apply = false;
+ final ControllerState currentState = states.getCurrent().getControllerState();
+ final ControllerState previousState = states.getPrevious().getControllerState();
+
+ if (!previousState.equals(currentState)) {
+ if (controllerName == null) {
+ controllerName = currentState.getControllerNames().get(controllerIndex);
+ }
+ final List<ControllerEvent> events = currentState.getEvents();
+ for (final ControllerEvent event : events) {
+ if (event.getControllerName().equals(controllerName)) {
+ apply = true;
+ }
+ }
+ }
+ return apply;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java new file mode 100644 index 0000000..aba7f9d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java @@ -0,0 +1,49 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.ControllerEvent;
+import com.ardor3d.input.ControllerState;
+import com.ardor3d.input.ControllerWrapper;
+import com.google.common.collect.PeekingIterator;
+
+public class DummyControllerWrapper implements ControllerWrapper {
+ public static final DummyControllerWrapper INSTANCE = new DummyControllerWrapper();
+
+ PeekingIterator<ControllerEvent> empty = new PeekingIterator<ControllerEvent>() {
+ public boolean hasNext() {
+ return false;
+ }
+
+ public void remove() {}
+
+ public ControllerEvent peek() {
+ return null;
+ }
+
+ public ControllerEvent next() {
+ return null;
+ }
+ };
+
+ public ControllerState getBlankState() {
+ return new ControllerState();
+ }
+
+ public PeekingIterator<ControllerEvent> getEvents() {
+ return empty;
+ }
+
+ public void init() {
+ ; // ignore, does nothing
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java new file mode 100644 index 0000000..7ff0c3e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import com.ardor3d.input.FocusWrapper; + +/** + * A "do-nothing" implementation of FocusWrapper useful when you want to ignore (or do not need) focus events. + */ +public class DummyFocusWrapper implements FocusWrapper { + public static final DummyFocusWrapper INSTANCE = new DummyFocusWrapper(); + + public void init() { + ; // ignore, does nothing + } + + public boolean getAndClearFocusLost() { + return false; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java new file mode 100644 index 0000000..3de004e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import com.ardor3d.input.KeyEvent; +import com.ardor3d.input.KeyboardWrapper; +import com.google.common.collect.PeekingIterator; + +/** + * A "do-nothing" implementation of KeyboardWrapper useful when you want to ignore (or do not need) key events. + */ +public class DummyKeyboardWrapper implements KeyboardWrapper { + public static final DummyKeyboardWrapper INSTANCE = new DummyKeyboardWrapper(); + + PeekingIterator<KeyEvent> empty = new PeekingIterator<KeyEvent>() { + + public boolean hasNext() { + return false; + } + + public void remove() {} + + public KeyEvent peek() { + return null; + } + + public KeyEvent next() { + return null; + } + }; + + public PeekingIterator<KeyEvent> getEvents() { + return empty; + } + + public void init() { + ; // ignore, does nothing. + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java new file mode 100644 index 0000000..dc47f24 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import com.ardor3d.input.MouseState; +import com.ardor3d.input.MouseWrapper; +import com.google.common.collect.PeekingIterator; + +/** + * A "do-nothing" implementation of MouseWrapper useful when you want to ignore (or do not need) mouse events. + */ +public class DummyMouseWrapper implements MouseWrapper { + public static final DummyMouseWrapper INSTANCE = new DummyMouseWrapper(); + + PeekingIterator<MouseState> empty = new PeekingIterator<MouseState>() { + + public boolean hasNext() { + return false; + } + + public void remove() {} + + public MouseState peek() { + return null; + } + + public MouseState next() { + return null; + } + }; + + public PeekingIterator<MouseState> getEvents() { + return empty; + } + + public void init() { + ; // ignore, does nothing + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java new file mode 100644 index 0000000..fb5119f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java @@ -0,0 +1,85 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.framework.Canvas;
+import com.google.common.base.Predicate;
+
+/**
+ * Defines an action to be performed when a specific input condition is met.
+ */
+@Immutable
+public final class InputTrigger {
+ private final Predicate<TwoInputStates> _condition;
+ private final TriggerAction _action;
+ private String _id;
+
+ /**
+ * Construct a new InputTrigger with the given condition and action.
+ *
+ * @param condition
+ * the predicate to test for this trigger
+ * @param action
+ * the action to take if the predicate returns true.
+ */
+ public InputTrigger(final Predicate<TwoInputStates> condition, final TriggerAction action) {
+ _condition = condition;
+ _action = action;
+ }
+
+ /**
+ * Construct a new InputTrigger with the given condition and action.
+ *
+ * @param condition
+ * the predicate to test for this trigger
+ * @param action
+ * the action to take if the predicate returns true.
+ * @param id
+ * an id, useful for identifying this trigger for deregistration, etc.
+ */
+ public InputTrigger(final Predicate<TwoInputStates> condition, final TriggerAction action, final String id) {
+ _condition = condition;
+ _action = action;
+ _id = id;
+ }
+
+ /**
+ * Checks if the condition is applicable, and if so, performs the action.
+ *
+ * @param source
+ * the Canvas that was the source of the current input
+ * @param states
+ * the input states to check
+ * @param tpf
+ * the time per frame in seconds
+ */
+ void performIfValid(final Canvas source, final TwoInputStates states, final double tpf) {
+ if (_condition.apply(states)) {
+ _action.perform(source, states, tpf);
+ }
+ }
+
+ /**
+ * @param id
+ * the id to set. This id can be used to uniquely identify a trigger.
+ */
+ public void setId(final String id) {
+ _id = id;
+ }
+
+ /**
+ * @return the id set, or null if none was set.
+ */
+ public String getId() {
+ return _id;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java new file mode 100644 index 0000000..e913b22 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java @@ -0,0 +1,43 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.Key;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true when a key is down in the current input state.
+ */
+@Immutable
+public final class KeyHeldCondition implements Predicate<TwoInputStates> {
+ private final Key key;
+
+ /**
+ * Construct a new KeyHeldCondition.
+ *
+ * @param key
+ * the key that should be held
+ * @throws NullPointerException
+ * if the key is null
+ */
+ public KeyHeldCondition(final Key key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ this.key = key;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ return states.getCurrent().getKeyboardState().isDown(key);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java new file mode 100644 index 0000000..fd56bb3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java @@ -0,0 +1,47 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given key was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class KeyPressedCondition implements Predicate<TwoInputStates> {
+ private final Key key;
+
+ /**
+ * Construct a new KeyPressedCondition.
+ *
+ * @param key
+ * the key that should be held
+ * @throws NullPointerException
+ * if the key is null
+ */
+ public KeyPressedCondition(final Key key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ this.key = key;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ return currentState.getKeyboardState().getKeysPressedSince(previousState.getKeyboardState()).contains(key);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java new file mode 100644 index 0000000..d7e313f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java @@ -0,0 +1,47 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true when a key was released from the previous to the current input state.
+ */
+@Immutable
+public final class KeyReleasedCondition implements Predicate<TwoInputStates> {
+ private final Key key;
+
+ /**
+ * Construct a new KeyReleasedCondition.
+ *
+ * @param key
+ * the key that should be held
+ * @throws NullPointerException
+ * if the key is null
+ */
+ public KeyReleasedCondition(final Key key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ this.key = key;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ return currentState.getKeyboardState().getKeysReleasedSince(previousState.getKeyboardState()).contains(key);
+ }
+}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java new file mode 100644 index 0000000..ed57f8c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import com.ardor3d.annotation.GuardedBy; +import com.ardor3d.annotation.MainThread; +import com.ardor3d.annotation.ThreadSafe; +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.InputState; +import com.ardor3d.input.PhysicalLayer; + +/** + * Implementation of a logical layer on top of the physical one, to be able to more easily trigger certain commands for + * certain combination of user input. + */ +@ThreadSafe +public final class LogicalLayer { + private final Set<InputSource> _inputs = new CopyOnWriteArraySet<InputSource>(); + private final Set<InputTrigger> _triggers = new CopyOnWriteArraySet<InputTrigger>(); + private LogicalTriggersApplier _applier = new BasicTriggersApplier(); + + public LogicalLayer() {} + + public void registerInput(final Canvas source, final PhysicalLayer physicalLayer) { + _inputs.add(new InputSource(source, physicalLayer)); + } + + /** + * Register a trigger for evaluation when the {@link #checkTriggers(double)} method is called. + * + * @param inputTrigger + * the trigger to check + */ + public void registerTrigger(final InputTrigger inputTrigger) { + _triggers.add(inputTrigger); + } + + /** + * Deregister a trigger for evaluation when the {@link #checkTriggers(double)} method is called. + * + * @param inputTrigger + * the trigger to stop checking + */ + public void deregisterTrigger(final InputTrigger inputTrigger) { + _triggers.remove(inputTrigger); + } + + /** + * Check all registered triggers to see if their respective conditions are met. For every trigger whose condition is + * true, perform the associated action. + * + * @param tpf + * time per frame in seconds + */ + @MainThread + public synchronized void checkTriggers(final double tpf) { + for (final InputSource is : _inputs) { + is.physicalLayer.readState(); + + final List<InputState> newStates = is.physicalLayer.drainAvailableStates(); + + if (newStates.isEmpty()) { + _applier.checkAndPerformTriggers(_triggers, is.source, new TwoInputStates(is.lastState, is.lastState), + tpf); + } else { + // used to spread tpf evenly among triggered actions + final double time = newStates.size() > 1 ? tpf / newStates.size() : tpf; + for (final InputState inputState : newStates) { + // no trigger is valid in the LOST_FOCUS state, so don't bother checking them + if (inputState != InputState.LOST_FOCUS) { + _applier.checkAndPerformTriggers(_triggers, is.source, new TwoInputStates(is.lastState, + inputState), time); + } + + is.lastState = inputState; + } + } + } + } + + public void setApplier(final LogicalTriggersApplier applier) { + _applier = applier; + } + + public LogicalTriggersApplier getApplier() { + return _applier; + } + + private static class InputSource { + private final Canvas source; + private final PhysicalLayer physicalLayer; + @GuardedBy("LogicalLayer.this") + private InputState lastState; + + public InputSource(final Canvas source, final PhysicalLayer physicalLayer) { + this.source = source; + this.physicalLayer = physicalLayer; + lastState = InputState.EMPTY; + } + } + + public Set<InputTrigger> getTriggers() { + return _triggers; + } + + public InputTrigger findTriggerById(final String id) { + for (final InputTrigger trigger : _triggers) { + if (id.equals(trigger.getId())) { + return trigger; + } + } + return null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java new file mode 100644 index 0000000..d034ce8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import java.util.Set; + +import com.ardor3d.framework.Canvas; + +/** + * Defines a class the handles applying the triggers of a LogicalLayer. + */ +public interface LogicalTriggersApplier { + + void checkAndPerformTriggers(Set<InputTrigger> triggers, Canvas source, TwoInputStates states, double tpf); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java new file mode 100644 index 0000000..3fb0153 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java @@ -0,0 +1,47 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given button was clicked (has a click count) when going from the previous input state
+ * to the current one.
+ */
+@Immutable
+public final class MouseButtonClickedCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonClickedCondition.
+ *
+ * @param button
+ * the button that should be "clicked" to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonClickedCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+
+ return currentState.getMouseState().getButtonsClicked().contains(_button);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java new file mode 100644 index 0000000..ecb03cb --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java @@ -0,0 +1,62 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.EnumMap;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
+/**
+ * A condition that checks the state of the two most commonly used mouse buttons.
+ */
+@Immutable
+public final class MouseButtonCondition implements Predicate<TwoInputStates> {
+ private final EnumMap<MouseButton, ButtonState> _states = Maps.newEnumMap(MouseButton.class);
+
+ public MouseButtonCondition(final EnumMap<MouseButton, ButtonState> states) {
+ _states.putAll(states);
+ }
+
+ public MouseButtonCondition(final ButtonState left, final ButtonState right, final ButtonState middle) {
+ if (left != ButtonState.UNDEFINED) {
+ _states.put(MouseButton.LEFT, left);
+ }
+ if (right != ButtonState.UNDEFINED) {
+ _states.put(MouseButton.RIGHT, right);
+ }
+ if (middle != ButtonState.UNDEFINED) {
+ _states.put(MouseButton.MIDDLE, middle);
+ }
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+
+ if (currentState == null) {
+ return false;
+ }
+
+ for (final MouseButton button : _states.keySet()) {
+ final ButtonState required = _states.get(button);
+ if (required != ButtonState.UNDEFINED) {
+ if (currentState.getMouseState().getButtonState(button) != required) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java new file mode 100644 index 0000000..b804246 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java @@ -0,0 +1,53 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given button was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class MouseButtonPressedCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonPressedCondition.
+ *
+ * @param button
+ * the button that should be pressed to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonPressedCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null || previousState == null
+ || !currentState.getMouseState().hasButtonState(ButtonState.DOWN)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getButtonsPressedSince(previousState.getMouseState()).contains(_button);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java new file mode 100644 index 0000000..461d889 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java @@ -0,0 +1,53 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given button was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class MouseButtonReleasedCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonPressedCondition.
+ *
+ * @param button
+ * the button that should be pressed to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonReleasedCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null || previousState == null
+ || !previousState.getMouseState().hasButtonState(ButtonState.DOWN)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getButtonsReleasedSince(previousState.getMouseState()).contains(_button);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java new file mode 100644 index 0000000..e8643a0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java @@ -0,0 +1,36 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if the mouse has moved between the two input states.
+ */
+@Immutable
+public final class MouseMovedCondition implements Predicate<TwoInputStates> {
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null) {
+ return false;
+ }
+
+ if (currentState.equals(previousState)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getDx() != 0 || currentState.getMouseState().getDy() != 0;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java new file mode 100644 index 0000000..f0377f9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java @@ -0,0 +1,36 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if the mouse wheel has moved between the two input states.
+ */
+@Immutable
+public final class MouseWheelMovedCondition implements Predicate<TwoInputStates> {
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null) {
+ return false;
+ }
+
+ if (currentState.equals(previousState)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getDwheel() != 0;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java new file mode 100644 index 0000000..48b5ac1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java @@ -0,0 +1,33 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.framework.Canvas;
+
+/**
+ * Defines an action to be performed when a given input condition is true.
+ */
+public interface TriggerAction {
+ /**
+ * Implementing classes should implementing this method to take whatever action is desired. This method will always
+ * be called on the main GL thread.
+ *
+ * @param source
+ * the Canvas that was the source of the current input
+ * @param inputState
+ * the current and previous states of the input system when the action was triggered
+ * @param tpf
+ * the time per frame in seconds
+ */
+ @MainThread
+ public void perform(Canvas source, TwoInputStates inputStates, double tpf);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java new file mode 100644 index 0000000..1f686a5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java @@ -0,0 +1,121 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.EnumMap;
+
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.util.Timer;
+import com.google.common.base.Predicate;
+
+/**
+ * Utility methods for getting standard TriggerConditions. To reduce object creation, it may be a good idea to use
+ * utility methods here to get immutable conditions that meet common criteria.
+ */
+public final class TriggerConditions {
+ private static final MouseMovedCondition MOUSE_MOVED_CONDITION = new MouseMovedCondition();
+ private static final MouseButtonCondition LEFT_DOWN_CONDITION = makeCondition(MouseButton.LEFT, ButtonState.DOWN);
+ private static final MouseButtonCondition RIGHT_DOWN_CONDITION = makeCondition(MouseButton.RIGHT, ButtonState.DOWN);
+ private static final MouseButtonCondition MIDDLE_DOWN_CONDITION = makeCondition(MouseButton.MIDDLE,
+ ButtonState.DOWN);
+
+ private static final Predicate<TwoInputStates> ALWAYS_TRUE = new Predicate<TwoInputStates>() {
+ @Override
+ public boolean apply(final TwoInputStates arg0) {
+ return true;
+ }
+ };
+
+ private static final Predicate<TwoInputStates> ALWAYS_FALSE = new Predicate<TwoInputStates>() {
+ @Override
+ public boolean apply(final TwoInputStates arg0) {
+ return true;
+ }
+ };
+
+ private static MouseButtonCondition makeCondition(final MouseButton button, final ButtonState state) {
+ final EnumMap<MouseButton, ButtonState> map = new EnumMap<MouseButton, ButtonState>(MouseButton.class);
+ for (final MouseButton b : MouseButton.values()) {
+ map.put(b, button != b ? ButtonState.UNDEFINED : state);
+ }
+ return new MouseButtonCondition(map);
+ }
+
+ // prevent instantiation
+ private TriggerConditions() {
+
+ }
+
+ /**
+ * @return a condition that evaluates to true if the mouse has moved
+ */
+ public static MouseMovedCondition mouseMoved() {
+ return MOUSE_MOVED_CONDITION;
+ }
+
+ /**
+ *
+ * @return a condition that is true if the left button is down
+ */
+ public static MouseButtonCondition leftButtonDown() {
+ return LEFT_DOWN_CONDITION;
+ }
+
+ /**
+ *
+ * @return a condition that is true if the right button is down
+ */
+ public static MouseButtonCondition rightButtonDown() {
+ return RIGHT_DOWN_CONDITION;
+ }
+
+ /**
+ *
+ * @return a condition that is true if the middle button is down
+ */
+ public static MouseButtonCondition middleButtonDown() {
+ return MIDDLE_DOWN_CONDITION;
+ }
+
+ /**
+ * @return a condition that is always true.
+ */
+ public static Predicate<TwoInputStates> alwaysTrue() {
+ return ALWAYS_TRUE;
+ }
+
+ /**
+ * @return a condition that is always false.
+ */
+ public static Predicate<TwoInputStates> alwaysFalse() {
+ return ALWAYS_FALSE;
+ }
+
+ /**
+ * @return a condition that is always false.
+ */
+ public static Predicate<TwoInputStates> passedThrottle(final double throttleTime, final Timer timer) {
+ return new Predicate<TwoInputStates>() {
+ private double lastPass = 0;
+
+ @Override
+ public boolean apply(final TwoInputStates arg0) {
+ final double now = timer.getTimeInSeconds();
+ if (now - lastPass >= throttleTime) {
+ lastPass = now;
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java new file mode 100644 index 0000000..2e058e1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.input.logical; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.ardor3d.annotation.Immutable; +import com.ardor3d.input.InputState; + +/** + * Wrapper class to make it possible to use {@link com.google.common.base.Predicate}-based conditions for triggering + * actions based on user input. + */ +@Immutable +public final class TwoInputStates { + private final InputState _previous; + private final InputState _current; + + /** + * Instantiates a new TwoInputStates. It is safe for both parameters to point to the same instance, but they cannot + * be null. + * + * @param previous + * the previous input state + * @param current + * the current input state + * + * @throws NullPointerException + * if either parameter is null + */ + public TwoInputStates(final InputState previous, final InputState current) { + _previous = checkNotNull(previous, "previous"); + _current = checkNotNull(current, "current"); + } + + public InputState getPrevious() { + return _previous; + } + + public InputState getCurrent() { + return _current; + } + + @Override + public int hashCode() { + // we don't expect this to be used in a map. + assert false : "hashCode not designed"; + return 42; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TwoInputStates)) { + return false; + } + final TwoInputStates comp = (TwoInputStates) o; + return _previous == comp._previous && _current == comp._current; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java new file mode 100644 index 0000000..b90a7eb --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.scenegraph.Mesh; + +/** + * BoundingCollisionResults creates a CollisionResults object that only cares about bounding volume accuracy. + * CollisionData objects are added to the collision list as they happen, these data objects only refer to the two + * meshes, not their triangle lists. While BoundingCollisionResults defines a processCollisions method, it is empty and + * should be further defined by the user if so desired. + */ +public class BoundingCollisionResults extends CollisionResults { + + /** + * adds a CollisionData object to this results list, the objects only refer to the collision meshes, not the + * triangles. + * + * @see com.ardor3d.intersection.CollisionResults#addCollision(com.ardor3d.scene.Geometry, + * com.ardor3d.scene.Geometry) + */ + @Override + public void addCollision(final Mesh s, final Mesh t) { + final CollisionData data = new CollisionData(s, t); + addCollisionData(data); + } + + /** + * empty implementation, it is highly recommended that you override this method to handle any collisions as needed. + * + * @see com.ardor3d.intersection.CollisionResults#processCollisions() + */ + @Override + public void processCollisions() { + + } + +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java new file mode 100644 index 0000000..ac3b284 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Ray3; + +/** + * BoundingPickResults implements the addPick of PickResults to use PickData objects that calculate bounding volume + * level accurate ray picks. + */ +public class BoundingPickResults extends PickResults { + @Override + public void addPick(final Ray3 ray, final Pickable p) { + if (p.intersectsWorldBound(ray)) { + addPickData(new PickData(ray, p, willCheckDistance())); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java new file mode 100644 index 0000000..bd616a8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import java.util.List; + +import com.ardor3d.scenegraph.Mesh; + +/** + * CollisionData contains information about a collision between two Mesh objects. The mesh that was hit by the relevant + * Mesh (the one making the collision check) is referenced as well as an ArrayList for the triangles that collided. + */ +public class CollisionData { + + private final Mesh _targetMesh; + private final Mesh _sourceMesh; + + private final List<PrimitiveKey> _sourcePrimitives; + private final List<PrimitiveKey> _targetPrimitives; + + /** + * instantiates a new CollisionData object. + * + * @param sourceMesh + * the relevant Geometry + * @param targetMesh + * the mesh the source Mesh collided with. + */ + public CollisionData(final Mesh sourceMesh, final Mesh targetMesh) { + this(sourceMesh, targetMesh, null, null); + } + + /** + * instantiates a new CollisionData object. + * + * @param sourceMesh + * the relevant Mesh + * @param targetMesh + * the mesh the source Mesh collided with. + * @param sourcePrimitives + * the primitives of the source Mesh that made contact. + * @param targetPrimitives + * the primitives of the second mesh that made contact. + */ + public CollisionData(final Mesh sourceMesh, final Mesh targetMesh, final List<PrimitiveKey> sourcePrimitives, + final List<PrimitiveKey> targetPrimitives) { + _targetMesh = targetMesh; + _sourceMesh = sourceMesh; + _targetPrimitives = targetPrimitives; + _sourcePrimitives = sourcePrimitives; + } + + public Mesh getTargetMesh() { + return _targetMesh; + } + + public Mesh getSourceMesh() { + return _sourceMesh; + } + + public List<PrimitiveKey> getSourcePrimitives() { + return _sourcePrimitives; + } + + public List<PrimitiveKey> getTargetPrimitives() { + return _targetPrimitives; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java new file mode 100644 index 0000000..0d4d693 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.scenegraph.Mesh; + +/** + * <code>CollisionResults</code> stores the results of a collision test by storing an ArrayList of CollisionData. + */ +public abstract class CollisionResults { + + private final List<CollisionData> _nodeList; + + /** + * Constructor instantiates a new <code>PickResults</code> object. + */ + public CollisionResults() { + _nodeList = new ArrayList<CollisionData>(); + } + + /** + * <code>addCollisionData</code> places a new <code>CollisionData</code> object into the results list. + * + * @param col + * The collision data to be placed in the results list. + */ + public void addCollisionData(final CollisionData col) { + _nodeList.add(col); + } + + /** + * <code>getNumber</code> retrieves the number of collisions that have been placed in the results. + * + * @return the number of collisions in the list. + */ + public int getNumber() { + return _nodeList.size(); + } + + /** + * <code>getCollisionData</code> retrieves a CollisionData from a specific index. + * + * @param i + * the index requested. + * @return the CollisionData at the specified index. + */ + public CollisionData getCollisionData(final int i) { + return _nodeList.get(i); + } + + /** + * <code>clear</code> clears the list of all CollisionData. + */ + public void clear() { + _nodeList.clear(); + } + + /** + * + * <code>addCollision</code> is an abstract method whose intent is the subclass determines what to do when two Mesh + * object's bounding volumes are determined to intersect. + * + * @param s + * the first Mesh that intersects. + * @param t + * the second Mesh that intersects. + */ + public abstract void addCollision(Mesh s, Mesh t); + + /** + * + * <code>processCollisions</code> is an abstract method whose intent is the subclass defines how to process the + * collision data that has been collected since the last clear. + * + * + */ + public abstract void processCollisions(); + +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java new file mode 100644 index 0000000..8245aa4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Vector3; + +/** + * <code>Intersection</code> provides functional methods for calculating the intersection of some objects. All the + * methods are static to allow for quick and easy calls. <code>Intersection</code> relays requests to specific classes + * to handle the actual work. By providing checks to just <code>BoundingVolume</code> the client application need not + * worry about what type of bounding volume is being used. + */ +public abstract class Intersection { + + public static boolean intersection(final Vector3[] verticesA, final Vector3[] verticesB) { + switch (verticesA.length) { + case 3: + switch (verticesB.length) { + case 3: + // Triangle on Triangle + return TriangleTriangleIntersect.intersectTriTri(verticesA[0], verticesA[1], verticesA[2], + verticesB[0], verticesB[1], verticesB[2]); + case 4: + // TODO: Triangle on Quad + return false; + case 2: + // TODO: Triangle on Line + return false; + case 1: + // TODO: Triangle on Point + return false; + } + case 4: + switch (verticesB.length) { + case 3: + // TODO: Quad on Triangle + return false; + case 4: + // TODO: Quad on Quad + return false; + case 2: + // TODO: Quad on Line + return false; + case 1: + // TODO: Quad on Point + return false; + } + case 2: + switch (verticesB.length) { + case 3: + // TODO: Line on Triangle + return false; + case 4: + // TODO: Line on Quad + return false; + case 2: + // TODO: Line on Line + return false; + case 1: + // TODO: Line on Point + return false; + } + case 1: + switch (verticesB.length) { + case 3: + // TODO: Point on Triangle + return false; + case 4: + // TODO: Point on Quad + return false; + case 2: + // TODO: Point on Line + return false; + case 1: + // Point on Point + return verticesA[0].equals(verticesB[0]); + } + } + return false; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java new file mode 100644 index 0000000..eb5fb3f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import java.util.Arrays; +import java.util.List; + +import com.ardor3d.math.Vector3; +import com.ardor3d.util.Ardor3dException; + +public class IntersectionRecord { + + private static final class Intersection implements Comparable<Intersection> { + private final double _distance; + private final Vector3 _point; + private final Vector3 _normal; + private final PrimitiveKey _primitiveKey; + + private Intersection(final double distance, final Vector3 point, final Vector3 normal, + final PrimitiveKey primitiveKey) { + _distance = distance; + _point = point; + _normal = normal; + _primitiveKey = primitiveKey; + } + + @Override + public int compareTo(final Intersection other) { + return _distance == other._distance ? 0 : (_distance < other._distance ? -1 : 1); + } + } + + private final Intersection[] _intersections; + + private boolean _isSorted = true; + + /** + * Instantiates a new IntersectionRecord defining the distances and points. + * + * @param distances + * the distances of this intersection. + * @param points + * the points of this intersection. + * @throws Ardor3dException + * if distances.length != points.length + */ + public IntersectionRecord(final double[] distances, final Vector3[] points) { + this(distances, points, null); + } + + /** + * Instantiates a new IntersectionRecord defining the distances and points. + * + * @param distances + * the distances of this intersection. + * @param points + * the points of this intersection. + * @param primitives + * the primitives at each index. May be null. + * @throws Ardor3dException + * if distances.length != points.length or points.length != primitives.size() (if primitives is not + * null) + */ + public IntersectionRecord(final double[] distances, final Vector3[] points, final List<PrimitiveKey> primitives) { + this(distances, points, null, primitives); + } + + /** + * Instantiates a new IntersectionRecord defining the distances and points. + * + * @param distances + * the distances of this intersection. + * @param points + * the points of this intersection. + * @param points + * the normals of this intersection. + * @param primitives + * the primitives at each index. May be null. + * @throws Ardor3dException + * if distances.length != points.length or points.length != primitives.size() (if primitives is not + * null) + */ + public IntersectionRecord(final double[] distances, final Vector3[] points, final Vector3[] normals, + final List<PrimitiveKey> primitives) { + if (distances.length != points.length || (primitives != null && points.length != primitives.size()) + || (normals != null && points.length != normals.length)) { + throw new Ardor3dException("All arguments must have an equal number of elements."); + } + _isSorted = distances.length < 2; + _intersections = new Intersection[distances.length]; + for (int i = 0; i < distances.length; i++) { + _intersections[i] = new Intersection(distances[i], points[i], normals != null ? normals[i] : null, + primitives != null ? primitives.get(i) : null); + } + } + + /** + * Sorts intersections from near to far + */ + public void sortIntersections() { + if (!_isSorted) { + Arrays.sort(_intersections); + _isSorted = true; + } + } + + /** + * @return the number of intersections that occurred. + */ + public int getNumberOfIntersections() { + return _intersections.length; + } + + /** + * Returns an intersection point at a provided index. + * + * @param index + * the index of the point to obtain. + * @return the point at the index of the array. + */ + public Vector3 getIntersectionPoint(final int index) { + return _intersections[index]._point; + } + + /** + * Returns an intersection normal at a provided index. + * + * @param index + * the index of the point to obtain. + * @return the normal at the index of the array. + */ + public Vector3 getIntersectionNormal(final int index) { + return _intersections[index]._normal; + } + + /** + * Returns an intersection distance at a provided index. + * + * @param index + * the index of the distance to obtain. + * @return the distance at the index of the array. + */ + public double getIntersectionDistance(final int index) { + return _intersections[index]._distance; + } + + /** + * @param index + * the index of the primitive to obtain. + * @return the primitive at the given index. + */ + public PrimitiveKey getIntersectionPrimitive(final int index) { + return _intersections[index]._primitiveKey; + } + + /** + * @return the smallest distance in the distance array or -1 if there are no distances in this. + */ + public double getClosestDistance() { + final int i = getClosestIntersection(); + return i != -1 ? _intersections[i]._distance : -1.0; + } + + /** + * @return the largest distance in the distance array or -1 if there are no distances in this. + */ + public double getFurthestDistance() { + final int i = getFurthestIntersection(); + return i != -1 ? _intersections[i]._distance : -1.0; + } + + /** + * @return the index in this record with the smallest relative distance or -1 if there are no distances in this + * record. + */ + public int getClosestIntersection() { + int index = -1; + if (_isSorted) { + index = _intersections.length > 0 ? 0 : -1; + } else { + double min = Double.MAX_VALUE; + for (int i = _intersections.length; --i >= 0;) { + final double val = _intersections[i]._distance; + if (val < min) { + min = val; + index = i; + } + } + } + return index; + } + + /** + * @return the index in this record with the largest relative distance or -1 if there are no distances in this + * record. + */ + public int getFurthestIntersection() { + int index = -1; + if (_isSorted) { + index = _intersections.length > 0 ? _intersections.length - 1 : -1; + } else { + double max = -Double.MAX_VALUE; + for (int i = _intersections.length; --i >= 0;) { + final double val = _intersections[i]._distance; + if (val > max) { + max = val; + index = i; + } + } + } + return index; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java new file mode 100644 index 0000000..4e2de16 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Ray3; + +/** + * PickData contains information about a picking operation (or Ray/Volume intersection). This data contains the mesh the + * ray hit, the triangles it hit, and the ray itself. + */ +public class PickData { + + private final Ray3 _ray; + private final Pickable _target; + protected IntersectionRecord _intersectionRecord; + + /** + * instantiates a new PickData object. Note: subclasses may want to make calc points false to prevent this extra + * work. + */ + public PickData(final Ray3 ray, final Pickable target, final boolean calcPoints) { + _ray = ray; + _target = target; + + if (calcPoints) { + _intersectionRecord = target.intersectsWorldBoundsWhere(ray); + } + } + + /** + * @return the pickable hit by the ray. + */ + public Pickable getTarget() { + return _target; + } + + /** + * @return the ray used in the test. + */ + public Ray3 getRay() { + return _ray; + } + + /** + * @return the intersection record generated for this pick. Will be null if calcPoints was false. + */ + public IntersectionRecord getIntersectionRecord() { + return _intersectionRecord; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java new file mode 100644 index 0000000..402b934 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.ardor3d.math.Ray3; + +/** + * PickResults stores information created during ray intersection tests. The results will contain a list of every + * {@link Pickable} element encountered in a pick test. Distance can be used to order the results. If checkDistance is + * set to true, objects will be ordered with the first element in the list being the closest picked object. + */ +public abstract class PickResults { + + private final List<PickData> _nodeList; + private boolean _checkDistance; + private DistanceComparator _distanceCompare; + + /** + * Constructor instantiates a new <code>PickResults</code> object. + */ + public PickResults() { + _nodeList = new ArrayList<PickData>(); + } + + /** + * remember modification of the list to allow sorting after all picks have been added - not each time. + */ + private boolean modified = false; + + /** + * Places a new geometry (enclosed in PickData) into the results list. + * + * @param data + * the PickData to be placed in the results list. + */ + public void addPickData(final PickData data) { + _nodeList.add(data); + modified = true; + } + + /** + * <code>getNumber</code> retrieves the number of geometries that have been placed in the results. + * + * @return the number of Mesh objects in the list. + */ + public int getNumber() { + return _nodeList.size(); + } + + /** + * Retrieves a geometry (enclosed in PickData) from a specific index. + * + * @param i + * the index requested. + * @return the data at the specified index. + */ + public PickData getPickData(final int i) { + if (modified) { + if (_checkDistance) { + Collections.sort(_nodeList, _distanceCompare); + } + modified = false; + } + return _nodeList.get(i); + } + + /** + * <code>clear</code> clears the list of all Mesh objects. + */ + public void clear() { + _nodeList.clear(); + } + + /** + * <code>addPick</code> generates an entry to be added to the list of picked objects. If checkDistance is true, the + * implementing class should order the object. + * + * @param ray + * the ray that was cast for the pick calculation. + * @param p + * the pickable object to add to the pick data. + */ + public abstract void addPick(Ray3 ray, Pickable p); + + /** + * Optional method that can be implemented by sub classes to define methods for handling picked objects. After + * calculating all pick results this method is called. + * + */ + public void processPick() {} + + /** + * Reports if these pick results will order the data by distance from the origin of the Ray. + * + * @return true if objects will be ordered by distance, false otherwise. + */ + public boolean willCheckDistance() { + return _checkDistance; + } + + /** + * Sets if these pick results will order the data by distance from the origin of the Ray. + * + * @param checkDistance + * true if objects will be ordered by distance, false otherwise. + */ + public void setCheckDistance(final boolean checkDistance) { + _checkDistance = checkDistance; + if (checkDistance) { + _distanceCompare = new DistanceComparator(); + } + } + + /** + * Implementation of comparator that uses the distance set in the pick data to order the objects. + */ + private static class DistanceComparator implements Comparator<PickData> { + + public int compare(final PickData o1, final PickData o2) { + if (o1.getIntersectionRecord().getClosestDistance() <= o2.getIntersectionRecord().getClosestDistance()) { + return -1; + } + + return 1; + } + } + + public PickData findFirstIntersectingPickData() { + int i = 0; + while (getNumber() > 0 && getPickData(i).getIntersectionRecord().getNumberOfIntersections() == 0 + && ++i < getNumber()) { + } + return getNumber() > i ? getPickData(i) : null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java new file mode 100644 index 0000000..832162b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Ray3; + +/** + * An interface describing objects that can be used with our PickingUtil class. + */ +public interface Pickable { + + /** + * @return true if this pickable supports intersectsWorldBoundsWhere. False if this method will always return null. + */ + boolean supportsBoundsIntersectionRecord(); + + /** + * @return true if this pickable supports intersectsPrimitivesWhere. False if this method will always return null. + */ + boolean supportsPrimitivesIntersectionRecord(); + + /** + * @param ray + * a ray, in world coordinates. + * @return true if the given ray intersects our world bounding volume. false if it does not. + * @throws NullPointerException + * if there is no bound to check. + */ + boolean intersectsWorldBound(Ray3 ray); + + /** + * @param ray + * a ray, in world coordinates. + * @return an intersection record containing information about where the ray intersected our bounding volume, or + * null if it does not. + * @throws NullPointerException + * if there is no bound to check. + */ + IntersectionRecord intersectsWorldBoundsWhere(Ray3 ray); + + /** + * @param ray + * a ray, in world coordinates. + * @return an intersection record containing information about where the ray intersected our primitives, or null if + * it does not. + */ + IntersectionRecord intersectsPrimitivesWhere(Ray3 ray); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java new file mode 100644 index 0000000..0c3c066 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java @@ -0,0 +1,205 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import java.util.List; + +import com.ardor3d.bounding.CollisionTree; +import com.ardor3d.bounding.CollisionTreeManager; +import com.ardor3d.math.Ray3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.PickingHint; + +public abstract class PickingUtil { + /** + * Finds a pick using the given ray starting at the scenegraph given as spatial. Results are stored in the given + * results value. + * + * NB: Spatials with CullHint ALWAYS will be skipped. + * + * @param spatial + * @param ray + * @param results + */ + public static void findPick(final Spatial spatial, final Ray3 ray, final PickResults results) { + findPick(spatial, ray, results, true); + } + + /** + * Finds a pick using the given ray starting at the scenegraph given as spatial. Results are stored in the given + * results value. + * + * @param spatial + * @param ray + * @param results + * @param ignoreCulled + * if true, Spatials with CullHint ALWAYS will be skipped. + */ + public static void findPick(final Spatial spatial, final Ray3 ray, final PickResults results, + final boolean ignoreCulled) { + if (spatial == null || !spatial.getSceneHints().isPickingHintEnabled(PickingHint.Pickable) + || (ignoreCulled && spatial.getSceneHints().getCullHint() == CullHint.Always) + || spatial.getWorldBound() == null || !spatial.getWorldBound().intersects(ray)) { + return; + } + + if (spatial instanceof Pickable) { + results.addPick(ray, (Pickable) spatial); + } else if (spatial instanceof Node) { + final Node node = (Node) spatial; + for (int i = node.getNumberOfChildren() - 1; i >= 0; i--) { + findPick(node.getChild(i), ray, results, ignoreCulled); + } + } + } + + public static void findCollisions(final Spatial spatial, final Spatial scene, final CollisionResults results) { + if (spatial == scene || spatial.getWorldBound() == null + || !spatial.getSceneHints().isPickingHintEnabled(PickingHint.Collidable) + || !scene.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) { + return; + } + + if (spatial instanceof Node) { + final Node node = (Node) spatial; + + if (node.getWorldBound().intersects(scene.getWorldBound())) { + // further checking needed. + for (int i = 0; i < node.getNumberOfChildren(); i++) { + PickingUtil.findCollisions(node.getChild(i), scene, results); + } + } + } else if (spatial instanceof Mesh) { + final Mesh mesh = (Mesh) spatial; + + if (mesh.getWorldBound().intersects(scene.getWorldBound())) { + if (scene instanceof Node) { + final Node parent = (Node) scene; + for (int i = 0; i < parent.getNumberOfChildren(); i++) { + PickingUtil.findCollisions(mesh, parent.getChild(i), results); + } + } else { + results.addCollision(mesh, (Mesh) scene); + } + } + } + } + + /** + * This function checks for intersection between this mesh and the given one. On the first intersection, true is + * returned. + * + * @param toCheck + * The intersection testing mesh. + * @return True if they intersect. + */ + public static boolean hasPrimitiveCollision(final Mesh testMesh, final Mesh toCheck) { + if (!testMesh.getSceneHints().isPickingHintEnabled(PickingHint.Collidable) + || !toCheck.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) { + return false; + } + + final CollisionTree thisCT = CollisionTreeManager.getInstance().getCollisionTree(testMesh); + final CollisionTree checkCT = CollisionTreeManager.getInstance().getCollisionTree(toCheck); + + if (thisCT == null || checkCT == null) { + return false; + } + + final ReadOnlyTransform worldTransform = testMesh.getWorldTransform(); + thisCT.getBounds().transform(worldTransform, thisCT.getWorldBounds()); + return thisCT.intersect(checkCT); + } + + /** + * This function finds all intersections between this mesh and the checking one. The intersections are stored as + * PrimitiveKeys. + * + * @param toCheck + * The Mesh to check. + * @param testIndex + * The array of PrimitiveKeys intersecting in this mesh. + * @param otherIndex + * The array of PrimitiveKeys intersecting in the given mesh. + */ + public static void findPrimitiveCollision(final Mesh testMesh, final Mesh toCheck, + final List<PrimitiveKey> testIndex, final List<PrimitiveKey> otherIndex) { + if (!testMesh.getSceneHints().isPickingHintEnabled(PickingHint.Collidable) + || !toCheck.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) { + return; + } + + final CollisionTree myTree = CollisionTreeManager.getInstance().getCollisionTree(testMesh); + final CollisionTree otherTree = CollisionTreeManager.getInstance().getCollisionTree(toCheck); + + if (myTree == null || otherTree == null) { + return; + } + + myTree.getBounds().transform(testMesh.getWorldTransform(), myTree.getWorldBounds()); + + myTree.intersect(otherTree, testIndex, otherIndex); + } + + public static boolean hasCollision(final Spatial spatial, final Spatial scene, final boolean checkPrimitives) { + if (spatial == scene || spatial.getWorldBound() == null + || !spatial.getSceneHints().isPickingHintEnabled(PickingHint.Collidable) + || !scene.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) { + return false; + } + + if (spatial instanceof Node) { + final Node node = (Node) spatial; + + if (node.getWorldBound().intersects(scene.getWorldBound())) { + if (node.getNumberOfChildren() == 0 && !checkPrimitives) { + return true; + } + // further checking needed. + for (int i = 0; i < node.getNumberOfChildren(); i++) { + if (PickingUtil.hasCollision(node.getChild(i), scene, checkPrimitives)) { + return true; + } + } + } + } else if (spatial instanceof Mesh) { + final Mesh mesh = (Mesh) spatial; + + if (mesh.getWorldBound().intersects(scene.getWorldBound())) { + if (scene instanceof Node) { + final Node parent = (Node) scene; + for (int i = 0; i < parent.getNumberOfChildren(); i++) { + if (PickingUtil.hasCollision(mesh, parent.getChild(i), checkPrimitives)) { + return true; + } + } + + return false; + } + + if (!checkPrimitives) { + return true; + } + + return PickingUtil.hasPrimitiveCollision(mesh, (Mesh) scene); + } + + return false; + + } + + return false; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java new file mode 100644 index 0000000..133312d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.scenegraph.Mesh; + +/** + * PrimitiveCollisionResults creates a CollisionResults object that calculates collisions to the primitive (quad, + * triangle, etc.) accuracy. CollisionData objects are added to the collision list as they happen, these data objects + * only refer to the two meshes, not their primitive lists. While PrimitiveCollisionResults defines a processCollisions + * method, it is empty and should be further defined by the user if so desired. + * + * NOTE: Only Mesh objects may obtain primitive accuracy, all others will result in Bounding accuracy. + */ +public class PrimitiveCollisionResults extends CollisionResults { + /* + * (non-Javadoc) + * + * @see com.ardor3d.intersection.CollisionResults#addCollision(com.ardor3d.scene.Geometry, + * com.ardor3d.scene.Geometry) + */ + @Override + public void addCollision(final Mesh s, final Mesh t) { + // find the triangle that is being hit. + // add this node and the triangle to the CollisionResults list. + final List<PrimitiveKey> a = new ArrayList<PrimitiveKey>(); + final List<PrimitiveKey> b = new ArrayList<PrimitiveKey>(); + PickingUtil.findPrimitiveCollision(s, t, a, b); + final CollisionData data = new CollisionData(s, t, a, b); + addCollisionData(data); + } + + /* + * (non-Javadoc) + * + * @see com.ardor3d.intersection.CollisionResults#processCollisions() + */ + @Override + public void processCollisions() { + + } + +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java new file mode 100644 index 0000000..c23d085 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +public class PrimitiveKey { + private final int _primitiveIndex; + private final int _section; + + public PrimitiveKey(final int primitiveIndex, final int section) { + _primitiveIndex = primitiveIndex; + _section = section; + } + + /** + * @return the primitiveIndex + */ + public int getPrimitiveIndex() { + return _primitiveIndex; + } + + /** + * @return the section + */ + public int getSection() { + return _section; + } + + @Override + public int hashCode() { + int result = 17; + + result += 31 * result + _primitiveIndex; + result += 31 * result + _section; + + return result; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PrimitiveKey)) { + return false; + } + final PrimitiveKey comp = (PrimitiveKey) o; + return _primitiveIndex == comp._primitiveIndex && _section == comp._section; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java new file mode 100644 index 0000000..33fd1f5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Ray3; + +/** + * Pick data for primitive accurate picking including sort by distance to intersection point. + */ +public class PrimitivePickData extends PickData { + public PrimitivePickData(final Ray3 ray, final Pickable target) { + super(ray, target, false); // hard coded to false + + _intersectionRecord = target.intersectsPrimitivesWhere(ray); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java new file mode 100644 index 0000000..76fb345 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Ray3; + +/** + * PrimitivePickResults implements the addPick of PickResults to use PickData objects that calculate primitive accurate + * ray picks. + */ +public class PrimitivePickResults extends PickResults { + protected float _maxPickableDistance = Float.MAX_VALUE; + + @Override + public void addPick(final Ray3 ray, final Pickable pickable) { + final IntersectionRecord record = pickable.intersectsWorldBoundsWhere(ray); + if (record != null && record.getClosestDistance() > _maxPickableDistance) { + return; + } + + final PrimitivePickData data = new PrimitivePickData(ray, pickable); + if (data.getIntersectionRecord() != null && data.getIntersectionRecord().getNumberOfIntersections() > 0) { + addPickData(data); + } + } + + public float getMaxPickableDistance() { + return _maxPickableDistance; + } + + public void setMaxPickableDistance(final float distance) { + _maxPickableDistance = distance; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java new file mode 100644 index 0000000..c718914 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java @@ -0,0 +1,393 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.intersection; + +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; + +public class TriangleTriangleIntersect { + + /** + * EPSILON represents the error buffer used to denote a hit. + */ + public static final double EPSILON = 1e-12; + + /** + * This method tests for the intersection between two triangles defined by their vertexes. Converted to java from C + * code found at http://jgt.akpeters.com/papers/Moller97/ + * + * @param v0 + * First triangle's first vertex. + * @param v1 + * First triangle's second vertex. + * @param v2 + * First triangle's third vertex. + * @param u0 + * Second triangle's first vertex. + * @param u1 + * Second triangle's second vertex. + * @param u2 + * Second triangle's third vertex. + * @return True if the two triangles intersect, false otherwise. + */ + public static boolean intersectTriTri(final Vector3 v0, final Vector3 v1, final Vector3 v2, final Vector3 u0, + final Vector3 u1, final Vector3 u2) { + final Vector3 e1 = Vector3.fetchTempInstance(); + final Vector3 e2 = Vector3.fetchTempInstance(); + final Vector3 n1 = Vector3.fetchTempInstance(); + final Vector3 n2 = Vector3.fetchTempInstance(); + final Vector3 d = Vector3.fetchTempInstance(); + try { + + double d1, d2; + double du0, du1, du2, dv0, dv1, dv2; + final double[] isect1 = new double[2]; + final double[] isect2 = new double[2]; + double du0du1, du0du2, dv0dv1, dv0dv2; + short index; + double vp0, vp1, vp2; + double up0, up1, up2; + double bb, cc, max; + double xx, yy, xxyy, tmp; + + /* compute plane equation of triangle(v0,v1,v2) */ + v1.subtract(v0, e1); + v2.subtract(v0, e2); + e1.cross(e2, n1); + d1 = -n1.dot(v0); + /* plane equation 1: n1.X+d1=0 */ + + /* + * put u0,u1,u2 into plane equation 1 to compute signed distances to the plane + */ + du0 = n1.dot(u0) + d1; + du1 = n1.dot(u1) + d1; + du2 = n1.dot(u2) + d1; + + /* coplanarity robustness check */ + if (Math.abs(du0) < EPSILON) { + du0 = 0.0f; + } + if (Math.abs(du1) < EPSILON) { + du1 = 0.0f; + } + if (Math.abs(du2) < EPSILON) { + du2 = 0.0f; + } + du0du1 = du0 * du1; + du0du2 = du0 * du2; + + if (du0du1 > 0.0f && du0du2 > 0.0f) { + return false; + } + + /* compute plane of triangle (u0,u1,u2) */ + u1.subtract(u0, e1); + u2.subtract(u0, e2); + e1.cross(e2, n2); + d2 = -n2.dot(u0); + /* plane equation 2: n2.X+d2=0 */ + + /* put v0,v1,v2 into plane equation 2 */ + dv0 = n2.dot(v0) + d2; + dv1 = n2.dot(v1) + d2; + dv2 = n2.dot(v2) + d2; + + if (Math.abs(dv0) < EPSILON) { + dv0 = 0.0f; + } + if (Math.abs(dv1) < EPSILON) { + dv1 = 0.0f; + } + if (Math.abs(dv2) < EPSILON) { + dv2 = 0.0f; + } + + dv0dv1 = dv0 * dv1; + dv0dv2 = dv0 * dv2; + + if (dv0dv1 > 0.0f && dv0dv2 > 0.0f) { /* + * same sign on all of them + not equal 0 ? + */ + return false; /* no intersection occurs */ + } + + /* compute direction of intersection line */ + n1.cross(n2, d); + + /* compute and index to the largest component of d */ + max = Math.abs(d.getX()); + index = 0; + bb = Math.abs(d.getY()); + cc = Math.abs(d.getZ()); + if (bb > max) { + max = bb; + index = 1; + } + if (cc > max) { + vp0 = v0.getZ(); + vp1 = v1.getZ(); + vp2 = v2.getZ(); + + up0 = u0.getZ(); + up1 = u1.getZ(); + up2 = u2.getZ(); + + } else if (index == 1) { + vp0 = v0.getY(); + vp1 = v1.getY(); + vp2 = v2.getY(); + + up0 = u0.getY(); + up1 = u1.getY(); + up2 = u2.getY(); + } else { + vp0 = v0.getX(); + vp1 = v1.getX(); + vp2 = v2.getX(); + + up0 = u0.getX(); + up1 = u1.getX(); + up2 = u2.getX(); + } + + /* compute interval for triangle 1 */ + { + final Vector3 abc = Vector3.fetchTempInstance(); + final Vector2 x0x1 = Vector2.fetchTempInstance(); + if (newComputeIntervals(vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, abc, x0x1)) { + return coplanarTriTri(n1, v0, v1, v2, u0, u1, u2); + } + + /* compute interval for triangle 2 */ + final Vector3 def = Vector3.fetchTempInstance(); + final Vector2 y0y1 = Vector2.fetchTempInstance(); + if (newComputeIntervals(up0, up1, up2, du0, du1, du2, du0du1, du0du2, def, y0y1)) { + return coplanarTriTri(n1, v0, v1, v2, u0, u1, u2); + } + + xx = x0x1.getX() * x0x1.getY(); + yy = y0y1.getX() * y0y1.getY(); + xxyy = xx * yy; + + tmp = abc.getX() * xxyy; + isect1[0] = tmp + abc.getY() * x0x1.getY() * yy; + isect1[1] = tmp + abc.getZ() * x0x1.getX() * yy; + + tmp = def.getX() * xxyy; + isect2[0] = tmp + def.getY() * xx * y0y1.getY(); + isect2[1] = tmp + def.getZ() * xx * y0y1.getX(); + + Vector3.releaseTempInstance(abc); + Vector3.releaseTempInstance(def); + + Vector2.releaseTempInstance(x0x1); + Vector2.releaseTempInstance(y0y1); + + sort(isect1); + sort(isect2); + } + if (isect1[1] < isect2[0] || isect2[1] < isect1[0]) { + return false; + } + + return true; + } finally { + Vector3.releaseTempInstance(e1); + Vector3.releaseTempInstance(e2); + Vector3.releaseTempInstance(n1); + Vector3.releaseTempInstance(n2); + Vector3.releaseTempInstance(d); + } + } + + private static void sort(final double[] f) { + if (f[0] > f[1]) { + final double c = f[0]; + f[0] = f[1]; + f[1] = c; + } + } + + private static boolean newComputeIntervals(final double vv0, final double vv1, final double vv2, final double d0, + final double d1, final double d2, final double d0d1, final double d0d2, final Vector3 abc, + final Vector2 x0x1) { + if (d0d1 > 0.0f) { + /* here we know that d0d2 <=0.0 */ + /* + * that is d0, d1 are on the same side, d2 on the other or on the plane + */ + abc.setX(vv2); + abc.setY((vv0 - vv2) * d2); + abc.setZ((vv1 - vv2) * d2); + x0x1.setX(d2 - d0); + x0x1.setY(d2 - d1); + } else if (d0d2 > 0.0f) { + /* here we know that d0d1 <=0.0 */ + abc.setX(vv1); + abc.setY((vv0 - vv1) * d1); + abc.setZ((vv2 - vv1) * d1); + x0x1.setX(d1 - d0); + x0x1.setY(d1 - d2); + } else if (d1 * d2 > 0.0f || d0 != 0.0f) { + /* here we know that d0d1 <=0.0 or that d0!=0.0 */ + abc.setX(vv0); + abc.setY((vv1 - vv0) * d0); + abc.setZ((vv2 - vv0) * d0); + x0x1.setX(d0 - d1); + x0x1.setY(d0 - d2); + } else if (d1 != 0.0f) { + abc.setX(vv1); + abc.setY((vv0 - vv1) * d1); + abc.setZ((vv2 - vv1) * d1); + x0x1.setX(d1 - d0); + x0x1.setY(d1 - d2); + } else if (d2 != 0.0f) { + abc.setX(vv2); + abc.setY((vv0 - vv2) * d2); + abc.setZ((vv1 - vv2) * d2); + x0x1.setX(d2 - d0); + x0x1.setY(d2 - d1); + } else { + /* triangles are coplanar */ + return true; + } + return false; + } + + private static boolean coplanarTriTri(final Vector3 n, final Vector3 v0, final Vector3 v1, final Vector3 v2, + final Vector3 u0, final Vector3 u1, final Vector3 u2) { + final Vector3 a = new Vector3(); + short i0, i1; + a.setX(Math.abs(n.getX())); + a.setY(Math.abs(n.getY())); + a.setZ(Math.abs(n.getZ())); + + if (a.getX() > a.getY()) { + if (a.getX() > a.getZ()) { + i0 = 1; /* a[0] is greatest */ + i1 = 2; + } else { + i0 = 0; /* a[2] is greatest */ + i1 = 1; + } + } else /* a[0] <=a[1] */{ + if (a.getZ() > a.getY()) { + i0 = 0; /* a[2] is greatest */ + i1 = 1; + } else { + i0 = 0; /* a[1] is greatest */ + i1 = 2; + } + } + + /* test all edges of triangle 1 against the edges of triangle 2 */ + final double[] v0f = new double[3]; + v0.toArray(v0f); + final double[] v1f = new double[3]; + v1.toArray(v1f); + final double[] v2f = new double[3]; + v2.toArray(v2f); + final double[] u0f = new double[3]; + u0.toArray(u0f); + final double[] u1f = new double[3]; + u1.toArray(u1f); + final double[] u2f = new double[3]; + u2.toArray(u2f); + if (edgeAgainstTriEdges(v0f, v1f, u0f, u1f, u2f, i0, i1)) { + return true; + } + + if (edgeAgainstTriEdges(v1f, v2f, u0f, u1f, u2f, i0, i1)) { + return true; + } + + if (edgeAgainstTriEdges(v2f, v0f, u0f, u1f, u2f, i0, i1)) { + return true; + } + + /* finally, test if tri1 is totally contained in tri2 or vice versa */ + pointInTri(v0f, u0f, u1f, u2f, i0, i1); + pointInTri(u0f, v0f, v1f, v2f, i0, i1); + + return false; + } + + private static boolean pointInTri(final double[] V0, final double[] U0, final double[] U1, final double[] U2, + final int i0, final int i1) { + double a, b, c, d0, d1, d2; + /* is T1 completly inside T2? */ + /* check if V0 is inside tri(U0,U1,U2) */ + a = U1[i1] - U0[i1]; + b = -(U1[i0] - U0[i0]); + c = -a * U0[i0] - b * U0[i1]; + d0 = a * V0[i0] + b * V0[i1] + c; + + a = U2[i1] - U1[i1]; + b = -(U2[i0] - U1[i0]); + c = -a * U1[i0] - b * U1[i1]; + d1 = a * V0[i0] + b * V0[i1] + c; + + a = U0[i1] - U2[i1]; + b = -(U0[i0] - U2[i0]); + c = -a * U2[i0] - b * U2[i1]; + d2 = a * V0[i0] + b * V0[i1] + c; + if (d0 * d1 > 0.0 && d0 * d2 > 0.0) { + return true; + } + + return false; + } + + private static boolean edgeAgainstTriEdges(final double[] v0, final double[] v1, final double[] u0, + final double[] u1, final double[] u2, final int i0, final int i1) { + double aX, aY; + aX = v1[i0] - v0[i0]; + aY = v1[i1] - v0[i1]; + /* test edge u0,u1 against v0,v1 */ + if (edgeEdgeTest(v0, u0, u1, i0, i1, aX, aY)) { + return true; + } + /* test edge u1,u2 against v0,v1 */ + if (edgeEdgeTest(v0, u1, u2, i0, i1, aX, aY)) { + return true; + } + /* test edge u2,u1 against v0,v1 */ + if (edgeEdgeTest(v0, u2, u0, i0, i1, aX, aY)) { + return true; + } + return false; + } + + private static boolean edgeEdgeTest(final double[] v0, final double[] u0, final double[] u1, final int i0, + final int i1, final double aX, final double Ay) { + final double Bx = u0[i0] - u1[i0]; + final double By = u0[i1] - u1[i1]; + final double Cx = v0[i0] - u0[i0]; + final double Cy = v0[i1] - u0[i1]; + final double f = Ay * Bx - aX * By; + final double d = By * Cx - Bx * Cy; + if ((f > 0 && d >= 0 && d <= f) || (f < 0 && d <= 0 && d >= f)) { + final double e = aX * Cy - Ay * Cx; + if (f > 0) { + if (e >= 0 && e <= f) { + return true; + } + } else { + if (e <= 0 && e >= f) { + return true; + } + } + } + return false; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java new file mode 100644 index 0000000..6bd3c58 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.light; + +import java.io.IOException; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>DirectionalLight</code> defines a light that is assumed to be infinitely far away (something similar to the + * sun). This means the direction of the light rays are all parallel. The direction field of this class identifies the + * direction in which the light is traveling, which is opposite how jME works. + */ +public class DirectionalLight extends Light { + private static final long serialVersionUID = 1L; + + private final Vector3 _direction = new Vector3(Vector3.UNIT_Z); + + /** + * Constructor instantiates a new <code>DirectionalLight</code> object. The initial light colors are white and the + * direction the light travels is along the positive z axis (0,0,1). + * + */ + public DirectionalLight() {} + + /** + * @return the direction the light traveling in. + */ + public ReadOnlyVector3 getDirection() { + return _direction; + } + + /** + * @param direction + * the direction the light is traveling in. + */ + public void setDirection(final ReadOnlyVector3 direction) { + _direction.set(direction); + } + + /** + * @param x + * the direction the light is traveling in on the x axis. + * @param y + * the direction the light is traveling in on the y axis. + * @param z + * the direction the light is traveling in on the z axis. + */ + public void setDirection(final double x, final double y, final double z) { + _direction.set(x, y, z); + } + + /** + * <code>getType</code> returns this light's type (Type.Directional). + * + * @see com.ardor3d.light.Light#getType() + */ + @Override + public Type getType() { + return Type.Directional; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z)); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z))); + + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java new file mode 100644 index 0000000..cf1a9cd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java @@ -0,0 +1,355 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.light; + +import java.io.IOException; +import java.io.Serializable; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * <code>Light</code> defines the attributes of a light element. This class is abstract and intended to be sub-classed + * by specific lighting types. A light will illuminate portions of the scene by assigning its properties to the objects + * in the scene. This will affect the objects color values, depending on the color of the ambient, diffuse and specular + * light components. + * + * Ambient light defines the general light of the scene, that is the intensity and color of lighting if no particular + * lights are affecting it. + * + * Diffuse lighting defines the reflection of light on matte surfaces. + * + * Specular lighting defines the reflection of light on shiny surfaces. + */ +public abstract class Light implements Serializable, Savable { + + private static final long serialVersionUID = 1L; + + /** + * dark grey (.4, .4, .4, 1) + */ + public static final ReadOnlyColorRGBA DEFAULT_AMBIENT = new ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f); + + /** + * white (1, 1, 1, 1) + */ + public static final ReadOnlyColorRGBA DEFAULT_DIFFUSE = new ColorRGBA(1, 1, 1, 1); + + /** + * white (1, 1, 1, 1) + */ + public static final ReadOnlyColorRGBA DEFAULT_SPECULAR = new ColorRGBA(1, 1, 1, 1); + + public enum Type { + Directional, Point, Spot + } + + // light attributes. + private final ColorRGBA _ambient = new ColorRGBA(DEFAULT_AMBIENT); + private final ColorRGBA _diffuse = new ColorRGBA(DEFAULT_DIFFUSE); + private final ColorRGBA _specular = new ColorRGBA(DEFAULT_SPECULAR); + + private boolean _attenuate; + private float _constant = 1; + private float _linear; + private float _quadratic; + + private int _lightMask = 0; + private int _backLightMask = 0; + + private boolean _enabled; + + private String _name; + + /** when true, indicates the lights in this lightState will cast shadows. */ + protected boolean _shadowCaster; + + /** + * Constructor instantiates a new <code>Light</code> object. All light color values are set to white. + * + */ + public Light() {} + + /** + * + * <code>getType</code> returns the type of the light that has been created. + * + * @return the type of light that has been created. + */ + public abstract Type getType(); + + /** + * <code>getConstant</code> returns the value for the constant attenuation. + * + * @return the value for the constant attenuation. + */ + public float getConstant() { + return _constant; + } + + /** + * <code>setConstant</code> sets the value for the constant attentuation. + * + * @param constant + * the value for the constant attenuation. + */ + public void setConstant(final float constant) { + _constant = constant; + } + + /** + * <code>getLinear</code> returns the value for the linear attenuation. + * + * @return the value for the linear attenuation. + */ + public float getLinear() { + return _linear; + } + + /** + * <code>setLinear</code> sets the value for the linear attentuation. + * + * @param linear + * the value for the linear attenuation. + */ + public void setLinear(final float linear) { + _linear = linear; + } + + /** + * <code>getQuadratic</code> returns the value for the quadratic attentuation. + * + * @return the value for the quadratic attenuation. + */ + public float getQuadratic() { + return _quadratic; + } + + /** + * <code>setQuadratic</code> sets the value for the quadratic attenuation. + * + * @param quadratic + * the value for the quadratic attenuation. + */ + public void setQuadratic(final float quadratic) { + _quadratic = quadratic; + } + + /** + * <code>isAttenuate</code> returns true if attenuation is to be used for this light. + * + * @return true if attenuation is to be used, false otherwise. + */ + public boolean isAttenuate() { + return _attenuate; + } + + /** + * <code>setAttenuate</code> sets if attenuation is to be used. True sets it on, false otherwise. + * + * @param attenuate + * true to use attenuation, false not to. + */ + public void setAttenuate(final boolean attenuate) { + _attenuate = attenuate; + } + + /** + * + * <code>isEnabled</code> returns true if the light is enabled, false otherwise. + * + * @return true if the light is enabled, false if it is not. + */ + public boolean isEnabled() { + return _enabled; + } + + /** + * + * <code>setEnabled</code> sets the light on or off. True turns it on, false turns it off. + * + * @param value + * true to turn the light on, false to turn it off. + */ + public void setEnabled(final boolean value) { + _enabled = value; + } + + /** + * <code>getSpecular</code> returns the specular color value for this light. + * + * @return the specular color value of the light. + */ + public ReadOnlyColorRGBA getSpecular() { + return _specular; + } + + /** + * <code>setSpecular</code> sets the specular color value for this light. + * + * @param specular + * the specular color value of the light. + */ + public void setSpecular(final ReadOnlyColorRGBA specular) { + this._specular.set(specular); + } + + /** + * <code>getDiffuse</code> returns the diffuse color value for this light. + * + * @return the diffuse color value for this light. + */ + public ReadOnlyColorRGBA getDiffuse() { + return _diffuse; + } + + /** + * <code>setDiffuse</code> sets the diffuse color value for this light. + * + * @param diffuse + * the diffuse color value for this light. + */ + public void setDiffuse(final ReadOnlyColorRGBA diffuse) { + this._diffuse.set(diffuse); + } + + /** + * <code>getAmbient</code> returns the ambient color value for this light. + * + * @return the ambient color value for this light. + */ + public ReadOnlyColorRGBA getAmbient() { + return _ambient; + } + + /** + * <code>setAmbient</code> sets the ambient color value for this light. + * + * @param ambient + * the ambient color value for this light. + */ + public void setAmbient(final ReadOnlyColorRGBA ambient) { + this._ambient.set(ambient); + } + + /** + * @return Returns the lightMask - default is 0 or not masked. + */ + public int getLightMask() { + return _lightMask; + } + + /** + * <code>setLightMask</code> sets what attributes of this light to apply as an int comprised of bitwise |'ed values + * from LightState.Mask_XXXX. LightMask.MASK_GLOBALAMBIENT is ignored. + * + * @param lightMask + * The lightMask to set. + */ + public void setLightMask(final int lightMask) { + _lightMask = lightMask; + } + + /** + * Saves the light mask to a back store. That backstore is recalled with popLightMask. Despite the name, this is not + * a stack and additional pushes will simply overwrite the backstored value. + */ + public void pushLightMask() { + _backLightMask = _lightMask; + } + + /** + * Recalls the light mask from a back store or 0 if none was pushed. + * + * @see com.ardor3d.light.Light#pushLightMask() + */ + public void popLightMask() { + _lightMask = _backLightMask; + } + + /** + * @return Returns whether this light is able to cast shadows. + */ + public boolean isShadowCaster() { + return _shadowCaster; + } + + /** + * @param mayCastShadows + * true if this light can be used to derive shadows (when used in conjunction with a shadow pass.) + */ + public void setShadowCaster(final boolean mayCastShadows) { + _shadowCaster = mayCastShadows; + } + + /** + * Copies the light values from the given light into this Light. + * + * @param light + * the Light to copy from. + */ + public void copyFrom(final Light light) { + _ambient.set(light._ambient); + _attenuate = light._attenuate; + _constant = light._constant; + _diffuse.set(light._diffuse); + _enabled = light._enabled; + _linear = light._linear; + _quadratic = light._quadratic; + _shadowCaster = light._shadowCaster; + _specular.set(light._specular); + } + + public String getName() { + return _name; + } + + public void setName(final String name) { + this._name = name; + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_ambient, "ambient", new ColorRGBA(DEFAULT_AMBIENT)); + capsule.write(_diffuse, "diffuse", new ColorRGBA(DEFAULT_DIFFUSE)); + capsule.write(_specular, "specular", new ColorRGBA(DEFAULT_SPECULAR)); + capsule.write(_attenuate, "attenuate", false); + capsule.write(_constant, "constant", 1); + capsule.write(_linear, "linear", 0); + capsule.write(_quadratic, "quadratic", 0); + capsule.write(_lightMask, "lightMask", 0); + capsule.write(_backLightMask, "backLightMask", 0); + capsule.write(_enabled, "enabled", false); + capsule.write(_shadowCaster, "shadowCaster", false); + capsule.write(_name, "name", null); + } + + public void read(final InputCapsule capsule) throws IOException { + _ambient.set((ColorRGBA) capsule.readSavable("ambient", new ColorRGBA(DEFAULT_AMBIENT))); + _diffuse.set((ColorRGBA) capsule.readSavable("diffuse", new ColorRGBA(DEFAULT_DIFFUSE))); + _specular.set((ColorRGBA) capsule.readSavable("specular", new ColorRGBA(DEFAULT_SPECULAR))); + _attenuate = capsule.readBoolean("attenuate", false); + _constant = capsule.readFloat("constant", 1); + _linear = capsule.readFloat("linear", 0); + _quadratic = capsule.readFloat("quadratic", 0); + _lightMask = capsule.readInt("lightMask", 0); + _backLightMask = capsule.readInt("backLightMask", 0); + _enabled = capsule.readBoolean("enabled", false); + _shadowCaster = capsule.readBoolean("shadowCaster", false); + _name = capsule.readString("name", null); + } + + public Class<? extends Light> getClassTag() { + return this.getClass(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java new file mode 100644 index 0000000..beace3f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.light; + +import java.io.IOException; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>PointLight</code> defines a light that has a location in space and emits light in all directions evenly. This + * would be something similar to a light bulb. Typically this light's values are attenuated based on the distance of the + * point light and the object it illuminates. + */ +public class PointLight extends Light { + private static final long serialVersionUID = 1L; + + // Position of the light. + private Vector3 _location; + + /** + * Constructor instantiates a new <code>PointLight</code> object. The initial position of the light is (0,0,0) and + * it's colors are white. + * + */ + public PointLight() { + super(); + _location = new Vector3(); + } + + /** + * <code>getLocation</code> returns the position of this light. + * + * @return the position of the light. + */ + public ReadOnlyVector3 getLocation() { + return _location; + } + + /** + * <code>setLocation</code> sets the position of the light. + * + * @param location + * the position of the light. + */ + public void setLocation(final ReadOnlyVector3 location) { + _location.set(location); + } + + /** + * <code>setLocation</code> sets the position of the light. + * + * @param x + * the x position of the light. + * @param y + * the y position of the light. + * @param z + * the z position of the light. + */ + public void setLocation(final double x, final double y, final double z) { + _location.set(x, y, z); + } + + /** + * <code>getType</code> returns the type of this light (Type.Point). + * + * @see com.ardor3d.light.Light#getType() + */ + @Override + public Type getType() { + return Type.Point; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_location, "location", new Vector3(Vector3.ZERO)); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _location = (Vector3) capsule.readSavable("location", new Vector3(Vector3.ZERO)); + + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java new file mode 100644 index 0000000..f44e2a3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.light; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * SpotLight defines a light that has a location in space and emits light within a cone. This cone is defined by an + * angle and exponent. Typically this light's values are attenuated based on the distance of the point light and the + * object it illuminates. + */ +public class SpotLight extends PointLight { + private static final long serialVersionUID = 1L; + + private float _angle; + private float _exponent; + private final Vector3 _direction = new Vector3(Vector3.UNIT_Z); + + /** + * Constructor instantiates a new <code>SpotLight</code> object. The initial position of the light is (0,0,0) with + * angle 0, and colors white. + * + */ + public SpotLight() { + super(); + setAmbient(new ColorRGBA(0, 0, 0, 1)); + } + + /** + * @return the direction the spot light is pointing. + */ + public ReadOnlyVector3 getDirection() { + return _direction; + } + + /** + * @param direction + * the direction the spot light is pointing. + */ + public void setDirection(final ReadOnlyVector3 direction) { + _direction.set(direction); + } + + /** + * <code>getAngle</code> returns the angle of the spot light. + * + * @see #setAngle(float) for more info + * @return the angle (in degrees) + */ + public float getAngle() { + return _angle; + } + + /** + * <code>setAngle</code> sets the angle of focus of the spot light measured from the direction vector. Think of this + * as the angle of a cone. Therefore, if you specify 10 degrees, you will get a 20 degree cone (10 degrees off + * either side of the direction vector.) 180 degrees means radiate in all directions. + * + * @param angle + * the angle (in degrees) which must be between 0 and 90 (inclusive) or the special case 180. + */ + public void setAngle(final float angle) { + if (angle < 0f || (angle > 90f && angle != 180f)) { + throw new Ardor3dException("invalid angle. Angle must be between 0 and 90, or 180"); + } + _angle = angle; + } + + /** + * <code>getExponent</code> gets the spot exponent of this light. + * + * @see #setExponent(float) for more info + * @return the spot exponent of this light. + */ + public float getExponent() { + return _exponent; + } + + /** + * <code>setExponent</code> sets the spot exponent of this light. This value represents how focused the light beam + * is. + * + * @param exponent + * the spot exponent of this light. Should be between 0-128 + */ + public void setExponent(final float exponent) { + if (exponent < 0f || exponent > 128f) { + throw new Ardor3dException("invalid exponent. Exponent must be between 0 and 128"); + } + _exponent = exponent; + } + + /** + * <code>getType</code> returns the type of this light (Type.Spot). + * + * @see com.ardor3d.light.Light#getType() + */ + @Override + public Type getType() { + return Type.Spot; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z)); + capsule.write(_angle, "angle", 0); + capsule.write(_exponent, "exponent", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z))); + _angle = capsule.readFloat("angle", 0); + _exponent = capsule.readFloat("exponent", 0); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java new file mode 100644 index 0000000..f7a8c2e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java @@ -0,0 +1,291 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.nio.IntBuffer; +import java.util.EnumMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.framework.Scene; +import com.ardor3d.image.Texture; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; + +public abstract class AbstractFBOTextureRenderer implements TextureRenderer { + private static final Logger logger = Logger.getLogger(AbstractFBOTextureRenderer.class.getName()); + + /** List of states that override any set states on a spatial if not null. */ + protected final EnumMap<RenderState.StateType, RenderState> _enforcedStates = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + protected final Camera _camera = new Camera(1, 1); + + protected final ColorRGBA _backgroundColor = new ColorRGBA(1, 1, 1, 1); + + protected int _active; + + protected int _fboID = 0, _depthRBID = 0; + protected int _msfboID = 0, _msdepthRBID = 0, _mscolorRBID = 0; + protected int _width = 0, _height = 0, _samples = 0, _depthBits = 0; + + protected IntBuffer _attachBuffer = null; + protected boolean _usingDepthRB = false; + protected final boolean _supportsDepthTexture; + protected final boolean _supportsMultisample; + protected boolean _neededClip; + + protected final Renderer _parentRenderer; + + public AbstractFBOTextureRenderer(final int width, final int height, final int depthBits, final int samples, + final Renderer parentRenderer, final ContextCapabilities caps) { + _parentRenderer = parentRenderer; + _samples = Math.min(samples, caps.getMaxFBOSamples()); + _depthBits = depthBits; + _supportsDepthTexture = caps.isDepthTextureSupported(); + _supportsMultisample = caps.getMaxFBOSamples() != 0; + + int w = width; + int h = height; + if (!caps.isNonPowerOfTwoTextureSupported()) { + // Check if we have non-power of two sizes. If so, find the smallest power of two size that is greater than + // the provided size. + if (!MathUtils.isPowerOfTwo(w)) { + int newWidth = 2; + do { + newWidth <<= 1; + + } while (newWidth < w); + w = newWidth; + } + + if (!MathUtils.isPowerOfTwo(h)) { + int newHeight = 2; + do { + newHeight <<= 1; + + } while (newHeight < h); + h = newHeight; + } + } + + logger.fine("Creating FBO sized: " + w + " x " + h); + + _width = w; + _height = h; + + _camera.resize(_width, _height); + _camera.setFrustum(1.0f, 1000.0f, -0.50f, 0.50f, 0.50f, -0.50f); + final Vector3 loc = new Vector3(0.0f, 0.0f, 0.0f); + final Vector3 left = new Vector3(-1.0f, 0.0f, 0.0f); + final Vector3 up = new Vector3(0.0f, 1.0f, 0.0f); + final Vector3 dir = new Vector3(0.0f, 0f, -1.0f); + _camera.setFrame(loc, left, up, dir); + } + + /** + * <code>getCamera</code> retrieves the camera this renderer is using. + * + * @return the camera this renderer is using. + */ + public Camera getCamera() { + return _camera; + } + + public void setBackgroundColor(final ReadOnlyColorRGBA c) { + _backgroundColor.set(c); + } + + public ReadOnlyColorRGBA getBackgroundColor() { + return _backgroundColor; + } + + public void render(final Spatial toDraw, final Texture tex, final int clear) { + try { + ContextManager.getCurrentContext().pushFBOTextureRenderer(this); + + setupForSingleTexDraw(tex); + + if (_samples > 0 && _supportsMultisample) { + setMSFBO(); + } + + switchCameraIn(clear); + doDraw(toDraw); + switchCameraOut(); + + if (_samples > 0 && _supportsMultisample) { + blitMSFBO(); + } + + takedownForSingleTexDraw(tex); + + ContextManager.getCurrentContext().popFBOTextureRenderer(); + } catch (final Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "render(Spatial, Texture, boolean)", "Exception", e); + } + } + + public void render(final Scene toDraw, final Texture tex, final int clear) { + try { + ContextManager.getCurrentContext().pushFBOTextureRenderer(this); + + setupForSingleTexDraw(tex); + + if (_samples > 0 && _supportsMultisample) { + setMSFBO(); + } + + switchCameraIn(clear); + doDraw(toDraw); + switchCameraOut(); + + if (_samples > 0 && _supportsMultisample) { + blitMSFBO(); + } + + takedownForSingleTexDraw(tex); + + ContextManager.getCurrentContext().popFBOTextureRenderer(); + } catch (final Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "render(Spatial, Texture, boolean)", "Exception", e); + } + } + + public void render(final List<? extends Spatial> toDraw, final Texture tex, final int clear) { + try { + ContextManager.getCurrentContext().pushFBOTextureRenderer(this); + + setupForSingleTexDraw(tex); + + if (_samples > 0 && _supportsMultisample) { + setMSFBO(); + } + + switchCameraIn(clear); + doDraw(toDraw); + switchCameraOut(); + + if (_samples > 0 && _supportsMultisample) { + blitMSFBO(); + } + + takedownForSingleTexDraw(tex); + + ContextManager.getCurrentContext().popFBOTextureRenderer(); + } catch (final Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "render(List<Spatial>, Texture, boolean)", + "Exception", e); + } + } + + protected abstract void activate(); + + protected abstract void setupForSingleTexDraw(Texture tex); + + protected abstract void takedownForSingleTexDraw(Texture tex); + + protected abstract void setMSFBO(); + + protected abstract void blitMSFBO(); + + protected abstract void deactivate(); + + private Camera _oldCamera; + + protected void switchCameraIn(final int clear) { + // grab non-rtt settings + _oldCamera = Camera.getCurrentCamera(); + + // swap to rtt settings + _parentRenderer.getQueue().pushBuckets(); + + // clear the scene + if (clear != 0) { + clearBuffers(clear); + } + + getCamera().update(); + getCamera().apply(_parentRenderer); + } + + protected abstract void clearBuffers(int clear); + + protected void switchCameraOut() { + _parentRenderer.flushFrame(false); + + // reset previous camera + _oldCamera.update(); + _oldCamera.apply(_parentRenderer); + + // back to the non rtt settings + _parentRenderer.getQueue().popBuckets(); + } + + protected void doDraw(final Spatial spat) { + // Override parent's last frustum test to avoid accidental incorrect cull + if (spat.getParent() != null) { + spat.getParent().setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); + } + + // do rtt scene render + spat.onDraw(_parentRenderer); + } + + protected void doDraw(final List<? extends Spatial> toDraw) { + for (int x = 0, max = toDraw.size(); x < max; x++) { + final Spatial spat = toDraw.get(x); + doDraw(spat); + } + } + + protected void doDraw(final Scene toDraw) { + toDraw.renderUnto(_parentRenderer); + } + + public int getWidth() { + return _width; + } + + public int getHeight() { + return _height; + } + + public Renderer getParentRenderer() { + return _parentRenderer; + } + + public void setMultipleTargets(final boolean multi) { + // ignore. Does not matter to FBO. + } + + public void enforceState(final RenderState state) { + _enforcedStates.put(state.getType(), state); + } + + public void enforceStates(final EnumMap<StateType, RenderState> states) { + _enforcedStates.putAll(states); + } + + public void clearEnforcedState(final StateType type) { + _enforcedStates.remove(type); + } + + public void clearEnforcedStates() { + _enforcedStates.clear(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java new file mode 100644 index 0000000..a428526 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.util.EnumMap; +import java.util.List; +import java.util.logging.Logger; + +import com.ardor3d.framework.DisplaySettings; +import com.ardor3d.framework.Scene; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; + +public abstract class AbstractPbufferTextureRenderer implements TextureRenderer { + private static final Logger logger = Logger.getLogger(AbstractPbufferTextureRenderer.class.getName()); + + /** List of states that override any set states on a spatial if not null. */ + protected final EnumMap<RenderState.StateType, RenderState> _enforcedStates = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + protected final Camera _camera = new Camera(1, 1); + + protected final ColorRGBA _backgroundColor = new ColorRGBA(1, 1, 1, 1); + protected boolean _bgColorDirty = true; + + protected boolean _useDirectRender = false; + + protected int _active; + + protected int _width = 0, _height = 0; + + protected final Renderer _parentRenderer; + protected final DisplaySettings _settings; + + public AbstractPbufferTextureRenderer(final DisplaySettings settings, final Renderer parentRenderer, + final ContextCapabilities caps) { + _parentRenderer = parentRenderer; + _settings = settings; + + int width = settings.getWidth(); + int height = settings.getHeight(); + if (!caps.isNonPowerOfTwoTextureSupported()) { + // Check if we have non-power of two sizes. If so, find the smallest power of two size that is greater than + // the provided size. + if (!MathUtils.isPowerOfTwo(width)) { + int newWidth = 2; + do { + newWidth <<= 1; + + } while (newWidth < width); + width = newWidth; + } + + if (!MathUtils.isPowerOfTwo(height)) { + int newHeight = 2; + do { + newHeight <<= 1; + + } while (newHeight < height); + height = newHeight; + } + } + + _width = width; + _height = height; + + logger.fine("Created Pbuffer sized: " + _width + " x " + _height); + + _camera.resize(_width, _height); + _camera.setFrustum(1.0f, 1000.0f, -0.50f, 0.50f, 0.50f, -0.50f); + final Vector3 loc = new Vector3(0.0f, 0.0f, 0.0f); + final Vector3 left = new Vector3(-1.0f, 0.0f, 0.0f); + final Vector3 up = new Vector3(0.0f, 1.0f, 0.0f); + final Vector3 dir = new Vector3(0.0f, 0f, -1.0f); + _camera.setFrame(loc, left, up, dir); + } + + protected RenderContext _oldContext; + + protected void switchCameraIn(final int clear) { + // Note: no need for storing and replacing old camera since pbuffer is a separate context. + + // swap to rtt settings + _parentRenderer.getQueue().pushBuckets(); + + // clear the scene + if (clear != 0) { + clearBuffers(clear); + } + + getCamera().update(); + getCamera().apply(_parentRenderer); + } + + protected abstract void clearBuffers(int clear); + + protected void switchCameraOut() { + _parentRenderer.flushFrame(false); + // back to the non rtt settings + _parentRenderer.getQueue().popBuckets(); + } + + protected void doDraw(final Spatial spat) { + // Override parent's last frustum test to avoid accidental incorrect cull + if (spat.getParent() != null) { + spat.getParent().setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); + } + + // do rtt scene render + spat.onDraw(_parentRenderer); + } + + protected void doDraw(final List<? extends Spatial> toDraw) { + for (int x = 0, max = toDraw.size(); x < max; x++) { + final Spatial spat = toDraw.get(x); + doDraw(spat); + } + } + + protected void doDraw(final Scene toDraw) { + toDraw.renderUnto(_parentRenderer); + } + + /** + * <code>getCamera</code> retrieves the camera this renderer is using. + * + * @return the camera this renderer is using. + */ + public Camera getCamera() { + return _camera; + } + + public void setBackgroundColor(final ReadOnlyColorRGBA c) { + _backgroundColor.set(c); + _bgColorDirty = true; + } + + public ReadOnlyColorRGBA getBackgroundColor() { + return _backgroundColor; + } + + public int getWidth() { + return _width; + } + + public int getHeight() { + return _height; + } + + public void enforceState(final RenderState state) { + _enforcedStates.put(state.getType(), state); + } + + public void enforceStates(final EnumMap<StateType, RenderState> states) { + _enforcedStates.putAll(states); + } + + public void clearEnforcedState(final StateType type) { + _enforcedStates.remove(type); + } + + public void clearEnforcedStates() { + _enforcedStates.clear(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java new file mode 100644 index 0000000..6384842 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.queue.RenderQueue; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.renderer.state.record.RendererRecord; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.util.Constants; +import com.ardor3d.util.stat.StatCollector; +import com.ardor3d.util.stat.StatType; + +/** + * Provides some common base level method implementations for Renderers. + */ +public abstract class AbstractRenderer implements Renderer { + // clear color + protected final ColorRGBA _backgroundColor = new ColorRGBA(ColorRGBA.BLACK); + + protected boolean _processingQueue; + + protected RenderQueue _queue = new RenderQueue(); + + protected boolean _inOrthoMode; + + protected int _stencilClearValue; + + protected RenderLogic renderLogic; + + /** List of default rendering states for this specific renderer type */ + protected final EnumMap<RenderState.StateType, RenderState> defaultStateList = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + public AbstractRenderer() { + for (final RenderState.StateType type : RenderState.StateType.values()) { + final RenderState state = RenderState.createState(type); + state.setEnabled(false); + defaultStateList.put(type, state); + } + } + + public boolean isInOrthoMode() { + return _inOrthoMode; + } + + public ReadOnlyColorRGBA getBackgroundColor() { + return _backgroundColor; + } + + public RenderQueue getQueue() { + return _queue; + } + + public boolean isProcessingQueue() { + return _processingQueue; + } + + public void applyState(final StateType type, final RenderState state) { + if (Constants.stats) { + StatCollector.startStat(StatType.STAT_STATES_TIMER); + } + + final RenderState tempState = getProperRenderState(type, state); + final RenderContext context = ContextManager.getCurrentContext(); + if (!RenderState._quickCompare.contains(type) || tempState.needsRefresh() + || tempState != context.getCurrentState(type)) { + doApplyState(tempState); + tempState.setNeedsRefresh(false); + } + + if (Constants.stats) { + StatCollector.endStat(StatType.STAT_STATES_TIMER); + } + } + + protected abstract void doApplyState(RenderState state); + + protected void addStats(final IndexMode indexMode, final int vertCount) { + final int primCount = IndexMode.getPrimitiveCount(indexMode, vertCount); + switch (indexMode) { + case Triangles: + case TriangleFan: + case TriangleStrip: + StatCollector.addStat(StatType.STAT_TRIANGLE_COUNT, primCount); + break; + case Lines: + case LineLoop: + case LineStrip: + StatCollector.addStat(StatType.STAT_LINE_COUNT, primCount); + break; + case Points: + StatCollector.addStat(StatType.STAT_POINT_COUNT, primCount); + break; + case Quads: + case QuadStrip: + StatCollector.addStat(StatType.STAT_QUAD_COUNT, primCount); + break; + } + } + + protected int getTotalInterleavedSize(final RenderContext context, final FloatBufferData vertexCoords, + final FloatBufferData normalCoords, final FloatBufferData colorCoords, + final List<FloatBufferData> textureCoords) { + final ContextCapabilities caps = context.getCapabilities(); + + int bufferSizeBytes = 0; + if (normalCoords != null) { + bufferSizeBytes += normalCoords.getBufferLimit() * 4; + } + if (colorCoords != null) { + bufferSizeBytes += colorCoords.getBufferLimit() * 4; + } + if (textureCoords != null) { + final TextureState ts = (TextureState) context.getCurrentState(RenderState.StateType.Texture); + if (ts != null) { + final int max = caps.isMultitextureSupported() ? Math.min(caps.getNumberOfFragmentTexCoordUnits(), + TextureState.MAX_TEXTURES) : 1; + boolean exists; + for (int i = 0; i < max; i++) { + exists = i < textureCoords.size() && textureCoords.get(i) != null + && i <= ts.getMaxTextureIndexUsed(); + + if (!exists) { + continue; + } + + final FloatBufferData textureBufferData = textureCoords.get(i); + if (textureBufferData != null) { + bufferSizeBytes += textureBufferData.getBufferLimit() * 4; + } + } + } + } + if (vertexCoords != null) { + bufferSizeBytes += vertexCoords.getBufferLimit() * 4; + } + + return bufferSizeBytes; + } + + public int getStencilClearValue() { + return _stencilClearValue; + } + + public void setStencilClearValue(final int stencilClearValue) { + _stencilClearValue = stencilClearValue; + } + + public boolean isClipTestEnabled() { + final RenderContext context = ContextManager.getCurrentContext(); + final RendererRecord record = context.getRendererRecord(); + return record.isClippingTestEnabled(); + } + + public RenderState getProperRenderState(final StateType type, final RenderState current) { + final RenderContext context = ContextManager.getCurrentContext(); + + // first look up in enforced states + final RenderState state = context.hasEnforcedStates() ? context.getEnforcedState(type) : null; + + // Not there? Use the state we received + if (state == null) { + if (current != null) { + return current; + } else { + return defaultStateList.get(type); + } + } else { + return state; + } + } + + public void setRenderLogic(final RenderLogic renderLogic) { + this.renderLogic = renderLogic; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java new file mode 100644 index 0000000..f281735 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java @@ -0,0 +1,1634 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.nio.FloatBuffer; +import java.util.Arrays; +import java.util.logging.Logger; + +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix4; +import com.ardor3d.math.Plane; +import com.ardor3d.math.Ray3; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.Vector4; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * This class represents a view into a 3d scene and how that view should map to a 2D rendering surface. + */ +public class Camera implements Savable, Externalizable { + + private static final long serialVersionUID = 1L; + + private static final Logger _logger = Logger.getLogger(Camera.class.getName()); + + public enum FrustumIntersect { + /** + * Object being compared to the frustum is completely outside of the frustum. + */ + Outside, + + /** + * Object being compared to the frustum is completely inside of the frustum. + */ + Inside, + + /** + * Object being compared to the frustum intersects one of the frustum planes and is thus both inside and outside + * of the frustum. + */ + Intersects; + } + + // planes of the frustum + /** + * LEFT_PLANE represents the left plane of the camera frustum. + */ + public static final int LEFT_PLANE = 0; + + /** + * RIGHT_PLANE represents the right plane of the camera frustum. + */ + public static final int RIGHT_PLANE = 1; + + /** + * BOTTOM_PLANE represents the bottom plane of the camera frustum. + */ + public static final int BOTTOM_PLANE = 2; + + /** + * TOP_PLANE represents the top plane of the camera frustum. + */ + public static final int TOP_PLANE = 3; + + /** + * FAR_PLANE represents the far plane of the camera frustum. + */ + public static final int FAR_PLANE = 4; + + /** + * NEAR_PLANE represents the near plane of the camera frustum. + */ + public static final int NEAR_PLANE = 5; + + /** + * FRUSTUM_PLANES represents the number of planes of the camera frustum. + */ + public static final int FRUSTUM_PLANES = 6; + + /** + * MAX_WORLD_PLANES holds the maximum planes allowed by the system. + */ + public static final int MAX_WORLD_PLANES = 32; + + // the location and orientation of the camera. + /** + * Camera's location + */ + protected final Vector3 _location = new Vector3(); + + /** + * Direction of camera's 'left' + */ + protected final Vector3 _left = new Vector3(); + + /** + * Direction of 'up' for camera. + */ + protected final Vector3 _up = new Vector3(); + + /** + * Direction the camera is facing. + */ + protected final Vector3 _direction = new Vector3(); + + /** + * The near range for mapping depth values from normalized device coordinates to window coordinates. + */ + protected double _depthRangeNear; + + /** + * The far range for mapping depth values from normalized device coordinates to window coordinates. + */ + protected double _depthRangeFar; + + /** + * Distance from camera to near frustum plane. + */ + protected double _frustumNear; + + /** + * Distance from camera to far frustum plane. + */ + protected double _frustumFar; + + /** + * Distance from camera to left frustum plane. + */ + protected double _frustumLeft; + + /** + * Distance from camera to right frustum plane. + */ + protected double _frustumRight; + + /** + * Distance from camera to top frustum plane. + */ + protected double _frustumTop; + + /** + * Distance from camera to bottom frustum plane. + */ + protected double _frustumBottom; + + /** + * Convenience store for fovY. Only set during setFrustumPerspective and never used. Retrieve by getFovY(). Default + * is NaN. + */ + protected double _fovY = Double.NaN; + + // Temporary values computed in onFrustumChange that are needed if a + // call is made to onFrameChange. + protected double _coeffLeft[]; + protected double _coeffRight[]; + protected double _coeffBottom[]; + protected double _coeffTop[]; + + /** Number of camera planes used by this camera. Default is 6. */ + protected int _planeQuantity; + + // view port coordinates + /** + * Percent value on display where horizontal viewing starts for this camera. Default is 0. + */ + protected double _viewPortLeft; + + /** + * Percent value on display where horizontal viewing ends for this camera. Default is 1. + */ + protected double _viewPortRight; + + /** + * Percent value on display where vertical viewing ends for this camera. Default is 1. + */ + protected double _viewPortTop; + + /** + * Percent value on display where vertical viewing begins for this camera. Default is 0. + */ + protected double _viewPortBottom; + + /** + * Array holding the planes that this camera will check for culling. + */ + protected Plane[] _worldPlane; + + protected final FloatBuffer _matrixBuffer = BufferUtils.createFloatBuffer(16); + + /** + * Computation vector used in various operations. + */ + protected final Vector3 _tempVector = new Vector3(); + + /** + * Projection mode used by the camera. + */ + public enum ProjectionMode { + Perspective, Parallel, Custom + } + + /** + * Current projection mode. + */ + private ProjectionMode _projectionMode = ProjectionMode.Perspective; + + private boolean _updateMVMatrix = true; + private boolean _updatePMatrix = true; + private boolean _updateMVPMatrix = true; + private boolean _updateInverseMVPMatrix = true; + + // NB: These matrices are column-major. + protected final Matrix4 _modelView = new Matrix4(); + protected final Matrix4 _projection = new Matrix4(); + private final Matrix4 _modelViewProjection = new Matrix4(); + private final Matrix4 _modelViewProjectionInverse = new Matrix4(); + + protected boolean _depthRangeDirty; + protected boolean _frustumDirty; + protected boolean _viewPortDirty; + protected boolean _frameDirty; + + /** + * A mask value set during contains() that allows fast culling of a Node's children. + */ + private int _planeState; + + protected int _width; + protected int _height; + + /** + * Construct a new Camera with a width and height of 100. + */ + public Camera() { + this(100, 100); + } + + /** + * Construct a new Camera with the given frame width and height. + * + * @param width + * @param height + */ + public Camera(final int width, final int height) { + _width = width; + _height = height; + + _location.set(0, 0, 0); + _left.set(-1, 0, 0); + _up.set(0, 1, 0); + _direction.set(0, 0, -1); + + _depthRangeNear = 0.0; + _depthRangeFar = 1.0; + _depthRangeDirty = true; + + _frustumNear = 1.0; + _frustumFar = 2.0; + _frustumLeft = -0.5; + _frustumRight = 0.5; + _frustumTop = 0.5; + _frustumBottom = -0.5; + + _coeffLeft = new double[2]; + _coeffRight = new double[2]; + _coeffBottom = new double[2]; + _coeffTop = new double[2]; + + _viewPortLeft = 0.0; + _viewPortRight = 1.0; + _viewPortTop = 1.0; + _viewPortBottom = 0.0; + + _planeQuantity = 6; + + _worldPlane = new Plane[MAX_WORLD_PLANES]; + for (int i = 0; i < MAX_WORLD_PLANES; i++) { + _worldPlane[i] = new Plane(); + } + + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + + _logger.fine("Camera created. W: " + width + " H: " + height); + } + + /** + * Construct a new camera, using the given source camera's values. + * + * @param source + */ + public Camera(final Camera source) { + + _coeffLeft = new double[2]; + _coeffRight = new double[2]; + _coeffBottom = new double[2]; + _coeffTop = new double[2]; + + _worldPlane = new Plane[MAX_WORLD_PLANES]; + for (int i = 0; i < MAX_WORLD_PLANES; i++) { + _worldPlane[i] = new Plane(); + } + + set(source); + + _logger.fine("Camera created. W: " + getWidth() + " H: " + getHeight()); + } + + /** + * Copy the source camera's fields to this camera + * + * @param source + * the camera to copy from + */ + public void set(final Camera source) { + _width = source.getWidth(); + _height = source.getHeight(); + + _location.set(source.getLocation()); + _left.set(source.getLeft()); + _up.set(source.getUp()); + _direction.set(source.getDirection()); + _fovY = source.getFovY(); + + _depthRangeNear = source.getDepthRangeNear(); + _depthRangeFar = source.getDepthRangeFar(); + _depthRangeDirty = true; + + _frustumNear = source.getFrustumNear(); + _frustumFar = source.getFrustumFar(); + _frustumLeft = source.getFrustumLeft(); + _frustumRight = source.getFrustumRight(); + _frustumTop = source.getFrustumTop(); + _frustumBottom = source.getFrustumBottom(); + + _viewPortLeft = source.getViewPortLeft(); + _viewPortRight = source.getViewPortRight(); + _viewPortTop = source.getViewPortTop(); + _viewPortBottom = source.getViewPortBottom(); + + _planeQuantity = 6; + + _projectionMode = source.getProjectionMode(); + + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + } + + public double getDepthRangeFar() { + return _depthRangeFar; + } + + /** + * @param depthRangeNear + * the far clipping plane for window coordinates. Should be in the range [0, 1]. Default is 1. + */ + public void setDepthRangeFar(final double depthRangeFar) { + _depthRangeFar = depthRangeFar; + _depthRangeDirty = true; + } + + public double getDepthRangeNear() { + return _depthRangeNear; + } + + /** + * @param depthRangeNear + * the near clipping plane for window coordinates. Should be in the range [0, 1]. Default is 0. + */ + public void setDepthRangeNear(final double depthRangeNear) { + _depthRangeNear = depthRangeNear; + _depthRangeDirty = true; + } + + /** + * @return the value of the bottom frustum plane. + */ + public double getFrustumBottom() { + return _frustumBottom; + } + + /** + * @param frustumBottom + * the new value of the bottom frustum plane. + */ + public void setFrustumBottom(final double frustumBottom) { + _frustumBottom = frustumBottom; + onFrustumChange(); + } + + /** + * = * @return the value of the far frustum plane. + */ + public double getFrustumFar() { + return _frustumFar; + } + + /** + * @param frustumFar + * the new value of the far frustum plane. + */ + public void setFrustumFar(final double frustumFar) { + _frustumFar = frustumFar; + onFrustumChange(); + } + + /** + * @return the value of the left frustum plane. + */ + public double getFrustumLeft() { + return _frustumLeft; + } + + /** + * @param frustumLeft + * the new value of the left frustum plane. + */ + public void setFrustumLeft(final double frustumLeft) { + _frustumLeft = frustumLeft; + onFrustumChange(); + } + + /** + * @return the value of the near frustum plane. + */ + public double getFrustumNear() { + return _frustumNear; + } + + /** + * @param frustumNear + * the new value of the near frustum plane. + */ + public void setFrustumNear(final double frustumNear) { + _frustumNear = frustumNear; + onFrustumChange(); + } + + /** + * @return frustumRight the value of the right frustum plane. + */ + public double getFrustumRight() { + return _frustumRight; + } + + /** + * @param frustumRight + * the new value of the right frustum plane. + */ + public void setFrustumRight(final double frustumRight) { + _frustumRight = frustumRight; + onFrustumChange(); + } + + /** + * @return the value of the top frustum plane. + */ + public double getFrustumTop() { + return _frustumTop; + } + + /** + * @param frustumTop + * the new value of the top frustum plane. + */ + public void setFrustumTop(final double frustumTop) { + _frustumTop = frustumTop; + onFrustumChange(); + } + + /** + * @return the current position of the camera. + */ + public ReadOnlyVector3 getLocation() { + return _location; + } + + /** + * @return the current direction the camera is facing. + */ + public ReadOnlyVector3 getDirection() { + return _direction; + } + + /** + * @return the left axis of the camera. + */ + public ReadOnlyVector3 getLeft() { + return _left; + } + + /** + * @return the up axis of the camera. + */ + public ReadOnlyVector3 getUp() { + return _up; + } + + /** + * @param location + * the new location or position of the camera. + */ + public void setLocation(final ReadOnlyVector3 location) { + _location.set(location); + onFrameChange(); + } + + /** + * @param location + * the new location or position of the camera. + */ + public void setLocation(final double x, final double y, final double z) { + _location.set(x, y, z); + onFrameChange(); + } + + /** + * Sets the new direction this camera is facing. This does not change left or up axes, so make sure those vectors + * are properly set as well. + * + * @param direction + * the new direction this camera is facing. + */ + public void setDirection(final ReadOnlyVector3 direction) { + _direction.set(direction); + onFrameChange(); + } + + /** + * Sets the new left axis of this camera. This does not change direction or up axis, so make sure those vectors are + * properly set as well. + * + * @param left + * the new left axis of this camera. + */ + public void setLeft(final ReadOnlyVector3 left) { + _left.set(left); + onFrameChange(); + } + + /** + * Sets the new up axis of this camera. This does not change direction or left axis, so make sure those vectors are + * properly set as well. + * + * @param up + * the new up axis of this camera. + */ + public void setUp(final ReadOnlyVector3 up) { + _up.set(up); + onFrameChange(); + } + + /** + * @param left + * the new left axis of the camera. + * @param up + * the new up axis of the camera. + * @param direction + * the new direction the camera is facing. + */ + public void setAxes(final ReadOnlyVector3 left, final ReadOnlyVector3 up, final ReadOnlyVector3 direction) { + _left.set(left); + _up.set(up); + _direction.set(direction); + onFrameChange(); + } + + /** + * Sets our left, up and direction values from the given rotation matrix. + * + * @param axes + * the matrix that defines the orientation of the camera. + */ + public void setAxes(final ReadOnlyMatrix3 axes) { + axes.getColumn(0, _left); + axes.getColumn(1, _up); + axes.getColumn(2, _direction); + onFrameChange(); + } + + /** + * Ensure our up, left and direction are unit-length vectors. + */ + public void normalize() { + _left.normalizeLocal(); + _up.normalizeLocal(); + _direction.normalizeLocal(); + onFrameChange(); + } + + /** + * Sets the frustum plane values of this camera using the given values. + * + * @param near + * @param far + * @param left + * @param right + * @param top + * @param bottom + */ + public void setFrustum(final double near, final double far, final double left, final double right, + final double top, final double bottom) { + _frustumNear = near; + _frustumFar = far; + _frustumLeft = left; + _frustumRight = right; + _frustumTop = top; + _frustumBottom = bottom; + onFrustumChange(); + } + + /** + * Sets the frustum plane values of this camera using those of a given source camera + * + * @param source + * a source camera. + */ + public void setFrustum(final Camera source) { + _frustumNear = source.getFrustumNear(); + _frustumFar = source.getFrustumFar(); + _frustumLeft = source.getFrustumLeft(); + _frustumRight = source.getFrustumRight(); + _frustumTop = source.getFrustumTop(); + _frustumBottom = source.getFrustumBottom(); + onFrustumChange(); + } + + /** + * Sets the frustum plane values of this camera using the given perspective values. + * + * @param fovY + * the full angle of view on the Y axis, in degrees. + * @param aspect + * the aspect ratio of our view (generally in [0,1]). Often this is canvas width / canvas height. + * @param near + * our near plane value + * @param far + * our far plane value + */ + public void setFrustumPerspective(final double fovY, final double aspect, final double near, final double far) { + if (Double.isNaN(aspect) || Double.isInfinite(aspect)) { + // ignore. + _logger.warning("Invalid aspect given to setFrustumPerspective: " + aspect); + return; + } + _fovY = fovY; + final double h = Math.tan(_fovY * MathUtils.DEG_TO_RAD * .5) * near; + final double w = h * aspect; + _frustumLeft = -w; + _frustumRight = w; + _frustumBottom = -h; + _frustumTop = h; + _frustumNear = near; + _frustumFar = far; + onFrustumChange(); + } + + /** + * Accessor for the fovY value. Note that this value is only present if setFrustumPerspective was previously called. + * + * @return the fovY value + */ + public double getFovY() { + return _fovY; + } + + /** + * Sets the axes and location of the camera. Similar to + * {@link #setAxes(ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3)}, but sets camera location as well. + * + * @param location + * @param left + * @param up + * @param direction + */ + public void setFrame(final ReadOnlyVector3 location, final ReadOnlyVector3 left, final ReadOnlyVector3 up, + final ReadOnlyVector3 direction) { + _left.set(left); + _up.set(up); + _direction.set(direction); + _location.set(location); + onFrameChange(); + } + + /** + * Sets the axes and location of the camera. Similar to {@link #setAxes(ReadOnlyMatrix3)}, but sets camera location + * as well. + * + * @param location + * the point position of the camera. + * @param axes + * the orientation of the camera. + */ + public void setFrame(final ReadOnlyVector3 location, final ReadOnlyMatrix3 axes) { + axes.getColumn(0, _left); + axes.getColumn(1, _up); + axes.getColumn(2, _direction); + _location.set(location); + onFrameChange(); + } + + /** + * Sets the axes and location of the camera using those of a given source camera + * + * @param source + * a source camera. + */ + public void setFrame(final Camera source) { + _left.set(source.getLeft()); + _up.set(source.getUp()); + _direction.set(source.getDirection()); + _location.set(source.getLocation()); + onFrameChange(); + } + + /** + * A convenience method for auto-setting the frame based on a world position the user desires the camera to look at. + * It points the camera towards the given position using the difference between that position and the current camera + * location as a direction vector and the general worldUpVector to compute up and left camera vectors. + * + * @param pos + * where to look at in terms of world coordinates + * @param worldUpVector + * a normalized vector indicating the up direction of the world. (often {@link Vector3#UNIT_Y} or + * {@link Vector3#UNIT_Z}) + */ + public void lookAt(final ReadOnlyVector3 pos, final ReadOnlyVector3 worldUpVector) { + lookAt(pos.getX(), pos.getY(), pos.getZ(), worldUpVector); + } + + /** + * A convenience method for auto-setting the frame based on a world position the user desires the camera to look at. + * It points the camera towards the given position using the difference between that position and the current camera + * location as a direction vector and the general worldUpVector to compute up and left camera vectors. + * + * @param x + * where to look at in terms of world coordinates (x) + * @param y + * where to look at in terms of world coordinates (y) + * @param z + * where to look at in terms of world coordinates (z) + * @param worldUpVector + * a normalized vector indicating the up direction of the world. (often {@link Vector3#UNIT_Y} or + * {@link Vector3#UNIT_Z}) + */ + public void lookAt(final double x, final double y, final double z, final ReadOnlyVector3 worldUpVector) { + final Vector3 newDirection = _tempVector; + newDirection.set(x, y, z).subtractLocal(_location).normalizeLocal(); + + // check to see if we haven't really updated camera -- no need to call sets. + if (newDirection.equals(_direction)) { + return; + } + _direction.set(newDirection); + + _up.set(worldUpVector).normalizeLocal(); + if (_up.equals(Vector3.ZERO)) { + _up.set(Vector3.UNIT_Y); + } + _left.set(_up).crossLocal(_direction).normalizeLocal(); + if (_left.equals(Vector3.ZERO)) { + if (_direction.getX() != 0.0) { + _left.set(_direction.getY(), -_direction.getX(), 0); + } else { + _left.set(0, _direction.getZ(), -_direction.getY()); + } + } + _up.set(_direction).crossLocal(_left).normalizeLocal(); + onFrameChange(); + } + + /** + * Forces all aspect of the camera to be updated from internal values, and sets all dirty flags to true so that the + * next apply() call will fully set this camera to the render context. + */ + public void update() { + _depthRangeDirty = true; + onFrustumChange(); + onViewPortChange(); + onFrameChange(); + } + + /** + * @return an internally used bitmask describing what frustum planes have been examined for culling thus far. + */ + public int getPlaneState() { + return _planeState; + } + + /** + * @param planeState + * a new value for planeState. + * @see #getPlaneState() + */ + public void setPlaneState(final int planeState) { + _planeState = planeState; + } + + /** + * @return the left boundary of the viewport + */ + public double getViewPortLeft() { + return _viewPortLeft; + } + + /** + * @param left + * the new left boundary of the viewport + */ + public void setViewPortLeft(final double left) { + _viewPortLeft = left; + onViewPortChange(); + } + + /** + * @return the right boundary of the viewport + */ + public double getViewPortRight() { + return _viewPortRight; + } + + /** + * @param right + * the new right boundary of the viewport + */ + public void setViewPortRight(final double right) { + _viewPortRight = right; + onViewPortChange(); + } + + /** + * @return the top boundary of the viewport + */ + public double getViewPortTop() { + return _viewPortTop; + } + + /** + * @param top + * the new top boundary of the viewport + */ + public void setViewPortTop(final double top) { + _viewPortTop = top; + onViewPortChange(); + } + + /** + * @return the bottom boundary of the viewport + */ + public double getViewPortBottom() { + return _viewPortBottom; + } + + /** + * @param bottom + * the new bottom boundary of the viewport + */ + public void setViewPortBottom(final double bottom) { + _viewPortBottom = bottom; + onViewPortChange(); + } + + /** + * Sets the boundaries of this camera's viewport to the given values + * + * @param left + * @param right + * @param bottom + * @param top + */ + public void setViewPort(final double left, final double right, final double bottom, final double top) { + setViewPortLeft(left); + setViewPortRight(right); + setViewPortBottom(bottom); + setViewPortTop(top); + } + + /** + * Checks a bounding volume against the planes of this camera's frustum and returns if it is completely inside of, + * outside of, or intersecting. + * + * @param bound + * the bound to check for culling + * @return intersection type + */ + public Camera.FrustumIntersect contains(final BoundingVolume bound) { + if (bound == null) { + return FrustumIntersect.Inside; + } + + int mask; + FrustumIntersect rVal = FrustumIntersect.Inside; + + for (int planeCounter = FRUSTUM_PLANES; planeCounter >= 0; planeCounter--) { + if (planeCounter == bound.getCheckPlane()) { + continue; // we have already checked this plane at first iteration + } + final int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter; + + mask = 1 << (planeId); + if ((_planeState & mask) == 0) { + switch (bound.whichSide(_worldPlane[planeId])) { + case Inside: + // object is outside of frustum + bound.setCheckPlane(planeId); + return FrustumIntersect.Outside; + case Outside: + // object is visible on *this* plane, so mark this plane + // so that we don't check it for sub nodes. + _planeState |= mask; + break; + case Neither: + rVal = FrustumIntersect.Intersects; + break; + } + } + } + + return rVal; + } + + /** + * Resizes this camera's view with the given width and height. + * + * @param width + * the view width + * @param height + * the view height + */ + public void resize(final int width, final int height) { + _width = width; + _height = height; + onViewPortChange(); + } + + /** + * Updates internal frustum coefficient values to reflect the current frustum plane values. + */ + public void onFrustumChange() { + if (getProjectionMode() == ProjectionMode.Perspective) { + final double nearSquared = _frustumNear * _frustumNear; + final double leftSquared = _frustumLeft * _frustumLeft; + final double rightSquared = _frustumRight * _frustumRight; + final double bottomSquared = _frustumBottom * _frustumBottom; + final double topSquared = _frustumTop * _frustumTop; + + double inverseLength = 1.0 / Math.sqrt(nearSquared + leftSquared); + _coeffLeft[0] = _frustumNear * inverseLength; + _coeffLeft[1] = -_frustumLeft * inverseLength; + + inverseLength = 1.0 / Math.sqrt(nearSquared + rightSquared); + _coeffRight[0] = -_frustumNear * inverseLength; + _coeffRight[1] = _frustumRight * inverseLength; + + inverseLength = 1.0 / Math.sqrt(nearSquared + bottomSquared); + _coeffBottom[0] = _frustumNear * inverseLength; + _coeffBottom[1] = -_frustumBottom * inverseLength; + + inverseLength = 1.0 / Math.sqrt(nearSquared + topSquared); + _coeffTop[0] = -_frustumNear * inverseLength; + _coeffTop[1] = _frustumTop * inverseLength; + } else if (getProjectionMode() == ProjectionMode.Parallel) { + if (_frustumRight > _frustumLeft) { + _coeffLeft[0] = -1; + _coeffLeft[1] = 0; + + _coeffRight[0] = 1; + _coeffRight[1] = 0; + } else { + _coeffLeft[0] = 1; + _coeffLeft[1] = 0; + + _coeffRight[0] = -1; + _coeffRight[1] = 0; + } + + if (_frustumTop > _frustumBottom) { + _coeffBottom[0] = -1; + _coeffBottom[1] = 0; + + _coeffTop[0] = 1; + _coeffTop[1] = 0; + } else { + _coeffBottom[0] = 1; + _coeffBottom[1] = 0; + + _coeffTop[0] = -1; + _coeffTop[1] = 0; + } + } + + _updatePMatrix = true; + _updateMVPMatrix = true; + _updateInverseMVPMatrix = true; + + _frustumDirty = true; + } + + /** + * Updates the values of the world planes associated with this camera. + */ + public void onFrameChange() { + final double dirDotLocation = _direction.dot(_location); + + final Vector3 planeNormal = Vector3.fetchTempInstance(); + + // left plane + planeNormal.setX(_left.getX() * _coeffLeft[0]); + planeNormal.setY(_left.getY() * _coeffLeft[0]); + planeNormal.setZ(_left.getZ() * _coeffLeft[0]); + planeNormal.addLocal(_direction.getX() * _coeffLeft[1], _direction.getY() * _coeffLeft[1], _direction.getZ() + * _coeffLeft[1]); + _worldPlane[LEFT_PLANE].setNormal(planeNormal); + _worldPlane[LEFT_PLANE].setConstant(_location.dot(planeNormal)); + + // right plane + planeNormal.setX(_left.getX() * _coeffRight[0]); + planeNormal.setY(_left.getY() * _coeffRight[0]); + planeNormal.setZ(_left.getZ() * _coeffRight[0]); + planeNormal.addLocal(_direction.getX() * _coeffRight[1], _direction.getY() * _coeffRight[1], _direction.getZ() + * _coeffRight[1]); + _worldPlane[RIGHT_PLANE].setNormal(planeNormal); + _worldPlane[RIGHT_PLANE].setConstant(_location.dot(planeNormal)); + + // bottom plane + planeNormal.setX(_up.getX() * _coeffBottom[0]); + planeNormal.setY(_up.getY() * _coeffBottom[0]); + planeNormal.setZ(_up.getZ() * _coeffBottom[0]); + planeNormal.addLocal(_direction.getX() * _coeffBottom[1], _direction.getY() * _coeffBottom[1], + _direction.getZ() * _coeffBottom[1]); + _worldPlane[BOTTOM_PLANE].setNormal(planeNormal); + _worldPlane[BOTTOM_PLANE].setConstant(_location.dot(planeNormal)); + + // top plane + planeNormal.setX(_up.getX() * _coeffTop[0]); + planeNormal.setY(_up.getY() * _coeffTop[0]); + planeNormal.setZ(_up.getZ() * _coeffTop[0]); + planeNormal.addLocal(_direction.getX() * _coeffTop[1], _direction.getY() * _coeffTop[1], _direction.getZ() + * _coeffTop[1]); + _worldPlane[TOP_PLANE].setNormal(planeNormal); + _worldPlane[TOP_PLANE].setConstant(_location.dot(planeNormal)); + + if (getProjectionMode() == ProjectionMode.Parallel) { + if (_frustumRight > _frustumLeft) { + _worldPlane[LEFT_PLANE].setConstant(_worldPlane[LEFT_PLANE].getConstant() + _frustumLeft); + _worldPlane[RIGHT_PLANE].setConstant(_worldPlane[RIGHT_PLANE].getConstant() - _frustumRight); + } else { + _worldPlane[LEFT_PLANE].setConstant(_worldPlane[LEFT_PLANE].getConstant() - _frustumLeft); + _worldPlane[RIGHT_PLANE].setConstant(_worldPlane[RIGHT_PLANE].getConstant() + _frustumRight); + } + + if (_frustumBottom > _frustumTop) { + _worldPlane[TOP_PLANE].setConstant(_worldPlane[TOP_PLANE].getConstant() + _frustumTop); + _worldPlane[BOTTOM_PLANE].setConstant(_worldPlane[BOTTOM_PLANE].getConstant() - _frustumBottom); + } else { + _worldPlane[TOP_PLANE].setConstant(_worldPlane[TOP_PLANE].getConstant() - _frustumTop); + _worldPlane[BOTTOM_PLANE].setConstant(_worldPlane[BOTTOM_PLANE].getConstant() + _frustumBottom); + } + } + + // far plane + planeNormal.set(_direction).negateLocal(); + _worldPlane[FAR_PLANE].setNormal(planeNormal); + _worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + _frustumFar)); + + // near plane + _worldPlane[NEAR_PLANE].setNormal(_direction); + _worldPlane[NEAR_PLANE].setConstant(dirDotLocation + _frustumNear); + + Vector3.releaseTempInstance(planeNormal); + + _updateMVMatrix = true; + _updateMVPMatrix = true; + _updateInverseMVPMatrix = true; + + _frameDirty = true; + } + + /** + * Updates the value of our projection matrix. + */ + protected void updateProjectionMatrix() { + if (getProjectionMode() == ProjectionMode.Parallel) { + _projection.setIdentity(); + _projection.setM00(2.0 / (_frustumRight - _frustumLeft)); + _projection.setM11(2.0 / (_frustumTop - _frustumBottom)); + _projection.setM22(-2.0 / (_frustumFar - _frustumNear)); + _projection.setM30(-(_frustumRight + _frustumLeft) / (_frustumRight - _frustumLeft)); + _projection.setM31(-(_frustumTop + _frustumBottom) / (_frustumTop - _frustumBottom)); + _projection.setM32(-(_frustumFar + _frustumNear) / (_frustumFar - _frustumNear)); + } else if (getProjectionMode() == ProjectionMode.Perspective) { + _projection.setIdentity(); + _projection.setM00((2.0 * _frustumNear) / (_frustumRight - _frustumLeft)); + _projection.setM11((2.0 * _frustumNear) / (_frustumTop - _frustumBottom)); + _projection.setM20((_frustumRight + _frustumLeft) / (_frustumRight - _frustumLeft)); + _projection.setM21((_frustumTop + _frustumBottom) / (_frustumTop - _frustumBottom)); + _projection.setM22(-(_frustumFar + _frustumNear) / (_frustumFar - _frustumNear)); + _projection.setM23(-1.0); + _projection.setM32(-(2.0 * _frustumFar * _frustumNear) / (_frustumFar - _frustumNear)); + _projection.setM33(-0.0); + } + + _updatePMatrix = false; + } + + public void setProjectionMatrix(final ReadOnlyMatrix4 projection) { + _projection.set(projection); + _frustumDirty = true; + } + + /** + * @return this camera's 4x4 projection matrix. + */ + public ReadOnlyMatrix4 getProjectionMatrix() { + checkProjection(); + + return _projection; + } + + /** + * Updates the value of our model view matrix. + */ + protected void updateModelViewMatrix() { + _modelView.setIdentity(); + _modelView.setM00(-_left.getX()); + _modelView.setM10(-_left.getY()); + _modelView.setM20(-_left.getZ()); + + _modelView.setM01(_up.getX()); + _modelView.setM11(_up.getY()); + _modelView.setM21(_up.getZ()); + + _modelView.setM02(-_direction.getX()); + _modelView.setM12(-_direction.getY()); + _modelView.setM22(-_direction.getZ()); + + _modelView.setM30(_left.dot(_location)); + _modelView.setM31(-_up.dot(_location)); + _modelView.setM32(_direction.dot(_location)); + } + + /** + * @return this camera's 4x4 model view matrix. + */ + public ReadOnlyMatrix4 getModelViewMatrix() { + checkModelView(); + + return _modelView; + } + + /** + * @return this camera's 4x4 model view X projection matrix. + */ + public ReadOnlyMatrix4 getModelViewProjectionMatrix() { + checkModelViewProjection(); + + return _modelViewProjection; + } + + /** + * @return the inverse of this camera's 4x4 model view X projection matrix. + */ + public ReadOnlyMatrix4 getModelViewProjectionInverseMatrix() { + checkInverseModelViewProjection(); + + return _modelViewProjectionInverse; + } + + /** + * Calculate a Pick Ray using the given screen position at the near plane of this camera and the camera's position + * in space. + * + * @param screenPosition + * the x, y position on the near space to pass the ray through. + * @param flipVertical + * if true, we'll flip the screenPosition on the y axis. This is useful when you are dealing with + * non-opengl coordinate systems. + * @param store + * the Ray to store the result in. If false, a new Ray is created and returned. + * @return the resulting Ray. + */ + public Ray3 getPickRay(final ReadOnlyVector2 screenPosition, final boolean flipVertical, final Ray3 store) { + final Vector2 pos = Vector2.fetchTempInstance().set(screenPosition); + if (flipVertical) { + pos.setY(getHeight() - screenPosition.getY()); + } + + Ray3 result = store; + if (result == null) { + result = new Ray3(); + } + final Vector3 origin = Vector3.fetchTempInstance(); + final Vector3 direction = Vector3.fetchTempInstance(); + getWorldCoordinates(pos, 0, origin); + getWorldCoordinates(pos, 0.3, direction).subtractLocal(origin).normalizeLocal(); + result.setOrigin(origin); + result.setDirection(direction); + Vector2.releaseTempInstance(pos); + Vector3.releaseTempInstance(origin); + Vector3.releaseTempInstance(direction); + return result; + } + + /** + * Converts a local x,y screen position and depth value to world coordinates based on the current settings of this + * camera. + * + * @param screenPosition + * the x,y coordinates of the screen position + * @param zDepth + * the depth into the camera view to take our point. 0 indicates the near plane of the camera and 1 + * indicates the far plane. + * @return a new vector containing the world coordinates. + */ + public Vector3 getWorldCoordinates(final ReadOnlyVector2 screenPosition, final double zDepth) { + return getWorldCoordinates(screenPosition, zDepth, null); + } + + /** + * Converts a local x,y screen position and depth value to world coordinates based on the current settings of this + * camera. + * + * @param screenPosition + * the x,y coordinates of the screen position + * @param zDepth + * the depth into the camera view to take our point. 0 indicates the near plane of the camera and 1 + * indicates the far plane. + * @param store + * Use to avoid object creation. if not null, the results are stored in the given vector and returned. + * Otherwise, a new vector is created. + * @return a vector containing the world coordinates. + */ + public Vector3 getWorldCoordinates(final ReadOnlyVector2 screenPosition, final double zDepth, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + checkInverseModelViewProjection(); + final Vector4 position = Vector4.fetchTempInstance(); + position.set((screenPosition.getX() / getWidth() - _viewPortLeft) / (_viewPortRight - _viewPortLeft) * 2 - 1, + (screenPosition.getY() / getHeight() - _viewPortBottom) / (_viewPortTop - _viewPortBottom) * 2 - 1, + zDepth * 2 - 1, 1); + _modelViewProjectionInverse.applyPre(position, position); + position.multiplyLocal(1.0 / position.getW()); + store.setX(position.getX()); + store.setY(position.getY()); + store.setZ(position.getZ()); + + Vector4.releaseTempInstance(position); + return store; + } + + /** + * Converts a position in world coordinate space to an x,y screen position and depth value using the current + * settings of this camera. + * + * @param worldPos + * the position in space to retrieve screen coordinates for. + * @return a new vector containing the screen coordinates as x and y and the distance as a percent between near and + * far planes. + */ + public Vector3 getScreenCoordinates(final ReadOnlyVector3 worldPos) { + return getScreenCoordinates(worldPos, null); + } + + /** + * Converts a position in world coordinate space to an x,y screen position and depth value using the current + * settings of this camera. + * + * @param worldPos + * the position in space to retrieve screen coordinates for. + * @param store + * Use to avoid object creation. if not null, the results are stored in the given vector and returned. + * Otherwise, a new vector is created. + * @return a vector containing the screen coordinates as x and y and the distance as a percent between near and far + * planes. + */ + public Vector3 getScreenCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) { + store = getNormalizedDeviceCoordinates(worldPosition, store); + + store.setX(((store.getX() + 1) * (_viewPortRight - _viewPortLeft) / 2) * getWidth()); + store.setY(((store.getY() + 1) * (_viewPortTop - _viewPortBottom) / 2) * getHeight()); + store.setZ((store.getZ() + 1) / 2); + + return store; + } + + /** + * Converts a position in world coordinate space to a x,y,z frustum position using the current settings of this + * camera. + * + * @param worldPos + * the position in space to retrieve frustum coordinates for. + * @return a new vector containing the x,y,z frustum position + */ + public Vector3 getFrustumCoordinates(final ReadOnlyVector3 worldPos) { + return getFrustumCoordinates(worldPos, null); + } + + /** + * Converts a position in world coordinate space to a x,y,z frustum position using the current settings of this + * camera. + * + * @param worldPos + * the position in space to retrieve frustum coordinates for. + * @param store + * Use to avoid object creation. if not null, the results are stored in the given vector and returned. + * Otherwise, a new vector is created. + * @return a vector containing the x,y,z frustum position + */ + public Vector3 getFrustumCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) { + store = getNormalizedDeviceCoordinates(worldPosition, store); + + store.setX(((store.getX() + 1) * (_frustumRight - _frustumLeft) / 2) + _frustumLeft); + store.setY(((store.getY() + 1) * (_frustumTop - _frustumBottom) / 2) + _frustumBottom); + store.setZ(((store.getZ() + 1) * (_frustumFar - _frustumNear) / 2) + _frustumNear); + + return store; + } + + public Vector3 getNormalizedDeviceCoordinates(final ReadOnlyVector3 worldPos) { + return getNormalizedDeviceCoordinates(worldPos, null); + } + + public Vector3 getNormalizedDeviceCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + checkModelViewProjection(); + final Vector4 position = Vector4.fetchTempInstance(); + position.set(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), 1); + _modelViewProjection.applyPre(position, position); + position.multiplyLocal(1.0 / position.getW()); + store.setX(position.getX()); + store.setY(position.getY()); + store.setZ(position.getZ()); + Vector4.releaseTempInstance(position); + + return store; + } + + /** + * update modelView if necessary. + */ + private void checkModelView() { + if (_updateMVMatrix) { + updateModelViewMatrix(); + _updateMVMatrix = false; + } + } + + /** + * update projection if necessary. + */ + private void checkProjection() { + if (_updatePMatrix) { + updateProjectionMatrix(); + _updatePMatrix = false; + } + } + + /** + * update modelViewProjection if necessary. + */ + private void checkModelViewProjection() { + if (_updateMVPMatrix) { + checkModelView(); + checkProjection(); + _modelViewProjection.set(getModelViewMatrix()).multiplyLocal(getProjectionMatrix()); + _updateMVPMatrix = false; + } + } + + /** + * update inverse modelViewProjection if necessary. + */ + private void checkInverseModelViewProjection() { + if (_updateInverseMVPMatrix) { + checkModelViewProjection(); + _modelViewProjection.invert(_modelViewProjectionInverse); + _updateInverseMVPMatrix = false; + } + } + + /** + * @return the height of the display. + */ + public int getHeight() { + return _height; + } + + public ProjectionMode getProjectionMode() { + return _projectionMode; + } + + public void setProjectionMode(final ProjectionMode projectionMode) { + _projectionMode = projectionMode; + } + + /** + * @return the width of the display. + */ + public int getWidth() { + return _width; + } + + /** + * Apply this camera's values to the given Renderer. Only values determined to be dirty (via updates, setters, etc.) + * will be applied. This method must be run in the same thread as a valid OpenGL context. + * + * @param renderer + * the Renderer to use. + */ + public void apply(final Renderer renderer) { + ContextManager.getCurrentContext().setCurrentCamera(this); + if (_depthRangeDirty) { + renderer.setDepthRange(_depthRangeNear, _depthRangeFar); + _depthRangeDirty = false; + } + if (_frustumDirty) { + applyProjectionMatrix(renderer); + _frustumDirty = false; + } + if (_viewPortDirty) { + applyViewport(renderer); + _viewPortDirty = false; + } + if (_frameDirty) { + applyModelViewMatrix(renderer); + _frameDirty = false; + } + } + + /** + * Mark view port dirty. + */ + protected void onViewPortChange() { + _viewPortDirty = true; + } + + /** + * Apply the camera's projection matrix using the given Renderer. + * + * @param renderer + * the Renderer to use. + */ + protected void applyProjectionMatrix(final Renderer renderer) { + _matrixBuffer.rewind(); + getProjectionMatrix().toFloatBuffer(_matrixBuffer); + _matrixBuffer.rewind(); + renderer.setProjectionMatrix(_matrixBuffer); + } + + /** + * Apply the camera's viewport using the given Renderer. + * + * @param renderer + * the Renderer to use. + */ + protected void applyViewport(final Renderer renderer) { + final int x = (int) (_viewPortLeft * _width); + final int y = (int) (_viewPortBottom * _height); + final int w = (int) ((_viewPortRight - _viewPortLeft) * _width); + final int h = (int) ((_viewPortTop - _viewPortBottom) * _height); + renderer.setViewport(x, y, w, h); + } + + /** + * Apply the camera's modelview matrix using the given Renderer. + * + * @param renderer + * the Renderer to use. + */ + protected void applyModelViewMatrix(final Renderer renderer) { + _matrixBuffer.rewind(); + getModelViewMatrix().toFloatBuffer(_matrixBuffer); + _matrixBuffer.rewind(); + renderer.setModelViewMatrix(_matrixBuffer); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_location, "location", new Vector3(Vector3.ZERO)); + capsule.write(_left, "left", new Vector3(Vector3.UNIT_X)); + capsule.write(_up, "up", new Vector3(Vector3.UNIT_Y)); + capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z)); + capsule.write(_frustumNear, "frustumNear", 1); + capsule.write(_frustumFar, "frustumFar", 2); + capsule.write(_frustumLeft, "frustumLeft", -0.5); + capsule.write(_frustumRight, "frustumRight", 0.5); + capsule.write(_frustumTop, "frustumTop", 0.5); + capsule.write(_frustumBottom, "frustumBottom", -0.5); + capsule.write(_coeffLeft, "coeffLeft", new double[2]); + capsule.write(_coeffRight, "coeffRight", new double[2]); + capsule.write(_coeffBottom, "coeffBottom", new double[2]); + capsule.write(_coeffTop, "coeffTop", new double[2]); + capsule.write(_planeQuantity, "planeQuantity", 6); + capsule.write(_viewPortLeft, "viewPortLeft", 0); + capsule.write(_viewPortRight, "viewPortRight", 1); + capsule.write(_viewPortTop, "viewPortTop", 1); + capsule.write(_viewPortBottom, "viewPortBottom", 0); + capsule.write(_width, "width", 0); + capsule.write(_height, "height", 0); + capsule.write(_depthRangeNear, "depthRangeNear", 0.0); + capsule.write(_depthRangeFar, "depthRangeFar", 1.0); + } + + public void read(final InputCapsule capsule) throws IOException { + _location.set((Vector3) capsule.readSavable("location", new Vector3(Vector3.ZERO))); + _left.set((Vector3) capsule.readSavable("left", new Vector3(Vector3.UNIT_X))); + _up.set((Vector3) capsule.readSavable("up", new Vector3(Vector3.UNIT_Y))); + _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z))); + _frustumNear = capsule.readDouble("frustumNear", 1); + _frustumFar = capsule.readDouble("frustumFar", 2); + _frustumLeft = capsule.readDouble("frustumLeft", -0.5); + _frustumRight = capsule.readDouble("frustumRight", 0.5); + _frustumTop = capsule.readDouble("frustumTop", 0.5); + _frustumBottom = capsule.readDouble("frustumBottom", -0.5); + _coeffLeft = capsule.readDoubleArray("coeffLeft", new double[2]); + _coeffRight = capsule.readDoubleArray("coeffRight", new double[2]); + _coeffBottom = capsule.readDoubleArray("coeffBottom", new double[2]); + _coeffTop = capsule.readDoubleArray("coeffTop", new double[2]); + _planeQuantity = capsule.readInt("planeQuantity", 6); + _viewPortLeft = capsule.readDouble("viewPortLeft", 0); + _viewPortRight = capsule.readDouble("viewPortRight", 1); + _viewPortTop = capsule.readDouble("viewPortTop", 1); + _viewPortBottom = capsule.readDouble("viewPortBottom", 0); + _width = capsule.readInt("width", 0); + _height = capsule.readInt("height", 0); + _depthRangeNear = capsule.readDouble("depthRangeNear", 0.0); + _depthRangeFar = capsule.readDouble("depthRangeFar", 1.0); + } + + public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { + _location.set((Vector3) in.readObject()); + _left.set((Vector3) in.readObject()); + _up.set((Vector3) in.readObject()); + _direction.set((Vector3) in.readObject()); + _frustumNear = in.readDouble(); + _frustumFar = in.readDouble(); + _frustumLeft = in.readDouble(); + _frustumRight = in.readDouble(); + _frustumTop = in.readDouble(); + _frustumBottom = in.readDouble(); + _coeffLeft = (double[]) in.readObject(); + _coeffRight = (double[]) in.readObject(); + _coeffBottom = (double[]) in.readObject(); + _coeffTop = (double[]) in.readObject(); + _planeQuantity = in.readInt(); + _viewPortLeft = in.readDouble(); + _viewPortRight = in.readDouble(); + _viewPortTop = in.readDouble(); + _viewPortBottom = in.readDouble(); + _width = in.readInt(); + _height = in.readInt(); + _depthRangeNear = in.readDouble(); + _depthRangeFar = in.readDouble(); + } + + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeObject(_location); + out.writeObject(_left); + out.writeObject(_up); + out.writeObject(_direction); + out.writeDouble(_frustumNear); + out.writeDouble(_frustumFar); + out.writeDouble(_frustumLeft); + out.writeDouble(_frustumRight); + out.writeDouble(_frustumTop); + out.writeDouble(_frustumBottom); + out.writeObject(_coeffLeft); + out.writeObject(_coeffRight); + out.writeObject(_coeffBottom); + out.writeObject(_coeffTop); + out.writeInt(_planeQuantity); + out.writeDouble(_viewPortLeft); + out.writeDouble(_viewPortRight); + out.writeDouble(_viewPortTop); + out.writeDouble(_viewPortBottom); + out.writeInt(_width); + out.writeInt(_height); + out.writeDouble(_depthRangeNear); + out.writeDouble(_depthRangeFar); + } + + @Override + public String toString() { + return "com.ardor3d.renderer.Camera: loc - " + Arrays.toString(getLocation().toArray(null)) + " dir - " + + Arrays.toString(getDirection().toArray(null)) + " up - " + Arrays.toString(getUp().toArray(null)) + + " left - " + Arrays.toString(getLeft().toArray(null)); + } + + public Class<? extends Camera> getClassTag() { + return getClass(); + } + + /** + * Convenience method for retrieving the Camera set on the current RenderContext. Similar to + * ContextManager.getCurrentContext().getCurrentCamera() but with null checks for current context. + * + * @return the Camera on the current RenderContext. + */ + public static Camera getCurrentCamera() { + if (ContextManager.getCurrentContext() == null) { + return null; + } + return ContextManager.getCurrentContext().getCurrentCamera(); + } + + public boolean isFrameDirty() { + return _frameDirty; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java new file mode 100644 index 0000000..f2ac794 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java @@ -0,0 +1,662 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +public class ContextCapabilities { + + protected boolean _supportsVBO = false; + protected boolean _supportsGL1_2 = false; + protected boolean _supportsMultisample = false; + + protected boolean _supportsConstantColor = false; + protected boolean _supportsEq = false; + protected boolean _supportsSeparateEq = false; + protected boolean _supportsSeparateFunc = false; + protected boolean _supportsMinMax = false; + protected boolean _supportsSubtract = false; + + protected boolean _supportsFogCoords = false; + + protected boolean _supportsPointSprites = false; + protected boolean _supportsPointParameters = false; + + protected boolean _supportsTextureLodBias = false; + protected float _maxTextureLodBias = 0f; + + protected boolean _supportsFragmentProgram = false; + protected boolean _supportsVertexProgram = false; + + protected boolean _glslSupported = false; + protected boolean _geometryShader4Supported = false; + protected boolean _geometryInstancingSupported = false; + protected boolean _tessellationShadersSupported = false; + protected int _maxGLSLVertexAttribs; + + protected boolean _pbufferSupported = false; + + protected boolean _fboSupported = false; + protected int _maxFBOColorAttachments = 1; + protected int _maxFBOSamples = 0; + + protected int _maxUserClipPlanes = 0; + + protected boolean _twoSidedStencilSupport = false; + protected boolean _stencilWrapSupport = false; + + /** The total number of available auxiliary draw buffers. */ + protected int _numAuxDrawBuffers = -1; + + /** The total number of supported texture units. */ + protected int _numTotalTexUnits = -1; + + /** The number of texture units availible for fixed functionality */ + protected int _numFixedTexUnits = -1; + + /** The number of texture units availible to vertex shader */ + protected int _numVertexTexUnits = -1; + + /** The number of texture units availible to fragment shader */ + protected int _numFragmentTexUnits = -1; + + /** The number of texture coordinate sets available */ + protected int _numFragmentTexCoordUnits = -1; + + /** The max side of a texture supported. */ + protected int _maxTextureSize = -1; + + protected float _maxAnisotropic = -1.0f; + + /** True if multitexturing is supported. */ + protected boolean _supportsMultiTexture = false; + + /** True if floating point textures are supported. */ + protected boolean _supportsFloatTextures = false; + + /** True if integer textures are supported. */ + protected boolean _supportsIntegerTextures = false; + + /** True if Red and RedGreen only textures are supported. */ + protected boolean _supportsOneTwoComponentTextures = false; + + /** True if combine dot3 is supported. */ + protected boolean _supportsEnvDot3 = false; + + /** True if combine dot3 is supported. */ + protected boolean _supportsEnvCombine = false; + + /** True if anisofiltering is supported. */ + protected boolean _supportsAniso = false; + + /** True if non pow 2 texture sizes are supported. */ + protected boolean _supportsNonPowerTwo = false; + + /** True if rectangular textures are supported (vs. only square textures) */ + protected boolean _supportsRectangular = false; + + /** True if S3TC compression is supported. */ + protected boolean _supportsS3TCCompression = false; + + /** True if LATC compression is supported. */ + protected boolean _supportsLATCCompression = false; + + /** True if generic (non-specific) texture compression is supported. */ + protected boolean _supportsGenericCompression = false; + + /** True if Texture3D is supported. */ + protected boolean _supportsTexture3D = false; + + /** True if TextureCubeMap is supported. */ + protected boolean _supportsTextureCubeMap = false; + + /** True if non-GLU mipmap generation (part of FBO) is supported. */ + protected boolean _automaticMipMaps = false; + + /** True if depth textures are supported */ + protected boolean _supportsDepthTexture = false; + + /** True if shadow mapping supported */ + protected boolean _supportsShadow = false; + + protected boolean _supportsMirroredRepeat; + protected boolean _supportsMirrorClamp; + protected boolean _supportsMirrorBorderClamp; + protected boolean _supportsMirrorEdgeClamp; + protected boolean _supportsBorderClamp; + protected boolean _supportsEdgeClamp; + + protected String _displayVendor; + protected String _displayRenderer; + protected String _displayVersion; + protected String _shadingLanguageVersion; + + public ContextCapabilities() {} + + public ContextCapabilities(final ContextCapabilities source) { + _automaticMipMaps = source._automaticMipMaps; + _displayRenderer = source._displayRenderer; + _displayVendor = source._displayVendor; + _displayVersion = source._displayVersion; + _fboSupported = source._fboSupported; + _geometryShader4Supported = source._geometryShader4Supported; + _glslSupported = source._glslSupported; + _geometryInstancingSupported = source._geometryInstancingSupported; + _tessellationShadersSupported = source._tessellationShadersSupported; + _maxAnisotropic = source._maxAnisotropic; + _maxFBOColorAttachments = source._maxFBOColorAttachments; + _maxFBOSamples = source._maxFBOSamples; + _maxGLSLVertexAttribs = source._maxGLSLVertexAttribs; + _maxTextureLodBias = source._maxTextureLodBias; + _maxTextureSize = source._maxTextureSize; + _maxUserClipPlanes = source._maxUserClipPlanes; + _numAuxDrawBuffers = source._numAuxDrawBuffers; + _numFixedTexUnits = source._numFixedTexUnits; + _numFragmentTexCoordUnits = source._numFragmentTexCoordUnits; + _numFragmentTexUnits = source._numFragmentTexUnits; + _numTotalTexUnits = source._numTotalTexUnits; + _numVertexTexUnits = source._numVertexTexUnits; + _pbufferSupported = source._pbufferSupported; + _shadingLanguageVersion = source._shadingLanguageVersion; + _stencilWrapSupport = source._stencilWrapSupport; + _supportsAniso = source._supportsAniso; + _supportsBorderClamp = source._supportsBorderClamp; + _supportsConstantColor = source._supportsConstantColor; + _supportsDepthTexture = source._supportsDepthTexture; + _supportsEdgeClamp = source._supportsEdgeClamp; + _supportsEnvCombine = source._supportsEnvCombine; + _supportsEnvDot3 = source._supportsEnvDot3; + _supportsEq = source._supportsEq; + _supportsFogCoords = source._supportsFogCoords; + _supportsFragmentProgram = source._supportsFragmentProgram; + _supportsGenericCompression = source._supportsGenericCompression; + _supportsGL1_2 = source._supportsGL1_2; + _supportsLATCCompression = source._supportsLATCCompression; + _supportsMinMax = source._supportsMinMax; + _supportsMirrorBorderClamp = source._supportsMirrorBorderClamp; + _supportsMirrorClamp = source._supportsMirrorClamp; + _supportsMirrorEdgeClamp = source._supportsMirrorEdgeClamp; + _supportsMirroredRepeat = source._supportsMirroredRepeat; + _supportsMultisample = source._supportsMultisample; + _supportsMultiTexture = source._supportsMultiTexture; + _supportsNonPowerTwo = source._supportsNonPowerTwo; + _supportsPointParameters = source._supportsPointParameters; + _supportsPointSprites = source._supportsPointSprites; + _supportsRectangular = source._supportsRectangular; + _supportsS3TCCompression = source._supportsS3TCCompression; + _supportsSeparateEq = source._supportsSeparateEq; + _supportsSeparateFunc = source._supportsSeparateFunc; + _supportsShadow = source._supportsShadow; + _supportsSubtract = source._supportsSubtract; + _supportsTexture3D = source._supportsTexture3D; + _supportsTextureCubeMap = source._supportsTextureCubeMap; + _supportsTextureLodBias = source._supportsTextureLodBias; + _supportsVBO = source._supportsVBO; + _supportsVertexProgram = source._supportsVertexProgram; + _twoSidedStencilSupport = source._twoSidedStencilSupport; + _supportsFloatTextures = source._supportsFloatTextures; + _supportsIntegerTextures = source._supportsIntegerTextures; + _supportsOneTwoComponentTextures = source._supportsOneTwoComponentTextures; + } + + /** + * @return true if we support Vertex Buffer Objects. + */ + public boolean isVBOSupported() { + return _supportsVBO; + } + + /** + * @return true if we support all of OpenGL 1.2 + */ + public boolean isOpenGL1_2Supported() { + return _supportsGL1_2; + } + + /** + * @return true if we support multisampling (antialiasing) + */ + public boolean isMultisampleSupported() { + return _supportsMultisample; + } + + /** + * @return true if we support setting a constant color for use with *Constant* type BlendFunctions. + */ + public boolean isConstantBlendColorSupported() { + return _supportsConstantColor; + } + + /** + * @return true if we support setting rgb and alpha functions separately for source and destination. + */ + public boolean isSeparateBlendFunctionsSupported() { + return _supportsSeparateFunc; + } + + /** + * @return true if we support setting the blend equation + */ + public boolean isBlendEquationSupported() { + return _supportsEq; + } + + /** + * @return true if we support setting the blend equation for alpha and rgb separately + */ + public boolean isSeparateBlendEquationsSupported() { + return _supportsSeparateEq; + } + + /** + * @return true if we support using min and max blend equations + */ + public boolean isMinMaxBlendEquationsSupported() { + return _supportsMinMax; + } + + /** + * @return true if we support using subtract blend equations + */ + public boolean isSubtractBlendEquationsSupported() { + return _supportsSubtract; + } + + /** + * @return true if mesh based fog coords are supported + */ + public boolean isFogCoordinatesSupported() { + return _supportsFogCoords; + } + + /** + * @return true if point sprites are supported + */ + public boolean isPointSpritesSupported() { + return _supportsPointSprites; + } + + /** + * @return true if point parameters are supported + */ + public boolean isPointParametersSupported() { + return _supportsPointParameters; + } + + /** + * @return true if texture lod bias is supported + */ + public boolean isTextureLodBiasSupported() { + return _supportsTextureLodBias; + } + + /** + * @return the max amount of texture lod bias that this context supports. + */ + public float getMaxLodBias() { + return _maxTextureLodBias; + } + + /** + * @return <code>true</code> if the GLSL is supported and GL_ARB_tessellation_shader is supported by current + * graphics configuration + */ + public boolean isTessellationShadersSupported() { + return _tessellationShadersSupported; + } + + /** + * @return true if the ARB_shader_objects extension is supported by current graphics configuration. + */ + public boolean isGLSLSupported() { + return _glslSupported; + } + + /** + * @return true if the GLSL is supported and GL_EXT_draw_instanced is supported by the current graphics + * configuration configuration. + */ + public boolean isGeometryInstancingSupported() { + return _geometryInstancingSupported; + } + + /** + * @return true if the GLSL is supported and ARB_geometry_shader4 extension is supported by current graphics + * configuration. + */ + public boolean isGeometryShader4Supported() { + return _geometryShader4Supported; + } + + /** + * @return true if the ARB_shader_objects extension is supported by current graphics configuration. + */ + public boolean isPbufferSupported() { + return _pbufferSupported; + } + + /** + * @return true if the EXT_framebuffer_object extension is supported by current graphics configuration. + */ + public boolean isFBOSupported() { + return _fboSupported; + } + + /** + * @return true if we can handle doing separate stencil operations for front and back facing polys in a single pass. + */ + public boolean isTwoSidedStencilSupported() { + return _twoSidedStencilSupport; + } + + /** + * @return true if we can handle wrapping increment/decrement operations. + */ + public boolean isStencilWrapSupported() { + return _stencilWrapSupport; + } + + /** + * <code>getNumberOfAuxiliaryDrawBuffers</code> returns the total number of available auxiliary draw buffers this + * context supports. + * + * @return the number of available auxiliary draw buffers supported by the context. + */ + public int getNumberOfAuxiliaryDrawBuffers() { + return _numAuxDrawBuffers; + } + + /** + * <code>getTotalNumberOfUnits</code> returns the total number of texture units this context supports. + * + * @return the total number of texture units supported by the context. + */ + public int getTotalNumberOfUnits() { + return _numTotalTexUnits; + } + + /** + * <code>getNumberOfFixedUnits</code> returns the number of texture units this context supports, for use in the + * fixed pipeline. + * + * @return the number units. + */ + public int getNumberOfFixedTextureUnits() { + return _numFixedTexUnits; + } + + /** + * <code>getNumberOfVertexUnits</code> returns the number of texture units available to a vertex shader that this + * context supports. + * + * @return the number of units. + */ + public int getNumberOfVertexUnits() { + return _numVertexTexUnits; + } + + /** + * <code>getNumberOfFragmentUnits</code> returns the number of texture units available to a fragment shader that + * this context supports. + * + * @return the number of units. + */ + public int getNumberOfFragmentTextureUnits() { + return _numFragmentTexUnits; + } + + /** + * <code>getNumberOfFragmentTexCoordUnits</code> returns the number of texture coordinate sets available that this + * context supports. + * + * @return the number of units. + */ + public int getNumberOfFragmentTexCoordUnits() { + return _numFragmentTexCoordUnits; + } + + /** + * @return the max size of texture (in terms of # pixels wide) that this context supports. + */ + public int getMaxTextureSize() { + return _maxTextureSize; + } + + /** + * <code>getNumberOfTotalUnits</code> returns the number of texture units this context supports. + * + * @return the number of units. + */ + public int getNumberOfTotalTextureUnits() { + return _numTotalTexUnits; + } + + /** + * <code>getMaxFBOColorAttachments</code> returns the MAX_COLOR_ATTACHMENTS for FBOs that this context supports. + * + * @return the number of buffers. + */ + public int getMaxFBOColorAttachments() { + return _maxFBOColorAttachments; + } + + /** + * Returns the maximum anisotropic filter. + * + * @return The maximum anisotropic filter. + */ + public float getMaxAnisotropic() { + return _maxAnisotropic; + } + + /** + * @return true if multi-texturing is supported in fixed function + */ + public boolean isMultitextureSupported() { + return _supportsMultiTexture; + } + + /** + * @return true if floating point textures are supported by this context. + */ + public boolean isFloatingPointTexturesSupported() { + return _supportsFloatTextures; + } + + /** + * @return true if integer textures are supported by this context. + */ + public boolean isIntegerTexturesSupported() { + return _supportsIntegerTextures; + } + + /** + * @return true if one- and two-component textures are supported by this context. + */ + public boolean isOneTwoComponentTexturesSupported() { + return _supportsOneTwoComponentTextures; + } + + /** + * @return true we support dot3 environment texture settings + */ + public boolean isEnvDot3TextureCombineSupported() { + return _supportsEnvDot3; + } + + /** + * @return true we support combine environment texture settings + */ + public boolean isEnvCombineSupported() { + return _supportsEnvCombine; + } + + /** + * Returns if S3TC compression is available for textures. + * + * @return true if S3TC is available. + */ + public boolean isS3TCSupported() { + return _supportsS3TCCompression; + } + + /** + * Returns if LATC compression is available for textures. + * + * @return true if LATC is available. + */ + public boolean isLATCSupported() { + return _supportsLATCCompression; + } + + /** + * Returns if generic (non-specific) compression is available for textures. + * + * @return true if available. + */ + public boolean isGenericTCSupported() { + return _supportsGenericCompression; + } + + /** + * Returns if Texture3D is available for textures. + * + * @return true if Texture3D is available. + */ + public boolean isTexture3DSupported() { + return _supportsTexture3D; + } + + /** + * Returns if TextureCubeMap is available for textures. + * + * @return true if TextureCubeMap is available. + */ + public boolean isTextureCubeMapSupported() { + return _supportsTextureCubeMap; + } + + /** + * Returns if AutomaticMipmap generation is available for textures. + * + * @return true if AutomaticMipmap generation is available. + */ + public boolean isAutomaticMipmapsSupported() { + return _automaticMipMaps; + } + + /** + * @return if Anisotropic texture filtering is supported + */ + public boolean isAnisoSupported() { + return _supportsAniso; + } + + /** + * @return true if non pow 2 texture sizes are supported + */ + public boolean isNonPowerOfTwoTextureSupported() { + return _supportsNonPowerTwo; + } + + /** + * @return true if rectangular texture sizes are supported (width != height) + */ + public boolean isRectangularTextureSupported() { + return _supportsRectangular; + } + + public boolean isFragmentProgramSupported() { + return _supportsFragmentProgram; + } + + public boolean isVertexProgramSupported() { + return _supportsVertexProgram; + } + + public int getMaxGLSLVertexAttributes() { + return _maxGLSLVertexAttribs; + } + + public boolean isDepthTextureSupported() { + return _supportsDepthTexture; + } + + public boolean isARBShadowSupported() { + return _supportsShadow; + } + + public boolean isTextureMirroredRepeatSupported() { + return _supportsMirroredRepeat; + } + + public boolean isTextureMirrorClampSupported() { + return _supportsMirrorClamp; + } + + public boolean isTextureMirrorEdgeClampSupported() { + return _supportsMirrorEdgeClamp; + } + + public boolean isTextureMirrorBorderClampSupported() { + return _supportsMirrorBorderClamp; + } + + public boolean isTextureBorderClampSupported() { + return _supportsBorderClamp; + } + + public boolean isTextureEdgeClampSupported() { + return _supportsEdgeClamp; + } + + public int getMaxFBOSamples() { + return _maxFBOSamples; + } + + public int getMaxUserClipPlanes() { + return _maxUserClipPlanes; + } + + /** + * Returns the vendor of the graphics adapter + * + * @return The vendor of the graphics adapter + */ + public String getDisplayVendor() { + return _displayVendor; + } + + /** + * Returns renderer details of the adapter + * + * @return The adapter details + */ + public String getDisplayRenderer() { + return _displayRenderer; + } + + /** + * Returns the version supported + * + * @return The version supported + */ + public String getDisplayVersion() { + return _displayVersion; + } + + /** + * Returns the supported shading language version. Needs OpenGL 2.0 support to query. + * + * @return The shading language version supported + */ + public String getShadingLanguageVersion() { + return _shadingLanguageVersion; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java new file mode 100644 index 0000000..839ac23 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +public interface ContextCleanListener { + + void cleanForContext(RenderContext renderContext); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java new file mode 100644 index 0000000..8f973d9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java @@ -0,0 +1,81 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+public class ContextManager {
+
+ protected static RenderContext currentContext = null;
+
+ private static List<ContextCleanListener> _cleanListeners = Lists.newArrayList();
+
+ protected static final Map<Object, RenderContext> contextStore = new MapMaker().weakKeys().makeMap();
+
+ /**
+ * @return a RenderContext object representing the current OpenGL context.
+ */
+ public static RenderContext getCurrentContext() {
+ return currentContext;
+ }
+
+ public static RenderContext switchContext(final Object contextKey) {
+ currentContext = contextStore.get(contextKey);
+ if (currentContext == null) {
+ throw new IllegalArgumentException("contextKey not found in context store.");
+ }
+ return currentContext;
+ }
+
+ public static void removeContext(final Object contextKey) {
+ contextStore.remove(contextKey);
+ }
+
+ public static void addContext(final Object contextKey, final RenderContext context) {
+ contextStore.put(contextKey, context);
+ }
+
+ public static RenderContext getContextForKey(final Object key) {
+ return contextStore.get(key);
+ }
+
+ /**
+ * Find the first context we manage that uses the given shared opengl context.
+ *
+ * @param glref
+ * @return
+ */
+ public static RenderContext getContextForRef(final Object glref) {
+ if (glref == null) {
+ return null;
+ }
+ for (final RenderContext context : contextStore.values()) {
+ if (glref.equals(context.getGlContextRep())) {
+ return context;
+ }
+ }
+ return null;
+ }
+
+ public static void fireCleanContextEvent(final RenderContext renderContext) {
+ for (final ContextCleanListener listener : _cleanListeners) {
+ listener.cleanForContext(renderContext);
+ }
+ }
+
+ public static void addContextCleanListener(final ContextCleanListener listener) {
+ _cleanListeners.add(listener);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java new file mode 100644 index 0000000..0dba3b5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +public enum DrawBufferTarget { + + /** + * No color buffers + */ + None, + + /** + * The front left color buffer + */ + FrontLeft, + + /** + * The front right color buffer + */ + FrontRight, + + /** + * The back left color buffer + */ + BackLeft, + + /** + * The back right color buffer + */ + BackRight, + + /** + * The front left and front right (if exists) color buffers. + */ + Front, + + /** + * The back left and front right (if exists) color buffers. + */ + Back, + + /** + * The front left and back left (if exists) color buffers. + */ + Left, + + /** + * The front right and back right (if exists) color buffers. + */ + Right, + + /** + * All of FrontLeft, FrontRight, BackLeft, BackRight, if exists. + */ + FrontAndBack, + + /** + * Auxiliary color buffer 0. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()} + * >= 1. + */ + Aux0, + + /** + * Auxiliary color buffer 1. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()} + * >= 2. + */ + Aux1, + + /** + * Auxiliary color buffer 2. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()} + * >= 3. + */ + Aux2, + + /** + * Auxiliary color buffer 3. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()} + * >= 4. + */ + Aux3; +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java new file mode 100644 index 0000000..b952d4a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java @@ -0,0 +1,122 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+public enum IndexMode {
+ // TRIMESH
+ /**
+ * Every three vertices referenced by the indexbuffer will be considered a stand-alone triangle.
+ */
+ Triangles(true),
+ /**
+ * The first three vertices referenced by the indexbuffer create a triangle, from there, every additional vertex is
+ * paired with the two preceding vertices to make a new triangle.
+ */
+ TriangleStrip(true),
+ /**
+ * The first three vertices (V0, V1, V2) referenced by the indexbuffer create a triangle, from there, every
+ * additional vertex is paired with the preceding vertex and the initial vertex (V0) to make a new triangle.
+ */
+ TriangleFan(true),
+
+ // QUADMESH
+ /**
+ * Every four vertices referenced by the indexbuffer will be considered a stand-alone quad.
+ */
+ Quads(true),
+ /**
+ * The first four vertices referenced by the indexbuffer create a triangle, from there, every two additional
+ * vertices are paired with the two preceding vertices to make a new quad.
+ */
+ QuadStrip(true),
+
+ // LINE
+ /**
+ * Every two vertices referenced by the indexbuffer will be considered a stand-alone line segment.
+ */
+ Lines(false),
+ /**
+ * The first two vertices referenced by the indexbuffer create a line, from there, every additional vertex is paired
+ * with the preceding vertex to make a new, connected line.
+ */
+ LineStrip(false),
+ /**
+ * Identical to <i>LineStrip</i> except the final indexed vertex is then connected back to the initial vertex to
+ * form a loop.
+ */
+ LineLoop(false),
+
+ // POINT
+ /**
+ * Identical to <i>Connected</i> except the final indexed vertex is then connected back to the initial vertex to
+ * form a loop.
+ */
+ Points(false);
+
+ private final boolean _hasPolygons;
+
+ private IndexMode(final boolean hasPolygons) {
+ _hasPolygons = hasPolygons;
+ }
+
+ public boolean hasPolygons() {
+ return _hasPolygons;
+ }
+
+ public int getVertexCount() {
+ switch (this) {
+ case Triangles:
+ case TriangleStrip:
+ case TriangleFan:
+ return 3;
+ case Quads:
+ case QuadStrip:
+ return 4;
+ case Lines:
+ case LineStrip:
+ case LineLoop:
+ return 2;
+ case Points:
+ return 1;
+ }
+ throw new IllegalArgumentException("Unhandled type: " + this);
+ }
+
+ /**
+ * @param indexMode
+ * @param size
+ * @return the number of primitives you would have if you connected an array of points of the given size using the
+ * given index mode.
+ */
+ public static int getPrimitiveCount(final IndexMode indexMode, final int size) {
+ switch (indexMode) {
+ case Triangles:
+ return size / 3;
+ case TriangleFan:
+ case TriangleStrip:
+ return size - 2;
+ case Quads:
+ return size / 4;
+ case QuadStrip:
+ return size / 2 - 1;
+ case Lines:
+ return size / 2;
+ case LineStrip:
+ return size - 1;
+ case LineLoop:
+ return size;
+ case Points:
+ return size;
+ }
+
+ throw new IllegalArgumentException("unimplemented index mode: " + indexMode);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java new file mode 100644 index 0000000..83f3392 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.util.EmptyStackException; +import java.util.EnumMap; +import java.util.Stack; + +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.renderer.state.record.LineRecord; +import com.ardor3d.renderer.state.record.RendererRecord; +import com.ardor3d.renderer.state.record.StateRecord; + +/** + * Represents the state of an individual context in OpenGL. + */ +public class RenderContext { + + /** List of states that override any set states on a spatial if not null. */ + protected final EnumMap<RenderState.StateType, RenderState> _enforcedStates = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + protected final Stack<EnumMap<StateType, RenderState>> _enforcedBackStack = new Stack<EnumMap<StateType, RenderState>>(); + + protected final Stack<AbstractFBOTextureRenderer> _textureRenderers = new Stack<AbstractFBOTextureRenderer>(); + + /** RenderStates a Spatial contains during rendering. */ + protected final EnumMap<RenderState.StateType, RenderState> _currentStates = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + protected final EnumMap<RenderState.StateType, StateRecord> _stateRecords = new EnumMap<RenderState.StateType, StateRecord>( + RenderState.StateType.class); + + protected final LineRecord _lineRecord = new LineRecord(); + protected final RendererRecord _rendererRecord = new RendererRecord(); + + /** Basically this object represents the sharable portion of a GL context... Textures, displayLists, etc. */ + protected final Object _glContextRep; + + protected final ContextCapabilities _capabilities; + + /** The object tied to this RenderContext, such as the Canvas, etc. */ + protected final Object _contextKey; + + protected Camera _currentCamera = null; + + public RenderContext(final Object key, final ContextCapabilities caps) { + this(key, caps, null); + } + + public RenderContext(final Object key, final ContextCapabilities caps, final RenderContext shared) { + _contextKey = key; + _capabilities = caps; + setupRecords(); + _glContextRep = (shared == null) ? new Object() : shared._glContextRep; + } + + protected void setupRecords() { + for (final RenderState.StateType type : RenderState.StateType.values()) { + _stateRecords.put(type, RenderState.createState(type).createStateRecord()); + } + } + + public void invalidateStates() { + for (final RenderState.StateType type : RenderState.StateType.values()) { + _stateRecords.get(type).invalidate(); + } + _lineRecord.invalidate(); + _rendererRecord.invalidate(); + + clearCurrentStates(); + } + + public ContextCapabilities getCapabilities() { + return _capabilities; + } + + public StateRecord getStateRecord(final RenderState.StateType type) { + return _stateRecords.get(type); + } + + public LineRecord getLineRecord() { + return _lineRecord; + } + + public RendererRecord getRendererRecord() { + return _rendererRecord; + } + + /** + * Enforce a particular state. In other words, the given state will override any state of the same type set on a + * scene object. Remember to clear the state when done enforcing. Very useful for multipass techniques where + * multiple sets of states need to be applied to a scenegraph drawn multiple times. + * + * @param state + * state to enforce + */ + public void enforceState(final RenderState state) { + _enforcedStates.put(state.getType(), state); + } + + /** + * Enforces the states referenced in the given EnumMap. + */ + public void enforceStates(final EnumMap<StateType, RenderState> states) { + _enforcedStates.putAll(states); + } + + /** + * Clears an enforced render state index by setting it to null. This allows object specific states to be used. + * + * @param type + * The type of RenderState to clear enforcement on. + */ + public void clearEnforcedState(final RenderState.StateType type) { + _enforcedStates.remove(type); + } + + /** + * sets all enforced states to null. + */ + public void clearEnforcedStates() { + _enforcedStates.clear(); + } + + /** + * sets all current states to null, and therefore forces the use of the default states. + */ + public void clearCurrentStates() { + _currentStates.clear(); + } + + /** + * @param type + * the state type to clear. + */ + public void clearCurrentState(final RenderState.StateType type) { + _currentStates.remove(type); + } + + public boolean hasEnforcedStates() { + return !_enforcedStates.isEmpty(); + } + + public RenderState getEnforcedState(final RenderState.StateType type) { + return _enforcedStates.get(type); + } + + public RenderState getCurrentState(final RenderState.StateType type) { + return _currentStates.get(type); + } + + public Object getContextKey() { + return _contextKey; + } + + public void setCurrentState(final StateType type, final RenderState state) { + _currentStates.put(type, state); + } + + public Camera getCurrentCamera() { + return _currentCamera; + } + + public void setCurrentCamera(final Camera cam) { + _currentCamera = cam; + } + + public Object getGlContextRep() { + return _glContextRep; + } + + /** + * Saves the currently set states to a stack. Does not changes the currently enforced states. + */ + public void pushEnforcedStates() { + _enforcedBackStack.push(new EnumMap<StateType, RenderState>(_enforcedStates)); + } + + /** + * Restores the enforced states from the stack. Any states enforced or cleared since the last push are reverted. + * + * @throws EmptyStackException + * if this method is called without first calling {@link #pushEnforcedStates()} + */ + public void popEnforcedStates() { + _enforcedStates.clear(); + _enforcedStates.putAll(_enforcedBackStack.pop()); + } + + public void pushFBOTextureRenderer(final AbstractFBOTextureRenderer top) { + if (_textureRenderers.size() > 0) { + _textureRenderers.peek().deactivate(); + } + _textureRenderers.push(top); + top.activate(); + } + + public void popFBOTextureRenderer() { + AbstractFBOTextureRenderer top = _textureRenderers.pop(); + top.deactivate(); + if (_textureRenderers.size() > 0) { + top = _textureRenderers.peek(); + top.activate(); + } + } + + /** + * Should only be called on a thread with an active context. + */ + public void contextLost() { + // Notify any interested parties of the deletion. + ContextManager.fireCleanContextEvent(this); + + // invalidate our render states + invalidateStates(); + + // force camera update + if (_currentCamera != null) { + _currentCamera.update(); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java new file mode 100644 index 0000000..1cd4869 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import com.ardor3d.scenegraph.Renderable; + +public interface RenderLogic { + void apply(Renderable renderable); + + void restore(Renderable renderable); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java new file mode 100644 index 0000000..4d14424 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java @@ -0,0 +1,591 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.Collection; +import java.util.List; + +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture1D; +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.Texture3D; +import com.ardor3d.image.TextureCubeMap; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyRectangle2; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.renderer.queue.RenderQueue; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.AbstractBufferData; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Renderable; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.NormalsMode; +import com.ardor3d.util.Ardor3dException; + +/** + * <code>Renderer</code> defines an interface for classes that handle displaying of graphics data to a render context. + * + * All rendering state and tasks should be handled through this class. + */ +public interface Renderer { + + /** + * No buffer. + */ + public static int BUFFER_NONE = 0x00; + /** + * A buffer storing color information generally for display to the user. + */ + public static int BUFFER_COLOR = 0x01; + /** + * A depth buffer allows sorting of pixels by depth or distance from the view port. + */ + public static int BUFFER_DEPTH = 0x02; + /** + * Often a higher precision buffer used to gather rendering results over time. + */ + public static int BUFFER_ACCUMULATION = 0x04; + /** + * A buffer used for masking out areas of the screen to prevent drawing. + */ + public static int BUFFER_STENCIL = 0x08; + + /** + * Convenience for those that find it too hard to do bitwise or. :) + */ + public static int BUFFER_COLOR_AND_DEPTH = BUFFER_COLOR | BUFFER_DEPTH; + + /** + * <code>setBackgroundColor</code> sets the color of window. This color will be shown for any pixel that is not set + * via typical rendering operations. + * + * @param c + * the color to set the background to. + */ + void setBackgroundColor(ReadOnlyColorRGBA c); + + /** + * <code>getBackgroundColor</code> retrieves the clear color of the current OpenGL context. + * + * @return the current clear color. + */ + ReadOnlyColorRGBA getBackgroundColor(); + + /** + * <code>clearBuffers</code> clears the given buffers (specified as a bitwise &). + * + * @param buffers + * the buffers to clear. + * @see #BUFFER_COLOR + * @see #BUFFER_DEPTH + * @see #BUFFER_ACCUMULATION + * @see #BUFFER_STENCIL + */ + void clearBuffers(int buffers); + + /** + * <code>clearBuffers</code> clears the given buffers (specified as a bitwise &). + * + * @param buffers + * the buffers to clear. + * @param strict + * if true, we'll limit the clearing to just the viewport area specified by the current camera. + * @see #BUFFER_COLOR + * @see #BUFFER_DEPTH + * @see #BUFFER_ACCUMULATION + * @see #BUFFER_STENCIL + */ + void clearBuffers(int buffers, boolean strict); + + /** + * <code>flushFrame</code> handles rendering any items still remaining in the render buckets and optionally swaps + * the back buffer with the currently displayed buffer. + * + * @param doSwap + * if true, will ask the underlying implementation to blit the back buffer contents to the display + * buffer. Usually this will be true, unless you are in a windowing toolkit that handles doing this for + * you. + */ + void flushFrame(boolean doSwap); + + /** + * + * <code>setOrtho</code> sets the display system to be in orthographic mode. If the system has already been set to + * orthographic mode a <code>Ardor3dException</code> is thrown. The origin (0,0) is the bottom left of the screen. + * + */ + void setOrtho(); + + /** + * + * <code>unsetOrhto</code> unsets the display system from orthographic mode back into regular projection mode. If + * the system is not in orthographic mode a <code>Ardor3dException</code> is thrown. + * + * + */ + void unsetOrtho(); + + /** + * @return true if the renderer is currently in ortho mode. + */ + boolean isInOrthoMode(); + + /** + * render queues - will first sort, then render, then finally clear the queue + */ + void renderBuckets(); + + /** + * render queues + * + * @param doSort + * if true, the queues will be sorted prior to rendering. + * @param doClear + * if true, the queues will be emptied after rendering. + */ + void renderBuckets(boolean doSort, boolean doClear); + + /** + * clear the render queue + */ + void clearQueue(); + + /** + * <code>grabScreenContents</code> reads a block of data as bytes from the current framebuffer. The format + * determines how many bytes per pixel are read and thus how big the buffer must be that you pass in. + * + * @param store + * a buffer to store contents in. + * @param format + * the format to read in bytes for. + * @param x + * - x starting point of block + * @param y + * - y starting point of block + * @param w + * - width of block + * @param h + * - height of block + */ + void grabScreenContents(ByteBuffer store, ImageDataFormat format, int x, int y, int w, int h); + + /** + * <code>draw</code> renders a scene. As it receives a base class of <code>Spatial</code> the renderer hands off + * management of the scene to spatial for it to determine when a <code>Geometry</code> leaf is reached. + * + * @param s + * the scene to render. + */ + void draw(Spatial s); + + /** + * <code>flush</code> tells the graphics hardware to send through all currently waiting commands in the buffer. + */ + void flushGraphics(); + + /** + * <code>finish</code> is similar to flush, however it blocks until all waiting hardware graphics commands have been + * finished. + */ + void finishGraphics(); + + /** + * Get the render queue associated with this Renderer. + * + * @return RenderQueue + */ + RenderQueue getQueue(); + + /** + * Return true if this renderer is in the middle of processing its RenderQueue. + * + * @return boolean + */ + boolean isProcessingQueue(); + + /** + * Check a given Spatial to see if it should be queued. return true if it was queued. + * + * @param s + * Spatial to check + * @return true if it was queued. + */ + boolean checkAndAdd(Spatial s); + + /** + * Attempts to delete the VBOs with the given id. Ignores null ids or ids < 1. + * + * @param ids + */ + void deleteVBOs(Collection<Integer> ids); + + /** + * Attempts to delete any VBOs associated with this buffer that are relevant to the current RenderContext. + * + * @param ids + */ + void deleteVBOs(AbstractBufferData<?> buffer); + + /** + * Unbind the current VBO elements. + */ + void unbindVBO(); + + /** + * Update all or a portion of an existing one dimensional texture object. + * + * @param destination + * the texture to update. Should already have been sent to the card (have a valid texture id.) + * @param dstOffsetX + * the offset into the destination to start our update. + * @param dstWidth + * the width of the region to update. + * @param source + * the data to update from. + * @param srcOffsetX + * the optional offset into our source data. + */ + void updateTexture1DSubImage(Texture1D destination, int dstOffsetX, int dstWidth, ByteBuffer source, int srcOffsetX); + + /** + * Update all or a portion of an existing two dimensional texture object. + * + * @param destination + * the texture to update. Should already have been sent to the card (have a valid texture id.) + * @param dstOffsetX + * the x offset into the destination to start our update. + * @param dstOffsetY + * the y offset into the destination to start our update. + * @param dstWidth + * the width of the region to update. + * @param dstHeight + * the height of the region to update. + * @param source + * the data to update from. + * @param srcOffsetX + * the optional X offset into our source data. + * @param srcOffsetY + * the optional Y offset into our source data. + * @param srcTotalWidth + * the total width of our source data, so we can properly walk through it. + */ + void updateTexture2DSubImage(Texture2D destination, int dstOffsetX, int dstOffsetY, int dstWidth, int dstHeight, + ByteBuffer source, int srcOffsetX, int srcOffsetY, int srcTotalWidth); + + /** + * Update all or a portion of an existing one dimensional texture object. + * + * @param destination + * the texture to update. Should already have been sent to the card (have a valid texture id.) + * @param dstOffsetX + * the x offset into the destination to start our update. + * @param dstOffsetY + * the y offset into the destination to start our update. + * @param dstOffsetZ + * the z offset into the destination to start our update. + * @param dstWidth + * the width of the region to update. + * @param dstHeight + * the height of the region to update. + * @param dstDepth + * the depth of the region to update. eg. 1 == one slice + * @param source + * the data to update from. + * @param srcOffsetX + * the optional X offset into our source data. + * @param srcOffsetY + * the optional Y offset into our source data. + * @param srcOffsetZ + * the optional Z offset into our source data. + * @param srcTotalWidth + * the total width of our source data, so we can properly walk through it. + * @param srcTotalHeight + * the total height of our source data, so we can properly walk through it. + */ + void updateTexture3DSubImage(Texture3D destination, int dstOffsetX, int dstOffsetY, int dstOffsetZ, int dstWidth, + int dstHeight, int dstDepth, ByteBuffer source, int srcOffsetX, int srcOffsetY, int srcOffsetZ, + int srcTotalWidth, int srcTotalHeight); + + /** + * Update all or a portion of a single two dimensional face on an existing cubemap texture object. + * + * @param destination + * the texture to update. Should already have been sent to the card (have a valid texture id.) + * @param dstFace + * the face to update. + * @param dstOffsetX + * the x offset into the destination to start our update. + * @param dstOffsetY + * the y offset into the destination to start our update. + * @param dstWidth + * the width of the region to update. + * @param dstHeight + * the height of the region to update. + * @param source + * the data to update from. + * @param srcOffsetX + * the optional X offset into our source data. + * @param srcOffsetY + * the optional Y offset into our source data. + * @param srcTotalWidth + * the total width of our source data, so we can properly walk through it. + */ + void updateTextureCubeMapSubImage(TextureCubeMap destination, TextureCubeMap.Face dstFace, int dstOffsetX, + int dstOffsetY, int dstWidth, int dstHeight, ByteBuffer source, int srcOffsetX, int srcOffsetY, + int srcTotalWidth); + + /** + * Check the underlying rendering system (opengl, etc.) for exceptions. + * + * @throws Ardor3dException + * if an error is found. + */ + void checkCardError() throws Ardor3dException; + + /** + * <code>draw</code> renders the renderable to the back buffer. + * + * @param renderable + * the text object to be rendered. + */ + void draw(Renderable renderable); + + /** + * <code>doTransforms</code> sets the current transform. + * + * @param transform + * transform to apply. + */ + boolean doTransforms(ReadOnlyTransform transform); + + /** + * <code>undoTransforms</code> reverts the latest transform. + * + * @param transform + * transform to revert. + */ + void undoTransforms(ReadOnlyTransform transform); + + // TODO: Arrays + void setupVertexData(FloatBufferData vertexCoords); + + void setupNormalData(FloatBufferData normalCoords); + + void setupColorData(FloatBufferData colorCoords); + + void setupFogData(FloatBufferData fogCoords); + + void setupTextureData(List<FloatBufferData> textureCoords); + + void drawElements(IndexBufferData<?> indices, int[] indexLengths, IndexMode[] indexModes, int primcount); + + void drawArrays(FloatBufferData vertexBuffer, int[] indexLengths, IndexMode[] indexModes, int primcount); + + void drawElementsVBO(IndexBufferData<?> indices, int[] indexLengths, IndexMode[] indexModes, int primcount); + + void applyNormalsMode(NormalsMode normMode, ReadOnlyTransform worldTransform); + + void applyDefaultColor(ReadOnlyColorRGBA defaultColor); + + // TODO: VBO + void setupVertexDataVBO(FloatBufferData vertexCoords); + + void setupNormalDataVBO(FloatBufferData normalCoords); + + void setupColorDataVBO(FloatBufferData colorCoords); + + void setupFogDataVBO(FloatBufferData fogCoords); + + void setupTextureDataVBO(List<FloatBufferData> textureCoords); + + void setupInterleavedDataVBO(FloatBufferData interleaved, FloatBufferData vertexCoords, + FloatBufferData normalCoords, FloatBufferData colorCoords, List<FloatBufferData> textureCoords); + + // TODO: Display List + void renderDisplayList(int displayListID); + + void setProjectionMatrix(FloatBuffer matrix); + + /** + * Gets the current projection matrix in row major order + * + * @param store + * The buffer to store in. If null or remaining is < 16, a new FloatBuffer will be created and returned. + * @return + */ + FloatBuffer getProjectionMatrix(FloatBuffer store); + + void setModelViewMatrix(FloatBuffer matrix); + + /** + * Gets the current modelview matrix in row major order + * + * @param store + * The buffer to store in. If null or remaining is < 16, a new FloatBuffer will be created and returned. + * @return + */ + FloatBuffer getModelViewMatrix(FloatBuffer store); + + void setViewport(int x, int y, int width, int height); + + void setDepthRange(double depthRangeNear, double depthRangeFar); + + /** + * Specify which color buffers are to be drawn into. + * + * @param target + */ + void setDrawBuffer(DrawBufferTarget target); + + /** + * This is a workaround until we make shared Record classes, or open up lower level opengl calls abstracted from + * lwjgl/jogl. + * + * @param lineWidth + * @param stippleFactor + * @param stipplePattern + * @param antialiased + */ + void setupLineParameters(float lineWidth, int stippleFactor, short stipplePattern, boolean antialiased); + + /** + * This is a workaround until we make shared Record classes, or open up lower level opengl calls abstracted from + * lwjgl/jogl. + * + * @param pointSize + * @param antialiased + * @param isSprite + * @param useDistanceAttenuation + * @param attenuationCoefficients + * @param minPointSize + * @param maxPointSize + */ + void setupPointParameters(float pointSize, boolean antialiased, boolean isSprite, boolean useDistanceAttenuation, + FloatBuffer attenuationCoefficients, float minPointSize, float maxPointSize); + + /** + * Apply the given state to the current RenderContext using this Renderer. + * + * @param type + * the state type + * @param state + * the render state. If null, the renderer's default is applied instead. + */ + void applyState(StateType type, RenderState state); + + /** + * Start a new display list. All further renderer commands that can be stored in a display list are part of this new + * list until {@link #endDisplayList()} is called. + * + * @return id of new display list + */ + int startDisplayList(); + + /** + * Ends a display list. Will likely cause an OpenGL exception if a display list is not currently being generated. + */ + void endDisplayList(); + + /** + * Loads a texture onto the card for the current OpenGL context. + * + * @param texture + * the texture obejct to load. + * @param unit + * the texture unit to load into + */ + void loadTexture(Texture texture, int unit); + + /** + * Explicitly remove this Texture from the graphics card. Note, the texture is only removed for the current context. + * If the texture is used in other contexts, those uses are not touched. If the texture is not used in this context, + * this is a no-op. + * + * @param texture + * the Texture object to remove. + */ + void deleteTexture(Texture texture); + + /** + * Removes the given texture ids from the current OpenGL context. + * + * @param ids + * a list/set of ids to remove. + */ + void deleteTextureIds(Collection<Integer> ids); + + /** + * Removes the given display lists by id from the current OpenGL context. + * + * @param ids + * a list/set of ids to remove. + */ + void deleteDisplayLists(Collection<Integer> collection); + + /** + * Add the given rectangle to the clip stack, clipping the rendering area by the given rectangle intersected with + * any existing scissor entries. Enable clipping if this is the first rectangle in the stack. + * + * @param rectangle + */ + void pushClip(ReadOnlyRectangle2 rectangle); + + /** + * Push a clip onto the stack indicating that future clips should not intersect with clips prior to this one. + * Basically this allows you to ignore prior clips for nested drawing. Make sure you pop this using + * {@link #popClip()}. + */ + void pushEmptyClip(); + + /** + * Pop the most recent rectangle from the stack and adjust the rendering area accordingly. + */ + void popClip(); + + /** + * Clear all rectangles from the clip stack and disable clipping. + */ + void clearClips(); + + /** + * @param enabled + * toggle clipping without effecting the current clip stack. + */ + void setClipTestEnabled(boolean enabled); + + /** + * @return true if the renderer believes clipping is enabled + */ + boolean isClipTestEnabled(); + + /** + * @param type + * the state type to grab + * @return the appropriate render state for the current context for the current type. This is the enforced state if + * one exists or the given current state if not null. Otherwise, the Renderer's default state is returned. + */ + RenderState getProperRenderState(StateType type, RenderState current); + + /** + * Set rendering logic that will be called during drawing of renderables + * + * @param logic + * logic to use in rendering. call with null to reset rendering. + * @see RenderLogic + */ + void setRenderLogic(RenderLogic logic); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java new file mode 100644 index 0000000..665317d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.util.concurrent.Callable; + +public abstract class RendererCallable<V> implements Callable<V> { + + private Renderer _renderer; + + public void setRenderer(final Renderer renderer) { + _renderer = renderer; + } + + public Renderer getRenderer() { + return _renderer; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java new file mode 100644 index 0000000..cf3eb10 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; + +/** + * Extension of Camera useful for tracking and updating a Stereo view camera. See + * http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/stereorender/ for a useful discussion on stereo viewing in + * OpenGL. + */ +public class StereoCamera extends Camera { + + private boolean _sideBySideMode = false; + + private final Camera _leftCamera; + private final Camera _rightCamera; + + private double _focalDistance = 100; + private double _eyeSeparation = _focalDistance / 30; + private double _aperture = 45 * MathUtils.DEG_TO_RAD; + + public StereoCamera() { + this(100, 100); + } + + /** + * + * @param width + * @param height + * @param sideBySideMode + */ + public StereoCamera(final int width, final int height) { + super(width, height); + _leftCamera = new Camera(width, height); + _rightCamera = new Camera(width, height); + } + + public StereoCamera(final Camera camera) { + super(camera); + _leftCamera = new Camera(camera); + _rightCamera = new Camera(camera); + } + + @Override + public void resize(final int width, final int height) { + super.resize(width, height); + _leftCamera.resize(width, height); + _rightCamera.resize(width, height); + } + + /** + * @return the sideBySideMode + * @see #setSideBySideMode(boolean) + */ + public boolean isSideBySideMode() { + return _sideBySideMode; + } + + /** + * @param sideBySideMode + * If true, left camera will be set up to the left half of the render context and right camera to the + * right half. If false, the views are set up to be the full size of the render context. + */ + public void setSideBySideMode(final boolean sideBySideMode) { + _sideBySideMode = sideBySideMode; + setupLeftRightCameras(); + } + + public void setupLeftRightCameras() { + // Set viewport: + // XXX: Could maybe make use of our current viewport? + if (_sideBySideMode) { + _leftCamera.setViewPort(0, .5, 0, 1); + _rightCamera.setViewPort(.5, 1, 0, 1); + } else { + _leftCamera.setViewPort(0, 1, 0, 1); + _rightCamera.setViewPort(0, 1, 0, 1); + } + + // Set frustum: + final double aspectRatio = (getWidth() / (double) getHeight() / (_sideBySideMode ? 2.0 : 1.0)); + final double halfView = getFrustumNear() * MathUtils.tan(_aperture / 2); + + final double top = halfView; + final double bottom = -halfView; + final double horizontalShift = 0.5 * _eyeSeparation * getFrustumNear() / _focalDistance; + + // LEFT: + { + final double left = -aspectRatio * halfView + horizontalShift; + final double right = aspectRatio * halfView + horizontalShift; + + _leftCamera.setFrustum(getFrustumNear(), getFrustumFar(), left, right, top, bottom); + } + + // RIGHT: + { + final double left = -aspectRatio * halfView - horizontalShift; + final double right = aspectRatio * halfView - horizontalShift; + + _rightCamera.setFrustum(getFrustumNear(), getFrustumFar(), left, right, top, bottom); + } + } + + public void updateLeftRightCameraFrames() { + // update camera frame + final Vector3 rightDir = Vector3.fetchTempInstance(); + final Vector3 work = Vector3.fetchTempInstance(); + rightDir.set(getDirection()).crossLocal(getUp()).multiplyLocal(_eyeSeparation / 2.0); + _leftCamera.setFrame(getLocation().subtract(rightDir, work), getLeft(), getUp(), getDirection()); + _rightCamera.setFrame(getLocation().add(rightDir, work), getLeft(), getUp(), getDirection()); + Vector3.releaseTempInstance(work); + Vector3.releaseTempInstance(rightDir); + } + + public void switchToLeftCamera(final Renderer r) { + _leftCamera.update(); + _leftCamera.apply(r); + } + + public void switchToRightCamera(final Renderer r) { + _rightCamera.update(); + _rightCamera.apply(r); + } + + /** + * @return the leftCamera + */ + public Camera getLeftCamera() { + return _leftCamera; + } + + /** + * @return the rightCamera + */ + public Camera getRightCamera() { + return _rightCamera; + } + + /** + * @return the focalDistance + */ + public double getFocalDistance() { + return _focalDistance; + } + + /** + * @param focalDistance + * the focalDistance to set + */ + public void setFocalDistance(final double focalDistance) { + _focalDistance = focalDistance; + } + + /** + * @return the eyeSeparation + */ + public double getEyeSeparation() { + return _eyeSeparation; + } + + /** + * @param eyeSeparation + * the eyeSeparation to set + */ + public void setEyeSeparation(final double eyeSeparation) { + _eyeSeparation = eyeSeparation; + } + + /** + * @return the aperture + */ + public double getAperture() { + return _aperture; + } + + /** + * @param radians + * the horizontal field of view, in radians + */ + public void setAperture(final double radians) { + _aperture = radians; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java new file mode 100644 index 0000000..7972f98 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java @@ -0,0 +1,213 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer; + +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.framework.Scene; +import com.ardor3d.image.Texture; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; + +/** + * <code>TextureRenderer</code> defines an abstract class that handles rendering a scene to a buffer and copying it to a + * texture. Creation of this object is usually handled via a TextureRendererFactory. Note that currently, only Texture2D + * is supported by Pbuffer versions of this class. Texture2D and TextureCubeMap are supported in FBO mode. + */ +public interface TextureRenderer { + + /** + * <code>getCamera</code> retrieves the camera this renderer is using. + * + * @return the camera this renderer is using. + */ + Camera getCamera(); + + /** + * + * @param scene + * the scene to render. + * @param tex + * the Texture to render to. This should be a Texture2D or TextureCubeMap. If the latter, its + * currentRTTFace will determine which cube face is drawn to. + * @param clear + * which buffers to clear before rendering, if any. + * @see Renderer#BUFFER_COLOR et al + */ + void render(Scene scene, Texture tex, int clear); + + /** + * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render + * time. + * + * @param scene + * the scene to render. + * @param texs + * a list of Textures to render to. These should be of type Texture2D or TextureCubeMap. If the latter, + * its currentRTTFace will determine which cube face is drawn to. + * @param clear + * which buffers to clear before rendering, if any. + * @see Renderer#BUFFER_COLOR et al + */ + void render(Scene scene, List<Texture> texs, int clear); + + /** + * + * @param spat + * the scene to render. + * @param tex + * the Texture to render to. This should be a Texture2D or TextureCubeMap. If the latter, its + * currentRTTFace will determine which cube face is drawn to. + * @param clear + * which buffers to clear before rendering, if any. + * @see Renderer#BUFFER_COLOR et al + */ + void render(Spatial spat, Texture tex, int clear); + + /** + * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render + * time. + * + * @param spat + * the scene to render. + * @param texs + * a list of Textures to render to. These should be of type Texture2D or TextureCubeMap. If the latter, + * its currentRTTFace will determine which cube face is drawn to. + * @param clear + * which buffers to clear before rendering, if any. + * @see Renderer#BUFFER_COLOR et al + */ + void render(Spatial spat, List<Texture> texs, int clear); + + /** + * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render + * time. + * + * @param spats + * an array of Spatials to render. + * @param tex + * the Texture to render to. This should be a Texture2D or TextureCubeMap. If the latter, its + * currentRTTFace will determine which cube face is drawn to. + * @param clear + * which buffers to clear before rendering, if any. + * @see Renderer#BUFFER_COLOR et al + */ + void render(List<? extends Spatial> spats, Texture tex, int clear); + + /** + * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render + * time. + * + * @param spats + * an array of Spatials to render. + * @param texs + * a list of Textures to render to. These should be of type Texture2D or TextureCubeMap. If the latter, + * its currentRTTFace will determine which cube face is drawn to. + * @param clear + * which buffers to clear before rendering, if any. + * @see Renderer#BUFFER_COLOR et al + */ + void render(List<? extends Spatial> spats, List<Texture> texs, int clear); + + /** + * <code>setBackgroundColor</code> sets the color of window. This color will be shown for any pixel that is not set + * via typical rendering operations. + * + * @param c + * the color to set the background to. + */ + void setBackgroundColor(ReadOnlyColorRGBA c); + + /** + * <code>getBackgroundColor</code> retrieves the color used for the window background. + * + * @return the background color that is currently set to the background. + */ + ReadOnlyColorRGBA getBackgroundColor(); + + /** + * <code>setupTexture</code> initializes a Texture object for use with TextureRenderer. Generates a valid gl texture + * id for this texture and sets up data storage for it. The texture will be equal to the texture renderer's size. + * + * Note that the texture renderer's size is not necessarily what is specified in the constructor. + * + * @param tex + * The texture to setup for use in Texture Rendering. This should be of type Texture2D or TextureCubeMap. + */ + void setupTexture(Texture tex); + + /** + * <code>copyToTexture</code> copies the current frame buffer contents to the given Texture. What is copied is based + * on the rttFormat of the texture object when it was setup. Note that the contents are copied with no scaling + * applied, so the texture must be big enough such that xoffset + width <= texture's width and yoffset + height <= + * texture's height. + * + * @param tex + * The Texture to copy into. This should be a Texture2D or TextureCubeMap. If the latter, its + * currentRTTFace will determine which cube face is drawn to. + * @param x + * the x offset into the framebuffer + * @param y + * the y offset into the framebuffer + * @param width + * the width of the rectangle to read from the framebuffer and copy 1:1 to the texture + * @param height + * the width of the rectangle to read from the framebuffer and copy 1:1 to the texture + * @param xoffset + * the x offset into the texture to draw at + * @param yoffset + * the y offset into the texture to draw at + */ + void copyToTexture(Texture tex, int x, int y, int width, int height, int xoffset, int yoffset); + + /** + * Any wrapping up and cleaning up of TextureRenderer information is performed here. + */ + void cleanup(); + + /** + * Set up this textureRenderer for use with multiple targets. If you are going to use this texture renderer to + * render to more than one texture, call this with true. + * + * @param multi + * true if you plan to use this texture renderer to render different content to more than one texture. + */ + void setMultipleTargets(boolean multi); + + int getWidth(); + + int getHeight(); + + /** + * Enforce a particular state whenever this texture renderer is used. In other words, the given state will override + * any state of the same type set on a scene object rendered with this texture renderer. + * + * @param state + * state to enforce + */ + void enforceState(RenderState state); + + void enforceStates(EnumMap<StateType, RenderState> states); + + /** + * @param type + * state type to clear + */ + void clearEnforcedState(StateType type); + + /** + * Clear all enforced states on this texture renderer. + */ + void clearEnforcedStates(); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java new file mode 100644 index 0000000..f6f8a8c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java @@ -0,0 +1,103 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import com.ardor3d.framework.DisplaySettings;
+
+public enum TextureRendererFactory {
+
+ INSTANCE;
+
+ private TextureRendererProvider _provider = null;
+
+ public void setProvider(final TextureRendererProvider provider) {
+ _provider = provider;
+ }
+
+ /**
+ * Create a TextureRenderer of the given width and height. All other params are considered undefined. We will
+ * attempt to make an FBO based renderer if supported, or a Pbuffer based renderer if supported, or null if neither
+ * are supported.
+ *
+ * @param width
+ * the width of our off screen rendering target
+ * @param height
+ * the height of our off screen rendering target
+ * @param renderer
+ * the renderer to use when rendering to this off screen target.
+ * @param caps
+ * the context capabilities, used for testing.
+ * @return a TextureRenderer
+ * @throws IllegalStateException
+ * if provider has not been set prior to calling this method.
+ */
+ public TextureRenderer createTextureRenderer(final int width, final int height, final Renderer renderer,
+ final ContextCapabilities caps) {
+ if (_provider == null) {
+ throw new IllegalStateException("No provider has been set on TextureRendererFactory.");
+ }
+ return _provider.createTextureRenderer(width, height, renderer, caps);
+ }
+
+ /**
+ * Create a TextureRenderer using params that are meaningful regardless of whether a Pbuffer or FBO renderer are
+ * used. We will attempt to make an FBO based renderer if supported, or a Pbuffer based renderer if supported, or
+ * null if neither are supported.
+ *
+ * @param width
+ * the width of our off screen rendering target
+ * @param height
+ * the height of our off screen rendering target
+ * @param depthBits
+ * the desired depth buffer size of our off screen rendering target
+ * @param samples
+ * the number of samples for our off screen rendering target
+ * @param renderer
+ * the renderer to use when rendering to this off screen target.
+ * @param caps
+ * the context capabilities, used for testing.
+ * @return a TextureRenderer
+ * @throws IllegalStateException
+ * if provider has not been set prior to calling this method.
+ */
+ public TextureRenderer createTextureRenderer(final int width, final int height, final int depthBits,
+ final int samples, final Renderer renderer, final ContextCapabilities caps) {
+ if (_provider == null) {
+ throw new IllegalStateException("No provider has been set on TextureRendererFactory.");
+ }
+ return _provider.createTextureRenderer(width, height, depthBits, samples, renderer, caps);
+ }
+
+ /**
+ * Create a TextureRenderer using as many of the given DisplaySettings that are meaningful for the chosen type.
+ * Unless forcePbuffer is true, we will attempt to make an FBO based renderer if supported, or a Pbuffer based
+ * renderer if supported, or null if neither are supported.
+ *
+ * @param settings
+ * a complete set of possible display settings to use. Some will only be valid if Pbuffer is used.
+ * @param forcePbuffer
+ * if true, we will return a pbuffer or null if pbuffers are not supported.
+ * @param renderer
+ * the renderer to use when rendering to this off screen target.
+ * @param caps
+ * the context capabilities, used for testing.
+ * @return a TextureRenderer
+ * @throws IllegalStateException
+ * if provider has not been set prior to calling this method.
+ */
+ public TextureRenderer createTextureRenderer(final DisplaySettings settings, final boolean forcePbuffer,
+ final Renderer renderer, final ContextCapabilities caps) {
+ if (_provider == null) {
+ throw new IllegalStateException("No provider has been set on TextureRendererFactory.");
+ }
+ return _provider.createTextureRenderer(settings, forcePbuffer, renderer, caps);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java new file mode 100644 index 0000000..6416824 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java @@ -0,0 +1,34 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import com.ardor3d.framework.DisplaySettings;
+
+public interface TextureRendererProvider {
+
+ /**
+ * @see TextureRendererFactory#createTextureRenderer(int, int, Renderer, ContextCapabilities)
+ */
+ TextureRenderer createTextureRenderer(int width, int height, Renderer renderer, ContextCapabilities caps);
+
+ /**
+ * @see TextureRendererFactory#createTextureRenderer(int, int, int, int, Renderer, ContextCapabilities)
+ */
+ TextureRenderer createTextureRenderer(int width, int height, int depthBits, int samples, Renderer renderer,
+ ContextCapabilities caps);
+
+ /**
+ * @see TextureRendererFactory#createTextureRenderer(DisplaySettings, boolean, Renderer, ContextCapabilities)
+ */
+ TextureRenderer createTextureRenderer(DisplaySettings settings, boolean forcePbuffer, Renderer renderer,
+ ContextCapabilities caps);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java new file mode 100644 index 0000000..8299c14 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import com.ardor3d.framework.DisplaySettings; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.Camera.ProjectionMode; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class EffectManager { + + protected final DisplaySettings _canvasSettings; + protected final List<RenderEffect> _effects = Lists.newArrayList(); + protected final Map<String, RenderTarget> _renderTargetMap = Maps.newHashMap(); + protected Renderer _currentRenderer = null; + protected RenderTarget _currentRenderTarget = null; + protected Camera _fsqCamera, _sceneCamera; + protected Mesh _fsq; + protected RenderTarget _inOutTargetA, _inOutTargetB; + protected boolean _swapTargets = false; + protected final TextureStoreFormat _outputFormat; + + public EffectManager(final DisplaySettings settings, final TextureStoreFormat outputformat) { + _canvasSettings = settings; + _fsqCamera = new Camera(settings.getWidth(), settings.getHeight()); + _fsqCamera.setFrustum(-1, 1, -1, 1, 1, -1); + _fsqCamera.setProjectionMode(ProjectionMode.Parallel); + _fsqCamera.setAxes(Vector3.NEG_UNIT_X, Vector3.UNIT_Y, Vector3.NEG_UNIT_Z); + + _outputFormat = outputformat; + setupDefaultTargets(outputformat); + } + + public void setupEffects() { + for (final RenderEffect effect : _effects) { + effect.prepare(this); + } + } + + protected void setupDefaultTargets(final TextureStoreFormat outputformat) { + _renderTargetMap.put("*Framebuffer", new RenderTarget_Framebuffer()); + _inOutTargetA = new RenderTarget_Texture2D(_canvasSettings.getWidth(), _canvasSettings.getHeight(), + outputformat); + _inOutTargetB = new RenderTarget_Texture2D(_canvasSettings.getWidth(), _canvasSettings.getHeight(), + outputformat); + } + + public void renderEffects(final Renderer renderer) { + _currentRenderer = renderer; + for (final RenderEffect effect : _effects) { + if (effect.isEnabled()) { + effect.render(this); + _swapTargets = !_swapTargets; + } + } + } + + public DisplaySettings getCanvasSettings() { + return _canvasSettings; + } + + public List<RenderEffect> getEffects() { + return _effects; + } + + public Map<String, RenderTarget> getRenderTargetMap() { + return _renderTargetMap; + } + + public Renderer getCurrentRenderer() { + return _currentRenderer; + } + + public RenderTarget getCurrentRenderTarget() { + return _currentRenderTarget; + } + + public void setCurrentRenderTarget(final RenderTarget target) { + _currentRenderTarget = target; + } + + public void addEffect(final RenderEffect effect) { + _effects.add(effect); + } + + public RenderTarget getRenderTarget(final String name) { + // Check for reserved words + if ("*Previous".equals(name)) { + return _swapTargets ? _inOutTargetB : _inOutTargetA; + } else if ("*Next".equals(name)) { + return _swapTargets ? _inOutTargetA : _inOutTargetB; + } else { + return _renderTargetMap.get(name); + } + } + + public boolean setCurrentRenderTarget(final String name) { + final RenderTarget target = getRenderTarget(name); + if (target != null) { + _currentRenderTarget = target; + return true; + } + return false; + } + + public void renderFullScreenQuad(final EnumMap<StateType, RenderState> enforcedStates) { + // render our -1,1 quad + _currentRenderTarget.render(this, _fsqCamera, getFullScreenQuad(), enforcedStates); + } + + protected Mesh getFullScreenQuad() { + if (_fsq != null) { + return _fsq; + } + + _fsq = new Mesh("fsq"); + _fsq.getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1)); + _fsq.getMeshData().setTextureBuffer(BufferUtils.createFloatBuffer(0, 0, 1, 0, 1, 1, 0, 1), 0); + _fsq.getMeshData().setIndexBuffer(BufferUtils.createIntBuffer(0, 1, 3, 1, 2, 3)); + + _fsq.getSceneHints().setCullHint(CullHint.Never); + _fsq.getSceneHints().setLightCombineMode(LightCombineMode.Off); + + final ZBufferState zState = new ZBufferState(); + zState.setEnabled(false); + _fsq.setRenderState(zState); + + _fsq.updateGeometricState(0); + + return _fsq; + } + + public TextureStoreFormat getOutputFormat() { + return _outputFormat; + } + + public Camera getSceneCamera() { + return _sceneCamera; + } + + public void setSceneCamera(final Camera sceneCamera) { + _sceneCamera = sceneCamera; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java new file mode 100644 index 0000000..4971d58 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +/** + * A specific instruction in a RenderEffect. + */ +public interface EffectStep { + + /** + * Apply this step. + * + * @param manager + */ + public void apply(final EffectManager manager); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java new file mode 100644 index 0000000..777daee --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.EnumMap; +import java.util.Map; + +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.google.common.collect.Maps; + +public class EffectStep_RenderScreenOverlay implements EffectStep { + + private final EnumMap<StateType, RenderState> _states = Maps.newEnumMap(StateType.class); + private final TextureState _texState = new TextureState(); + private final Map<String, Integer> _targetMap = Maps.newHashMap(); + + public EffectStep_RenderScreenOverlay() { + _states.put(StateType.Texture, _texState); + } + + @Override + public void apply(final EffectManager manager) { + // prepare our texture state + for (final String key : _targetMap.keySet()) { + final RenderTarget target = manager.getRenderTarget(key); + final Integer unit = _targetMap.get(key); + _texState.setTexture(target.getTexture(), unit.intValue()); + } + + // render a quad to the screen using our states. + manager.renderFullScreenQuad(_states); + } + + public TextureState getTextureState() { + return _texState; + } + + public EnumMap<StateType, RenderState> getEnforcedStates() { + return _states; + } + + public Map<String, Integer> getTargetMap() { + return _targetMap; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java new file mode 100644 index 0000000..ee03266 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class EffectStep_RenderSpatials implements EffectStep { + private final EnumMap<StateType, RenderState> _states = Maps.newEnumMap(StateType.class); + private final List<Spatial> _spatials = Lists.newArrayList(); + private final Camera _trackedCamera; + + public EffectStep_RenderSpatials(final Camera trackedCamera) { + _trackedCamera = trackedCamera; + } + + @Override + public void apply(final EffectManager manager) { + manager.getCurrentRenderTarget().render(manager, + _trackedCamera != null ? _trackedCamera : manager.getSceneCamera(), _spatials, _states); + } + + public List<Spatial> getSpatials() { + return _spatials; + } + + public EnumMap<StateType, RenderState> getEnforcedStates() { + return _states; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java new file mode 100644 index 0000000..8b36d29 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +public class EffectStep_SetRenderTarget implements EffectStep { + + private final String _target; + + public EffectStep_SetRenderTarget(final String target) { + _target = target; + } + + @Override + public void apply(final EffectManager manager) { + manager.setCurrentRenderTarget(_target); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java new file mode 100644 index 0000000..76a9338 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java @@ -0,0 +1,31 @@ + +package com.ardor3d.renderer.effect; + +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.RenderState.StateType; + +public class FrameBufferOutputEffect extends RenderEffect { + + private BlendState _blend = null; + + @Override + public void prepare(final EffectManager manager) { + _steps.clear(); + _steps.add(new EffectStep_SetRenderTarget("*Framebuffer")); + + final EffectStep_RenderScreenOverlay drawStep = new EffectStep_RenderScreenOverlay(); + drawStep.getTargetMap().put("*Previous", 0); + drawStep.getEnforcedStates().put(StateType.Blend, _blend); + _steps.add(drawStep); + + super.prepare(manager); + } + + public BlendState getBlend() { + return _blend; + } + + public void setBlend(final BlendState blend) { + _blend = blend; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java new file mode 100644 index 0000000..6fda5a9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * A RenderEffect object represents a complete set of instructions necessary for applying a specific effect to our + * render output. Each effect is comprised of a set of 1 or more steps (EffectStep). + */ +public abstract class RenderEffect { + + /** A list of logical steps that comprise our effect. */ + protected final List<EffectStep> _steps = Lists.newArrayList(); + + /** Is this render effect active? */ + protected boolean _enabled = true; + + /** + * Do any setup necessary for our effect prior. This should be called only once, or on changes to the effect chain. + * + * @param manager + */ + public void prepare(final EffectManager manager) {} + + /** + * Render this effect. + * + * @param manager + */ + public void render(final EffectManager manager) { + for (final EffectStep step : _steps) { + step.apply(manager); + } + } + + public boolean isEnabled() { + return _enabled; + } + + public void setEnabled(final boolean enabled) { + _enabled = enabled; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java new file mode 100644 index 0000000..7a4a55f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.image.Texture; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; + +public interface RenderTarget { + + void render(final EffectManager effectManager, final Camera camera, final Spatial spatial, + EnumMap<StateType, RenderState> enforcedStates); + + void render(final EffectManager effectManager, final Camera camera, final List<Spatial> spatials, + EnumMap<StateType, RenderState> enforcedStates); + + Texture getTexture(); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java new file mode 100644 index 0000000..54eec7f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.image.Texture; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; + +public class RenderTarget_Framebuffer implements RenderTarget { + + @Override + public void render(final EffectManager effectManager, final Camera camera, final List<Spatial> spatials, + final EnumMap<StateType, RenderState> enforcedStates) { + render(effectManager.getCurrentRenderer(), camera, spatials, null, enforcedStates); + } + + @Override + public void render(final EffectManager effectManager, final Camera camera, final Spatial spatial, + final EnumMap<StateType, RenderState> enforcedStates) { + render(effectManager.getCurrentRenderer(), camera, null, spatial, enforcedStates); + } + + public void render(final Renderer renderer, final Camera camera, final List<Spatial> spatials, + final Spatial spatial, final EnumMap<StateType, RenderState> enforcedStates) { + if (camera != Camera.getCurrentCamera()) { + camera.update(); + } + camera.apply(renderer); + + final RenderContext context = ContextManager.getCurrentContext(); + + context.enforceStates(enforcedStates); + + if (spatial != null) { + spatial.onDraw(renderer); + } else { + for (final Spatial spat : spatials) { + spat.onDraw(renderer); + } + } + + renderer.renderBuckets(); + context.clearEnforcedStates(); + } + + @Override + public Texture getTexture() { + throw new UnsupportedOperationException(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java new file mode 100644 index 0000000..338bda8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; + +public class RenderTarget_Texture2D implements RenderTarget { + + private final Texture2D _texture = new Texture2D(); + private final int _width, _height; + private boolean _texSetup = false; + private final ColorRGBA _backgroundColor = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA); + + public RenderTarget_Texture2D(final int width, final int height) { + this(width, height, TextureStoreFormat.RGB8); + } + + public RenderTarget_Texture2D(final int width, final int height, final TextureStoreFormat format) { + _width = width; + _height = height; + _texture.setTextureStoreFormat(format); + } + + @Override + public void render(final EffectManager effectManager, final Camera camera, final List<Spatial> spatials, + final EnumMap<StateType, RenderState> enforcedStates) { + render(effectManager.getCurrentRenderer(), camera, spatials, null, enforcedStates); + } + + @Override + public void render(final EffectManager effectManager, final Camera camera, final Spatial spatial, + final EnumMap<StateType, RenderState> enforcedStates) { + render(effectManager.getCurrentRenderer(), camera, null, spatial, enforcedStates); + } + + protected void render(final Renderer renderer, final Camera camera, final List<Spatial> spatials, + final Spatial spatial, final EnumMap<StateType, RenderState> enforcedStates) { + final TextureRenderer texRend = TextureRendererPool.fetch(_width, _height, renderer); + if (!_texSetup) { + texRend.setupTexture(_texture); + _texSetup = true; + } + + // set desired bg color + texRend.setBackgroundColor(_backgroundColor); + + // setup camera + if (camera != null) { + texRend.getCamera().setFrame(camera); + texRend.getCamera().setFrustum(camera); + texRend.getCamera().setProjectionMode(camera.getProjectionMode()); + } + + texRend.enforceStates(enforcedStates); + + // draw to texture + if (spatial != null) { + texRend.render(spatial, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + } else { + texRend.render(spatials, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + } + + texRend.clearEnforcedStates(); + TextureRendererPool.release(texRend); + } + + @Override + public Texture2D getTexture() { + return _texture; + } + + public int getWidth() { + return _width; + } + + public int getHeight() { + return _height; + } + + public ReadOnlyColorRGBA getBackgroundColor() { + return _backgroundColor; + } + + public void setBackgroundColor(final ReadOnlyColorRGBA color) { + _backgroundColor.set(color); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java new file mode 100644 index 0000000..a49dfa2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java @@ -0,0 +1,29 @@ + +package com.ardor3d.renderer.effect; + +import java.util.Arrays; + +import com.ardor3d.renderer.Camera; +import com.ardor3d.scenegraph.Spatial; + +public class SpatialRTTEffect extends RenderEffect { + + private final EffectStep_SetRenderTarget _targetStep; + private final EffectStep_RenderSpatials _drawStep; + + public SpatialRTTEffect(final String targetName, final Camera trackedCamera, final Spatial... spatials) { + _targetStep = new EffectStep_SetRenderTarget(targetName); + _drawStep = new EffectStep_RenderSpatials(trackedCamera); + _drawStep.getSpatials().addAll(Arrays.asList(spatials)); + } + + @Override + public void prepare(final EffectManager manager) { + _steps.clear(); + _steps.add(_targetStep); + _steps.add(_drawStep); + + super.prepare(manager); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java new file mode 100644 index 0000000..4f42df3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.effect; + +import java.util.Iterator; +import java.util.List; + +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.renderer.TextureRendererFactory; +import com.google.common.collect.Lists; + +public enum TextureRendererPool { + INSTANCE; + + private final List<TextureRenderer> renderers = Lists.newLinkedList(); + + public static TextureRenderer fetch(final int width, final int height, final Renderer renderer) { + for (final Iterator<TextureRenderer> it = INSTANCE.renderers.iterator(); it.hasNext();) { + final TextureRenderer texRend = it.next(); + if (texRend.getWidth() == width && texRend.getHeight() == height) { + it.remove(); + return texRend; + } + } + + // none found, make one + return TextureRendererFactory.INSTANCE.createTextureRenderer(width, height, renderer, ContextManager + .getCurrentContext().getCapabilities()); + } + + public static void release(final TextureRenderer texRend) { + INSTANCE.renderers.add(texRend); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java new file mode 100644 index 0000000..6597e59 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.pass; + +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.image.Texture; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; + +/** + * <code>BasicPassManager</code> controls a set of passes and sends through calls to render and update. + */ +public class BasicPassManager { + + protected List<Pass> _passes = new ArrayList<Pass>(); + + public void add(final Pass toAdd) { + if (toAdd != null) { + _passes.add(toAdd); + } + } + + public void insert(final Pass toAdd, final int index) { + _passes.add(index, toAdd); + } + + public boolean contains(final Pass s) { + return _passes.contains(s); + } + + public boolean remove(final Pass toRemove) { + return _passes.remove(toRemove); + } + + public Pass get(final int index) { + return _passes.get(index); + } + + public int passes() { + return _passes.size(); + } + + public void clearAll() { + cleanUp(); + _passes.clear(); + } + + public void cleanUp() { + for (int i = 0, sSize = _passes.size(); i < sSize; i++) { + final Pass p = _passes.get(i); + p.cleanUp(); + } + } + + public void renderPasses(final Renderer r) { + for (int i = 0, sSize = _passes.size(); i < sSize; i++) { + final Pass p = _passes.get(i); + p.renderPass(r); + } + } + + public void renderPasses(final TextureRenderer r, final int clear, final List<Texture> texs) { + for (int i = 0, sSize = _passes.size(); i < sSize; i++) { + final Pass p = _passes.get(i); + p.renderPass(r, clear, texs); + } + } + + public void updatePasses(final double tpf) { + for (int i = 0, sSize = _passes.size(); i < sSize; i++) { + final Pass p = _passes.get(i); + p.updatePass(tpf); + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java new file mode 100644 index 0000000..e4896fc --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.pass; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.CullState; +import com.ardor3d.renderer.state.LightState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.WireframeState; +import com.ardor3d.renderer.state.CullState.Face; + +/** + * This Pass can be used for drawing an outline around geometry objects. It does this by first drawing the geometry as + * normal, and then drawing an outline using the geometry's wireframe. + */ +public class OutlinePass extends RenderPass { + + private static final long serialVersionUID = 1L; + + public static final float DEFAULT_LINE_WIDTH = 3f; + public static final ReadOnlyColorRGBA DEFAULT_OUTLINE_COLOR = new ColorRGBA(ColorRGBA.BLACK); + + // render states needed to draw the outline + private final CullState _frontCull; + private final CullState _backCull; + private final WireframeState _wireframeState; + private final LightState _noLights; + private final TextureState _noTexture; + private BlendState _blendState; + + public OutlinePass(final boolean antialiased) { + _wireframeState = new WireframeState(); + _wireframeState.setFace(WireframeState.Face.FrontAndBack); + _wireframeState.setLineWidth(DEFAULT_LINE_WIDTH); + _wireframeState.setEnabled(true); + + _frontCull = new CullState(); + _frontCull.setCullFace(Face.Front); + + _backCull = new CullState(); + _backCull.setCullFace(Face.Back); + + _wireframeState.setAntialiased(antialiased); + + _noLights = new LightState(); + _noLights.setGlobalAmbient(DEFAULT_OUTLINE_COLOR); + _noLights.setEnabled(true); + + _noTexture = new TextureState(); + _noTexture.setEnabled(true); + + _blendState = new BlendState(); + _blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + _blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + _blendState.setBlendEnabled(true); + _blendState.setEnabled(true); + + } + + @Override + public void doRender(final Renderer renderer) { + // if there's nothing to do + if (_spatials.size() == 0) { + return; + } + + // normal render + _context.enforceState(_frontCull); + super.doRender(renderer); + + // set up the render states + // CullState.setFlippedCulling(true); + _context.enforceState(_backCull); + _context.enforceState(_wireframeState); + _context.enforceState(_noLights); + _context.enforceState(_noTexture); + _context.enforceState(_blendState); + + // this will draw the wireframe + super.doRender(renderer); + + // revert state changes + // CullState.setFlippedCulling(false); + _context.clearEnforcedStates(); + } + + public void setOutlineWidth(final float width) { + _wireframeState.setLineWidth(width); + } + + public float getOutlineWidth() { + return _wireframeState.getLineWidth(); + } + + public void setOutlineColor(final ReadOnlyColorRGBA outlineColor) { + _noLights.setGlobalAmbient(outlineColor); + } + + public ReadOnlyColorRGBA getOutlineColor() { + return _noLights.getGlobalAmbient(); + } + + public BlendState getBlendState() { + return _blendState; + } + + public void setBlendState(final BlendState alphaState) { + _blendState = alphaState; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java new file mode 100644 index 0000000..12fadf3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.pass; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.image.Texture; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.scenegraph.Spatial; + +/** + * <code>Pass</code> encapsulates logic necessary for rendering one or more steps in a multipass technique. + * + * Rendering: + * + * When renderPass is called, a check is first made to see if the pass isEnabled(). Then any states set on this pass are + * enforced via Spatial.enforceState(RenderState). This is useful for doing things such as causing this pass to be + * blended to a previous pass via enforcing an BlendState, etc. Next, doRender(Renderer) is called to do the actual + * rendering work. Finally, any enforced states set before this pass was run are restored. + */ +public abstract class Pass implements Serializable { + + private static final long serialVersionUID = 1L; + + /** list of Spatial objects registered with this pass. */ + protected List<Spatial> _spatials = new ArrayList<Spatial>(); + + /** if false, pass will not be updated or rendered. */ + protected boolean _enabled = true; + + /** + * RenderStates registered with this pass - if a given state is not null it overrides the corresponding state set + * during rendering. + */ + protected final EnumMap<RenderState.StateType, RenderState> _passStates = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + protected RenderContext _context = null; + + /** if enabled, set the states for this pass and then render. */ + public final void renderPass(final Renderer r) { + if (!_enabled) { + return; + } + _context = ContextManager.getCurrentContext(); + _context.pushEnforcedStates(); + _context.enforceStates(_passStates); + doRender(r); + _context.popEnforcedStates(); + _context = null; + } + + /** if enabled, set the states for this pass and then render. */ + public final void renderPass(final TextureRenderer r, final int clear, final List<Texture> texs) { + if (!_enabled) { + return; + } + _context = ContextManager.getCurrentContext(); + _context.pushEnforcedStates(); + _context.enforceStates(_passStates); + doRender(r, clear, texs); + _context.popEnforcedStates(); + _context = null; + } + + /** + * Enforce a particular state. In other words, the given state will override any state of the same type set on a + * scene object. Remember to clear the state when done enforcing. Very useful for multipass techniques where + * multiple sets of states need to be applied to a scenegraph drawn multiple times. + * + * @param state + * state to enforce + */ + public void setPassState(final RenderState state) { + _passStates.put(state.getType(), state); + } + + /** + * Clears an enforced render state index by setting it to null. This allows object specific states to be used. + * + * @param type + * The type of RenderState to clear enforcement on. + */ + public void clearPassState(final RenderState.StateType type) { + _passStates.remove(type); + } + + /** + * sets all enforced states to null. + * + * @see RenderContext#clearEnforcedState(int) + */ + public void clearPassStates() { + _passStates.clear(); + } + + protected abstract void doRender(Renderer r); + + protected void doRender(final TextureRenderer r, final int clear, final List<Texture> texs) { + throw new UnsupportedOperationException("This pass type does not support RTT use."); + } + + /** if enabled, call doUpdate to update information for this pass. */ + public final void updatePass(final double tpf) { + if (!_enabled) { + return; + } + doUpdate(tpf); + } + + protected void doUpdate(final double tpf) {} + + public void add(final Spatial toAdd) { + _spatials.add(toAdd); + } + + public Spatial get(final int index) { + return _spatials.get(index); + } + + public boolean contains(final Spatial s) { + return _spatials.contains(s); + } + + public boolean remove(final Spatial toRemove) { + return _spatials.remove(toRemove); + } + + public int size() { + return _spatials.size(); + } + + /** + * @return Returns the enabled. + */ + public boolean isEnabled() { + return _enabled; + } + + /** + * @param enabled + * The enabled to set. + */ + public void setEnabled(final boolean enabled) { + _enabled = enabled; + } + + public void cleanUp() {} + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java new file mode 100644 index 0000000..e814a54 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.pass; + +import java.util.List; + +import com.ardor3d.image.Texture; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.scenegraph.Spatial; + +/** + * <code>RenderPass</code> renders the spatials attached to it as normal, including rendering the renderqueue at the end + * of the pass. + */ +public class RenderPass extends Pass { + + private static final long serialVersionUID = 1L; + + @Override + public void doRender(final Renderer r) { + for (int i = 0, sSize = _spatials.size(); i < sSize; i++) { + final Spatial s = _spatials.get(i); + r.draw(s); + } + r.renderBuckets(); + } + + @Override + public void doRender(final TextureRenderer r, final int clear, final List<Texture> texs) { + r.render(_spatials, texs, clear); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java new file mode 100644 index 0000000..3350df3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.queue; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Stack; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.SortUtil; + +public class AbstractRenderBucket implements RenderBucket { + + protected Comparator<Spatial> _comparator; + + protected Spatial[] _currentList, _tempList; + protected int _currentListSize; + + protected Stack<Spatial[]> _listStack = new Stack<Spatial[]>(); + protected Stack<Spatial[]> _listStackPool = new Stack<Spatial[]>(); + protected Stack<Integer> _listSizeStack = new Stack<Integer>(); + + public AbstractRenderBucket() { + _currentList = new Spatial[32]; + _tempList = new Spatial[_currentList.length]; + } + + public void add(final Spatial spatial) { + spatial._queueDistance = Double.NEGATIVE_INFINITY; + if (_currentListSize >= _currentList.length) { + // grow if necessary + final Spatial[] temp = new Spatial[_currentListSize * 2]; + System.arraycopy(_currentList, 0, temp, 0, _currentListSize); + _currentList = temp; + if (_tempList.length < temp.length) { + _tempList = new Spatial[temp.length]; + } + } + _currentList[_currentListSize++] = spatial; + } + + public void remove(final Spatial spatial) { + int index = 0; + for (int i = 0; i < _currentListSize; i++) { + if (_currentList[index] == spatial) { + break; + } + index++; + } + for (int i = index; i < _currentListSize - 1; i++) { + _currentList[i] = _currentList[i + 1]; + } + + _currentListSize--; + } + + public void clear() { + if (_currentListSize > 0) { + Arrays.fill(_currentList, 0, _currentListSize, null); + _currentListSize = 0; + } + } + + public void render(final Renderer renderer) { + for (int i = 0; i < _currentListSize; i++) { + _currentList[i].draw(renderer); + } + } + + public void sort() { + // only sort if we have more than one item in our bucket. + if (_currentListSize > 1) { + if (_currentListSize <= SortUtil.SHELL_SORT_THRESHOLD) { + // shell sort + SortUtil.shellSort(_currentList, 0, _currentListSize - 1, _comparator); + } else { + // copy in our list for use in the merge sort. + System.arraycopy(_currentList, 0, _tempList, 0, _currentListSize); + + // merge sort + SortUtil.msort(_tempList, _currentList, 0, _currentListSize - 1, _comparator); + + // null fill to remove references + Arrays.fill(_tempList, 0, _currentListSize, null); + } + } + } + + public void pushBucket() { + _listStack.push(_currentList); + if (_listStackPool.isEmpty()) { + _currentList = new Spatial[32]; + } else { + _currentList = _listStackPool.pop(); + } + + _listSizeStack.push(_currentListSize); + _currentListSize = 0; + } + + public void popBucket() { + if (_currentList != null) { + _listStackPool.push(_currentList); + } + _currentList = _listStack.pop(); + _currentListSize = _listSizeStack.pop(); + } + + /** + * Calculates the distance from a spatial to the camera. Distance is a squared distance. + * + * @param spat + * Spatial to check distance. + * @return Distance from Spatial to current context's camera. + */ + protected double distanceToCam(final Spatial spat) { + if (spat._queueDistance != Double.NEGATIVE_INFINITY) { + return spat._queueDistance; + } + + final Camera cam = Camera.getCurrentCamera(); + + if (spat.getWorldBound() != null && Vector3.isValid(spat.getWorldBound().getCenter())) { + spat._queueDistance = spat.getWorldBound().distanceToEdge(cam.getLocation()); + } else { + final ReadOnlyVector3 spatPosition = spat.getWorldTranslation(); + if (!Vector3.isValid(spatPosition)) { + spat._queueDistance = Double.POSITIVE_INFINITY; + } else { + spat._queueDistance = cam.getLocation().distance(spatPosition); + } + } + + return spat._queueDistance; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java new file mode 100644 index 0000000..6ced7e2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java @@ -0,0 +1,96 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Comparator;
+
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.TextureKey;
+
+public class OpaqueRenderBucket extends AbstractRenderBucket {
+
+ public OpaqueRenderBucket() {
+ super();
+
+ _comparator = new OpaqueComparator();
+ }
+
+ private class OpaqueComparator implements Comparator<Spatial> {
+ public int compare(final Spatial o1, final Spatial o2) {
+ if (o1 instanceof Mesh && o2 instanceof Mesh) {
+ return compareByStates((Mesh) o1, (Mesh) o2);
+ }
+
+ final double d1 = distanceToCam(o1);
+ final double d2 = distanceToCam(o2);
+ if (d1 > d2) {
+ return 1;
+ } else if (d1 < d2) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Compare opaque items by their texture states - generally the most expensive switch. Later this might expand
+ * to comparisons by other states as well, such as lighting or material.
+ */
+ private int compareByStates(final Mesh mesh1, final Mesh mesh2) {
+ final TextureState ts1 = (TextureState) mesh1.getWorldRenderState(RenderState.StateType.Texture);
+ final TextureState ts2 = (TextureState) mesh2.getWorldRenderState(RenderState.StateType.Texture);
+ if (ts1 == ts2) {
+ return 0;
+ } else if (ts1 == null && ts2 != null) {
+ return -1;
+ } else if (ts2 == null && ts1 != null) {
+ return 1;
+ }
+
+ for (int x = 0, maxIndex = Math.min(ts1.getMaxTextureIndexUsed(), ts2.getMaxTextureIndexUsed()); x <= maxIndex; x++) {
+
+ final TextureKey key1 = ts1.getTextureKey(x);
+ final TextureKey key2 = ts2.getTextureKey(x);
+
+ if (key1 == null) {
+ if (key2 == null) {
+ continue;
+ } else {
+ return -1;
+ }
+ } else if (key2 == null) {
+ return 1;
+ }
+
+ final int tid1 = key1.hashCode();
+ final int tid2 = key2.hashCode();
+
+ if (tid1 == tid2) {
+ continue;
+ } else if (tid1 < tid2) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ if (ts1.getMaxTextureIndexUsed() != ts2.getMaxTextureIndexUsed()) {
+ return ts2.getMaxTextureIndexUsed() - ts1.getMaxTextureIndexUsed();
+ }
+
+ return 0;
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java new file mode 100644 index 0000000..afc7d70 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java @@ -0,0 +1,51 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Comparator;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Spatial;
+
+public class OrthoRenderBucket extends AbstractRenderBucket {
+
+ public OrthoRenderBucket() {
+ super();
+
+ _comparator = new OrthoComparator();
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ if (_currentListSize > 0) {
+ try {
+ renderer.setOrtho();
+ for (int i = 0; i < _currentListSize; i++) {
+ _currentList[i].draw(renderer);
+ }
+ } finally {
+ renderer.unsetOrtho();
+ }
+ }
+ }
+
+ private static class OrthoComparator implements Comparator<Spatial> {
+ public int compare(final Spatial o1, final Spatial o2) {
+ if (o2.getSceneHints().getOrthoOrder() == o1.getSceneHints().getOrthoOrder()) {
+ return 0;
+ } else if (o2.getSceneHints().getOrthoOrder() < o1.getSceneHints().getOrthoOrder()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java new file mode 100644 index 0000000..abc888e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java @@ -0,0 +1,31 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Spatial;
+
+public interface RenderBucket {
+ void add(Spatial spatial);
+
+ void remove(Spatial spatial);
+
+ void clear();
+
+ void sort();
+
+ void render(Renderer renderer);
+
+ void pushBucket();
+
+ void popBucket();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java new file mode 100644 index 0000000..bd48c03 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.queue; + +import java.util.HashMap; + +public final class RenderBucketType { + + public static final RenderBucketType getRenderBucketType(final String name) { + if (name == null) { + throw new IllegalArgumentException("name must not be null!"); + } + + RenderBucketType bucketType = bucketTypeMap.get(name); + if (bucketType == null) { + bucketType = new RenderBucketType(name); + } + return bucketType; + } + + private static final HashMap<String, RenderBucketType> bucketTypeMap = new HashMap<String, RenderBucketType>(); + + private final String name; + + private RenderBucketType(final String name) { + this.name = name; + } + + public String name() { + return name; + } + + @Override + public String toString() { + return name(); + } + + @Override + public boolean equals(final Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + /** + * Use your parent's RenderBucketType. If you do not have a parent, {@link #Opaque} will be used instead. + */ + public static final RenderBucketType Inherit = getRenderBucketType("Inherit"); + + /** + * Used for objects that we want to guarantee will be rendered first. + */ + public static final RenderBucketType PreBucket = getRenderBucketType("PreBucket"); + + /** + * TODO: Add definition. + */ + public static final RenderBucketType Shadow = getRenderBucketType("Shadow"); + + /** + * Used for surfaces that are fully opaque - can not be seen through. Drawn from front to back. + */ + public static final RenderBucketType Opaque = getRenderBucketType("Opaque"); + + /** + * Used for surfaces that are partially transparent or translucent - can be seen through. Drawn from back to front. + * See also the flag {@link com.ardor3d.renderer.queue.TransparentRenderBucket#setTwoPassTransparency(boolean) + * TransparentRenderBucket.setTwoPassTransparency(boolean)} allowing you to enable two pass transparency for more + * accurate results. + */ + public static final RenderBucketType Transparent = getRenderBucketType("Transparent"); + + /** + * Draw in orthographic mode where the x and y coordinates are in screen space with the origin in the lower left + * corner. Uses {@link com.ardor3d.scenegraph.hint.SceneHints#getOrthoOrder() SceneHints.getOrthoOrder()} to + * determine draw order. + */ + public static final RenderBucketType Ortho = getRenderBucketType("Ortho"); + + /** + * Used for objects that we want to guarantee will be rendered last. + */ + public static final RenderBucketType PostBucket = getRenderBucketType("PostBucket"); + + /** + * Do not use bucket system. Instead, draw the spatial immediately to the back buffer. + */ + public static final RenderBucketType Skip = getRenderBucketType("Skip"); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java new file mode 100644 index 0000000..facd51c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.queue; + +import java.util.Map; + +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.InstancingManager; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.Constants; +import com.google.common.collect.Maps; + +public class RenderQueue { + + private final Map<RenderBucketType, RenderBucket> renderBuckets = Maps.newLinkedHashMap(); + + public RenderQueue() { + setupDefaultBuckets(); + } + + public void setupBuckets(final RenderBucketType[] renderBucketTypes, final RenderBucket[] buckets) { + if (renderBucketTypes.length != buckets.length) { + throw new Ardor3dException("Can't setup buckets, RenderBucketType and RenderBucket counts don't match."); + } + removeRenderBuckets(); + for (int i = 0; i < renderBucketTypes.length; i++) { + setRenderBucket(renderBucketTypes[i], buckets[i]); + } + } + + private void setupDefaultBuckets() { + setRenderBucket(RenderBucketType.PreBucket, new OpaqueRenderBucket()); + setRenderBucket(RenderBucketType.Shadow, new OpaqueRenderBucket()); + setRenderBucket(RenderBucketType.Opaque, new OpaqueRenderBucket()); + setRenderBucket(RenderBucketType.Transparent, new TransparentRenderBucket()); + setRenderBucket(RenderBucketType.Ortho, new OrthoRenderBucket()); + setRenderBucket(RenderBucketType.PostBucket, new OpaqueRenderBucket()); + } + + public void removeRenderBuckets() { + renderBuckets.clear(); + } + + public void removeRenderBucket(final RenderBucketType type) { + renderBuckets.remove(type); + } + + public void setRenderBucket(final RenderBucketType type, final RenderBucket renderBucket) { + renderBuckets.put(type, renderBucket); + } + + public RenderBucket getRenderBucket(final RenderBucketType type) { + return renderBuckets.get(type); + } + + public void addToQueue(final Spatial spatial, final RenderBucketType type) { + if (type == RenderBucketType.Inherit || type == RenderBucketType.Skip) { + throw new Ardor3dException("Can't add spatial to bucket of type: " + type); + } + + if (Constants.enableInstancedGeometrySupport && prepareForInstancing(spatial)) { + return; + } + + final RenderBucket renderBucket = getRenderBucket(type); + if (renderBucket != null) { + renderBucket.add(spatial); + } else { + throw new Ardor3dException("No bucket exists of type: " + type); + } + } + + private final boolean prepareForInstancing(final Spatial spatial) { + boolean skipRenderQueue = false; + + if (spatial instanceof Mesh) { + final Mesh mesh = (Mesh) spatial; + final InstancingManager instancing = mesh.getMeshData().getInstancingManager(); + // Only one instance needs to be added to the render queue + if (instancing != null) { + skipRenderQueue = instancing.isAddedToRenderQueue(); + instancing.registerMesh(mesh); + } + } + return skipRenderQueue; + } + + public void removeFromQueue(final Spatial spatial, final RenderBucketType type) { + if (type == RenderBucketType.Inherit || type == RenderBucketType.Skip) { + throw new Ardor3dException("Can't remove spatial from bucket of type: " + type); + } + + final RenderBucket renderBucket = getRenderBucket(type); + if (renderBucket != null) { + renderBucket.remove(spatial); + } else { + throw new Ardor3dException("No bucket exists of type: " + type); + } + } + + public void clearBuckets() { + for (final RenderBucket renderBucket : renderBuckets.values()) { + renderBucket.clear(); + } + } + + public void sortBuckets() { + for (final RenderBucket renderBucket : renderBuckets.values()) { + renderBucket.sort(); + } + } + + public void renderOnly(final Renderer renderer) { + for (final RenderBucket renderBucket : renderBuckets.values()) { + renderBucket.render(renderer); + } + } + + public void renderBuckets(final Renderer renderer) { + for (final RenderBucket renderBucket : renderBuckets.values()) { + renderBucket.sort(); + renderBucket.render(renderer); + renderBucket.clear(); + } + } + + public void pushBuckets() { + for (final RenderBucket renderBucket : renderBuckets.values()) { + renderBucket.pushBucket(); + } + } + + public void popBuckets() { + for (final RenderBucket renderBucket : renderBuckets.values()) { + renderBucket.popBucket(); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java new file mode 100644 index 0000000..fe3e73d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.queue; + +import java.util.Comparator; + +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.CullState; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.renderer.state.CullState.Face; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.TransparencyType; + +public class TransparentRenderBucket extends AbstractRenderBucket { + /** CullState for two pass transparency rendering. */ + private final CullState _tranparentCull; + + /** ZBufferState for two pass transparency rendering. */ + private final ZBufferState _transparentZBuff; + + public TransparentRenderBucket() { + super(); + + _tranparentCull = new CullState(); + _transparentZBuff = new ZBufferState(); + _transparentZBuff.setWritable(false); + _transparentZBuff.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo); + + _comparator = new TransparentComparator(); + } + + @Override + public void render(final Renderer renderer) { + // Grab our render context - used to enforce renderstates + final RenderContext context = ContextManager.getCurrentContext(); + + // go through our bucket contents + Spatial spatial; + for (int i = 0; i < _currentListSize; i++) { + spatial = _currentList[i]; + + // make sure we have a real spatial + if (spatial == null) { + continue; + } + + // we only care about altering the Mesh... perhaps could be altered later to some interface shared by Mesh + // and other leaf nodes. + if (spatial instanceof Mesh) { + + // get our transparency rendering type. + final TransparencyType renderType = spatial.getSceneHints().getTransparencyType(); + + // check for one of the two pass types... + if (renderType != TransparencyType.OnePass) { + + // get handle to Mesh + final Mesh mesh = (Mesh) spatial; + + // check if we have a Cull state set or enforced. If one is explicitly set and is not Face.None, + // we'll not do two-pass transparency. + RenderState setState = context.hasEnforcedStates() ? context.getEnforcedState(StateType.Cull) + : null; + if (setState == null) { + setState = mesh.getWorldRenderState(RenderState.StateType.Cull); + } + + // Do the described check. + if (setState == null || ((CullState) setState).getCullFace() == Face.None) { + + // pull any currently enforced cull or zstate. We'll put them back afterwards + final RenderState oldCullState = context.getEnforcedState(StateType.Cull); + final RenderState oldZState = context.getEnforcedState(StateType.ZBuffer); + + // enforce our cull and zstate. The zstate is setup to respect depth, but not write to it. + context.enforceState(_tranparentCull); + context.enforceState(_transparentZBuff); + + // first render back-facing tris only + _tranparentCull.setCullFace(CullState.Face.Front); + mesh.draw(renderer); + + // revert z state + context.clearEnforcedState(StateType.ZBuffer); + if (oldZState != null) { + context.enforceState(oldZState); + } + + // render front-facing tris + _tranparentCull.setCullFace(CullState.Face.Back); + mesh.draw(renderer); + + // revert cull state + if (oldCullState != null) { + context.enforceState(oldCullState); + } else { + context.clearEnforcedState(StateType.Cull); + } + continue; + } + } + } + + // go ahead and draw as usual + spatial.draw(renderer); + } + } + + private class TransparentComparator implements Comparator<Spatial> { + public int compare(final Spatial o1, final Spatial o2) { + final double d1 = distanceToCam(o1); + final double d2 = distanceToCam(o2); + if (d1 > d2) { + return -1; + } else if (d1 < d2) { + return 1; + } else { + return 0; + } + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java new file mode 100644 index 0000000..5b2ed39 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java @@ -0,0 +1,636 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.record.BlendStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>BlendState</code> maintains the state of the blending values of a particular node and its children. The blend + * state provides a method for blending a source pixel with a destination pixel. The blend value provides a transparent + * or translucent surfaces. For example, this would allow for the rendering of green glass. Where you could see all + * objects behind this green glass but they would be tinted green. + */ +public class BlendState extends RenderState { + + public enum SourceFunction { + /** + * The source value of the blend function is all zeros. + */ + Zero(false), + /** + * The source value of the blend function is all ones. + */ + One(false), + /** + * The source value of the blend function is the destination color. + */ + DestinationColor(false), + /** + * The source value of the blend function is 1 - the destination color. + */ + OneMinusDestinationColor(false), + /** + * The source value of the blend function is the source alpha value. + */ + SourceAlpha(false), + /** + * The source value of the blend function is 1 - the source alpha value. + */ + OneMinusSourceAlpha(false), + /** + * The source value of the blend function is the destination alpha. + */ + DestinationAlpha(false), + /** + * The source value of the blend function is 1 - the destination alpha. + */ + OneMinusDestinationAlpha(false), + /** + * The source value of the blend function is the minimum of alpha or 1 - alpha. + */ + SourceAlphaSaturate(false), + /** + * The source value of the blend function is the value of the constant color. (Rc, Gc, Bc, Ac) If not set, black + * with alpha = 0 is used. If not supported, falls back to One. + */ + ConstantColor(true), + /** + * The source value of the blend function is 1 minus the value of the constant color. (1-Rc, 1-Gc, 1-Bc, 1-Ac) + * If color is not set, black with alpha = 0 is used. If not supported, falls back to One. + */ + OneMinusConstantColor(true), + /** + * The source value of the blend function is the value of the constant color's alpha. (Ac, Ac, Ac, Ac) If not + * set, black with alpha = 0 is used. If not supported, falls back to One. + */ + ConstantAlpha(true), + /** + * The source value of the blend function is 1 minus the value of the constant color's alpha. (1-Ac, 1-Ac, 1-Ac, + * 1-Ac) If color is not set, black with alpha = 0 is used. If not supported, falls back to One. + */ + OneMinusConstantAlpha(true); + + private boolean usesConstantColor; + + private SourceFunction(final boolean usesConstantColor) { + this.usesConstantColor = usesConstantColor; + } + + public boolean usesConstantColor() { + return usesConstantColor; + } + } + + public enum DestinationFunction { + /** + * The destination value of the blend function is all zeros. + */ + Zero(false), + /** + * The destination value of the blend function is all ones. + */ + One(false), + /** + * The destination value of the blend function is the source color. + */ + SourceColor(false), + /** + * The destination value of the blend function is 1 - the source color. + */ + OneMinusSourceColor(false), + /** + * The destination value of the blend function is the source alpha value. + */ + SourceAlpha(false), + /** + * The destination value of the blend function is 1 - the source alpha value. + */ + OneMinusSourceAlpha(false), + /** + * The destination value of the blend function is the destination alpha value. + */ + DestinationAlpha(false), + /** + * The destination value of the blend function is 1 - the destination alpha value. + */ + OneMinusDestinationAlpha(false), + /** + * The destination value of the blend function is the value of the constant color. (Rc, Gc, Bc, Ac) If not set, + * black with alpha = 0 is used. If not supported, falls back to One. + */ + ConstantColor(true), + /** + * The destination value of the blend function is 1 minus the value of the constant color. (1-Rc, 1-Gc, 1-Bc, + * 1-Ac) If color is not set, black with alpha = 0 is used. If not supported, falls back to One. + */ + OneMinusConstantColor(true), + /** + * The destination value of the blend function is the value of the constant color's alpha. (Ac, Ac, Ac, Ac) If + * not set, black with alpha = 0 is used. If not supported, falls back to One. + */ + ConstantAlpha(true), + /** + * The destination value of the blend function is 1 minus the value of the constant color's alpha. (1-Ac, 1-Ac, + * 1-Ac, 1-Ac) If color is not set, black with alpha = 0 is used. If not supported, falls back to One. + */ + OneMinusConstantAlpha(true); + + private boolean usesConstantColor; + + private DestinationFunction(final boolean usesConstantColor) { + this.usesConstantColor = usesConstantColor; + } + + public boolean usesConstantColor() { + return usesConstantColor; + } + } + + public enum TestFunction { + /** + * Never passes the depth test. + */ + Never, + /** + * Always passes the depth test. + */ + Always, + /** + * Pass the test if this alpha is equal to the reference alpha. + */ + EqualTo, + /** + * Pass the test if this alpha is not equal to the reference alpha. + */ + NotEqualTo, + /** + * Pass the test if this alpha is less than the reference alpha. + */ + LessThan, + /** + * Pass the test if this alpha is less than or equal to the reference alpha. + */ + LessThanOrEqualTo, + /** + * Pass the test if this alpha is greater than the reference alpha. + */ + GreaterThan, + /** + * Pass the test if this alpha is greater than or equal to the reference alpha. + */ + GreaterThanOrEqualTo; + + } + + public enum BlendEquation { + /** + * Sets the blend equation so that the source and destination data are added. (Default) Clamps to [0,1] Useful + * for things like antialiasing and transparency. + */ + Add, + /** + * Sets the blend equation so that the source and destination data are subtracted (Src - Dest). Clamps to [0,1] + * Falls back to Add if supportsSubtract is false. + */ + Subtract, + /** + * Same as Subtract, but the order is reversed (Dst - Src). Clamps to [0,1] Falls back to Add if + * supportsSubtract is false. + */ + ReverseSubtract, + /** + * sets the blend equation so that each component of the result color is the minimum of the corresponding + * components of the source and destination colors. This and Max are useful for applications that analyze image + * data (image thresholding against a constant color, for example). Falls back to Add if supportsMinMax is + * false. + */ + Min, + /** + * sets the blend equation so that each component of the result color is the maximum of the corresponding + * components of the source and destination colors. This and Min are useful for applications that analyze image + * data (image thresholding against a constant color, for example). Falls back to Add if supportsMinMax is + * false. + */ + Max; + } + + // attributes + /** The current value of if blend is enabled. */ + private boolean _blendEnabled = false; + + /** The blend color used in constant blend operations. */ + private ColorRGBA _constantColor = new ColorRGBA(0, 0, 0, 0); + + /** The current source blend function. */ + private SourceFunction _sourceFunctionRGB = SourceFunction.SourceAlpha; + /** The current destination blend function. */ + private DestinationFunction _destinationFunctionRGB = DestinationFunction.OneMinusSourceAlpha; + /** The current blend equation. */ + private BlendEquation _blendEquationRGB = BlendEquation.Add; + + /** The current source blend function. */ + private SourceFunction _sourceFunctionAlpha = SourceFunction.SourceAlpha; + /** The current destination blend function. */ + private DestinationFunction _destinationFunctionAlpha = DestinationFunction.OneMinusSourceAlpha; + /** The current blend equation. */ + private BlendEquation _blendEquationAlpha = BlendEquation.Add; + + /** If enabled, alpha testing done. */ + private boolean _testEnabled = false; + /** Alpha test value. */ + private TestFunction _testFunction = TestFunction.GreaterThan; + /** The reference value to which incoming alpha values are compared. */ + private float _reference = 0; + + /** Enables conversion of alpha values to masks - a form of dithering. */ + private boolean _sampleAlphaToCoverageEnabled = false; + /** Replaces alpha sample with max value. */ + private boolean _sampleAlphaToOneEnabled = false; + /** Enables fragment mask modification. */ + private boolean _sampleCoverageEnabled = false; + /** a mask that modifies the coverage of multi-sampled pixel fragments. Must be [0, 1] */ + private float _sampleCoverage = 1.0f; + /** If enabled, sample coverage mask is inverted. */ + private boolean _sampleCoverageInverted = false; + + /** + * Constructor instantiates a new <code>BlendState</code> object with default values. + */ + public BlendState() {} + + @Override + public StateType getType() { + return StateType.Blend; + } + + /** + * <code>isBlendEnabled</code> returns true if blending is turned on, otherwise false is returned. + * + * @return true if blending is enabled, false otherwise. + */ + public boolean isBlendEnabled() { + return _blendEnabled; + } + + /** + * <code>setBlendEnabled</code> sets whether or not blending is enabled. + * + * @param value + * true to enable the blending, false to disable it. + */ + public void setBlendEnabled(final boolean value) { + _blendEnabled = value; + setNeedsRefresh(true); + } + + /** + * <code>setSrcFunction</code> sets the source function for the blending equation for both rgb and alpha values. + * + * @param function + * the source function for the blending equation. + * @throws IllegalArgumentException + * if function is null + */ + public void setSourceFunction(final SourceFunction function) { + setSourceFunctionRGB(function); + setSourceFunctionAlpha(function); + } + + /** + * <code>setSrcFunction</code> sets the source function for the blending equation. If supportsSeparateFunc is false, + * this value will be used for RGB and Alpha. + * + * @param function + * the source function for the blending equation. + * @throws IllegalArgumentException + * if function is null + */ + public void setSourceFunctionRGB(final SourceFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _sourceFunctionRGB = function; + setNeedsRefresh(true); + } + + /** + * <code>setSourceFunctionAlpha</code> sets the source function for the blending equation used with alpha values. + * + * @param function + * the source function for the blending equation for alpha values. + * @throws IllegalArgumentException + * if function is null + */ + public void setSourceFunctionAlpha(final SourceFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _sourceFunctionAlpha = function; + setNeedsRefresh(true); + } + + /** + * <code>getSourceFunction</code> returns the source function for the blending function. + * + * @return the source function for the blending function. + */ + public SourceFunction getSourceFunctionRGB() { + return _sourceFunctionRGB; + } + + /** + * <code>getSourceFunction</code> returns the source function for the blending function. + * + * @return the source function for the blending function. + */ + public SourceFunction getSourceFunctionAlpha() { + return _sourceFunctionAlpha; + } + + /** + * <code>setDestinationFunction</code> sets the destination function for the blending equation for both Alpha and + * RGB values. + * + * @param function + * the destination function for the blending equation. + * @throws IllegalArgumentException + * if function is null + */ + public void setDestinationFunction(final DestinationFunction function) { + setDestinationFunctionRGB(function); + setDestinationFunctionAlpha(function); + } + + /** + * <code>setDestinationFunctionRGB</code> sets the destination function for the blending equation. If + * supportsSeparateFunc is false, this value will be used for RGB and Alpha. + * + * @param function + * the destination function for the blending equation for RGB values. + * @throws IllegalArgumentException + * if function is null + */ + public void setDestinationFunctionRGB(final DestinationFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _destinationFunctionRGB = function; + setNeedsRefresh(true); + } + + /** + * <code>setDestinationFunctionAlpha</code> sets the destination function for the blending equation. + * + * @param function + * the destination function for the blending equation for Alpha values. + * @throws IllegalArgumentException + * if function is null + */ + public void setDestinationFunctionAlpha(final DestinationFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _destinationFunctionAlpha = function; + setNeedsRefresh(true); + } + + /** + * <code>getDestinationFunction</code> returns the destination function for the blending function. + * + * @return the destination function for the blending function. + */ + public DestinationFunction getDestinationFunctionRGB() { + return _destinationFunctionRGB; + } + + /** + * <code>getDestinationFunction</code> returns the destination function for the blending function. + * + * @return the destination function for the blending function. + */ + public DestinationFunction getDestinationFunctionAlpha() { + return _destinationFunctionAlpha; + } + + public void setBlendEquation(final BlendEquation blendEquation) { + setBlendEquationRGB(blendEquation); + setBlendEquationAlpha(blendEquation); + } + + public void setBlendEquationRGB(final BlendEquation blendEquation) { + if (blendEquation == null) { + throw new IllegalArgumentException("blendEquation can not be null."); + } + _blendEquationRGB = blendEquation; + } + + public void setBlendEquationAlpha(final BlendEquation blendEquation) { + if (blendEquation == null) { + throw new IllegalArgumentException("blendEquation can not be null."); + } + _blendEquationAlpha = blendEquation; + } + + public BlendEquation getBlendEquationRGB() { + return _blendEquationRGB; + } + + public BlendEquation getBlendEquationAlpha() { + return _blendEquationAlpha; + } + + /** + * <code>isTestEnabled</code> returns true if alpha testing is enabled, false otherwise. + * + * @return true if alpha testing is enabled, false otherwise. + */ + public boolean isTestEnabled() { + return _testEnabled; + } + + /** + * <code>setTestEnabled</code> turns alpha testing on and off. True turns on the testing, while false diables it. + * + * @param value + * true to enabled alpha testing, false to disable it. + */ + public void setTestEnabled(final boolean value) { + _testEnabled = value; + setNeedsRefresh(true); + } + + /** + * <code>setTestFunction</code> sets the testing function used for the alpha testing. If an invalid value is passed, + * the default TF_ALWAYS is used. + * + * @param function + * the testing function used for the alpha testing. + * @throws IllegalArgumentException + * if function is null + */ + public void setTestFunction(final TestFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _testFunction = function; + setNeedsRefresh(true); + } + + /** + * <code>getTestFunction</code> returns the testing function used for the alpha testing. + * + * @return the testing function used for the alpha testing. + */ + public TestFunction getTestFunction() { + return _testFunction; + } + + /** + * <code>setReference</code> sets the reference value that incoming alpha values are compared to when doing alpha + * testing. This is clamped to [0, 1]. + * + * @param reference + * the reference value that alpha values are compared to. + */ + public void setReference(float reference) { + if (reference < 0) { + reference = 0; + } + + if (reference > 1) { + reference = 1; + } + _reference = reference; + setNeedsRefresh(true); + } + + /** + * <code>getReference</code> returns the reference value that incoming alpha values are compared to. + * + * @return the reference value that alpha values are compared to. + */ + public float getReference() { + return _reference; + } + + /** + * @return the color used in constant blending functions. (0,0,0,0) is the default. + */ + public ReadOnlyColorRGBA getConstantColor() { + return _constantColor; + } + + public void setConstantColor(final ReadOnlyColorRGBA constantColor) { + _constantColor.set(constantColor); + } + + public boolean isSampleAlphaToCoverageEnabled() { + return _sampleAlphaToCoverageEnabled; + } + + public void setSampleAlphaToCoverageEnabled(final boolean sampleAlphaToCoverageEnabled) { + _sampleAlphaToCoverageEnabled = sampleAlphaToCoverageEnabled; + } + + public boolean isSampleAlphaToOneEnabled() { + return _sampleAlphaToOneEnabled; + } + + public void setSampleAlphaToOneEnabled(final boolean sampleAlphaToOneEnabled) { + _sampleAlphaToOneEnabled = sampleAlphaToOneEnabled; + } + + public boolean isSampleCoverageEnabled() { + return _sampleCoverageEnabled; + } + + public void setSampleCoverageEnabled(final boolean sampleCoverageEnabled) { + _sampleCoverageEnabled = sampleCoverageEnabled; + } + + public float getSampleCoverage() { + return _sampleCoverage; + } + + /** + * @param value + * new sample coverage value - must be in range [0f, 1f] + * @throws IllegalArgumentException + * if value is not in correct range. + */ + public void setSampleCoverage(final float value) { + if (value > 1.0f || value < 0.0f) { + throw new IllegalArgumentException("value must be in range [0f, 1f]"); + } + _sampleCoverage = value; + } + + public boolean isSampleCoverageInverted() { + return _sampleCoverageInverted; + } + + public void setSampleCoverageInverted(final boolean sampleCoverageInverted) { + _sampleCoverageInverted = sampleCoverageInverted; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_blendEnabled, "blendEnabled", false); + capsule.write(_sourceFunctionRGB, "sourceFunctionRGB", SourceFunction.SourceAlpha); + capsule.write(_destinationFunctionRGB, "destinationFunctionRGB", DestinationFunction.OneMinusSourceAlpha); + capsule.write(_blendEquationRGB, "blendEquationRGB", BlendEquation.Add); + capsule.write(_sourceFunctionAlpha, "sourceFunctionAlpha", SourceFunction.SourceAlpha); + capsule.write(_destinationFunctionAlpha, "destinationFunctionAlpha", DestinationFunction.OneMinusSourceAlpha); + capsule.write(_blendEquationAlpha, "blendEquationAlpha", BlendEquation.Add); + capsule.write(_testEnabled, "testEnabled", false); + capsule.write(_testFunction, "test", TestFunction.GreaterThan); + capsule.write(_reference, "reference", 0); + capsule.write(_constantColor, "constantColor", null); + capsule.write(_sampleAlphaToCoverageEnabled, "sampleAlphaToCoverageEnabled", false); + capsule.write(_sampleAlphaToOneEnabled, "sampleAlphaToOneEnabled", false); + capsule.write(_sampleCoverageEnabled, "sampleCoverageEnabled", false); + capsule.write(_sampleCoverageInverted, "sampleCoverageInverted", false); + capsule.write(_sampleCoverage, "sampleCoverage", 1.0f); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _blendEnabled = capsule.readBoolean("blendEnabled", false); + _sourceFunctionRGB = capsule.readEnum("sourceFunctionRGB", SourceFunction.class, SourceFunction.SourceAlpha); + _destinationFunctionRGB = capsule.readEnum("destinationFunctionRGB", DestinationFunction.class, + DestinationFunction.OneMinusSourceAlpha); + _blendEquationRGB = capsule.readEnum("blendEquationRGB", BlendEquation.class, BlendEquation.Add); + _sourceFunctionAlpha = capsule + .readEnum("sourceFunctionAlpha", SourceFunction.class, SourceFunction.SourceAlpha); + _destinationFunctionAlpha = capsule.readEnum("destinationFunctionAlpha", DestinationFunction.class, + DestinationFunction.OneMinusSourceAlpha); + _blendEquationAlpha = capsule.readEnum("blendEquationAlpha", BlendEquation.class, BlendEquation.Add); + _testEnabled = capsule.readBoolean("testEnabled", false); + _testFunction = capsule.readEnum("test", TestFunction.class, TestFunction.GreaterThan); + _reference = capsule.readFloat("reference", 0); + _constantColor = (ColorRGBA) capsule.readSavable("constantColor", null); + } + + @Override + public StateRecord createStateRecord() { + return new BlendStateRecord(); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java new file mode 100644 index 0000000..0d809e9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.ClipStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>ClipState</code> specifies a plane to test for clipping of the nodes. This can be used to take "slices" out of + * geometric objects. ClipPlane can add an additional (to the normal frustum planes) six planes to clip against. + */ +public class ClipState extends RenderState { + + /** + * Max supported number of user-defined clip planes in Ardor3D. Note that a user may or may not have access to all 6 + * (or even any!) or their particular platform. Check ContextCapabilities to confirm as necessary. + */ + public static final int MAX_CLIP_PLANES = 6; + + protected boolean[] enabledClipPlanes = new boolean[MAX_CLIP_PLANES]; + + protected double[][] planeEquations = new double[MAX_CLIP_PLANES][4]; + + @Override + public StateType getType() { + return StateType.Clip; + } + + /** + * Enables/disables a specific clip plane + * + * @param planeIndex + * Plane to enable/disable (CLIP_PLANE0-CLIP_PLANE5) + * @param enabled + * true/false + */ + public void setEnableClipPlane(final int planeIndex, final boolean enabled) { + if (planeIndex < 0 || planeIndex >= MAX_CLIP_PLANES) { + return; + } + + enabledClipPlanes[planeIndex] = enabled; + setNeedsRefresh(true); + } + + /** + * Sets plane equation for a specific clip plane + * + * @param planeIndex + * Plane to set equation for (CLIP_PLANE0-CLIP_PLANE5) + * @param clipX + * plane x variable + * @param clipY + * plane y variable + * @param clipZ + * plane z variable + * @param clipW + * plane w variable + */ + public void setClipPlaneEquation(final int planeIndex, final double clipX, final double clipY, final double clipZ, + final double clipW) { + if (planeIndex < 0 || planeIndex >= MAX_CLIP_PLANES) { + return; + } + + planeEquations[planeIndex][0] = clipX; + planeEquations[planeIndex][1] = clipY; + planeEquations[planeIndex][2] = clipZ; + planeEquations[planeIndex][3] = clipW; + setNeedsRefresh(true); + } + + /** + * @param index + * plane to check + * @return true if given clip plane is enabled + */ + public boolean getPlaneEnabled(final int index) { + return enabledClipPlanes[index]; + } + + public double[] getPlaneEquations(final int plane) { + return planeEquations[plane]; + } + + public double getPlaneEquation(final int plane, final int eqIndex) { + return planeEquations[plane][eqIndex]; + } + + public void setPlaneEq(final int plane, final int eqIndex, final double value) { + planeEquations[plane][eqIndex] = value; + setNeedsRefresh(true); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(enabledClipPlanes, "enabledClipPlanes", new boolean[MAX_CLIP_PLANES]); + capsule.write(planeEquations, "planeEquations", new double[MAX_CLIP_PLANES][4]); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + enabledClipPlanes = capsule.readBooleanArray("enabledClipPlanes", new boolean[MAX_CLIP_PLANES]); + planeEquations = capsule.readDoubleArray2D("planeEquations", new double[MAX_CLIP_PLANES][4]); + } + + @Override + public StateRecord createStateRecord() { + return new ClipStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java new file mode 100644 index 0000000..b07fb0c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.ColorMaskStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>ColorMaskState</code> + */ +public class ColorMaskState extends RenderState { + + protected boolean blue = true; + protected boolean green = true; + protected boolean red = true; + protected boolean alpha = true; + + @Override + public StateType getType() { + return StateType.ColorMask; + } + + public void setAll(final boolean on) { + blue = on; + green = on; + red = on; + alpha = on; + setNeedsRefresh(true); + } + + /** + * @return Returns the alpha. + */ + public boolean getAlpha() { + return alpha; + } + + /** + * @param alpha + * The alpha to set. + */ + public void setAlpha(final boolean alpha) { + this.alpha = alpha; + setNeedsRefresh(true); + } + + /** + * @return Returns the blue. + */ + public boolean getBlue() { + return blue; + } + + /** + * @param blue + * The blue to set. + */ + public void setBlue(final boolean blue) { + this.blue = blue; + setNeedsRefresh(true); + } + + /** + * @return Returns the green. + */ + public boolean getGreen() { + return green; + } + + /** + * @param green + * The green to set. + */ + public void setGreen(final boolean green) { + this.green = green; + setNeedsRefresh(true); + } + + /** + * @return Returns the red. + */ + public boolean getRed() { + return red; + } + + /** + * @param red + * The red to set. + */ + public void setRed(final boolean red) { + this.red = red; + setNeedsRefresh(true); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(blue, "blue", true); + capsule.write(green, "green", true); + capsule.write(red, "red", true); + capsule.write(alpha, "alpha", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + blue = capsule.readBoolean("blue", true); + green = capsule.readBoolean("green", true); + red = capsule.readBoolean("red", true); + alpha = capsule.readBoolean("alpha", true); + } + + @Override + public StateRecord createStateRecord() { + return new ColorMaskStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java new file mode 100644 index 0000000..3ad5cc4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.CullStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>CullState</code> determines which side of a model will be visible when it is rendered. By default, both sides + * are visible. Define front as the side that traces its vertexes counter clockwise and back as the side that traces its + * vertexes clockwise, a side (front or back) can be culled, or not shown when the model is rendered. Instead, the side + * will be transparent. <br> + * <b>NOTE:</b> Any object that is placed in the transparent queue with two sided transparency will not use the + * cullstate that is attached to it. Instead, using the CullStates necessary for rendering two sided transparency. + */ +public class CullState extends RenderState { + + public enum Face { + /** Neither front or back face is culled. This is default. */ + None, + /** Cull the front faces. */ + Front, + /** Cull the back faces. */ + Back, + /** Cull both the front and back faces. */ + FrontAndBack; + } + + public enum PolygonWind { + /** Polygons whose vertices are specified in CCW order are front facing. This is default. */ + CounterClockWise, + /** Polygons whose vertices are specified in CW order are front facing. */ + ClockWise; + } + + /** The cull face set for this CullState. */ + private Face cullFace = Face.None; + + /** The polygonWind order set for this CullState. */ + private PolygonWind polygonWind = PolygonWind.CounterClockWise; + + @Override + public StateType getType() { + return StateType.Cull; + } + + /** + * @param face + * The new face to cull. + */ + public void setCullFace(final Face face) { + cullFace = face; + setNeedsRefresh(true); + } + + /** + * @return the currently set face to cull. + */ + public Face getCullFace() { + return cullFace; + } + + /** + * @param windOrder + * The new polygonWind order. + */ + public void setPolygonWind(final PolygonWind windOrder) { + polygonWind = windOrder; + setNeedsRefresh(true); + } + + /** + * @return the currently set polygonWind order. + */ + public PolygonWind getPolygonWind() { + return polygonWind; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(cullFace, "cullFace", Face.None); + capsule.write(polygonWind, "polygonWind", PolygonWind.CounterClockWise); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + cullFace = capsule.readEnum("cullFace", Face.class, Face.None); + polygonWind = capsule.readEnum("polygonWind", PolygonWind.class, PolygonWind.CounterClockWise); + } + + @Override + public StateRecord createStateRecord() { + return new CullStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java new file mode 100644 index 0000000..eae1444 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.record.FogStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>FogState</code> maintains the fog qualities for a node and it's children. The fogging function, color, start, + * end and density are all set and maintained. Please note that fog does not affect alpha. + */ +public class FogState extends RenderState { + + public enum DensityFunction { + /** + * The fog blending function defined as: (end - z) / (end - start). + */ + Linear, + /** + * The fog blending function defined as: e^-(density*z) + */ + Exponential, + /** + * The fog blending function defined as: e^((-density*z)^2) + */ + ExponentialSquared; + } + + public enum CoordinateSource { + /** The source of the fogging value is based on the depth buffer */ + Depth, + /** The source of the fogging value is based on the specified fog coordinates */ + FogCoords + } + + public enum Quality { + /** + * Each vertex color is altered by the fogging function. + */ + PerVertex, + /** + * Each pixel color is altered by the fogging function. + */ + PerPixel; + } + + // fogging attributes. + protected float start = 0; + protected float end = 1; + protected float density = 1.0f; + protected final ColorRGBA color = new ColorRGBA(); + protected DensityFunction densityFunction = DensityFunction.Exponential; + protected Quality quality = Quality.PerVertex; + protected CoordinateSource source = CoordinateSource.Depth; + + /** + * Constructor instantiates a new <code>FogState</code> with default fog values. + */ + public FogState() {} + + /** + * <code>setQuality</code> sets the quality used for the fog attributes. + * + * @param quality + * the quality used for the fog application. + * @throws IllegalArgumentException + * if quality is null + */ + public void setQuality(final Quality quality) { + if (quality == null) { + throw new IllegalArgumentException("quality can not be null."); + } + this.quality = quality; + setNeedsRefresh(true); + } + + /** + * <code>setDensityFunction</code> sets the density function used for the fog blending. + * + * @param function + * the function used for the fog density. + * @throws IllegalArgumentException + * if function is null + */ + public void setDensityFunction(final DensityFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + densityFunction = function; + setNeedsRefresh(true); + } + + /** + * <code>setColor</code> sets the color of the fog. + * + * @param color + * the color of the fog. This value is COPIED into the state. Further changes to the object after calling + * this method will have no affect on this state. + */ + public void setColor(final ReadOnlyColorRGBA color) { + this.color.set(color); + setNeedsRefresh(true); + } + + /** + * <code>setDensity</code> sets the density of the fog. This value is clamped to [0, 1]. + * + * @param density + * the density of the fog. + */ + public void setDensity(float density) { + if (density < 0) { + density = 0; + } + + if (density > 1) { + density = 1; + } + this.density = density; + setNeedsRefresh(true); + } + + /** + * <code>setEnd</code> sets the end distance, or the distance where fog is at it's thickest. + * + * @param end + * the distance where the fog is the thickest. + */ + public void setEnd(final float end) { + this.end = end; + setNeedsRefresh(true); + } + + /** + * <code>setStart</code> sets the start distance, or where fog begins to be applied. + * + * @param start + * the start distance of the fog. + */ + public void setStart(final float start) { + this.start = start; + setNeedsRefresh(true); + } + + public void setSource(final CoordinateSource source) { + this.source = source; + } + + public CoordinateSource getSource() { + return source; + } + + @Override + public StateType getType() { + return StateType.Fog; + } + + public Quality getQuality() { + return quality; + } + + public ReadOnlyColorRGBA getColor() { + return color; + } + + public float getDensity() { + return density; + } + + public DensityFunction getDensityFunction() { + return densityFunction; + } + + public float getEnd() { + return end; + } + + public float getStart() { + return start; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(start, "start", 0); + capsule.write(end, "end", 0); + capsule.write(density, "density", 0); + capsule.write(color, "color", new ColorRGBA(ColorRGBA.WHITE)); + capsule.write(densityFunction, "densityFunction", DensityFunction.Exponential); + capsule.write(quality, "applyFunction", Quality.PerPixel); + capsule.write(source, "source", CoordinateSource.Depth); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + start = capsule.readFloat("start", 0); + end = capsule.readFloat("end", 0); + density = capsule.readFloat("density", 0); + color.set((ColorRGBA) capsule.readSavable("color", new ColorRGBA(ColorRGBA.WHITE))); + densityFunction = capsule.readEnum("densityFunction", DensityFunction.class, DensityFunction.Exponential); + quality = capsule.readEnum("applyFunction", Quality.class, Quality.PerPixel); + source = capsule.readEnum("source", CoordinateSource.class, CoordinateSource.Depth); + } + + @Override + public StateRecord createStateRecord() { + return new FogStateRecord(); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java new file mode 100644 index 0000000..7646547 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.renderer.state.record.FragmentProgramStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class FragmentProgramState extends RenderState { + private static final Logger logger = Logger.getLogger(FragmentProgramState.class.getName()); + + /** If any local parameters for this FP state are set */ + protected boolean usingParameters = false; + + /** Parameters local to this fragment program */ + protected float[][] parameters; + protected ByteBuffer program; + protected int _programID = -1; + + /** + * <code>setEnvParameter</code> sets an environmental fragment program parameter that is accessable by all fragment + * programs in memory. + * + * @param param + * four-element array of floating point numbers + * @param paramID + * identity number of the parameter, ranging from 0 to 95 + */ + // TODO: Reevaluate how this is done. + /* + * public static void setEnvParameter(float[] param, int paramID){ if (paramID < 0 || paramID > 95) throw new + * IllegalArgumentException("Invalid parameter ID"); if (param != null && param.length != 4) throw new + * IllegalArgumentException("Vertex program parameters must be of type float[4]"); + * + * envparameters[paramID] = param; } + */ + + public FragmentProgramState() { + parameters = new float[24][]; + } + + /** + * <code>setParameter</code> sets a parameter for this fragment program. + * + * @param paramID + * identity number of the parameter, ranging from 0 to 23 + * @param param + * four-element array of floating point numbers + */ + public void setParameter(final float[] param, final int paramID) { + if (paramID < 0 || paramID > 23) { + throw new IllegalArgumentException("Invalid parameter ID"); + } + if (param != null && param.length != 4) { + throw new IllegalArgumentException("Fragment program parameters must be of type float[4]"); + } + + usingParameters = true; + parameters[paramID] = param; + setNeedsRefresh(true); + } + + @Override + public StateType getType() { + return StateType.FragmentProgram; + } + + /** + * Loads the fragment program into a byte array. + * + * @see com.ardor3d.renderer.state.FragmentProgramState#load(java.net.URL) + */ + public void load(final java.net.URL file) { + InputStream inputStream = null; + try { + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16 * 1024); + inputStream = new BufferedInputStream(file.openStream()); + final byte[] buffer = new byte[1024]; + int byteCount = -1; + + // Read the byte content into the output stream first + while ((byteCount = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, byteCount); + } + + // Set data with byte content from stream + final byte data[] = outputStream.toByteArray(); + + // Release resources + inputStream.close(); + outputStream.close(); + + program = BufferUtils.createByteBuffer(data.length); + program.put(data); + program.rewind(); + _programID = -1; + setNeedsRefresh(true); + } catch (final Exception e) { + logger.severe("Could not load fragment program: " + e); + logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e); + } finally { + // Ensure that the stream is closed, even if there is an exception. + if (inputStream != null) { + try { + inputStream.close(); + } catch (final IOException closeFailure) { + logger.log(Level.WARNING, "Failed to close the fragment program", closeFailure); + } + } + } + } + + /** + * Loads the fragment program into a byte array. + * + * @see com.ardor3d.renderer.state.FragmentProgramState#load(java.net.URL) + */ + public void load(final String programContents) { + try { + final byte[] bytes = programContents.getBytes(); + program = BufferUtils.createByteBuffer(bytes.length); + program.put(bytes); + program.rewind(); + _programID = -1; + setNeedsRefresh(true); + } catch (final Exception e) { + logger.severe("Could not load fragment program: " + e); + logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e); + } + } + + public ByteBuffer getProgramAsBuffer() { + return program; + } + + public int _getProgramID() { + return _programID; + } + + public void _setProgramID(final int id) { + _programID = id; + } + + public boolean isUsingParameters() { + return usingParameters; + } + + public float[][] _getParameters() { + return parameters; + } + + /** + * Used with Serialization. Do not call this directly. + * + * @param s + * @throws IOException + * @see java.io.Serializable + */ + private void writeObject(final java.io.ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + if (program == null) { + s.writeInt(0); + } else { + s.writeInt(program.capacity()); + program.rewind(); + for (int x = 0, len = program.capacity(); x < len; x++) { + s.writeByte(program.get()); + } + } + } + + /** + * Used with Serialization. Do not call this directly. + * + * @param s + * @throws IOException + * @throws ClassNotFoundException + * @see java.io.Serializable + */ + private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { + s.defaultReadObject(); + final int len = s.readInt(); + if (len == 0) { + program = null; + } else { + program = BufferUtils.createByteBuffer(len); + for (int x = 0; x < len; x++) { + program.put(s.readByte()); + } + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(usingParameters, "usingParameters", false); + capsule.write(parameters, "parameters", new float[24][]); + capsule.write(program, "program", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + usingParameters = capsule.readBoolean("usingParameters", false); + parameters = capsule.readFloatArray2D("parameters", new float[24][]); + program = capsule.readByteBuffer("program", null); + } + + @Override + public StateRecord createStateRecord() { + return new FragmentProgramStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java new file mode 100644 index 0000000..a2a5036 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Mesh; + +/** + * Logic responsible for transferring data from a geometry to a shader before rendering + */ +public interface GLSLShaderDataLogic { + /** + * Responsible for transferring data from a Mesh object to a shader before rendering + * + * @param shader + * Shader to update with new data(setUniform/setAttribute) + * @param meshData + * MeshData to retrieve data from + * @param renderer + * Current renderer + */ + void applyData(GLSLShaderObjectsState shader, Mesh mesh, Renderer renderer); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java new file mode 100644 index 0000000..dcbacf4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java @@ -0,0 +1,1333 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.math.type.ReadOnlyVector4; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.state.record.ShaderObjectsStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.scenegraph.ByteBufferData; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.IntBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.ShortBufferData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.shader.ShaderVariable; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat2; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat3; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat4; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloatArray; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt2; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt3; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt4; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableIntArray; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableMatrix3; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableMatrix4; +import com.ardor3d.util.shader.uniformtypes.ShaderVariableMatrix4Array; +import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerByte; +import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerFloat; +import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerFloatMatrix; +import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerInt; +import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerShort; + +/** + * Implementation of the GL_ARB_shader_objects extension. + */ +public class GLSLShaderObjectsState extends RenderState { + private static final Logger logger = Logger.getLogger(GLSLShaderObjectsState.class.getName()); + + /** Storage for shader uniform values */ + protected List<ShaderVariable> _shaderUniforms = new ArrayList<ShaderVariable>(); + /** Storage for shader attribute values */ + protected List<ShaderVariable> _shaderAttributes = new ArrayList<ShaderVariable>(); + + protected ByteBuffer _vertShader, _fragShader, _geomShader, _tessControlShader, _tessEvalShader; + + // XXX: The below fields are public for brevity mostly as a way to remember that this class needs revisiting. + + /** + * Optional logic for setting shadervariables based on the current geom. Note: If this object does not implement + * Savable, it will be ignored during write. + */ + public GLSLShaderDataLogic _shaderDataLogic; + + /** The Mesh this shader currently operates on during rendering */ + public Mesh _mesh; + + public boolean _needSendShader = true; + + /** OpenGL id for this program. * */ + public int _programID = -1; + + /** OpenGL id for the attached vertex shader. */ + public int _vertexShaderID = -1; + + /** OpenGL id for the attached fragment shader. */ + public int _fragmentShaderID = -1; + + /** OpenGL id for the attached geometry shader. */ + public int _geometryShaderID = -1; + + /** OpenGL id for the attached tessellation control shader. */ + public int _tessellationControlShaderID = -1; + + /** OpenGL id for the attached tessellation evaluation shader. */ + public int _tessellationEvaluationShaderID = -1; + + /** if true, we'll send our vertex attributes to the shader via vbo */ + private boolean _useAttributeVBO; + + /** optional name for our vertex shader, used for debugging details. */ + public String _vertexShaderName; + + /** optional name for our fragment shader, used for debugging details. */ + public String _fragmentShaderName; + + /** optional name for our geometry shader, used for debugging details. */ + public String _geometryShaderName; + + /** optional name for our tessellation control shader, used for debugging details. */ + public String _tessellationControlShaderName; + + /** optional name for our tessellation evaluation shader, used for debugging details. */ + public String _tessellationEvaluationShaderName; + + /** + * Gets the currently loaded vertex shader. + * + * @return + */ + public ByteBuffer getVertexShader() { + return _vertShader; + } + + /** + * Gets the currently loaded fragment shader. + * + * @return + */ + public ByteBuffer getFragmentShader() { + return _fragShader; + } + + /** + * Gets the currently loaded geometry shader. + * + * @return + */ + public ByteBuffer getGeometryShader() { + return _geomShader; + } + + /** + * Gets the currently loaded tessellation control shader. + * + * @return + */ + public ByteBuffer getTessellationControlShader() { + return _tessControlShader; + } + + /** + * Gets the currently loaded tessellation evaluation shader. + * + * @return + */ + public ByteBuffer getTessellationEvaluationShader() { + return _tessEvalShader; + } + + public void setVertexShader(final InputStream stream) throws IOException { + setVertexShader(stream, ""); + } + + public void setVertexShader(final InputStream stream, final String name) throws IOException { + setVertexShader(load(stream)); + _vertexShaderName = name; + } + + public void setFragmentShader(final InputStream stream) throws IOException { + setFragmentShader(stream, ""); + } + + public void setFragmentShader(final InputStream stream, final String name) throws IOException { + setFragmentShader(load(stream)); + _fragmentShaderName = name; + } + + public void setGeometryShader(final InputStream stream) throws IOException { + setGeometryShader(stream, ""); + } + + public void setGeometryShader(final InputStream stream, final String name) throws IOException { + setGeometryShader(load(stream)); + _geometryShaderName = name; + } + + public void setTessellationControlShader(final InputStream stream) throws IOException { + setTessellationControlShader(stream, ""); + } + + public void setTessellationControlShader(final InputStream stream, final String name) throws IOException { + setTessellationControlShader(load(stream)); + _tessellationControlShaderName = name; + } + + public void setTessellationEvaluationShader(final InputStream stream) throws IOException { + setTessellationEvaluationShader(stream, ""); + } + + public void setTessellationEvaluationShader(final InputStream stream, final String name) throws IOException { + setTessellationEvaluationShader(load(stream)); + _tessellationEvaluationShaderName = name; + } + + protected ByteBuffer load(final InputStream in) throws IOException { + DataInputStream dataStream = null; + try { + final BufferedInputStream bufferedInputStream = new BufferedInputStream(in); + dataStream = new DataInputStream(bufferedInputStream); + final byte shaderCode[] = new byte[bufferedInputStream.available()]; + dataStream.readFully(shaderCode); + bufferedInputStream.close(); + dataStream.close(); + final ByteBuffer shaderByteBuffer = BufferUtils.createByteBuffer(shaderCode.length); + shaderByteBuffer.put(shaderCode); + shaderByteBuffer.rewind(); + + return shaderByteBuffer; + } finally { + // Ensure that the stream is closed, even if there is an exception. + if (dataStream != null) { + try { + dataStream.close(); + } catch (final IOException closeFailure) { + logger.log(Level.WARNING, "Failed to close the shader object", closeFailure); + } + } + } + } + + /** + * Set the contents for our vertex shader + * + * @param shader + * the shader contents. + */ + public void setVertexShader(final ByteBuffer shader) { + setVertexShader(shader, ""); + } + + /** + * Set the contents for our vertex shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setVertexShader(final ByteBuffer shader, final String name) { + _vertShader = shader; + _vertexShaderName = name; + } + + /** + * Set the contents for our fragment shader + * + * @param shader + * the shader contents. + */ + public void setFragmentShader(final ByteBuffer shader) { + setFragmentShader(shader, ""); + } + + /** + * Set the contents for our fragment shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setFragmentShader(final ByteBuffer shader, final String name) { + _fragShader = shader; + _fragmentShaderName = name; + } + + /** + * Set the contents for our geometry shader + * + * @param shader + * the shader contents. + */ + public void setGeometryShader(final ByteBuffer shader) { + setGeometryShader(shader, ""); + } + + /** + * Set the contents for our geometry shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setGeometryShader(final ByteBuffer shader, final String name) { + _geomShader = shader; + _geometryShaderName = name; + } + + /** + * Set the contents for our tessellation control shader + * + * @param shader + * the shader contents. + */ + public void setTessellationControlShader(final ByteBuffer shader) { + setTessellationControlShader(shader, ""); + } + + /** + * Set the contents for our tessellation control shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setTessellationControlShader(final ByteBuffer shader, final String name) { + _tessControlShader = shader; + _tessellationControlShaderName = name; + } + + /** + * Set the contents for our tessellation evaluation shader + * + * @param shader + * the shader contents. + */ + public void setTessellationEvaluationShader(final ByteBuffer shader) { + setTessellationEvaluationShader(shader, ""); + } + + /** + * Set the contents for our tessellation evaluation shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setTessellationEvaluationShader(final ByteBuffer shader, final String name) { + _tessEvalShader = shader; + _tessellationEvaluationShaderName = name; + } + + /** + * Set the contents for our vertex shader + * + * @param shader + * the shader contents. + */ + public void setVertexShader(final String shader) { + setVertexShader(shader, ""); + } + + /** + * Set the contents for our vertex shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setVertexShader(final String shader, final String name) { + _vertShader = stringToByteBuffer(shader); + _vertexShaderName = name; + } + + /** + * Set the contents for our fragment shader + * + * @param shader + * the shader contents. + */ + public void setFragmentShader(final String shader) { + setFragmentShader(shader, ""); + } + + /** + * Set the contents for our fragment shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setFragmentShader(final String shader, final String name) { + _fragShader = stringToByteBuffer(shader); + _fragmentShaderName = name; + } + + /** + * Set the contents for our geometry shader + * + * @param shader + * the shader contents. + */ + public void setGeometryShader(final String shader) { + setGeometryShader(shader, ""); + } + + /** + * Set the contents for our geometry shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setGeometryShader(final String shader, final String name) { + _geomShader = stringToByteBuffer(shader); + _geometryShaderName = name; + } + + /** + * Set the contents for our tessellation control shader + * + * @param shader + * the shader contents. + */ + public void setTessellationControlShader(final String shader) { + setTessellationControlShader(shader, ""); + } + + /** + * Set the contents for our tessellation control shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setTessellationControlShader(final String shader, final String name) { + _tessControlShader = stringToByteBuffer(shader); + _tessellationControlShaderName = name; + } + + /** + * Set the contents for our tessellation evaluation shader + * + * @param shader + * the shader contents. + */ + public void setTessellationEvaluationShader(final String shader) { + setTessellationEvaluationShader(shader, ""); + } + + /** + * Set the contents for our tessellation evaluation shader + * + * @param shader + * the shader contents. + * @param name + * a label for this shader, displayer upon shader errors. + */ + public void setTessellationEvaluationShader(final String shader, final String name) { + _tessEvalShader = stringToByteBuffer(shader); + _tessellationEvaluationShaderName = name; + } + + private ByteBuffer stringToByteBuffer(final String str) { + final byte[] bytes = str.getBytes(); + final ByteBuffer buf = BufferUtils.createByteBuffer(bytes.length); + buf.put(bytes); + buf.rewind(); + return buf; + } + + /** + * Gets all shader uniforms variables. + * + * @return + */ + public List<ShaderVariable> getShaderUniforms() { + return _shaderUniforms; + } + + /** + * Retrieves a shader uniform by name. + * + * @param uniformName + * @return + */ + public ShaderVariable getUniformByName(final String uniformName) { + for (final ShaderVariable shaderVar : _shaderUniforms) { + if (shaderVar.name.equals(uniformName)) { + return shaderVar; + } + } + + return null; + } + + /** + * Gets all shader attribute variables. + * + * @return + */ + public List<ShaderVariable> getShaderAttributes() { + return _shaderAttributes; + } + + /** + * Retrieves a shader attribute by name. + * + * @param uniformName + * @return + */ + public ShaderVariable getAttributeByName(final String attributeName) { + for (final ShaderVariable shaderVar : _shaderAttributes) { + if (shaderVar.name.equals(attributeName)) { + return shaderVar; + } + } + + return null; + } + + /** + * + * @param meshData + */ + public void setMesh(final Mesh mesh) { + _mesh = mesh; + } + + /** + * Logic to handle setting mesh-specific data to a shader before rendering + * + * @param shaderDataLogic + */ + public void setShaderDataLogic(final GLSLShaderDataLogic shaderDataLogic) { + _shaderDataLogic = shaderDataLogic; + } + + public GLSLShaderDataLogic getShaderDataLogic() { + return _shaderDataLogic; + } + + public boolean isUseAttributeVBO() { + return _useAttributeVBO; + } + + /** + * @param useAttributeVBO + * if true, and we support VBO, we'll use VBO for shader attributes. + */ + public void setUseAttributeVBO(final boolean useAttributeVBO) { + _useAttributeVBO = useAttributeVBO; + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final boolean value) { + final ShaderVariableInt shaderUniform = getShaderUniform(name, ShaderVariableInt.class); + shaderUniform.value1 = value ? 1 : 0; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final int value) { + final ShaderVariableInt shaderUniform = getShaderUniform(name, ShaderVariableInt.class); + shaderUniform.value1 = value; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final float value) { + final ShaderVariableFloat shaderUniform = getShaderUniform(name, ShaderVariableFloat.class); + shaderUniform.value1 = value; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + */ + public void setUniform(final String name, final boolean value1, final boolean value2) { + final ShaderVariableInt2 shaderUniform = getShaderUniform(name, ShaderVariableInt2.class); + shaderUniform.value1 = value1 ? 1 : 0; + shaderUniform.value2 = value2 ? 1 : 0; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + */ + public void setUniform(final String name, final int value1, final int value2) { + final ShaderVariableInt2 shaderUniform = getShaderUniform(name, ShaderVariableInt2.class); + shaderUniform.value1 = value1; + shaderUniform.value2 = value2; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + */ + public void setUniform(final String name, final float value1, final float value2) { + final ShaderVariableFloat2 shaderUniform = getShaderUniform(name, ShaderVariableFloat2.class); + shaderUniform.value1 = value1; + shaderUniform.value2 = value2; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + * @param value3 + * the new value + */ + public void setUniform(final String name, final boolean value1, final boolean value2, final boolean value3) { + final ShaderVariableInt3 shaderUniform = getShaderUniform(name, ShaderVariableInt3.class); + shaderUniform.value1 = value1 ? 1 : 0; + shaderUniform.value2 = value2 ? 1 : 0; + shaderUniform.value3 = value3 ? 1 : 0; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + * @param value3 + * the new value + */ + public void setUniform(final String name, final int value1, final int value2, final int value3) { + final ShaderVariableInt3 shaderUniform = getShaderUniform(name, ShaderVariableInt3.class); + shaderUniform.value1 = value1; + shaderUniform.value2 = value2; + shaderUniform.value3 = value3; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + * @param value3 + * the new value + */ + public void setUniform(final String name, final float value1, final float value2, final float value3) { + final ShaderVariableFloat3 shaderUniform = getShaderUniform(name, ShaderVariableFloat3.class); + shaderUniform.value1 = value1; + shaderUniform.value2 = value2; + shaderUniform.value3 = value3; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + * @param value3 + * the new value + * @param value4 + * the new value + */ + public void setUniform(final String name, final boolean value1, final boolean value2, final boolean value3, + final boolean value4) { + final ShaderVariableInt4 shaderUniform = getShaderUniform(name, ShaderVariableInt4.class); + shaderUniform.value1 = value1 ? 1 : 0; + shaderUniform.value2 = value2 ? 1 : 0; + shaderUniform.value3 = value3 ? 1 : 0; + shaderUniform.value4 = value4 ? 1 : 0; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + * @param value3 + * the new value + * @param value4 + * the new value + */ + public void setUniform(final String name, final int value1, final int value2, final int value3, final int value4) { + final ShaderVariableInt4 shaderUniform = getShaderUniform(name, ShaderVariableInt4.class); + shaderUniform.value1 = value1; + shaderUniform.value2 = value2; + shaderUniform.value3 = value3; + shaderUniform.value4 = value4; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value1 + * the new value + * @param value2 + * the new value + * @param value3 + * the new value + * @param value4 + * the new value + */ + public void setUniform(final String name, final float value1, final float value2, final float value3, + final float value4) { + final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class); + shaderUniform.value1 = value1; + shaderUniform.value2 = value2; + shaderUniform.value3 = value3; + shaderUniform.value4 = value4; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new float data + * @param size + * the number of components per entry (must be 1, 2, 3, or 4) + */ + public void setUniform(final String name, final FloatBuffer value, final int size) { + assert (size >= 1 && size <= 4) : "Size must be 1, 2, 3 or 4"; + final ShaderVariableFloatArray shaderUniform = getShaderUniform(name, ShaderVariableFloatArray.class); + shaderUniform.value = value; + shaderUniform.size = size; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final float[] value) { + final ShaderVariableFloatArray shaderUniform = getShaderUniform(name, ShaderVariableFloatArray.class); + shaderUniform.value = BufferUtils.createFloatBuffer(value); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final IntBuffer value) { + final ShaderVariableIntArray shaderUniform = getShaderUniform(name, ShaderVariableIntArray.class); + shaderUniform.value = value; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final int[] value) { + final ShaderVariableIntArray shaderUniform = getShaderUniform(name, ShaderVariableIntArray.class); + shaderUniform.value = BufferUtils.createIntBuffer(value); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final ReadOnlyVector2 value) { + final ShaderVariableFloat2 shaderUniform = getShaderUniform(name, ShaderVariableFloat2.class); + shaderUniform.value1 = (float) value.getX(); + shaderUniform.value2 = (float) value.getY(); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final ReadOnlyVector3 value) { + final ShaderVariableFloat3 shaderUniform = getShaderUniform(name, ShaderVariableFloat3.class); + shaderUniform.value1 = (float) value.getX(); + shaderUniform.value2 = (float) value.getY(); + shaderUniform.value3 = (float) value.getZ(); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final ReadOnlyVector4 value) { + final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class); + shaderUniform.value1 = (float) value.getX(); + shaderUniform.value2 = (float) value.getY(); + shaderUniform.value3 = (float) value.getZ(); + shaderUniform.value4 = (float) value.getW(); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final ReadOnlyColorRGBA value) { + final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class); + shaderUniform.value1 = value.getRed(); + shaderUniform.value2 = value.getGreen(); + shaderUniform.value3 = value.getBlue(); + shaderUniform.value4 = value.getAlpha(); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + */ + public void setUniform(final String name, final ReadOnlyQuaternion value) { + final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class); + shaderUniform.value1 = (float) value.getX(); + shaderUniform.value2 = (float) value.getY(); + shaderUniform.value3 = (float) value.getZ(); + shaderUniform.value4 = (float) value.getW(); + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + * @param rowMajor + * true if is this in row major order + */ + public void setUniform(final String name, final ReadOnlyMatrix3 value, final boolean rowMajor) { + final ShaderVariableMatrix3 shaderUniform = getShaderUniform(name, ShaderVariableMatrix3.class); + // prepare buffer for writing + shaderUniform.matrixBuffer.rewind(); + value.toFloatBuffer(shaderUniform.matrixBuffer); + // prepare buffer for reading + shaderUniform.matrixBuffer.rewind(); + shaderUniform.rowMajor = rowMajor; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + * @param rowMajor + * true if is this in row major order + */ + public void setUniform(final String name, final ReadOnlyMatrix4 value, final boolean rowMajor) { + final ShaderVariableMatrix4 shaderUniform = getShaderUniform(name, ShaderVariableMatrix4.class); + // prepare buffer for writing + shaderUniform.matrixBuffer.rewind(); + value.toFloatBuffer(shaderUniform.matrixBuffer); + // prepare buffer for reading + shaderUniform.matrixBuffer.rewind(); + shaderUniform.rowMajor = rowMajor; + + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform Matrix4 variable to change + * @param value + * the new value, assumed row major + */ + public void setUniformMatrix4(final String name, final FloatBuffer value) { + final ShaderVariableMatrix4 shaderUniform = getShaderUniform(name, ShaderVariableMatrix4.class); + // prepare buffer for writing + shaderUniform.matrixBuffer.rewind(); + shaderUniform.matrixBuffer.put(value); + // prepare buffer for reading + shaderUniform.matrixBuffer.rewind(); + value.rewind(); + shaderUniform.rowMajor = true; + setNeedsRefresh(true); + } + + /** + * Set an uniform value for this shader object. + * + * @param name + * uniform variable to change + * @param value + * the new value + * @param rowMajor + * true if is this in row major order + */ + public void setUniform(final String name, final ReadOnlyMatrix4[] values, final boolean rowMajor) { + final ShaderVariableMatrix4Array shaderUniform = getShaderUniform(name, ShaderVariableMatrix4Array.class); + // prepare buffer for writing + FloatBuffer matrixBuffer = shaderUniform.matrixBuffer; + if (matrixBuffer == null || matrixBuffer.capacity() < values.length * 16) { + matrixBuffer = BufferUtils.createFloatBuffer(values.length * 16); + shaderUniform.matrixBuffer = matrixBuffer; + } + + matrixBuffer.clear(); + for (final ReadOnlyMatrix4 value : values) { + value.toFloatBuffer(matrixBuffer); + } + matrixBuffer.flip(); + + // prepare buffer for reading + shaderUniform.rowMajor = rowMajor; + + setNeedsRefresh(true); + } + + /** <code>clearUniforms</code> clears all uniform values from this state. */ + public void clearUniforms() { + _shaderUniforms.clear(); + } + + /** + * Set an attribute pointer value for this shader object. + * + * @param name + * attribute variable to change + * @param size + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, + * 3, or 4. + * @param normalized + * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point + * values when they are accessed. + * @param stride + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), + * the attribute values are understood to be tightly packed in the array. + * @param data + * The actual data to use as attribute pointer + */ + public void setAttributePointer(final String name, final int size, final boolean normalized, final int stride, + final FloatBufferData data) { + final ShaderVariablePointerFloat shaderUniform = getShaderAttribute(name, ShaderVariablePointerFloat.class); + shaderUniform.size = size; + shaderUniform.normalized = normalized; + shaderUniform.stride = stride; + shaderUniform.data = data; + + setNeedsRefresh(true); + } + + /** + * Set an attribute pointer value for this shader object. + * + * @param name + * attribute variable to change + * @param size + * the number of rows and cols in the matrix. Must be 2, 3, or 4. + * @param normalized + * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point + * values when they are accessed. + * @param data + * The actual data to use as attribute pointer + */ + public void setAttributePointerMatrix(final String name, final int size, final boolean normalized, + final FloatBufferData data) { + final ShaderVariablePointerFloatMatrix shaderUniform = getShaderAttribute(name, + ShaderVariablePointerFloatMatrix.class); + shaderUniform.size = size; + shaderUniform.normalized = normalized; + shaderUniform.data = data; + + setNeedsRefresh(true); + } + + /** + * Set an attribute pointer value for this shader object. + * + * @param name + * attribute variable to change + * @param size + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, + * 3, or 4. + * @param normalized + * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point + * values when they are accessed. + * @param unsigned + * Specifies wheter the data is signed or unsigned + * @param stride + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), + * the attribute values are understood to be tightly packed in the array. + * @param data + * The actual data to use as attribute pointer + */ + public void setAttributePointer(final String name, final int size, final boolean normalized, + final boolean unsigned, final int stride, final ByteBufferData data) { + final ShaderVariablePointerByte shaderUniform = getShaderAttribute(name, ShaderVariablePointerByte.class); + shaderUniform.size = size; + shaderUniform.normalized = normalized; + shaderUniform.unsigned = unsigned; + shaderUniform.stride = stride; + shaderUniform.data = data; + + setNeedsRefresh(true); + } + + /** + * Set an attribute pointer value for this shader object. + * + * @param name + * attribute variable to change + * @param size + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, + * 3, or 4. + * @param normalized + * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point + * values when they are accessed. + * @param unsigned + * Specifies wheter the data is signed or unsigned + * @param stride + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), + * the attribute values are understood to be tightly packed in the array. + * @param data + * The actual data to use as attribute pointer + */ + public void setAttributePointer(final String name, final int size, final boolean normalized, + final boolean unsigned, final int stride, final IntBufferData data) { + final ShaderVariablePointerInt shaderUniform = getShaderAttribute(name, ShaderVariablePointerInt.class); + shaderUniform.size = size; + shaderUniform.normalized = normalized; + shaderUniform.unsigned = unsigned; + shaderUniform.stride = stride; + shaderUniform.data = data; + + setNeedsRefresh(true); + } + + /** + * Set an attribute pointer value for this shader object. + * + * @param name + * attribute variable to change + * @param size + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, + * 3, or 4. + * @param normalized + * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point + * values when they are accessed. + * @param unsigned + * Specifies wheter the data is signed or unsigned + * @param stride + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), + * the attribute values are understood to be tightly packed in the array. + * @param data + * The actual data to use as attribute pointer + */ + public void setAttributePointer(final String name, final int size, final boolean normalized, + final boolean unsigned, final int stride, final ShortBufferData data) { + final ShaderVariablePointerShort shaderUniform = getShaderAttribute(name, ShaderVariablePointerShort.class); + shaderUniform.size = size; + shaderUniform.normalized = normalized; + shaderUniform.unsigned = unsigned; + shaderUniform.stride = stride; + shaderUniform.data = data; + + setNeedsRefresh(true); + } + + /** + * <code>clearAttributes</code> clears all attribute values from this state. + */ + public void clearAttributes() { + _shaderAttributes.clear(); + } + + @Override + public StateType getType() { + return StateType.GLSLShader; + } + + /** + * Creates or retrieves a uniform shadervariable. + * + * @param name + * Name of the uniform shadervariable to retrieve or create + * @param classz + * Class type of the shadervariable + * @return + */ + private <T extends ShaderVariable> T getShaderUniform(final String name, final Class<T> classz) { + final T shaderVariable = getShaderVariable(name, classz, _shaderUniforms); + return shaderVariable; + } + + /** + * Creates or retrieves a attribute shadervariable. + * + * @param name + * Name of the attribute shadervariable to retrieve or create + * @param classz + * Class type of the shadervariable + * @return + */ + private <T extends ShaderVariable> T getShaderAttribute(final String name, final Class<T> classz) { + final T shaderVariable = getShaderVariable(name, classz, _shaderAttributes); + checkAttributeSizeLimits(); + return shaderVariable; + } + + /** + * @param name + * Name of the shadervariable to retrieve or create + * @param classz + * Class type of the shadervariable + * @param shaderVariableList + * List retrieve shadervariable from + * @return + */ + @SuppressWarnings("unchecked") + private <T extends ShaderVariable> T getShaderVariable(final String name, final Class<T> classz, + final List<ShaderVariable> shaderVariableList) { + for (int i = shaderVariableList.size(); --i >= 0;) { + final ShaderVariable temp = shaderVariableList.get(i); + if (name.equals(temp.name)) { + temp.needsRefresh = true; + return (T) temp; + } + } + + try { + final T shaderUniform = classz.newInstance(); + shaderUniform.name = name; + shaderVariableList.add(shaderUniform); + + return shaderUniform; + } catch (final InstantiationException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), + "getShaderVariable(name, classz, shaderVariableList)", "Exception", e); + } catch (final IllegalAccessException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), + "getShaderVariable(name, classz, shaderVariableList)", "Exception", e); + } + + return null; + } + + /** + * Check if we are keeping the size limits in terms of attribute locations on the card. + */ + public void checkAttributeSizeLimits() { + final RenderContext context = ContextManager.getCurrentContext(); + final ContextCapabilities caps = context.getCapabilities(); + if (_shaderAttributes.size() > caps.getMaxGLSLVertexAttributes()) { + logger.severe("Too many shader attributes(standard+defined): " + _shaderAttributes.size() + " maximum: " + + caps.getMaxGLSLVertexAttributes()); + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.writeSavableList(_shaderUniforms, "shaderUniforms", new ArrayList<ShaderVariable>()); + capsule.writeSavableList(_shaderAttributes, "shaderAttributes", new ArrayList<ShaderVariable>()); + capsule.write(_vertShader, "vertShader", null); + capsule.write(_fragShader, "fragShader", null); + capsule.write(_geomShader, "geomShader", null); + capsule.write(_geomShader, "geomShader", null); + capsule.write(_tessControlShader, "tessControlShader", null); + capsule.write(_tessEvalShader, "tessEvalShader", null); + capsule.write(_useAttributeVBO, "useAttributeVBO", false); + + if (_shaderDataLogic instanceof Savable) { + capsule.write((Savable) _shaderDataLogic, "shaderDataLogic", null); + } + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _shaderUniforms = capsule.readSavableList("shaderUniforms", new ArrayList<ShaderVariable>()); + _shaderAttributes = capsule.readSavableList("shaderAttributes", new ArrayList<ShaderVariable>()); + _vertShader = capsule.readByteBuffer("vertShader", null); + _fragShader = capsule.readByteBuffer("fragShader", null); + _geomShader = capsule.readByteBuffer("geomShader", null); + _tessControlShader = capsule.readByteBuffer("tessControlShader", null); + _tessEvalShader = capsule.readByteBuffer("tessEvalShader", null); + _useAttributeVBO = capsule.readBoolean("useAttributeVBO", false); + + final Savable shaderDataLogic = capsule.readSavable("shaderDataLogic", null); + // only override set _shaderDataLogic if we have something in the capsule. + if (shaderDataLogic != null) { + if (shaderDataLogic instanceof GLSLShaderDataLogic) { + _shaderDataLogic = (GLSLShaderDataLogic) shaderDataLogic; + } else { + logger.warning("Deserialized shaderDataLogic is not of type GLSLShaderDataLogic. " + + shaderDataLogic.getClass().getName()); + } + } + } + + @Override + public StateRecord createStateRecord() { + return new ShaderObjectsStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java new file mode 100644 index 0000000..6f1648a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java @@ -0,0 +1,397 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import com.ardor3d.light.Light; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.record.LightStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>LightState</code> maintains a collection of lights up to the set number of maximum lights allowed. Any subclass + * of <code>Light</code> can be added to the light state. Each light is processed and used to modify the color of the + * scene. + */ +public class LightState extends RenderState { + + /** + * Debug flag for turning off all lighting. + */ + public static boolean LIGHTS_ENABLED = true; + + /** + * defines the maximum number of lights that are allowed to be maintained at one time. + */ + public static final int MAX_LIGHTS_ALLOWED = 8; + + /** + * When applied to lightMask, implies ambient light should be set to 0 for this lightstate + */ + public static final int MASK_AMBIENT = 1; + + /** + * When applied to lightMask, implies diffuse light should be set to 0 for this lightstate + */ + public static final int MASK_DIFFUSE = 2; + + /** + * When applied to lightMask, implies specular light should be set to 0 for this lightstate + */ + public static final int MASK_SPECULAR = 4; + + /** + * When applied to lightMask, implies global ambient light should be set to 0 for this lightstate + */ + public static final int MASK_GLOBALAMBIENT = 8; + + // holds the lights + private List<Light> lightList; + + // mask value - default is no masking + protected int lightMask = 0; + + // mask value stored by pushLightMask, retrieved by popLightMask + protected int backLightMask = 0; + + /** When true, both sides of the model will be lighted. */ + protected boolean twoSidedOn = true; + + protected ColorRGBA _globalAmbient = new ColorRGBA(DEFAULT_GLOBAL_AMBIENT); + + public static final ReadOnlyColorRGBA DEFAULT_GLOBAL_AMBIENT = new ColorRGBA(0, 0, 0, 1); + + /** + * When true, the eye position (as opposed to just the view direction) will be taken into account when computing + * specular reflections. + */ + protected boolean localViewerOn; + + /** + * When true, specular highlights will be computed separately and added to fragments after texturing. + */ + protected boolean separateSpecularOn; + + /** + * Constructor instantiates a new <code>LightState</code> object. Initially there are no lights set. + */ + public LightState() { + lightList = new ArrayList<Light>(); + } + + @Override + public StateType getType() { + return StateType.Light; + } + + /** + * + * <code>attach</code> places a light in the queue to be processed. If there are already eight lights placed in the + * queue, the light is ignored and false is returned. Otherwise, true is returned to indicate success. + * + * @param light + * the light to add to the queue. + * @return true if the light was added successfully, false if there are already eight lights in the queue. + */ + public boolean attach(final Light light) { + if (!lightList.contains(light)) { + lightList.add(light); + setNeedsRefresh(true); + return true; + } + return false; + } + + /** + * + * <code>detach</code> removes a light from the queue for processing. + * + * @param light + * the light to be removed. + */ + public void detach(final Light light) { + lightList.remove(light); + setNeedsRefresh(true); + } + + /** + * + * <code>detachAll</code> clears the queue of all lights to be processed. + * + */ + public void detachAll() { + lightList.clear(); + setNeedsRefresh(true); + } + + /** + * Retrieves all lights handled by this LightState + * + * @return List of lights handled + */ + public List<Light> getLightList() { + return lightList; + } + + /** + * + * <code>get</code> retrieves a particular light defined by an index. If there exists no light at a particular + * index, null is returned. + * + * @param i + * the index to retrieve the light from the queue. + * @return the light at the given index, null if no light exists at this index. + */ + public Light get(final int i) { + return lightList.get(i); + } + + /** + * + * <code>getNumberOfChildren</code> returns the number of lights currently in the queue. + * + * @return the number of lights currently in the queue. + */ + public int getNumberOfChildren() { + return lightList.size() > MAX_LIGHTS_ALLOWED ? MAX_LIGHTS_ALLOWED : lightList.size(); + } + + /** + * Sets if two sided lighting should be enabled for this LightState. Two sided lighting will cause the back of + * surfaces to be colored using the inverse of the surface normal as well as the Material properties set for + * MaterialFace.Back. + * + * @param twoSidedOn + * If true, two sided lighting is enabled. + */ + public void setTwoSidedLighting(final boolean twoSidedOn) { + this.twoSidedOn = twoSidedOn; + setNeedsRefresh(true); + } + + /** + * Returns the current state of two sided lighting for this LightState. By default, it is off. + * + * @return True if two sided lighting is enabled. + */ + public boolean getTwoSidedLighting() { + return twoSidedOn; + } + + /** + * Sets if local viewer mode should be enabled for this LightState. + * + * @param localViewerOn + * If true, local viewer mode is enabled. + */ + public void setLocalViewer(final boolean localViewerOn) { + this.localViewerOn = localViewerOn; + setNeedsRefresh(true); + } + + /** + * Returns the current state of local viewer mode for this LightState. By default, it is off. + * + * @return True if local viewer mode is enabled. + */ + public boolean getLocalViewer() { + return localViewerOn; + } + + /** + * Sets if separate specular mode should be enabled for this LightState. + * + * @param separateSpecularOn + * If true, separate specular mode is enabled. + */ + public void setSeparateSpecular(final boolean separateSpecularOn) { + this.separateSpecularOn = separateSpecularOn; + setNeedsRefresh(true); + } + + /** + * Returns the current state of separate specular mode for this LightState. By default, it is off. + * + * @return True if separate specular mode is enabled. + */ + public boolean getSeparateSpecular() { + return separateSpecularOn; + } + + public void setGlobalAmbient(final ReadOnlyColorRGBA color) { + _globalAmbient.set(color); + setNeedsRefresh(true); + } + + /** + * + * @param store + * @return + */ + public ReadOnlyColorRGBA getGlobalAmbient() { + return _globalAmbient; + } + + /** + * @return Returns the lightMask - default is 0 or not masked. + */ + public int getLightMask() { + return lightMask; + } + + /** + * <code>setLightMask</code> sets what attributes of this lightstate to apply as an int comprised of bitwise or'ed + * values. + * + * @param lightMask + * The lightMask to set. + */ + public void setLightMask(final int lightMask) { + this.lightMask = lightMask; + setNeedsRefresh(true); + } + + /** + * Saves the light mask to a back store. That backstore is recalled with popLightMask. Despite the name, this is not + * a stack and additional pushes will simply overwrite the backstored value. + */ + public void pushLightMask() { + backLightMask = lightMask; + } + + /** + * Recalls the light mask from a back store or 0 if none was pushed. + * + * @see com.ardor3d.renderer.state.LightState#pushLightMask() + */ + public void popLightMask() { + lightMask = backLightMask; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.writeSavableList(lightList, "lightList", new ArrayList<Light>()); + capsule.write(lightMask, "lightMask", 0); + capsule.write(backLightMask, "backLightMask", 0); + capsule.write(twoSidedOn, "twoSidedOn", false); + capsule.write(_globalAmbient, "globalAmbient", new ColorRGBA(DEFAULT_GLOBAL_AMBIENT)); + capsule.write(localViewerOn, "localViewerOn", false); + capsule.write(separateSpecularOn, "separateSpecularOn", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + lightList = capsule.readSavableList("lightList", new ArrayList<Light>()); + lightMask = capsule.readInt("lightMask", 0); + backLightMask = capsule.readInt("backLightMask", 0); + twoSidedOn = capsule.readBoolean("twoSidedOn", false); + _globalAmbient = (ColorRGBA) capsule.readSavable("globalAmbient", new ColorRGBA(DEFAULT_GLOBAL_AMBIENT)); + localViewerOn = capsule.readBoolean("localViewerOn", false); + separateSpecularOn = capsule.readBoolean("separateSpecularOn", false); + } + + @Override + public RenderState extract(final Stack<? extends RenderState> stack, final Spatial spat) { + if (spat == null) { + return stack.peek(); + } + + final LightCombineMode mode = spat.getSceneHints().getLightCombineMode(); + + final Mesh mesh = (Mesh) spat; + LightState lightState = mesh.getLightState(); + if (lightState == null) { + lightState = new LightState(); + mesh.setLightState(lightState); + } + + lightState.detachAll(); + + if (mode == LightCombineMode.Replace || (mode != LightCombineMode.Off && stack.size() == 1)) { + // todo: use dummy state if off? + + final LightState copyLightState = (LightState) stack.peek(); + copyLightState(copyLightState, lightState); + } else { + // accumulate the lights in the stack into a single LightState object + final Object states[] = stack.toArray(); + boolean foundEnabled = false; + switch (mode) { + case CombineClosest: + case CombineClosestEnabled: + for (int iIndex = states.length - 1; iIndex >= 0; iIndex--) { + final LightState pkLState = (LightState) states[iIndex]; + if (!pkLState.isEnabled()) { + if (mode == LightCombineMode.CombineClosestEnabled) { + break; + } + + continue; + } + + foundEnabled = true; + copyLightState(pkLState, lightState); + } + break; + case CombineFirst: + for (int iIndex = 0, max = states.length; iIndex < max; iIndex++) { + final LightState pkLState = (LightState) states[iIndex]; + if (!pkLState.isEnabled()) { + continue; + } + + foundEnabled = true; + copyLightState(pkLState, lightState); + } + break; + case Off: + break; + } + lightState.setEnabled(foundEnabled); + } + + return lightState; + } + + private static void copyLightState(final LightState source, final LightState destination) { + destination.setTwoSidedLighting(source.getTwoSidedLighting()); + destination.setLocalViewer(source.getLocalViewer()); + destination.setSeparateSpecular(source.getSeparateSpecular()); + destination.setEnabled(source.isEnabled()); + destination.setGlobalAmbient(source.getGlobalAmbient()); + destination.setLightMask(source.getLightMask()); + destination.setNeedsRefresh(true); + + for (int i = 0, maxL = source.getLightList().size(); i < maxL; i++) { + final Light pkLight = source.get(i); + if (pkLight != null) { + destination.attach(pkLight); + } + } + } + + @Override + public StateRecord createStateRecord() { + return new LightStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java new file mode 100644 index 0000000..112a955 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.light.Light; +import com.ardor3d.light.PointLight; +import com.ardor3d.light.SpotLight; +import com.ardor3d.math.Plane; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; + +public abstract class LightUtil { + private static class LightComparator implements Comparator<Light> { + private Spatial _sp; + + public void setSpatial(final Spatial sp) { + _sp = sp; + } + + public int compare(final Light l1, final Light l2) { + final double v1 = getValueFor(l1, _sp.getWorldBound()); + final double v2 = getValueFor(l2, _sp.getWorldBound()); + final double cmp = v1 - v2; + if (0 > cmp) { + return 1; + } else if (0 < cmp) { + return -1; + } else { + return 0; + } + } + } + + private static LightComparator lightComparator = new LightComparator(); + + public static void sort(final Mesh geometry, final List<Light> lights) { + lightComparator.setSpatial(geometry); + Collections.sort(lights, lightComparator); + } + + protected static double getValueFor(final Light l, final BoundingVolume val) { + if (l == null || !l.isEnabled()) { + return 0; + } else if (l.getType() == Light.Type.Directional) { + return getColorValue(l); + } else if (l.getType() == Light.Type.Point) { + return getValueFor((PointLight) l, val); + } else if (l.getType() == Light.Type.Spot) { + return getValueFor((SpotLight) l, val); + } + // If a new type of light was added and this was not updated return .3 + return .3; + } + + protected static double getValueFor(final PointLight l, final BoundingVolume val) { + if (val == null) { + return 0; + } + if (l.isAttenuate()) { + final ReadOnlyVector3 location = l.getLocation(); + final double dist = val.distanceTo(location); + + final double color = getColorValue(l); + final double amlat = l.getConstant() + l.getLinear() * dist + l.getQuadratic() * dist * dist; + + return color / amlat; + } + + return getColorValue(l); + } + + protected static double getValueFor(final SpotLight l, final BoundingVolume val) { + if (val == null) { + return 0; + } + final ReadOnlyVector3 direction = l.getDirection(); + final ReadOnlyVector3 location = l.getLocation(); + // direction is copied into Plane, not reused. + final Plane p = new Plane(direction, direction.dot(location)); + if (val.whichSide(p) != Plane.Side.Inside) { + return getValueFor((PointLight) l, val); + } + + return 0; + } + + protected static double getColorValue(final Light l) { + return strength(l.getAmbient()) + strength(l.getDiffuse()); + } + + protected static double strength(final ReadOnlyColorRGBA color) { + return Math.sqrt(color.getRed() * color.getRed() + color.getGreen() * color.getGreen() + color.getBlue() + * color.getBlue()); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java new file mode 100644 index 0000000..15eb46c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java @@ -0,0 +1,401 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.record.MaterialStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * MaterialState defines parameters used in conjunction with the lighting model to produce a surface color. Please note + * therefore that this state has no effect if lighting is disabled. It is also worth noting that material properties set + * for Front face will in fact affect both front and back unless two sided lighting is enabled. + * + * @see LightState + * @see LightState#setTwoSidedLighting(boolean) + */ +public class MaterialState extends RenderState { + + public enum ColorMaterial { + /** Mesh colors are ignored. This is default. */ + None, + + /** Mesh colors determine material ambient color. */ + Ambient, + + /** Mesh colors determine material diffuse color. */ + Diffuse, + + /** Mesh colors determine material ambient and diffuse colors. */ + AmbientAndDiffuse, + + /** Mesh colors determine material specular colors. */ + Specular, + + /** Mesh colors determine material emissive color. */ + Emissive; + } + + public enum MaterialFace { + /** Apply material property to front face only. */ + Front, + + /** + * Apply material property to back face only. Note that this only has an affect if two sided lighting is + * enabled. + */ + Back, + + /** Apply material property to front and back faces. */ + FrontAndBack; + } + + /** Default ambient color for all material states. (.2, .2, .2, 1) */ + public static final ReadOnlyColorRGBA DEFAULT_AMBIENT = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f); + + /** Default diffuse color for all material states. (.8, .8, .8, 1) */ + public static final ReadOnlyColorRGBA DEFAULT_DIFFUSE = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); + + /** Default specular color for all material states. (0, 0, 0, 1) */ + public static final ReadOnlyColorRGBA DEFAULT_SPECULAR = new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f); + + /** Default emissive color for all material states. (0, 0, 0, 1) */ + public static final ReadOnlyColorRGBA DEFAULT_EMISSIVE = new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f); + + /** Default shininess for all material states. */ + public static final float DEFAULT_SHININESS = 0.0f; + + /** Default color material mode for all material states. */ + public static final ColorMaterial DEFAULT_COLOR_MATERIAL = ColorMaterial.None; + + /** Default color material face for all material states. */ + public static final MaterialFace DEFAULT_COLOR_MATERIAL_FACE = MaterialFace.FrontAndBack; + + // front face attributes of the material (used for back face if lighting is not two sided) + protected final ColorRGBA _frontAmbient = new ColorRGBA(DEFAULT_AMBIENT); + protected final ColorRGBA _frontDiffuse = new ColorRGBA(DEFAULT_DIFFUSE); + protected final ColorRGBA _frontSpecular = new ColorRGBA(DEFAULT_SPECULAR); + protected final ColorRGBA _frontEmissive = new ColorRGBA(DEFAULT_EMISSIVE); + protected float _frontShininess = DEFAULT_SHININESS; + + // back face attributes of the material (only used if lighting is two sided) + protected final ColorRGBA _backAmbient = new ColorRGBA(DEFAULT_AMBIENT); + protected final ColorRGBA _backDiffuse = new ColorRGBA(DEFAULT_DIFFUSE); + protected final ColorRGBA _backSpecular = new ColorRGBA(DEFAULT_SPECULAR); + protected final ColorRGBA _backEmissive = new ColorRGBA(DEFAULT_EMISSIVE); + protected float _backShininess = DEFAULT_SHININESS; + + protected ColorMaterial _colorMaterial = DEFAULT_COLOR_MATERIAL; + protected MaterialFace _colorMaterialFace = DEFAULT_COLOR_MATERIAL_FACE; + + /** + * Constructor instantiates a new <code>MaterialState</code> object. + */ + public MaterialState() {} + + /** + * @return the ambient color (or front face color, if two sided lighting is used) of this material. + */ + public ReadOnlyColorRGBA getAmbient() { + return _frontAmbient; + } + + /** + * @return the ambient back face color of this material. This is only used if two sided lighting is used. + */ + public ReadOnlyColorRGBA getBackAmbient() { + return _backAmbient; + } + + /** + * Sets the ambient color for front and back to the given value. + * + * @param ambient + * the new ambient color + */ + public void setAmbient(final ReadOnlyColorRGBA ambient) { + setAmbient(MaterialFace.FrontAndBack, ambient); + } + + /** + * @param face + * the face to apply the ambient color to + * @param ambient + * the new ambient color + */ + public void setAmbient(final MaterialFace face, final ReadOnlyColorRGBA ambient) { + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + _frontAmbient.set(ambient); + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + _backAmbient.set(ambient); + } + setNeedsRefresh(true); + } + + /** + * @return the diffuse color (or front face color, if two sided lighting is used) of this material. + */ + public ReadOnlyColorRGBA getDiffuse() { + return _frontDiffuse; + } + + /** + * @return the diffuse back face color of this material. This is only used if two sided lighting is used. + */ + public ReadOnlyColorRGBA getBackDiffuse() { + return _backDiffuse; + } + + /** + * Sets the diffuse color for front and back to the given value. + * + * @param diffuse + * the new diffuse color + */ + public void setDiffuse(final ReadOnlyColorRGBA diffuse) { + setDiffuse(MaterialFace.FrontAndBack, diffuse); + } + + /** + * @param face + * the face to apply the diffuse color to + * @param diffuse + * the new diffuse color + */ + public void setDiffuse(final MaterialFace face, final ReadOnlyColorRGBA diffuse) { + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + _frontDiffuse.set(diffuse); + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + _backDiffuse.set(diffuse); + } + setNeedsRefresh(true); + } + + /** + * @return the emissive color (or front face color, if two sided lighting is used) of this material. + */ + public ReadOnlyColorRGBA getEmissive() { + return _frontEmissive; + } + + /** + * @return the emissive back face color of this material. This is only used if two sided lighting is used. + */ + public ReadOnlyColorRGBA getBackEmissive() { + return _backEmissive; + } + + /** + * Sets the emissive color for front and back to the given value. + * + * @param emissive + * the new emissive color + */ + public void setEmissive(final ReadOnlyColorRGBA emissive) { + setEmissive(MaterialFace.FrontAndBack, emissive); + } + + /** + * @param face + * the face to apply the emissive color to + * @param emissive + * the new emissive color + */ + public void setEmissive(final MaterialFace face, final ReadOnlyColorRGBA emissive) { + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + _frontEmissive.set(emissive); + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + _backEmissive.set(emissive); + } + setNeedsRefresh(true); + } + + /** + * @return the specular color (or front face color, if two sided lighting is used) of this material. + */ + public ReadOnlyColorRGBA getSpecular() { + return _frontSpecular; + } + + /** + * @return the specular back face color of this material. This is only used if two sided lighting is used. + */ + public ReadOnlyColorRGBA getBackSpecular() { + return _backSpecular; + } + + /** + * Sets the specular color for front and back to the given value. + * + * @param specular + * the new specular color + */ + public void setSpecular(final ReadOnlyColorRGBA specular) { + setSpecular(MaterialFace.FrontAndBack, specular); + } + + /** + * @param face + * the face to apply the specular color to + * @param specular + * the new specular color + */ + public void setSpecular(final MaterialFace face, final ReadOnlyColorRGBA specular) { + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + _frontSpecular.set(specular); + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + _backSpecular.set(specular); + } + setNeedsRefresh(true); + } + + /** + * @return the shininess value (or front face shininess value, if two sided lighting is used) of the material. + */ + public float getShininess() { + return _frontShininess; + } + + /** + * @return the shininess value of the back face of this material. This is only used if two sided lighting is used. + */ + public float getBackShininess() { + return _backShininess; + } + + /** + * Sets the shininess value for front and back to the given value. + * + * @param shininess + * the new shininess for this material. Must be between 0 and 128. Higher numbers result in "tighter" + * specular reflections. + */ + public void setShininess(final float shininess) { + setShininess(MaterialFace.FrontAndBack, shininess); + } + + /** + * @param face + * the face to apply the shininess color to + * @param shininess + * the new shininess for this material. Must be between 0 and 128. Higher numbers result in "tighter" + * specular reflections. + */ + public void setShininess(final MaterialFace face, final float shininess) { + if (shininess < 0 || shininess > 128) { + throw new IllegalArgumentException("Shininess must be between 0 and 128."); + } + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + _frontShininess = shininess; + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + _backShininess = shininess; + } + setNeedsRefresh(true); + } + + /** + * @return the color material mode of this material, which determines how geometry colors affect the material. + * @see ColorMaterial + */ + public ColorMaterial getColorMaterial() { + return _colorMaterial; + } + + /** + * @param material + * the new color material mode + * @throws IllegalArgumentException + * if material is null + */ + public void setColorMaterial(final ColorMaterial material) { + if (material == null) { + throw new IllegalArgumentException("material can not be null."); + } + _colorMaterial = material; + setNeedsRefresh(true); + } + + /** + * @return the color material face of this material, which determines how geometry colors affect the material. + * @see ColorMaterial + */ + public MaterialFace getColorMaterialFace() { + return _colorMaterialFace; + } + + /** + * @param face + * the new color material face + * @throws IllegalArgumentException + * if face is null + */ + public void setColorMaterialFace(final MaterialFace face) { + if (face == null) { + throw new IllegalArgumentException("face can not be null."); + } + _colorMaterialFace = face; + setNeedsRefresh(true); + } + + @Override + public StateType getType() { + return StateType.Material; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_frontAmbient, "frontAmbient", (ColorRGBA) DEFAULT_AMBIENT); + capsule.write(_frontDiffuse, "frontDiffuse", (ColorRGBA) DEFAULT_DIFFUSE); + capsule.write(_frontSpecular, "frontSpecular", (ColorRGBA) DEFAULT_SPECULAR); + capsule.write(_frontEmissive, "frontEmissive", (ColorRGBA) DEFAULT_EMISSIVE); + capsule.write(_frontShininess, "frontShininess", DEFAULT_SHININESS); + capsule.write(_backAmbient, "backAmbient", (ColorRGBA) DEFAULT_AMBIENT); + capsule.write(_backDiffuse, "backDiffuse", (ColorRGBA) DEFAULT_DIFFUSE); + capsule.write(_backSpecular, "backSpecular", (ColorRGBA) DEFAULT_SPECULAR); + capsule.write(_backEmissive, "backEmissive", (ColorRGBA) DEFAULT_EMISSIVE); + capsule.write(_backShininess, "backShininess", DEFAULT_SHININESS); + capsule.write(_colorMaterial, "colorMaterial", DEFAULT_COLOR_MATERIAL); + capsule.write(_colorMaterialFace, "colorMaterialFace", DEFAULT_COLOR_MATERIAL_FACE); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _frontAmbient.set((ColorRGBA) capsule.readSavable("frontAmbient", (ColorRGBA) DEFAULT_AMBIENT)); + _frontDiffuse.set((ColorRGBA) capsule.readSavable("frontDiffuse", (ColorRGBA) DEFAULT_DIFFUSE)); + _frontSpecular.set((ColorRGBA) capsule.readSavable("frontSpecular", (ColorRGBA) DEFAULT_SPECULAR)); + _frontEmissive.set((ColorRGBA) capsule.readSavable("frontEmissive", (ColorRGBA) DEFAULT_EMISSIVE)); + _frontShininess = capsule.readFloat("frontShininess", DEFAULT_SHININESS); + _backAmbient.set((ColorRGBA) capsule.readSavable("backAmbient", (ColorRGBA) DEFAULT_AMBIENT)); + _backDiffuse.set((ColorRGBA) capsule.readSavable("backDiffuse", (ColorRGBA) DEFAULT_DIFFUSE)); + _backSpecular.set((ColorRGBA) capsule.readSavable("backSpecular", (ColorRGBA) DEFAULT_SPECULAR)); + _backEmissive.set((ColorRGBA) capsule.readSavable("backEmissive", (ColorRGBA) DEFAULT_EMISSIVE)); + _backShininess = capsule.readFloat("backShininess", DEFAULT_SHININESS); + _colorMaterial = capsule.readEnum("colorMaterial", ColorMaterial.class, DEFAULT_COLOR_MATERIAL); + _colorMaterialFace = capsule.readEnum("colorMaterialFace", MaterialFace.class, DEFAULT_COLOR_MATERIAL_FACE); + } + + @Override + public StateRecord createStateRecord() { + return new MaterialStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java new file mode 100644 index 0000000..8f42100 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; +import java.util.EnumSet; + +import com.ardor3d.renderer.state.record.OffsetStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>OffsetState</code> controls depth offset for rendering. + */ +public class OffsetState extends RenderState { + + public enum OffsetType { + /** Apply offset to filled polygons. */ + Fill, + + /** Apply offset to lines. */ + Line, + + /** Apply offset to points. */ + Point; + } + + private final EnumSet<OffsetType> _enabledOffsets = EnumSet.noneOf(OffsetType.class); + + private float _factor; + + private float _units; + + /** + * Constructor instantiates a new <code>OffsetState</code> object. + */ + public OffsetState() {} + + /** + * Sets an offset param to the zbuffer to be used when comparing an incoming fragment for depth buffer pass/fail. + * + * @param offset + * Is multiplied by an implementation-specific value to create a constant depth offset. The initial value + * is 0. + */ + public void setFactor(final float factor) { + _factor = factor; + setNeedsRefresh(true); + } + + /** + * @return the currently set offset factor. + */ + public float getFactor() { + return _factor; + } + + /** + * Sets an offset param to the zbuffer to be used when comparing an incoming fragment for depth buffer pass/fail. + * + * @param units + * Is multiplied by an implementation-specific value to create a constant depth offset. The initial value + * is 0. + */ + public void setUnits(final float units) { + _units = units; + setNeedsRefresh(true); + } + + /** + * @return the currently set offset units. + */ + public float getUnits() { + return _units; + } + + /** + * Enable or disable depth offset for a particular type. + * + * @param type + * @param enabled + */ + public void setTypeEnabled(final OffsetType type, final boolean enabled) { + if (enabled) { + _enabledOffsets.add(type); + } else { + _enabledOffsets.remove(type); + } + setNeedsRefresh(true); + } + + /** + * + * @param type + * the type to check + * @return true if offset is enabled for that type. (default is false for all types.) + */ + public boolean isTypeEnabled(final OffsetType type) { + return _enabledOffsets.contains(type); + } + + @Override + public StateType getType() { + return StateType.Offset; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_factor, "factor", 0); + capsule.write(_units, "units", 0); + capsule.write(_enabledOffsets.contains(OffsetType.Fill), "typeFill", false); + capsule.write(_enabledOffsets.contains(OffsetType.Line), "typeLine", false); + capsule.write(_enabledOffsets.contains(OffsetType.Point), "typePoint", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _factor = capsule.readFloat("factor", 0); + _units = capsule.readFloat("units", 0); + _enabledOffsets.clear(); + if (capsule.readBoolean("typeFill", false)) { + _enabledOffsets.add(OffsetType.Fill); + } + if (capsule.readBoolean("typeLine", false)) { + _enabledOffsets.add(OffsetType.Line); + } + if (capsule.readBoolean("typePoint", false)) { + _enabledOffsets.add(OffsetType.Point); + } + } + + @Override + public StateRecord createStateRecord() { + return new OffsetStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java new file mode 100644 index 0000000..a09e152 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java @@ -0,0 +1,279 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Stack; + +import com.ardor3d.math.ObjectPool; +import com.ardor3d.math.Poolable; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.Constants; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.google.common.collect.Maps; + +/** + * <code>RenderState</code> is the base class for all states that affect the rendering of a piece of geometry. They + * aren't created directly, but are created for users from the renderer. The renderstate of a parent can affect its + * children and it is OK to assign to more than one Spatial the same render state. + */ +public abstract class RenderState implements Savable { + + // XXX: This enum may change, particularly the shader portions + public enum StateType { + Blend, Fog, Light, Material, Shading, Texture, Wireframe, ZBuffer, Cull, VertexProgram, FragmentProgram, Stencil, GLSLShader, ColorMask, Clip, Offset; + + // cached + public static StateType[] values = values(); + } + + /** + * <p> + * If false, each renderstate of that type is always applied in the renderer and only field by field checks are done + * to minimize jni overhead. This is slower than setting to true, but relieves the programmer from situations where + * he has to remember to update the needsRefresh field of a state. + * </p> + * <p> + * If true, each renderstate of that type is checked for == with the last applied renderstate of the same type. If + * same and the state's needsRefresh method returns false, then application of the renderstate is skipped. This can + * be much faster than setting false, but in certain circumstances, the programmer must manually set needsRefresh + * (for example, in a FogState, if you call getFogColor().set(....) to change the color, the fogstate will not set + * the needsRefresh field. In non-quick compare mode, this is not a problem because it will go into the apply method + * and do an actual check of the current fog color in opengl vs. the color in the state being applied.) + * </p> + * <p> + * DEFAULTS: + * <ul> + * <li>Blend: true</li> + * <li>Fog: true</li> + * <li>Light: false - because you can change a light object directly without telling the state</li> + * <li>Material: true</li> + * <li>Shading: true</li> + * <li>Texture: false - because you can change a texture object directly without telling the state</li> + * <li>Wireframe: false - because line attributes can change when drawing regular lines, affecting wireframe lines</li> + * <li>ZBuffer: true</li> + * <li>Cull: true</li> + * <li>VertexShader1: true</li> + * <li>FragmentShader1: true</li> + * <li>Stencil: false</li> + * <li>GLSLShader: true</li> + * <li>ColorMask: true</li> + * <li>Clip: true</li> + * <li>Offset: true</li> + * </ul> + */ + public static final EnumSet<StateType> _quickCompare = EnumSet.noneOf(StateType.class); + static { + _quickCompare.add(StateType.Blend); + _quickCompare.add(StateType.Fog); + _quickCompare.add(StateType.Material); + _quickCompare.add(StateType.Shading); + _quickCompare.add(StateType.ZBuffer); + _quickCompare.add(StateType.Cull); + _quickCompare.add(StateType.VertexProgram); + _quickCompare.add(StateType.FragmentProgram); + _quickCompare.add(StateType.GLSLShader); + _quickCompare.add(StateType.ColorMask); + _quickCompare.add(StateType.Offset); + } + + private static final ObjectPool<StateStack> STATESTACKS_POOL = ObjectPool.create(StateStack.class, + Constants.maxStatePoolSize); + + static public class StateStack implements Poolable { + + private final EnumMap<RenderState.StateType, Stack<RenderState>> stacks = Maps + .newEnumMap(RenderState.StateType.class); + + public StateStack() {} + + public final static StateStack fetchTempInstance() { + if (Constants.useStatePools) { + final StateStack s = STATESTACKS_POOL.fetch(); + // re-use already allocated stacks + for (final Stack<RenderState> stack : s.stacks.values()) { + stack.clear(); + } + return s; + } else { + return new StateStack(); + } + } + + public final static void releaseTempInstance(final StateStack s) { + if (Constants.useStatePools) { + STATESTACKS_POOL.release(s); + } + } + + public void push(final RenderState state) { + Stack<RenderState> stack = stacks.get(state.getType()); + if (stack == null) { + stack = new Stack<RenderState>(); + stacks.put(state.getType(), stack); + } + stack.push(state); + } + + public void pop(final RenderState state) { + final Stack<RenderState> stack = stacks.get(state.getType()); + stack.pop(); + } + + public void extract(final EnumMap<StateType, RenderState> states, final Spatial caller) { + RenderState state; + for (final Stack<RenderState> stack : stacks.values()) { + if (!stack.isEmpty()) { + state = stack.peek().extract(stack, caller); + states.put(state.getType(), state); + } + } + } + }; + + private boolean _enabled = true; + private boolean _needsRefresh = false; + + /** + * Constructs a new RenderState. The state is enabled by default. + */ + public RenderState() {} + + /** + * @return An statetype enum value for the subclass. + * @see StateType + */ + public abstract StateType getType(); + + /** + * Returns if this render state is enabled during rendering. Disabled states are ignored. + * + * @return True if this state is enabled. + */ + public boolean isEnabled() { + return _enabled; + } + + /** + * Sets if this render state is enabled during rendering. Disabled states are ignored. + * + * @param value + * False if the state is to be disabled, true otherwise. + */ + public void setEnabled(final boolean value) { + _enabled = value; + setNeedsRefresh(true); + } + + /** + * Extracts from the stack the correct renderstate that should apply to the given spatial. This is mainly used for + * RenderStates that can be cumulitive such as TextureState or LightState. By default, the top of the static is + * returned. This function should not be called by users directly. + * + * @param stack + * The stack to extract render states from. + * @param spat + * The spatial to apply the render states too. + * @return The render state to use. + */ + public RenderState extract(final Stack<? extends RenderState> stack, final Spatial spat) { + // The default behavior is to return the top of the stack, the last item + // pushed during the recursive traversal. + return stack.peek(); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_enabled, "enabled", true); + } + + public void read(final InputCapsule capsule) throws IOException { + _enabled = capsule.readBoolean("enabled", true); + } + + public Class<? extends RenderState> getClassTag() { + return this.getClass(); + } + + public abstract StateRecord createStateRecord(); + + /** + * @return true if we should apply this state even if we think it is the current state of its type in the current + * context. Is reset to false after apply is finished. + */ + public boolean needsRefresh() { + return _needsRefresh; + } + + /** + * This should be called by states when it knows internal data has been altered. + * + * @param refresh + * true if we should apply this state even if we think it is the current state of its type in the current + * context. + */ + public void setNeedsRefresh(final boolean refresh) { + _needsRefresh = refresh; + } + + /** + * @see #_quickCompare + * @param enabled + */ + public static void setQuickCompares(final boolean enabled) { + _quickCompare.clear(); + if (enabled) { + _quickCompare.addAll(EnumSet.allOf(StateType.class)); + } + } + + public static RenderState createState(final StateType type) { + switch (type) { + case Blend: + return new BlendState(); + case Clip: + return new ClipState(); + case ColorMask: + return new ColorMaskState(); + case Cull: + return new CullState(); + case Fog: + return new FogState(); + case FragmentProgram: + return new FragmentProgramState(); + case GLSLShader: + return new GLSLShaderObjectsState(); + case Light: + return new LightState(); + case Material: + return new MaterialState(); + case Offset: + return new OffsetState(); + case Shading: + return new ShadingState(); + case Stencil: + return new StencilState(); + case Texture: + return new TextureState(); + case VertexProgram: + return new VertexProgramState(); + case Wireframe: + return new WireframeState(); + case ZBuffer: + return new ZBufferState(); + } + throw new IllegalArgumentException("Unknown state type: " + type); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java new file mode 100644 index 0000000..1ca1b72 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.ShadingStateRecord; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>ShadeState</code> maintains the interpolation of color between vertices. Smooth shades the colors with proper + * linear interpolation, while flat provides no smoothing. If this state is not enabled, Smooth is used. + */ +public class ShadingState extends RenderState { + + public enum ShadingMode { + /** + * Pick the color of just one vertex of a triangle and rasterize all pixels of the triangle with this color. + */ + Flat, + /** + * Smoothly interpolate the color values between the three colors of the three vertices. (Default) + */ + Smooth; + } + + // shade mode. + protected ShadingMode _shadeMode = ShadingMode.Smooth; + + /** + * Constructor instantiates a new <code>ShadeState</code> object with the default mode being smooth. + */ + public ShadingState() {} + + /** + * <code>getShade</code> returns the current shading mode. + * + * @return the current shading mode. + */ + public ShadingMode getShadingMode() { + return _shadeMode; + } + + /** + * <code>setShadeMode</code> sets the current shading mode. + * + * @param shadeMode + * the new shading mode. + * @throws IllegalArgumentException + * if shadeMode is null + */ + public void setShadingMode(final ShadingMode shadeMode) { + if (shadeMode == null) { + throw new IllegalArgumentException("shadeMode can not be null."); + } + _shadeMode = shadeMode; + setNeedsRefresh(true); + } + + @Override + public StateType getType() { + return StateType.Shading; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_shadeMode, "shadeMode", ShadingMode.Smooth); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _shadeMode = capsule.readEnum("shadeMode", ShadingMode.class, ShadingMode.Smooth); + } + + @Override + public StateRecord createStateRecord() { + return new ShadingStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java new file mode 100644 index 0000000..56b5745 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java @@ -0,0 +1,580 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.renderer.state.record.StencilStateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * The StencilState RenderState allows the user to set the attributes of the stencil buffer of the renderer. The + * Stenciling is similar to Z-Buffering in that it allows enabling and disabling drawing on a per pixel basis. You can + * use the stencil plane to mask out portions of the rendering to create special effects, such as outlining or planar + * shadows. Our stencil state supports setting operations for front and back facing polygons separately. If your card + * does not support setting faces independently, the front face values will be used for both sides. + */ +public class StencilState extends RenderState { + + public enum StencilFunction { + /** A stencil function that never passes. */ + Never, + /** A stencil function that passes if (ref & mask) < (stencil & mask). */ + LessThan, + /** A stencil function that passes if (ref & mask) <= (stencil & mask). */ + LessThanOrEqualTo, + /** A stencil function that passes if (ref & mask) > (stencil & mask). */ + GreaterThan, + /** A stencil function that passes if (ref & mask) >= (stencil & mask). */ + GreaterThanOrEqualTo, + /** A stencil function that passes if (ref & mask) == (stencil & mask). */ + EqualTo, + /** A stencil function that passes if (ref & mask) != (stencil & mask). */ + NotEqualTo, + /** A stencil function that always passes. (Default) */ + Always; + } + + public enum StencilOperation { + /** A stencil function result that keeps the current value. */ + Keep, + /** A stencil function result that sets the stencil buffer value to 0. */ + Zero, + /** + * A stencil function result that sets the stencil buffer value to ref, as specified by stencil function. + */ + Replace, + /** + * A stencil function result that increments the current stencil buffer value. + */ + Increment, + /** + * A stencil function result that decrements the current stencil buffer value. + */ + Decrement, + /** + * A stencil function result that increments the current stencil buffer value and wraps around to the lowest + * stencil value if it reaches the max. (if the renderer does not support stencil wrap, we'll fall back to + * Increment) + */ + IncrementWrap, + /** + * A stencil function result that decrements the current stencil buffer and wraps around to the highest stencil + * value if it reaches the min. value. (if the renderer does not support stencil wrap, we'll fall back to + * Decrement) + */ + DecrementWrap, + /** + * A stencil function result that bitwise inverts the current stencil buffer value. + */ + Invert; + } + + private boolean _useTwoSided = false; + + // Front + private StencilFunction _stencilFunctionFront = StencilFunction.Always; + + private int _stencilReferenceFront = 0; + private int _stencilFuncMaskFront = ~0; + private int _stencilWriteMaskFront = ~0; + + private StencilOperation _stencilOpFailFront = StencilOperation.Keep; + private StencilOperation _stencilOpZFailFront = StencilOperation.Keep; + private StencilOperation _stencilOpZPassFront = StencilOperation.Keep; + + // Back + private StencilFunction _stencilFunctionBack = StencilFunction.Always; + + private int _stencilReferenceBack = 0; + private int _stencilFuncMaskBack = ~0; + private int _stencilWriteMaskBack = ~0; + + private StencilOperation _stencilOpFailBack = StencilOperation.Keep; + private StencilOperation _stencilOpZFailBack = StencilOperation.Keep; + private StencilOperation _stencilOpZPassBack = StencilOperation.Keep; + + @Override + public StateType getType() { + return StateType.Stencil; + } + + /** + * Sets the function that defines if a stencil test passes or not for both faces. + * + * @param function + * The new stencil function for both faces. + * @throws IllegalArgumentException + * if function is null + */ + public void setStencilFunction(final StencilFunction function) { + setStencilFunctionFront(function); + setStencilFunctionBack(function); + } + + /** + * Sets the stencil reference to be used during the stencil function for both faces. + * + * @param reference + * The new stencil reference for both faces. + */ + public void setStencilReference(final int reference) { + setStencilReferenceFront(reference); + setStencilReferenceBack(reference); + } + + /** + * Convienence method for setting both types of stencil masks at once for both faces. + * + * @param mask + * The new stencil write and func mask for both faces. + */ + public void setStencilMask(final int mask) { + setStencilMaskFront(mask); + setStencilMaskBack(mask); + } + + /** + * Controls which stencil bitplanes are written for both faces. + * + * @param mask + * The new stencil write mask for both faces. + */ + public void setStencilWriteMask(final int mask) { + setStencilWriteMaskFront(mask); + setStencilWriteMaskBack(mask); + } + + /** + * Sets the stencil mask to be used during stencil functions for both faces. + * + * @param mask + * The new stencil function mask for both faces. + */ + public void setStencilFuncMask(final int mask) { + setStencilFuncMaskFront(mask); + setStencilFuncMaskBack(mask); + } + + /** + * Specifies the aciton to take when the stencil test fails for both faces. + * + * @param operation + * The new stencil operation for both faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpFail(final StencilOperation operation) { + setStencilOpFailFront(operation); + setStencilOpFailBack(operation); + } + + /** + * Specifies stencil action when the stencil test passes, but the depth test fails for both faces. + * + * @param operation + * The Z test operation to set for both faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpZFail(final StencilOperation operation) { + setStencilOpZFailFront(operation); + setStencilOpZFailBack(operation); + } + + /** + * Specifies stencil action when both the stencil test and the depth test pass, or when the stencil test passes and + * either there is no depth buffer or depth testing is not enabled. + * + * @param operation + * The new Z test pass operation to set for both faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpZPass(final StencilOperation operation) { + setStencilOpZPassFront(operation); + setStencilOpZPassBack(operation); + } + + /** + * Sets the function that defines if a stencil test passes or not for front faces. + * + * @param function + * The new stencil function for front faces. + * @throws IllegalArgumentException + * if function is null + */ + public void setStencilFunctionFront(final StencilFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _stencilFunctionFront = function; + setNeedsRefresh(true); + } + + /** + * @return The current stencil function for front faces. Default is StencilFunction.Always + */ + public StencilFunction getStencilFunctionFront() { + return _stencilFunctionFront; + } + + /** + * Sets the stencil reference to be used during the stencil function for front faces. + * + * @param reference + * The new stencil reference for front faces. + */ + public void setStencilReferenceFront(final int reference) { + _stencilReferenceFront = reference; + setNeedsRefresh(true); + } + + /** + * @return The current stencil reference for front faces. Default is 0 + */ + public int getStencilReferenceFront() { + return _stencilReferenceFront; + } + + /** + * Convienence method for setting both types of stencil masks at once for front faces. + * + * @param mask + * The new stencil write and func mask for front faces. + */ + public void setStencilMaskFront(final int mask) { + setStencilWriteMaskFront(mask); + setStencilFuncMaskFront(mask); + } + + /** + * Controls which stencil bitplanes are written for front faces. + * + * @param mask + * The new stencil write mask for front faces. + */ + public void setStencilWriteMaskFront(final int mask) { + _stencilWriteMaskFront = mask; + setNeedsRefresh(true); + } + + /** + * @return The current stencil write mask for front faces. Default is all 1's (~0) + */ + public int getStencilWriteMaskFront() { + return _stencilWriteMaskFront; + } + + /** + * Sets the stencil mask to be used during stencil functions for front faces. + * + * @param mask + * The new stencil function mask for front faces. + */ + public void setStencilFuncMaskFront(final int mask) { + _stencilFuncMaskFront = mask; + setNeedsRefresh(true); + } + + /** + * @return The current stencil function mask for front faces. Default is all 1's (~0) + */ + public int getStencilFuncMaskFront() { + return _stencilFuncMaskFront; + } + + /** + * Specifies the aciton to take when the stencil test fails for front faces. + * + * @param operation + * The new stencil operation for front faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpFailFront(final StencilOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("operation can not be null."); + } + _stencilOpFailFront = operation; + setNeedsRefresh(true); + } + + /** + * @return The current stencil operation for front faces. Default is StencilOperation.Keep + */ + public StencilOperation getStencilOpFailFront() { + return _stencilOpFailFront; + } + + /** + * Specifies stencil action when the stencil test passes, but the depth test fails for front faces. + * + * @param operation + * The Z test operation to set for front faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpZFailFront(final StencilOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("operation can not be null."); + } + _stencilOpZFailFront = operation; + setNeedsRefresh(true); + } + + /** + * @return The current Z op fail function for front faces. Default is StencilOperation.Keep + */ + public StencilOperation getStencilOpZFailFront() { + return _stencilOpZFailFront; + } + + /** + * Specifies stencil action when both the stencil test and the depth test pass, or when the stencil test passes and + * either there is no depth buffer or depth testing is not enabled. + * + * @param operation + * The new Z test pass operation to set for front faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpZPassFront(final StencilOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("operation can not be null."); + } + _stencilOpZPassFront = operation; + setNeedsRefresh(true); + } + + /** + * @return The current Z op pass function for front faces. Default is StencilOperation.Keep + */ + public StencilOperation getStencilOpZPassFront() { + return _stencilOpZPassFront; + } + + /** + * Sets the function that defines if a stencil test passes or not for back faces. + * + * @param function + * The new stencil function for back faces. + * @throws IllegalArgumentException + * if function is null + */ + public void setStencilFunctionBack(final StencilFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _stencilFunctionBack = function; + setNeedsRefresh(true); + } + + /** + * @return The current stencil function for back faces. Default is StencilFunction.Always + */ + public StencilFunction getStencilFunctionBack() { + return _stencilFunctionBack; + } + + /** + * Sets the stencil reference to be used during the stencil function for back faces. + * + * @param reference + * The new stencil reference for back faces. + */ + public void setStencilReferenceBack(final int reference) { + _stencilReferenceBack = reference; + setNeedsRefresh(true); + } + + /** + * @return The current stencil reference for back faces. Default is 0 + */ + public int getStencilReferenceBack() { + return _stencilReferenceBack; + } + + /** + * Convienence method for setting both types of stencil masks at once for back faces. + * + * @param mask + * The new stencil write and func mask for back faces. + */ + public void setStencilMaskBack(final int mask) { + setStencilWriteMaskBack(mask); + setStencilFuncMaskBack(mask); + } + + /** + * Controls which stencil bitplanes are written for back faces. + * + * @param mask + * The new stencil write mask for back faces. + */ + public void setStencilWriteMaskBack(final int mask) { + _stencilWriteMaskBack = mask; + setNeedsRefresh(true); + } + + /** + * @return The current stencil write mask for back faces. Default is all 1's (~0) + */ + public int getStencilWriteMaskBack() { + return _stencilWriteMaskBack; + } + + /** + * Sets the stencil mask to be used during stencil functions for back faces. + * + * @param mask + * The new stencil function mask for back faces. + */ + public void setStencilFuncMaskBack(final int mask) { + _stencilFuncMaskBack = mask; + setNeedsRefresh(true); + } + + /** + * @return The current stencil function mask for back faces. Default is all 1's (~0) + */ + public int getStencilFuncMaskBack() { + return _stencilFuncMaskBack; + } + + /** + * Specifies the aciton to take when the stencil test fails for back faces. + * + * @param operation + * The new stencil operation for back faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpFailBack(final StencilOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("operation can not be null."); + } + _stencilOpFailBack = operation; + setNeedsRefresh(true); + } + + /** + * @return The current stencil operation for back faces. Default is StencilOperation.Keep + */ + public StencilOperation getStencilOpFailBack() { + return _stencilOpFailBack; + } + + /** + * Specifies stencil action when the stencil test passes, but the depth test fails. + * + * @param operation + * The Z test operation to set for back faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpZFailBack(final StencilOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("operation can not be null."); + } + _stencilOpZFailBack = operation; + setNeedsRefresh(true); + } + + /** + * @return The current Z op fail function for back faces. Default is StencilOperation.Keep + */ + public StencilOperation getStencilOpZFailBack() { + return _stencilOpZFailBack; + } + + /** + * Specifies stencil action when both the stencil test and the depth test pass, or when the stencil test passes and + * either there is no depth buffer or depth testing is not enabled. + * + * @param operation + * The new Z test pass operation to set for back faces. + * @throws IllegalArgumentException + * if operation is null + */ + public void setStencilOpZPassBack(final StencilOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("operation can not be null."); + } + _stencilOpZPassBack = operation; + setNeedsRefresh(true); + } + + /** + * @return The current Z op pass function for back faces. Default is StencilOperation.Keep + */ + public StencilOperation getStencilOpZPassBack() { + return _stencilOpZPassBack; + } + + public boolean isUseTwoSided() { + return _useTwoSided; + } + + public void setUseTwoSided(final boolean useTwoSided) { + _useTwoSided = useTwoSided; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_useTwoSided, "useTwoSided", false); + capsule.write(_stencilFunctionFront, "stencilFuncFront", StencilFunction.Always); + capsule.write(_stencilReferenceFront, "stencilRefFront", 0); + capsule.write(_stencilWriteMaskFront, "stencilWriteMaskFront", ~0); + capsule.write(_stencilFuncMaskFront, "stencilFuncMaskFront", ~0); + capsule.write(_stencilOpFailFront, "stencilOpFailFront", StencilOperation.Keep); + capsule.write(_stencilOpZFailFront, "stencilOpZFailFront", StencilOperation.Keep); + capsule.write(_stencilOpZPassFront, "stencilOpZPassFront", StencilOperation.Keep); + + capsule.write(_stencilFunctionBack, "stencilFuncBack", StencilFunction.Always); + capsule.write(_stencilReferenceBack, "stencilRefBack", 0); + capsule.write(_stencilWriteMaskBack, "stencilWriteMaskBack", ~0); + capsule.write(_stencilFuncMaskBack, "stencilFuncMaskBack", ~0); + capsule.write(_stencilOpFailBack, "stencilOpFailBack", StencilOperation.Keep); + capsule.write(_stencilOpZFailBack, "stencilOpZFailBack", StencilOperation.Keep); + capsule.write(_stencilOpZPassBack, "stencilOpZPassBack", StencilOperation.Keep); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _useTwoSided = capsule.readBoolean("useTwoSided", false); + _stencilFunctionFront = capsule.readEnum("stencilFuncFront", StencilFunction.class, StencilFunction.Always); + _stencilReferenceFront = capsule.readInt("stencilRefFront", 0); + _stencilWriteMaskFront = capsule.readInt("stencilWriteMaskFront", ~0); + _stencilFuncMaskFront = capsule.readInt("stencilFuncMaskFront", ~0); + _stencilOpFailFront = capsule.readEnum("stencilOpFailFront", StencilOperation.class, StencilOperation.Keep); + _stencilOpZFailFront = capsule.readEnum("stencilOpZFailFront", StencilOperation.class, StencilOperation.Keep); + _stencilOpZPassFront = capsule.readEnum("stencilOpZPassFront", StencilOperation.class, StencilOperation.Keep); + + _stencilFunctionBack = capsule.readEnum("stencilFuncBack", StencilFunction.class, StencilFunction.Always); + _stencilReferenceBack = capsule.readInt("stencilRefBack", 0); + _stencilWriteMaskBack = capsule.readInt("stencilWriteMaskBack", ~0); + _stencilFuncMaskBack = capsule.readInt("stencilFuncMaskBack", ~0); + _stencilOpFailBack = capsule.readEnum("stencilOpFailBack", StencilOperation.class, StencilOperation.Keep); + _stencilOpZFailBack = capsule.readEnum("stencilOpZFailBack", StencilOperation.class, StencilOperation.Keep); + _stencilOpZPassBack = capsule.readEnum("stencilOpZPassBack", StencilOperation.class, StencilOperation.Keep); + } + + @Override + public StateRecord createStateRecord() { + return new StencilStateRecord(); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java new file mode 100644 index 0000000..d895eb7 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java @@ -0,0 +1,376 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.image.Image; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture2D; +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.renderer.state.record.TextureStateRecord; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.util.TextureKey; +import com.ardor3d.util.TextureManager; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.ardor3d.util.resource.ResourceSource; +import com.ardor3d.util.resource.URLResourceSource; + +/** + * <code>TextureState</code> maintains a texture state for a given node and it's children. The number of states that a + * TextureState can maintain at one time is equal to the number of texture units available on the GPU. It is not within + * the scope of this class to generate the texture, and is recommended that <code>TextureManager</code> be used to + * create the Texture objects. + * + * @see com.ardor3d.util.TextureManager + */ +public class TextureState extends RenderState { + private static final Logger logger = Logger.getLogger(TextureState.class.getName()); + + public static final int MAX_TEXTURES = 32; + + protected static Texture _defaultTexture = null; + protected static boolean defaultTextureLoaded = false; + + public enum CorrectionType { + /** + * Correction modifier makes no color corrections, and is the fastest. + */ + Affine, + + /** + * Correction modifier makes color corrections based on perspective and is slower than CM_AFFINE. (Default) + */ + Perspective; + } + + /** The texture(s). */ + protected List<Texture> _texture = new ArrayList<Texture>(1); + + /** + * Perspective correction to use for the object rendered with this texture state. Default is + * CorrectionType.Perspective. + */ + private CorrectionType _correctionType = CorrectionType.Perspective; + + public transient TextureKey[] _keyCache = new TextureKey[MAX_TEXTURES]; + + public static ResourceSource DEFAULT_TEXTURE_SOURCE; + static { + try { + DEFAULT_TEXTURE_SOURCE = new URLResourceSource(ResourceLocatorTool.getClassPathResource(TextureState.class, + "com/ardor3d/renderer/state/notloaded.tga")); + } catch (final Exception e) { + // ignore. + DEFAULT_TEXTURE_SOURCE = null; + } + } + + /** + * Constructor instantiates a new <code>TextureState</code> object. + */ + public TextureState() { + if (!defaultTextureLoaded) { + loadDefaultTexture(); + } + } + + @Override + public StateType getType() { + return StateType.Texture; + } + + /** + * <code>setTexture</code> sets a single texture to the first texture unit. + * + * @param texture + * the texture to set. + */ + public void setTexture(final Texture texture) { + if (_texture.size() == 0) { + _texture.add(texture); + } else { + _texture.set(0, texture); + } + setNeedsRefresh(true); + } + + /** + * <code>getTexture</code> gets the texture that is assigned to the first texture unit. + * + * @return the texture in the first texture unit. + */ + public Texture getTexture() { + if (_texture.size() > 0) { + return _texture.get(0); + } else { + return null; + } + } + + /** + * <code>setTexture</code> sets the texture object to be used by the state. The texture unit that this texture uses + * is set, if the unit is not valid, i.e. less than zero or greater than the number of texture units supported by + * the graphics card, it is ignored. + * + * @param texture + * the texture to be used by the state. + * @param textureUnit + * the texture unit this texture will fill. + */ + public void setTexture(final Texture texture, final int textureUnit) { + if (textureUnit >= 0 && textureUnit < MAX_TEXTURES) { + while (textureUnit >= _texture.size()) { + _texture.add(null); + } + _texture.set(textureUnit, texture); + } + setNeedsRefresh(true); + } + + /** + * <code>getTexture</code> retrieves the texture being used by the state in a particular texture unit. + * + * @param textureUnit + * the texture unit to retrieve the texture from. + * @return the texture being used by the state. If the texture unit is invalid, null is returned. + */ + public Texture getTexture(final int textureUnit) { + if (textureUnit < _texture.size() && textureUnit >= 0) { + return _texture.get(textureUnit); + } + + return null; + } + + public boolean removeTexture(final Texture tex) { + + final int index = _texture.indexOf(tex); + if (index == -1) { + return false; + } + + _texture.set(index, null); + _keyCache[index] = null; + return true; + } + + public boolean removeTexture(final int textureUnit) { + if (textureUnit < 0 || textureUnit >= MAX_TEXTURES || textureUnit >= _texture.size()) { + return false; + } + + final Texture t = _texture.get(textureUnit); + if (t == null) { + return false; + } + + _texture.set(textureUnit, null); + _keyCache[textureUnit] = null; + return true; + + } + + /** + * Removes all textures in this texture state. Does not delete them from the graphics card. + */ + public void clearTextures() { + for (int i = _texture.size(); --i >= 0;) { + removeTexture(i); + } + } + + /** + * <code>setCorrectionType</code> sets the image correction type for this texture state. + * + * @param type + * the correction type for this texture. + * @throws IllegalArgumentException + * if type is null + */ + public void setCorrectionType(final CorrectionType type) { + if (type == null) { + throw new IllegalArgumentException("type can not be null."); + } + _correctionType = type; + setNeedsRefresh(true); + } + + /** + * <code>getCorrectionType</code> returns the correction mode for the texture state. + * + * @return the correction type for the texture state. + */ + public CorrectionType getCorrectionType() { + return _correctionType; + } + + /** + * Returns the number of textures this texture manager is maintaining. + * + * @return the number of textures. + */ + public int getNumberOfSetTextures() { + int set = 0; + for (int i = 0; i < _texture.size(); i++) { + if (_texture.get(i) != null) { + set++; + } + } + return set; + } + + /** + * Returns the max index in this TextureState that contains a non-null Texture. + * + * @return the max index, or -1 if no textures are contained by this state. + */ + public int getMaxTextureIndexUsed() { + int max = _texture.size() - 1; + while (max > 0 && _texture.get(max) == null) { + max--; + } + return max; + } + + /** + * Fast access for retrieving a TextureKey. A return is guaranteed when <code>textureUnit</code> is any number under + * or equal to the highest texture unit currently in use. This value can be retrieved with + * <code>getNumberOfSetTextures</code>. A higher value might result in unexpected behavior such as an exception + * being thrown. + * + * @param textureUnit + * The texture unit from which to retrieve the TextureKey. + * @return the TextureKey, or null if there is none. + */ + public final TextureKey getTextureKey(final int textureUnit) { + if (textureUnit < _keyCache.length && textureUnit >= 0) { + return _keyCache[textureUnit]; + } + + return null; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.writeSavableList(_texture, "texture", new ArrayList<Texture>(1)); + capsule.write(_correctionType, "correctionType", CorrectionType.Perspective); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _texture = capsule.readSavableList("texture", new ArrayList<Texture>(1)); + _correctionType = capsule.readEnum("correctionType", CorrectionType.class, CorrectionType.Perspective); + } + + public static Image getDefaultTextureImage() { + return _defaultTexture != null ? _defaultTexture.getImage() : null; + } + + public static Texture getDefaultTexture() { + if (!defaultTextureLoaded) { + loadDefaultTexture(); + } + return _defaultTexture.createSimpleClone(); + } + + private static void loadDefaultTexture() { + synchronized (logger) { + if (!defaultTextureLoaded) { + defaultTextureLoaded = true; + _defaultTexture = new Texture2D(); + try { + _defaultTexture = TextureManager.load(DEFAULT_TEXTURE_SOURCE, Texture.MinificationFilter.Trilinear, + true); + } catch (final Exception e) { + logger.log(Level.WARNING, "Failed to load default texture: notloaded.tga", e); + } + } + } + } + + @Override + public StateRecord createStateRecord() { + return new TextureStateRecord(); + } + + @Override + public RenderState extract(final Stack<? extends RenderState> stack, final Spatial spat) { + if (spat == null) { + return stack.peek(); + } + + final TextureCombineMode mode = spat.getSceneHints().getTextureCombineMode(); + if (mode == TextureCombineMode.Replace || (mode != TextureCombineMode.Off && stack.size() == 1)) { + // todo: use dummy state if off? + return stack.peek(); + } + + // accumulate the textures in the stack into a single TextureState object + final TextureState newTState = new TextureState(); + boolean foundEnabled = false; + final Object states[] = stack.toArray(); + switch (mode) { + case CombineClosest: + case CombineClosestEnabled: + for (int iIndex = states.length - 1; iIndex >= 0; iIndex--) { + final TextureState pkTState = (TextureState) states[iIndex]; + if (!pkTState.isEnabled()) { + if (mode == TextureCombineMode.CombineClosestEnabled) { + break; + } + + continue; + } + + foundEnabled = true; + for (int i = 0, max = pkTState.getMaxTextureIndexUsed(); i <= max; i++) { + final Texture pkText = pkTState.getTexture(i); + if (newTState.getTexture(i) == null) { + newTState.setTexture(pkText, i); + } + } + } + break; + case CombineFirst: + for (int iIndex = 0, max = states.length; iIndex < max; iIndex++) { + final TextureState pkTState = (TextureState) states[iIndex]; + if (!pkTState.isEnabled()) { + continue; + } + + foundEnabled = true; + for (int i = 0; i < TextureState.MAX_TEXTURES; i++) { + final Texture pkText = pkTState.getTexture(i); + if (newTState.getTexture(i) == null) { + newTState.setTexture(pkText, i); + } + } + } + break; + case Off: + break; + } + newTState.setEnabled(foundEnabled); + return newTState; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java new file mode 100644 index 0000000..ea8564b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.renderer.state.record.VertexProgramStateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Implementation of the GL_ARB_vertex_program extension. + */ +public class VertexProgramState extends RenderState { + private static final Logger logger = Logger.getLogger(VertexProgramState.class.getName()); + + /** Environmental parameters applied to all vertex programs */ + protected static float[][] _envparameters = new float[96][]; + + /** If any local parameters for this VP state are set */ + protected boolean _usingParameters = false; + + /** Parameters local to this vertex program */ + protected float[][] _parameters; + protected ByteBuffer _program; + + protected int _programID = -1; + + /** + * <code>setEnvParameter</code> sets an environmental vertex program parameter that is accessible by all vertex + * programs in memory. + * + * @param param + * four-element array of floating point numbers + * @param paramID + * identity number of the parameter, ranging from 0 to 95 + */ + public static void setEnvParameter(final float[] param, final int paramID) { + if (paramID < 0 || paramID > 95) { + throw new IllegalArgumentException("Invalid parameter ID"); + } + if (param != null && param.length != 4) { + throw new IllegalArgumentException("Vertex program parameters must be of type float[4]"); + } + + _envparameters[paramID] = param; + } + + /** + * Creates a new VertexProgramState. <code>load(URL)</code> must be called before the state can be used. + */ + public VertexProgramState() { + _parameters = new float[96][]; + } + + /** + * <code>setParameter</code> sets a parameter for this vertex program. + * + * @param paramID + * identity number of the parameter, ranging from 0 to 95 + * @param param + * four-element array of floating point numbers + */ + public void setParameter(final float[] param, final int paramID) { + if (paramID < 0 || paramID > 95) { + throw new IllegalArgumentException("Invalid parameter ID"); + } + if (param != null && param.length != 4) { + throw new IllegalArgumentException("Vertex program parameters must be of type float[4]"); + } + + _usingParameters = true; + _parameters[paramID] = param; + setNeedsRefresh(true); + } + + @Override + public StateType getType() { + return StateType.VertexProgram; + } + + public void load(final java.net.URL file) { + InputStream inputStream = null; + try { + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16 * 1024); + inputStream = new BufferedInputStream(file.openStream()); + final byte[] buffer = new byte[1024]; + int byteCount = -1; + + // Read the byte content into the output stream first + while ((byteCount = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, byteCount); + } + + // Set data with byte content from stream + final byte[] data = outputStream.toByteArray(); + + // Release resources + inputStream.close(); + outputStream.close(); + + _program = BufferUtils.createByteBuffer(data.length); + _program.put(data); + _program.rewind(); + _programID = -1; + setNeedsRefresh(true); + + } catch (final Exception e) { + logger.severe("Could not load vertex program: " + e); + logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e); + } finally { + // Ensure that the stream is closed, even if there is an exception. + if (inputStream != null) { + try { + inputStream.close(); + } catch (final IOException closeFailure) { + logger.log(Level.WARNING, "Failed to close the vertex program", closeFailure); + } + } + + } + } + + /** + * Loads the vertex program into a byte array. + * + * @see com.ardor3d.renderer.state.VertexProgramState#load(java.net.URL) + */ + public void load(final String programContents) { + try { + final byte[] bytes = programContents.getBytes(); + _program = BufferUtils.createByteBuffer(bytes.length); + _program.put(bytes); + _program.rewind(); + _programID = -1; + setNeedsRefresh(true); + + } catch (final Exception e) { + logger.severe("Could not load vertex program: " + e); + logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e); + } + } + + public ByteBuffer getProgramAsBuffer() { + return _program; + } + + public int _getProgramID() { + return _programID; + } + + public void _setProgramID(final int id) { + _programID = id; + } + + public boolean isUsingParameters() { + return _usingParameters; + } + + public float[][] _getParameters() { + return _parameters; + } + + public static float[][] _getEnvParameters() { + return _envparameters; + } + + /** + * Used with Serialization. Do not call this directly. + * + * @param s + * @throws IOException + * @see java.io.Serializable + */ + private void writeObject(final java.io.ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + if (_program == null) { + s.writeInt(0); + } else { + s.writeInt(_program.capacity()); + _program.rewind(); + for (int x = 0, len = _program.capacity(); x < len; x++) { + s.writeByte(_program.get()); + } + } + } + + /** + * Used with Serialization. Do not call this directly. + * + * @param s + * @throws IOException + * @throws ClassNotFoundException + * @see java.io.Serializable + */ + private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { + s.defaultReadObject(); + final int len = s.readInt(); + if (len == 0) { + _program = null; + } else { + _program = BufferUtils.createByteBuffer(len); + for (int x = 0; x < len; x++) { + _program.put(s.readByte()); + } + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_usingParameters, "usingParameters", false); + capsule.write(_parameters, "parameters", new float[96][]); + capsule.write(_program, "program", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _usingParameters = capsule.readBoolean("usingParameters", false); + _parameters = capsule.readFloatArray2D("parameters", new float[96][]); + _program = capsule.readByteBuffer("program", null); + } + + @Override + public StateRecord createStateRecord() { + return new VertexProgramStateRecord(); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java new file mode 100644 index 0000000..dbb099c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.renderer.state.record.WireframeStateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>WireframeState</code> maintains whether a node and it's children should be drawn in wireframe or solid fill. By + * default all nodes are rendered solid. + */ +public class WireframeState extends RenderState { + + public enum Face { + /** The front will be wireframed, but the back will be solid. */ + Front, + /** The back will be wireframed, but the front will be solid. */ + Back, + /** Both sides of the model are wireframed. */ + FrontAndBack; + } + + /** Default wireframe of front and back. */ + protected Face _face = Face.FrontAndBack; + /** Default line width of 1 pixel. */ + protected float _lineWidth = 1.0f; + /** Default line style */ + protected boolean _antialiased = false; + + @Override + public StateType getType() { + return StateType.Wireframe; + } + + /** + * <code>setLineWidth</code> sets the width of lines the wireframe is drawn in. Attempting to set a line width + * smaller than 0.0 throws an <code>IllegalArgumentException</code>. + * + * @param width + * the line width, in pixels + */ + public void setLineWidth(final float width) { + if (width < 0.0f) { + throw new IllegalArgumentException("Line width must be positive"); + } + + _lineWidth = width; + setNeedsRefresh(true); + } + + /** + * Returns the current lineWidth. + * + * @return the current LineWidth + */ + public float getLineWidth() { + return _lineWidth; + } + + /** + * <code>setFace</code> sets which face will recieve the wireframe. + * + * @param face + * which face will be rendered in wireframe. + * @throws IllegalArgumentException + * if face is null + */ + public void setFace(final Face face) { + if (face == null) { + throw new IllegalArgumentException("face can not be null."); + } + _face = face; + setNeedsRefresh(true); + } + + /** + * Returns the face state of this wireframe state. + * + * @return The face state (one of WS_FRONT, WS_BACK, or WS_FRONT_AND_BACK) + */ + public Face getFace() { + return _face; + } + + /** + * Set whether this wireframe should use antialiasing when drawing lines. May decrease performance. If you want to + * enabled antialiasing, you should also use an alphastate with a source of SourceFunction.SourceAlpha and a + * destination of DB_ONE_MINUS_SRC_ALPHA or DB_ONE. + * + * @param antialiased + * true for using smoothed antialiased lines. + */ + public void setAntialiased(final boolean antialiased) { + _antialiased = antialiased; + setNeedsRefresh(true); + } + + /** + * @return whether this wireframe uses antialiasing for drawing lines. + */ + public boolean isAntialiased() { + return _antialiased; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_face, "face", Face.FrontAndBack); + capsule.write(_lineWidth, "lineWidth", 1); + capsule.write(_antialiased, "antialiased", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _face = capsule.readEnum("face", Face.class, Face.FrontAndBack); + _lineWidth = capsule.readFloat("lineWidth", 1); + _antialiased = capsule.readBoolean("antialiased", false); + } + + @Override + public StateRecord createStateRecord() { + return new WireframeStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java new file mode 100644 index 0000000..ca79425 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state; + +import java.io.IOException; + +import com.ardor3d.renderer.state.record.StateRecord; +import com.ardor3d.renderer.state.record.ZBufferStateRecord; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>ZBufferState</code> maintains how the use of the depth buffer is to occur. Depth buffer comparisons are used to + * evaluate what incoming fragment will be used. This buffer is based on z depth, or distance between the pixel source + * and the eye. + */ +public class ZBufferState extends RenderState { + + public enum TestFunction { + /** + * Depth comparison never passes. + */ + Never, + /** + * Depth comparison always passes. + */ + Always, + /** + * Passes if the incoming value is the same as the stored value. + */ + EqualTo, + /** + * Passes if the incoming value is not equal to the stored value. + */ + NotEqualTo, + /** + * Passes if the incoming value is less than the stored value. + */ + LessThan, + /** + * Passes if the incoming value is less than or equal to the stored value. + */ + LessThanOrEqualTo, + /** + * Passes if the incoming value is greater than the stored value. + */ + GreaterThan, + /** + * Passes if the incoming value is greater than or equal to the stored value. + */ + GreaterThanOrEqualTo; + + } + + /** Depth function. */ + protected TestFunction _function = TestFunction.LessThan; + /** Depth mask is writable or not. */ + protected boolean _writable = true; + + /** + * Constructor instantiates a new <code>ZBufferState</code> object. The initial values are TestFunction.LessThan and + * depth writing on. + */ + public ZBufferState() {} + + /** + * <code>getFunction</code> returns the current depth function. + * + * @return the depth function currently used. + */ + public TestFunction getFunction() { + return _function; + } + + /** + * <code>setFunction</code> sets the depth function. + * + * @param function + * the depth function. + * @throws IllegalArgumentException + * if function is null + */ + public void setFunction(final TestFunction function) { + if (function == null) { + throw new IllegalArgumentException("function can not be null."); + } + _function = function; + setNeedsRefresh(true); + } + + /** + * <code>isWritable</code> returns if the depth mask is writable or not. + * + * @return true if the depth mask is writable, false otherwise. + */ + public boolean isWritable() { + return _writable; + } + + /** + * <code>setWritable</code> sets the depth mask writable or not. + * + * @param writable + * true to turn on depth writing, false otherwise. + */ + public void setWritable(final boolean writable) { + _writable = writable; + setNeedsRefresh(true); + } + + @Override + public StateType getType() { + return StateType.ZBuffer; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_function, "function", TestFunction.LessThan); + capsule.write(_writable, "writable", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _function = capsule.readEnum("function", TestFunction.class, TestFunction.LessThan); + _writable = capsule.readBoolean("writable", true); + } + + @Override + public StateRecord createStateRecord() { + return new ZBufferStateRecord(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java new file mode 100644 index 0000000..4687bba --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import com.ardor3d.math.ColorRGBA; + +public class BlendStateRecord extends StateRecord { + public boolean blendEnabled = false; + public boolean testEnabled = false; + + // RGB or primary + public int srcFactorRGB = -1; + public int dstFactorRGB = -1; + public int blendEqRGB = -1; + + // Alpha (if supported) + public int srcFactorAlpha = -1; + public int dstFactorAlpha = -1; + public int blendEqAlpha = -1; + + public int alphaFunc = -1; + public float alphaRef = -1; + + public ColorRGBA blendColor = new ColorRGBA(-1, -1, -1, -1); + + // sample coverage + public boolean sampleAlphaToCoverageEnabled = false; + public boolean sampleAlphaToOneEnabled = false; + public boolean sampleCoverageEnabled = false; + public boolean sampleCoverageInverted = false; + public float sampleCoverage = 1f; + + @Override + public void invalidate() { + super.invalidate(); + + blendEnabled = false; + testEnabled = false; + + srcFactorRGB = -1; + dstFactorRGB = -1; + blendEqRGB = -1; + + srcFactorAlpha = -1; + dstFactorAlpha = -1; + blendEqAlpha = -1; + + alphaFunc = -1; + alphaRef = -1; + + blendColor.set(-1, -1, -1, -1); + + sampleAlphaToCoverageEnabled = false; + sampleAlphaToOneEnabled = false; + sampleCoverageEnabled = false; + sampleCoverageInverted = false; + sampleCoverage = -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java new file mode 100644 index 0000000..7dba025 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.nio.DoubleBuffer; +import java.util.Arrays; + +import com.ardor3d.renderer.state.ClipState; +import com.ardor3d.util.geom.BufferUtils; + +public class ClipStateRecord extends StateRecord { + + public final boolean[] planeEnabled = new boolean[ClipState.MAX_CLIP_PLANES]; + public final DoubleBuffer buf = BufferUtils.createDoubleBuffer(4); + + @Override + public void invalidate() { + super.invalidate(); + + Arrays.fill(planeEnabled, false); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java new file mode 100644 index 0000000..559050a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public class ColorMaskStateRecord extends StateRecord { + public boolean red = true; + public boolean green = true; + public boolean blue = true; + public boolean alpha = true; + + public boolean is(final boolean red, final boolean green, final boolean blue, final boolean alpha) { + if (this.alpha != alpha) { + return false; + } else if (this.red != red) { + return false; + } else if (this.green != green) { + return false; + } else if (this.blue != blue) { + return false; + } else { + return true; + } + } + + public void set(final boolean red, final boolean green, final boolean blue, final boolean alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + @Override + public void invalidate() { + super.invalidate(); + + red = green = blue = alpha = true; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java new file mode 100644 index 0000000..55a39f8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import com.ardor3d.renderer.state.CullState.PolygonWind; + +public class CullStateRecord extends StateRecord { + public boolean enabled = false; + public int face = -1; + public PolygonWind windOrder = null; + + @Override + public void invalidate() { + super.invalidate(); + + enabled = false; + face = -1; + windOrder = null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java new file mode 100644 index 0000000..7217f88 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.nio.FloatBuffer; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.renderer.state.FogState; +import com.ardor3d.util.geom.BufferUtils; + +public class FogStateRecord extends StateRecord { + + public boolean enabled = false; + public float fogStart = -1; + public float fogEnd = -1; + public float density = -1; + public int fogMode = -1; + public int fogHint = -1; + public ColorRGBA fogColor = null; + public FloatBuffer colorBuff = null; + public FogState.CoordinateSource source = null; + + public FogStateRecord() { + fogColor = new ColorRGBA(0, 0, 0, -1); + colorBuff = BufferUtils.createColorBuffer(1); + } + + @Override + public void invalidate() { + super.invalidate(); + + enabled = false; + fogStart = -1; + fogEnd = -1; + density = -1; + fogMode = -1; + fogHint = -1; + fogColor.set(0, 0, 0, -1); + source = null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java new file mode 100644 index 0000000..3c5e6bc --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import com.ardor3d.renderer.state.FragmentProgramState; + +public class FragmentProgramStateRecord extends StateRecord { + private FragmentProgramState reference = null; + + public FragmentProgramState getReference() { + return reference; + } + + public void setReference(final FragmentProgramState reference) { + this.reference = reference; + } + + @Override + public void invalidate() { + super.invalidate(); + reference = null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java new file mode 100644 index 0000000..cee7c73 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Matrix4; +import com.ardor3d.math.Vector4; + +public class LightRecord extends StateRecord { + public ColorRGBA ambient = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA diffuse = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA specular = new ColorRGBA(-1, -1, -1, -1); + private float constant = -1; + private float linear = -1; + private float quadratic = -1; + private float spotExponent = -1; + private float spotCutoff = -1; + private boolean enabled = false; + + public Vector4 position = new Vector4(); + public Matrix4 modelViewMatrix = new Matrix4(); + + private boolean attenuate; + + public boolean isAttenuate() { + return attenuate; + } + + public void setAttenuate(final boolean attenuate) { + this.attenuate = attenuate; + } + + public float getConstant() { + return constant; + } + + public void setConstant(final float constant) { + this.constant = constant; + } + + public float getLinear() { + return linear; + } + + public void setLinear(final float linear) { + this.linear = linear; + } + + public float getQuadratic() { + return quadratic; + } + + public void setQuadratic(final float quadratic) { + this.quadratic = quadratic; + } + + public float getSpotExponent() { + return spotExponent; + } + + public void setSpotExponent(final float exponent) { + spotExponent = exponent; + } + + public float getSpotCutoff() { + return spotCutoff; + } + + public void setSpotCutoff(final float spotCutoff) { + this.spotCutoff = spotCutoff; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + @Override + public void invalidate() { + super.invalidate(); + + ambient.set(-1, -1, -1, -1); + diffuse.set(-1, -1, -1, -1); + specular.set(-1, -1, -1, -1); + constant = -1; + linear = -1; + quadratic = -1; + spotExponent = -1; + spotCutoff = -1; + enabled = false; + + position.set(-1, -1, -1, -1); + modelViewMatrix.setIdentity(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java new file mode 100644 index 0000000..3a04e45 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.util.geom.BufferUtils; + +public class LightStateRecord extends StateRecord { + private final List<LightRecord> lightList = new ArrayList<LightRecord>(); + private int lightMask; + private int backLightMask; + private boolean twoSidedOn; + public ColorRGBA globalAmbient = new ColorRGBA(-1, -1, -1, -1); + private boolean enabled; + private boolean localViewer; + private boolean separateSpecular; + + // buffer for light colors. + public FloatBuffer lightBuffer = BufferUtils.createColorBuffer(1); + + public int getBackLightMask() { + return backLightMask; + } + + public void setBackLightMask(final int backLightMask) { + this.backLightMask = backLightMask; + } + + public LightRecord getLightRecord(final int index) { + if (lightList.size() <= index) { + return null; + } + + return lightList.get(index); + } + + public void setLightRecord(final LightRecord lr, final int index) { + while (lightList.size() <= index) { + lightList.add(null); + } + + lightList.set(index, lr); + } + + public int getLightMask() { + return lightMask; + } + + public void setLightMask(final int lightMask) { + this.lightMask = lightMask; + } + + public boolean isTwoSidedOn() { + return twoSidedOn; + } + + public void setTwoSidedOn(final boolean twoSidedOn) { + this.twoSidedOn = twoSidedOn; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + public boolean isLocalViewer() { + return localViewer; + } + + public void setLocalViewer(final boolean localViewer) { + this.localViewer = localViewer; + } + + public boolean isSeparateSpecular() { + return separateSpecular; + } + + public void setSeparateSpecular(final boolean seperateSpecular) { + separateSpecular = seperateSpecular; + } + + @Override + public void invalidate() { + super.invalidate(); + for (final LightRecord record : lightList) { + record.invalidate(); + } + + lightMask = -1; + backLightMask = -1; + twoSidedOn = false; + enabled = false; + localViewer = false; + separateSpecular = false; + globalAmbient.set(-1, -1, -1, -1); + } + + @Override + public void validate() { + super.validate(); + for (final LightRecord record : lightList) { + record.validate(); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java new file mode 100644 index 0000000..64a1c1b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public class LineRecord extends StateRecord { + public boolean smoothed = false; + public boolean stippled = false; + public int smoothHint = -1; + public float width = -1; + public int stippleFactor = -1; + public short stipplePattern = -1; + + @Override + public void invalidate() { + super.invalidate(); + + smoothed = false; + stippled = false; + smoothHint = -1; + width = -1; + stippleFactor = -1; + stipplePattern = -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java new file mode 100644 index 0000000..543649f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.nio.FloatBuffer; +import java.util.logging.Logger; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.state.MaterialState.ColorMaterial; +import com.ardor3d.renderer.state.MaterialState.MaterialFace; +import com.ardor3d.util.geom.BufferUtils; + +public class MaterialStateRecord extends StateRecord { + private static final Logger logger = Logger.getLogger(MaterialStateRecord.class.getName()); + + public ColorRGBA frontAmbient = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA frontDiffuse = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA frontSpecular = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA frontEmissive = new ColorRGBA(-1, -1, -1, -1); + public float frontShininess = Float.NEGATIVE_INFINITY; + + public ColorRGBA backAmbient = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA backDiffuse = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA backSpecular = new ColorRGBA(-1, -1, -1, -1); + public ColorRGBA backEmissive = new ColorRGBA(-1, -1, -1, -1); + public float backShininess = Float.NEGATIVE_INFINITY; + + public ColorMaterial colorMaterial = null; + public MaterialFace colorMaterialFace = null; + + public FloatBuffer tempColorBuff = BufferUtils.createColorBuffer(1); + + public boolean isSetColor(final MaterialFace face, final ColorMaterial glMatColor, final ReadOnlyColorRGBA color, + final MaterialStateRecord record) { + if (face == MaterialFace.Front) { + switch (glMatColor) { + case Ambient: + return color.equals(frontAmbient); + case Diffuse: + return color.equals(frontDiffuse); + case Specular: + return color.equals(frontSpecular); + case Emissive: + return color.equals(frontEmissive); + default: + logger.warning("bad isSetColor"); + } + } else if (face == MaterialFace.FrontAndBack) { + switch (glMatColor) { + case Ambient: + return color.equals(frontAmbient) && color.equals(backAmbient); + case Diffuse: + return color.equals(frontDiffuse) && color.equals(backDiffuse); + case Specular: + return color.equals(frontSpecular) && color.equals(backSpecular); + case Emissive: + return color.equals(frontEmissive) && color.equals(backEmissive); + default: + logger.warning("bad isSetColor"); + } + } else if (face == MaterialFace.Back) { + switch (glMatColor) { + case Ambient: + return color.equals(backAmbient); + case Diffuse: + return color.equals(backDiffuse); + case Specular: + return color.equals(backSpecular); + case Emissive: + return color.equals(backEmissive); + default: + logger.warning("bad isSetColor"); + } + } + return false; + } + + public boolean isSetShininess(final MaterialFace face, final float shininess, final MaterialStateRecord record) { + if (face == MaterialFace.Front) { + return shininess == frontShininess; + } else if (face == MaterialFace.FrontAndBack) { + return shininess == frontShininess && shininess == backShininess; + } else if (face == MaterialFace.Back) { + return shininess == backShininess; + } + return false; + } + + public void setColor(final MaterialFace face, final ColorMaterial glMatColor, final ReadOnlyColorRGBA color) { + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + switch (glMatColor) { + case Ambient: + frontAmbient.set(color); + break; + case Diffuse: + frontDiffuse.set(color); + break; + case Specular: + frontSpecular.set(color); + break; + case Emissive: + frontEmissive.set(color); + break; + default: + logger.warning("bad setColor"); + } + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + switch (glMatColor) { + case Ambient: + backAmbient.set(color); + break; + case Diffuse: + backDiffuse.set(color); + break; + case Specular: + backSpecular.set(color); + break; + case Emissive: + backEmissive.set(color); + break; + default: + logger.warning("bad setColor"); + } + } + } + + public void resetColorsForCM(final MaterialFace face, final ColorMaterial glMatColor) { + if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) { + switch (glMatColor) { + case Ambient: + frontAmbient.set(-1, -1, -1, -1); + break; + case Diffuse: + frontDiffuse.set(-1, -1, -1, -1); + break; + case AmbientAndDiffuse: + frontAmbient.set(-1, -1, -1, -1); + frontDiffuse.set(-1, -1, -1, -1); + break; + case Emissive: + frontEmissive.set(-1, -1, -1, -1); + break; + case Specular: + frontSpecular.set(-1, -1, -1, -1); + break; + } + } + if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) { + switch (glMatColor) { + case Ambient: + backAmbient.set(-1, -1, -1, -1); + break; + case Diffuse: + backDiffuse.set(-1, -1, -1, -1); + break; + case AmbientAndDiffuse: + backAmbient.set(-1, -1, -1, -1); + backDiffuse.set(-1, -1, -1, -1); + break; + case Emissive: + backEmissive.set(-1, -1, -1, -1); + break; + case Specular: + backSpecular.set(-1, -1, -1, -1); + break; + } + } + } + + @Override + public void invalidate() { + super.invalidate(); + + frontAmbient.set(-1, -1, -1, -1); + frontDiffuse.set(-1, -1, -1, -1); + frontSpecular.set(-1, -1, -1, -1); + frontEmissive.set(-1, -1, -1, -1); + frontShininess = Float.NEGATIVE_INFINITY; + + backAmbient.set(-1, -1, -1, -1); + backDiffuse.set(-1, -1, -1, -1); + backSpecular.set(-1, -1, -1, -1); + backEmissive.set(-1, -1, -1, -1); + backShininess = Float.NEGATIVE_INFINITY; + + colorMaterial = null; + colorMaterialFace = null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java new file mode 100644 index 0000000..b982024 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.util.EnumSet; + +import com.ardor3d.renderer.state.OffsetState.OffsetType; + +public class OffsetStateRecord extends StateRecord { + public boolean enabled = false; + public float factor = 0; + public float units = 0; + public EnumSet<OffsetType> enabledOffsets = EnumSet.noneOf(OffsetType.class); + + @Override + public void invalidate() { + super.invalidate(); + + enabled = false; + factor = 0; + units = 0; + enabledOffsets.clear(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java new file mode 100644 index 0000000..9f51cbc --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.util.Stack; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyRectangle2; +import com.ardor3d.renderer.DrawBufferTarget; + +public class RendererRecord extends StateRecord { + private int _matrixMode = -1; + private int _currentElementVboId = -1, _currentVboId = -1; + private boolean _matrixValid; + private boolean _vboValid; + private boolean _elementVboValid; + private boolean _clippingTestValid; + private boolean _clippingTestEnabled; + private transient final ColorRGBA _tempColor = new ColorRGBA(); + private DrawBufferTarget _drawBufferTarget = null; + private final Stack<ReadOnlyRectangle2> _clips = new Stack<ReadOnlyRectangle2>(); + private int _normalMode = -1; // signifies disabled + private int _enabledTextures = 0; + private boolean _texturesValid = false; + private int _currentTextureArraysUnit = 0; + + @Override + public void invalidate() { + invalidateMatrix(); + invalidateVBO(); + _drawBufferTarget = null; + _clippingTestValid = false; + _texturesValid = false; + _normalMode = -1; + _currentTextureArraysUnit = -1; + } + + @Override + public void validate() { + // ignore - validate per item or locally + } + + public void invalidateMatrix() { + _matrixValid = false; + _matrixMode = -1; + } + + public void invalidateVBO() { + _vboValid = false; + _elementVboValid = false; + _currentElementVboId = _currentVboId = -1; + } + + public int getMatrixMode() { + return _matrixMode; + } + + public void setMatrixMode(final int matrixMode) { + _matrixMode = matrixMode; + } + + public int getCurrentElementVboId() { + return _currentElementVboId; + } + + public void setCurrentElementVboId(final int currentElementVboId) { + _currentElementVboId = currentElementVboId; + } + + public int getCurrentVboId() { + return _currentVboId; + } + + public void setCurrentVboId(final int currentVboId) { + _currentVboId = currentVboId; + } + + public boolean isMatrixValid() { + return _matrixValid; + } + + public void setMatrixValid(final boolean matrixValid) { + _matrixValid = matrixValid; + } + + public boolean isVboValid() { + return _vboValid; + } + + public void setVboValid(final boolean vboValid) { + _vboValid = vboValid; + } + + public boolean isElementVboValid() { + return _elementVboValid; + } + + public void setElementVboValid(final boolean elementVboValid) { + _elementVboValid = elementVboValid; + } + + public ColorRGBA getTempColor() { + return _tempColor; + } + + public DrawBufferTarget getDrawBufferTarget() { + return _drawBufferTarget; + } + + public void setDrawBufferTarget(final DrawBufferTarget target) { + _drawBufferTarget = target; + } + + public Stack<ReadOnlyRectangle2> getScissorClips() { + return _clips; + } + + public boolean isClippingTestEnabled() { + return _clippingTestEnabled; + } + + public void setClippingTestEnabled(final boolean enabled) { + _clippingTestEnabled = enabled; + } + + public boolean isClippingTestValid() { + return _clippingTestValid; + } + + public void setClippingTestValid(final boolean valid) { + _clippingTestValid = valid; + } + + public int getEnabledTextures() { + return _enabledTextures; + } + + public void setEnabledTextures(final int enabledTextures) { + _enabledTextures = enabledTextures; + } + + public int getNormalMode() { + return _normalMode; + } + + public void setNormalMode(final int normalMode) { + _normalMode = normalMode; + } + + public boolean isTexturesValid() { + return _texturesValid; + } + + public void setTexturesValid(final boolean texturesValid) { + _texturesValid = texturesValid; + } + + public int getCurrentTextureArraysUnit() { + return _currentTextureArraysUnit; + } + + public void setCurrentTextureArraysUnit(final int currentTextureArraysUnit) { + _currentTextureArraysUnit = currentTextureArraysUnit; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java new file mode 100644 index 0000000..b542051 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.util.List; + +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.util.shader.ShaderVariable; +import com.google.common.collect.Lists; + +public class ShaderObjectsStateRecord extends StateRecord { + // XXX NOTE: This is non-standard. Due to the fact that shader implementations + // XXX will be changed this record simply makes use of the old reference + // XXX checking system. + GLSLShaderObjectsState reference = null; + + public List<ShaderVariable> enabledAttributes = Lists.newArrayList(); + + public int shaderId = -1; + + public GLSLShaderObjectsState getReference() { + return reference; + } + + public void setReference(final GLSLShaderObjectsState reference) { + this.reference = reference; + } + + @Override + public void invalidate() { + super.invalidate(); + + reference = null; + shaderId = -1; + enabledAttributes.clear(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java new file mode 100644 index 0000000..a561eed --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public class ShadingStateRecord extends StateRecord { + public int lastShade = -1; + + @Override + public void invalidate() { + super.invalidate(); + lastShade = -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java new file mode 100644 index 0000000..1c6aead --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public abstract class StateRecord { + + // If false, don't trust any of the values in this record. + protected boolean valid = false; + + /** + * @return true if ardor3d thinks this state holds trusted information about the opengl state machine. + */ + public boolean isValid() { + return valid; + } + + /** + * Invalidate this record - iow, we don't trust this record's information about the opengl state machine. + */ + public void invalidate() { + valid = false; + } + + /** + * Validate this record - iow, we trust this record's information about the opengl state machine. + */ + public void validate() { + valid = true; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java new file mode 100644 index 0000000..de76d39 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public class StencilStateRecord extends StateRecord { + public boolean enabled = false; + + // public int[] func = { -1, -1, -1 }; + // public int[] ref = { -1, -1, -1 }; + // public int[] writeMask = { -1, -1, -1 }; + // public int[] funcMask = { -1, -1, -1 }; + // public int[] fail = { -1, -1, -1 }; + // public int[] zfail = { -1, -1, -1 }; + // public int[] zpass = { -1, -1, -1 }; + + public boolean useTwoSided = false; + + @Override + public void invalidate() { + super.invalidate(); + + enabled = false; + useTwoSided = false; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java new file mode 100644 index 0000000..3baf5d5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.nio.FloatBuffer; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.util.geom.BufferUtils; + +public class TextureRecord extends StateRecord { + + public int wrapS, wrapT, wrapR; + public int magFilter, minFilter; + public int depthTextureMode, depthTextureCompareFunc, depthTextureCompareMode; + public float anisoLevel = -1; + public static FloatBuffer colorBuffer = BufferUtils.createColorBuffer(1); + public ColorRGBA borderColor = new ColorRGBA(-1, -1, -1, -1); + + public TextureRecord() {} + + @Override + public void invalidate() { + super.invalidate(); + wrapS = wrapT = wrapR = 0; + magFilter = minFilter = 0; + depthTextureMode = depthTextureCompareFunc = depthTextureCompareMode = 0; + anisoLevel = -1; + borderColor.set(-1, -1, -1, -1); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java new file mode 100644 index 0000000..062c555 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.util.Collection; +import java.util.HashMap; + +import com.ardor3d.image.Texture; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector4; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Maps; + +public class TextureStateRecord extends StateRecord { + + public FloatBuffer plane = BufferUtils.createFloatBuffer(4); + + public static final float[] DEFAULT_S_PLANE = { 1f, 0f, 0f, 0f }; + public static final float[] DEFAULT_T_PLANE = { 0f, 1f, 0f, 0f }; + public static final float[] DEFAULT_R_PLANE = { 0f, 0f, 1f, 0f }; + public static final float[] DEFAULT_Q_PLANE = { 0f, 0f, 0f, 1f }; + + public HashMap<Integer, TextureRecord> textures; + public TextureUnitRecord[] units; + public int hint = -1; + public int currentUnit = -1; + + /** + * temporary rotation axis vector to flatline memory usage. + */ + public final Vector3 tmp_rotation1 = new Vector3(); + + /** + * temporary matrix buffer to flatline memory usage. + */ + public final DoubleBuffer tmp_matrixBuffer = BufferUtils.createDoubleBuffer(16); + + public TextureStateRecord() { + textures = Maps.newHashMap(); + units = new TextureUnitRecord[TextureState.MAX_TEXTURES]; + for (int i = 0; i < units.length; i++) { + units[i] = new TextureUnitRecord(); + } + } + + public TextureRecord getTextureRecord(final Integer textureId, final Texture.Type type) { + TextureRecord tr = textures.get(textureId); + if (tr == null) { + tr = new TextureRecord(); + textures.put(textureId, tr); + } + return tr; + } + + public void removeTextureRecord(final Integer textureId) { + if (textureId == null) { + return; + } + textures.remove(textureId); + for (int i = 0; i < units.length; i++) { + if (units[i].boundTexture == textureId.intValue()) { + units[i].boundTexture = -1; + } + } + } + + @Override + public void invalidate() { + super.invalidate(); + currentUnit = -1; + hint = -1; + final Collection<TextureRecord> texs = textures.values(); + for (final TextureRecord tr : texs) { + tr.invalidate(); + } + for (int i = 0; i < units.length; i++) { + units[i].invalidate(); + } + } + + @Override + public void validate() { + super.validate(); + final Collection<TextureRecord> texs = textures.values(); + for (final TextureRecord tr : texs) { + tr.validate(); + } + for (int i = 0; i < units.length; i++) { + units[i].validate(); + } + } + + public void prepPlane(final ReadOnlyVector4 planeEq, final float[] defaultVal) { + if (planeEq == null) { + plane.put(defaultVal); + } else { + plane.put(planeEq.getXf()); + plane.put(planeEq.getYf()); + plane.put(planeEq.getZf()); + plane.put(planeEq.getWf()); + } + plane.rewind(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java new file mode 100644 index 0000000..64a0133 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import java.util.Arrays; + +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture.ApplyMode; +import com.ardor3d.image.Texture.CombinerFunctionAlpha; +import com.ardor3d.image.Texture.CombinerFunctionRGB; +import com.ardor3d.image.Texture.CombinerOperandAlpha; +import com.ardor3d.image.Texture.CombinerOperandRGB; +import com.ardor3d.image.Texture.CombinerScale; +import com.ardor3d.image.Texture.CombinerSource; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Matrix4; +import com.ardor3d.math.Vector3; + +/** + * Represents a texture unit in opengl + */ +public class TextureUnitRecord extends StateRecord { + public boolean enabled[] = new boolean[Texture.Type.values().length]; + public Matrix4 texMatrix = new Matrix4(); + public Vector3 texScale = new Vector3(); + public int boundTexture = -1; + public ApplyMode envMode = null; + public CombinerScale envRGBScale = null; + public CombinerScale envAlphaScale = null; + public ColorRGBA blendColor = new ColorRGBA(-1, -1, -1, -1); + public CombinerFunctionRGB rgbCombineFunc = null; + public CombinerFunctionAlpha alphaCombineFunc = null; + public CombinerSource combSrcRGB0 = null, combSrcRGB1 = null, combSrcRGB2 = null; + public CombinerOperandRGB combOpRGB0 = null, combOpRGB1 = null, combOpRGB2 = null; + public CombinerSource combSrcAlpha0 = null, combSrcAlpha1 = null, combSrcAlpha2 = null; + public CombinerOperandAlpha combOpAlpha0 = null, combOpAlpha1 = null, combOpAlpha2 = null; + public boolean identityMatrix = true; + public float lodBias = 0f; + + public boolean textureGenQ = false, textureGenR = false, textureGenS = false, textureGenT = false; + public int textureGenQMode = -1, textureGenRMode = -1, textureGenSMode = -1, textureGenTMode = -1; + + public TextureUnitRecord() {} + + @Override + public void invalidate() { + super.invalidate(); + + Arrays.fill(enabled, false); + texMatrix.setIdentity(); + texScale.zero(); + boundTexture = -1; + lodBias = 0; + envMode = null; + envRGBScale = null; + envAlphaScale = null; + blendColor.set(-1, -1, -1, -1); + rgbCombineFunc = null; + alphaCombineFunc = null; + combSrcRGB0 = null; + combSrcRGB1 = null; + combSrcRGB2 = null; + combOpRGB0 = null; + combOpRGB1 = null; + combOpRGB2 = null; + combSrcAlpha0 = null; + combSrcAlpha1 = null; + combSrcAlpha2 = null; + combOpAlpha0 = null; + combOpAlpha1 = null; + combOpAlpha2 = null; + identityMatrix = false; + + textureGenQ = false; + textureGenR = false; + textureGenS = false; + textureGenT = false; + textureGenQMode = -1; + textureGenRMode = -1; + textureGenSMode = -1; + textureGenTMode = -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java new file mode 100644 index 0000000..f630739 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +import com.ardor3d.renderer.state.VertexProgramState; + +public class VertexProgramStateRecord extends StateRecord { + // XXX NOTE: This is non-standard. Due to the fact that shader + // implementations + // XXX will be changed this record simply makes use of the old reference + // XXX checking system. + VertexProgramState reference = null; + + public VertexProgramState getReference() { + return reference; + } + + public void setReference(final VertexProgramState reference) { + this.reference = reference; + } + + @Override + public void invalidate() { + super.invalidate(); + reference = null; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java new file mode 100644 index 0000000..1f27367 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public class WireframeStateRecord extends StateRecord { + + public int frontMode = -1; + public int backMode = -1; + + @Override + public void invalidate() { + super.invalidate(); + + frontMode = -1; + backMode = -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java new file mode 100644 index 0000000..88c63c1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.renderer.state.record; + +public class ZBufferStateRecord extends StateRecord { + public boolean depthTest = false; + public boolean writable = false; + public int depthFunc = -1; + + @Override + public void invalidate() { + super.invalidate(); + depthTest = false; + writable = false; + depthFunc = -1; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java new file mode 100644 index 0000000..9274fb0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java @@ -0,0 +1,320 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.nio.Buffer; +import java.util.Map; +import java.util.Set; + +import com.ardor3d.renderer.ContextCleanListener; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.RendererCallable; +import com.ardor3d.util.Constants; +import com.ardor3d.util.ContextIdReference; +import com.ardor3d.util.GameTaskQueueManager; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Multimap; + +public abstract class AbstractBufferData<T extends Buffer> { + + private static Map<AbstractBufferData<?>, Object> _identityCache = new MapMaker().weakKeys().makeMap(); + private static final Object STATIC_REF = new Object(); + + private static ReferenceQueue<AbstractBufferData<?>> _vboRefQueue = new ReferenceQueue<AbstractBufferData<?>>(); + + static { + ContextManager.addContextCleanListener(new ContextCleanListener() { + public void cleanForContext(final RenderContext renderContext) { + AbstractBufferData.cleanAllVBOs(null, renderContext); + } + }); + } + + protected transient ContextIdReference<AbstractBufferData<T>> _vboIdCache; + + /** Buffer holding the data. */ + protected T _buffer; + + /** Access mode of the buffer when using Vertex Buffer Objects. */ + public enum VBOAccessMode { + StaticDraw, StaticCopy, StaticRead, StreamDraw, StreamCopy, StreamRead, DynamicDraw, DynamicCopy, DynamicRead + } + + /** VBO Access mode for this buffer. */ + protected VBOAccessMode _vboAccessMode = VBOAccessMode.StaticDraw; + + /** Flag for notifying the renderer that the VBO buffer needs to be updated. */ + protected boolean _needsRefresh = false; + + AbstractBufferData() { + _identityCache.put(this, STATIC_REF); + } + + /** + * @return the number of bytes per entry in the buffer. For example, an IntBuffer would return 4. + */ + public abstract int getByteCount(); + + /** + * Gets the count. + * + * @return the count + */ + public int getBufferLimit() { + if (_buffer != null) { + return _buffer.limit(); + } + + return 0; + } + + /** + * Gets the count. + * + * @return the count + */ + public int getBufferCapacity() { + if (_buffer != null) { + return _buffer.capacity(); + } + + return 0; + } + + /** + * Get the buffer holding the data. + * + * @return the buffer + */ + public T getBuffer() { + return _buffer; + } + + /** + * Set the buffer holding the data. + * + * @param buffer + * the buffer to set + */ + public void setBuffer(final T buffer) { + _buffer = buffer; + } + + /** + * @param glContext + * the object representing the OpenGL context a vbo belongs to. See + * {@link RenderContext#getGlContextRep()} + * @return the vbo id of a vbo in the given context. If the vbo is not found in the given context, 0 is returned. + */ + public int getVBOID(final Object glContext) { + if (_vboIdCache != null) { + final Integer id = _vboIdCache.getValue(glContext); + if (id != null) { + return id.intValue(); + } + } + return 0; + } + + /** + * Removes any vbo id from this buffer's data for the given OpenGL context. + * + * @param glContext + * the object representing the OpenGL context a vbo would belong to. See + * {@link RenderContext#getGlContextRep()} + * @return the id removed or 0 if not found. + */ + public int removeVBOID(final Object glContext) { + if (_vboIdCache != null) { + return _vboIdCache.removeValue(glContext); + } else { + return 0; + } + } + + /** + * Sets the id for a vbo based on this buffer's data in regards to the given OpenGL context. + * + * @param glContextRep + * the object representing the OpenGL context a vbo belongs to. See + * {@link RenderContext#getGlContextRep()} + * @param vboId + * the vbo id of a vbo. To be valid, this must be not equals to 0. + * @throws IllegalArgumentException + * if vboId is less than or equal to 0. + */ + public void setVBOID(final Object glContextRep, final int vboId) { + if (vboId == 0) { + throw new IllegalArgumentException("vboId must != 0"); + } + + if (_vboIdCache == null) { + _vboIdCache = new ContextIdReference<AbstractBufferData<T>>(this, _vboRefQueue); + } + _vboIdCache.put(glContextRep, vboId); + } + + public VBOAccessMode getVboAccessMode() { + return _vboAccessMode; + } + + public void setVboAccessMode(final VBOAccessMode vboAccessMode) { + this._vboAccessMode = vboAccessMode; + } + + public boolean isNeedsRefresh() { + return _needsRefresh; + } + + public void setNeedsRefresh(final boolean needsRefresh) { + this._needsRefresh = needsRefresh; + } + + public static void cleanAllVBOs(final Renderer deleter) { + final Multimap<Object, Integer> idMap = ArrayListMultimap.create(); + + // gather up expired vbos... these don't exist in our cache + gatherGCdIds(idMap); + + // Walk through the cached items and delete those too. + for (final AbstractBufferData<?> buf : _identityCache.keySet()) { + if (buf._vboIdCache != null) { + if (Constants.useMultipleContexts) { + final Set<Object> contextObjects = buf._vboIdCache.getContextObjects(); + for (final Object o : contextObjects) { + // Add id to map + idMap.put(o, buf.getVBOID(o)); + } + } else { + idMap.put(ContextManager.getCurrentContext().getGlContextRep(), buf.getVBOID(null)); + } + buf._vboIdCache.clear(); + } + } + + handleVBODelete(deleter, idMap); + } + + public static void cleanAllVBOs(final Renderer deleter, final RenderContext context) { + final Multimap<Object, Integer> idMap = ArrayListMultimap.create(); + + // gather up expired vbos... these don't exist in our cache + gatherGCdIds(idMap); + + final Object glRep = context.getGlContextRep(); + // Walk through the cached items and delete those too. + for (final AbstractBufferData<?> buf : _identityCache.keySet()) { + // only worry about buffers that have received ids. + if (buf._vboIdCache != null) { + final Integer id = buf._vboIdCache.removeValue(glRep); + if (id != null && id.intValue() != 0) { + idMap.put(context.getGlContextRep(), id); + } + } + } + + handleVBODelete(deleter, idMap); + } + + /** + * Clean any VBO ids from the hardware, using the given Renderer object to do the work immediately, if given. If + * not, we will delete in the next execution of the appropriate context's game task render queue. + * + * @param deleter + * the Renderer to use. If null, execution will not occur immediately. + */ + public static void cleanExpiredVBOs(final Renderer deleter) { + // gather up expired vbos... + final Multimap<Object, Integer> idMap = gatherGCdIds(null); + + if (idMap != null) { + // send to be deleted (perhaps on next render.) + handleVBODelete(deleter, idMap); + } + } + + /** + * @return a deep copy of this buffer data object + */ + public abstract AbstractBufferData<T> makeCopy(); + + @SuppressWarnings("unchecked") + private static final Multimap<Object, Integer> gatherGCdIds(Multimap<Object, Integer> store) { + // Pull all expired vbos from ref queue and add to an id multimap. + ContextIdReference<AbstractBufferData<?>> ref; + while ((ref = (ContextIdReference<AbstractBufferData<?>>) _vboRefQueue.poll()) != null) { + if (Constants.useMultipleContexts) { + final Set<Object> contextObjects = ref.getContextObjects(); + for (final Object o : contextObjects) { + // Add id to map + final Integer id = ref.getValue(o); + if (id != null) { + if (store == null) { // lazy init + store = ArrayListMultimap.create(); + } + store.put(o, id); + } + } + } else { + final Integer id = ref.getValue(null); + if (id != null) { + if (store == null) { // lazy init + store = ArrayListMultimap.create(); + } + store.put(ContextManager.getCurrentContext().getGlContextRep(), id); + } + } + ref.clear(); + } + + return store; + } + + private static void handleVBODelete(final Renderer deleter, final Multimap<Object, Integer> idMap) { + Object currentGLRef = null; + // Grab the current context, if any. + if (deleter != null && ContextManager.getCurrentContext() != null) { + currentGLRef = ContextManager.getCurrentContext().getGlContextRep(); + } + // For each affected context... + for (final Object glref : idMap.keySet()) { + // If we have a deleter and the context is current, immediately delete + if (deleter != null && glref.equals(currentGLRef)) { + deleter.deleteVBOs(idMap.get(glref)); + } + // Otherwise, add a delete request to that context's render task queue. + else { + GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref)).render( + new RendererCallable<Void>() { + public Void call() throws Exception { + getRenderer().deleteVBOs(idMap.get(glref)); + return null; + } + }); + } + } + } + + public void read(final InputCapsule capsule) throws IOException { + _vboAccessMode = capsule.readEnum("vboAccessMode", VBOAccessMode.class, VBOAccessMode.StaticDraw); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_vboAccessMode, "vboAccessMode", VBOAccessMode.StaticDraw); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java new file mode 100644 index 0000000..89d516e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Simple data class storing a buffer of bytes + */ +public class ByteBufferData extends IndexBufferData<ByteBuffer> implements Savable { + + /** + * Instantiates a new ByteBufferData. + */ + public ByteBufferData() {} + + /** + * Instantiates a new ByteBufferData with a buffer of the given size. + */ + public ByteBufferData(final int size) { + this(BufferUtils.createByteBuffer(size)); + } + + /** + * Creates a new ByteBufferData. + * + * @param buffer + * Buffer holding the data. Must not be null. + */ + public ByteBufferData(final ByteBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("Buffer can not be null!"); + } + + _buffer = buffer; + } + + public Class<? extends ByteBufferData> getClassTag() { + return getClass(); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _buffer = capsule.readByteBuffer("buffer", null); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_buffer, "buffer", null); + } + + @Override + public int get() { + return _buffer.get() & 0xFF; + } + + @Override + public int get(final int index) { + return _buffer.get(index) & 0xFF; + } + + @Override + public ByteBufferData put(final int value) { + if (value < 0 || value >= 256) { + throw new IllegalArgumentException("Invalid value passed to byte buffer: " + value); + } + _buffer.put((byte) value); + return this; + } + + @Override + public ByteBufferData put(final int index, final int value) { + if (value < 0 || value >= 256) { + throw new IllegalArgumentException("Invalid value passed to byte buffer: " + value); + } + _buffer.put(index, (byte) value); + return this; + } + + @Override + public void put(final IndexBufferData<?> buf) { + if (buf instanceof ByteBufferData) { + _buffer.put((ByteBuffer) buf.getBuffer()); + } else { + while (buf.getBuffer().hasRemaining()) { + put(buf.get()); + } + } + } + + @Override + public int getByteCount() { + return 1; + } + + @Override + public ByteBuffer getBuffer() { + return _buffer; + } + + @Override + public IntBuffer asIntBuffer() { + final ByteBuffer source = getBuffer().duplicate(); + source.rewind(); + final IntBuffer buff = BufferUtils.createIntBufferOnHeap(source.limit()); + for (int i = 0, max = source.limit(); i < max; i++) { + buff.put(source.get() & 0xFF); + } + buff.flip(); + return buff; + } + + @Override + public ByteBufferData makeCopy() { + final ByteBufferData copy = new ByteBufferData(); + copy._buffer = BufferUtils.clone(_buffer); + copy._vboAccessMode = _vboAccessMode; + return copy; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java new file mode 100644 index 0000000..19d0a16 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Simple data class storing a buffer of floats and a number that indicates how many floats to group together to make up + * a "tuple" + */ +public class FloatBufferData extends AbstractBufferData<FloatBuffer> implements Savable { + + /** Specifies the number of coordinates per vertex. Must be 1 - 4. */ + private int _valuesPerTuple; + + /** + * Instantiates a new FloatBufferData. + */ + public FloatBufferData() {} + + /** + * Instantiates a new FloatBufferData with a buffer of the given size. + */ + public FloatBufferData(final int size, final int valuesPerTuple) { + this(BufferUtils.createFloatBuffer(size), valuesPerTuple); + } + + /** + * Creates a new FloatBufferData. + * + * @param buffer + * Buffer holding the data. Must not be null. + * @param valuesPerTuple + * Specifies the number of values per tuple. Can not be < 1. + */ + public FloatBufferData(final FloatBuffer buffer, final int valuesPerTuple) { + if (buffer == null) { + throw new IllegalArgumentException("Buffer can not be null!"); + } + + if (valuesPerTuple < 1) { + throw new IllegalArgumentException("valuesPerTuple must be greater than 1."); + } + + _buffer = buffer; + _valuesPerTuple = valuesPerTuple; + } + + @Override + public int getByteCount() { + return 4; + } + + public int getTupleCount() { + return getBufferLimit() / _valuesPerTuple; + } + + /** + * @return number of values per tuple + */ + public int getValuesPerTuple() { + return _valuesPerTuple; + } + + /** + * Set number of values per tuple. This method should only be used internally. + * + * @param valuesPerTuple + * number of values per tuple + */ + void setValuesPerTuple(final int valuesPerTuple) { + _valuesPerTuple = valuesPerTuple; + } + + /** + * Scale the data in this buffer by the given value(s) + * + * @param scales + * the scale values to use. The Nth buffer element is scaled by the (N % scales.length) scales element. + */ + public void scaleData(final float... scales) { + _buffer.rewind(); + for (int i = 0; i < _buffer.limit();) { + _buffer.put(_buffer.get(i) * scales[i % scales.length]); + i++; + } + _buffer.rewind(); + } + + /** + * Translate the data in this buffer by the given value(s) + * + * @param translates + * the translation values to use. The Nth buffer element is translated by the (N % translates.length) + * translates element. + */ + public void translateData(final float... translates) { + _buffer.rewind(); + for (int i = 0; i < _buffer.limit();) { + _buffer.put(_buffer.get(i) + translates[i % translates.length]); + i++; + } + _buffer.rewind(); + } + + @Override + public FloatBufferData makeCopy() { + final FloatBufferData copy = new FloatBufferData(); + copy._buffer = BufferUtils.clone(_buffer); + copy._valuesPerTuple = _valuesPerTuple; + copy._vboAccessMode = _vboAccessMode; + return copy; + } + + public Class<? extends FloatBufferData> getClassTag() { + return getClass(); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _buffer = capsule.readFloatBuffer("buffer", null); + _valuesPerTuple = capsule.readInt("valuesPerTuple", 0); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_buffer, "buffer", null); + capsule.write(_valuesPerTuple, "valuesPerTuple", 0); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java new file mode 100644 index 0000000..422dabe --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java @@ -0,0 +1,66 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class FloatBufferDataUtil {
+ public static FloatBufferData makeNew(final ReadOnlyVector2[] coords) {
+ if (coords == null) {
+ return null;
+ }
+
+ return new FloatBufferData(BufferUtils.createFloatBuffer(coords), 2);
+ }
+
+ public static FloatBufferData makeNew(final ReadOnlyVector3[] coords) {
+ if (coords == null) {
+ return null;
+ }
+
+ return new FloatBufferData(BufferUtils.createFloatBuffer(coords), 3);
+ }
+
+ public static FloatBufferData makeNew(final float[] coords) {
+ if (coords == null) {
+ return null;
+ }
+
+ return new FloatBufferData(BufferUtils.createFloatBuffer(coords), 1);
+ }
+
+ /**
+ * Check an incoming TexCoords object for null and correct size.
+ *
+ * @param tc
+ * @param vertexCount
+ * @param perVert
+ * @return tc if it is not null and the right size, otherwise it will be a new TexCoords object.
+ */
+ public static FloatBufferData ensureSize(final FloatBufferData tc, final int vertexCount, final int coordsPerVertex) {
+ if (tc == null) {
+ return new FloatBufferData(BufferUtils.createFloatBuffer(vertexCount * coordsPerVertex), coordsPerVertex);
+ }
+
+ if (tc.getBuffer().limit() == coordsPerVertex * vertexCount && tc.getValuesPerTuple() == coordsPerVertex) {
+ tc.getBuffer().rewind();
+ return tc;
+ } else if (tc.getBuffer().limit() == coordsPerVertex * vertexCount) {
+ tc.setValuesPerTuple(coordsPerVertex);
+ } else {
+ return new FloatBufferData(BufferUtils.createFloatBuffer(vertexCount * coordsPerVertex), coordsPerVertex);
+ }
+
+ return tc;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java new file mode 100644 index 0000000..9c39fae --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.nio.Buffer; +import java.nio.IntBuffer; + +public abstract class IndexBufferData<T extends Buffer> extends AbstractBufferData<T> { + + /** + * @return the next value from this object, as an int, incrementing our position by 1 entry. Buffer types smaller + * than an int should return unsigned values. + */ + public abstract int get(); + + /** + * @param index + * the absolute position to get our value from. This is in entries, not bytes, and is 0 based. So for a + * ShortBuffer, 2 would be the 3rd short from the beginning, etc. + * @return the value from this object, as an int, at the given absolute entry position. Buffer types smaller than an + * int should return unsigned values. + */ + public abstract int get(int index); + + /** + * @return a new, non-direct IntBuffer containing a snapshot of the contents of this buffer. + */ + public abstract IntBuffer asIntBuffer(); + + /** + * Sets the value of this buffer at the current position, incrementing our position by 1 entry. + * + * @param value + * the value to place into this object at the current position. + * @return this object, for chaining. + */ + public abstract IndexBufferData<T> put(int value); + + /** + * Sets the value of this buffer at the given index. + * + * @param index + * the absolute position to put our value at. This is in entries, not bytes, and is 0 based. So for a + * ShortBuffer, 2 would be the 3rd short from the beginning, etc. + * @param value + * the value to place into this object + * @return + */ + public abstract IndexBufferData<T> put(int index, int value); + + /** + * Write the contents of the given IndexBufferData into this one. Note that data conversion is handled using the + * get/put methods in IndexBufferData. + * + * @param buf + * the source buffer object. + */ + public abstract void put(IndexBufferData<?> buf); + + /** + * Get the underlying nio buffer. + */ + @Override + public abstract T getBuffer(); + + /** + * @see Buffer#remaining(); + */ + public int remaining() { + return getBuffer().remaining(); + } + + /** + * @see Buffer#position(); + */ + public int position() { + return getBuffer().position(); + } + + /** + * @see Buffer#position(int); + */ + public void position(final int position) { + getBuffer().position(position); + } + + /** + * @see Buffer#limit(); + */ + public int limit() { + return getBuffer().limit(); + } + + /** + * @see Buffer#limit(int); + */ + public void limit(final int limit) { + getBuffer().limit(limit); + } + + /** + * @see Buffer#capacity(); + */ + public int capacity() { + return getBuffer().capacity(); + } + + /** + * @see Buffer#rewind(); + */ + public void rewind() { + getBuffer().rewind(); + } + + /** + * @see Buffer#reset(); + */ + public void reset() { + getBuffer().reset(); + } + + @Override + public abstract IndexBufferData<T> makeCopy(); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java new file mode 100644 index 0000000..e6673a5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2008-2010 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.nio.FloatBuffer; +import java.util.List; + +import com.ardor3d.math.Matrix4; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Lists; + +public class InstancingManager { + + private int _maxBatchSize = 30; + private final List<Mesh> _visibleMeshes = Lists.newArrayListWithCapacity(_maxBatchSize); + private FloatBuffer _transformBuffer; + private int _primCount; + private int _meshesToDraw = 0; + + /** + * Register a mesh for instancing for this current frame (internal use only) + * + * @param mesh + */ + public void registerMesh(final Mesh mesh) { + _visibleMeshes.add(mesh); + _meshesToDraw++; + } + + /** + * Fill the buffer with the transforms and return it + */ + protected FloatBuffer fillTransformBuffer() { + + _primCount = Math.min(_visibleMeshes.size(), _maxBatchSize); + + final int nrOfFloats = _primCount * 16; /* 16 floats per matrix */ + + // re-init buffer when it is too small of more than twice the required size + if (_transformBuffer == null || nrOfFloats > _transformBuffer.capacity()) { + _transformBuffer = BufferUtils.createFloatBuffer(nrOfFloats); + } + + _transformBuffer.rewind(); + _transformBuffer.limit(nrOfFloats); + + final Matrix4 mat = Matrix4.fetchTempInstance(); + + for (int i = 0; i < _maxBatchSize && _meshesToDraw > 0; i++) { + final Mesh mesh = _visibleMeshes.get(--_meshesToDraw); + final Matrix4 transform = mesh.getWorldTransform().getHomogeneousMatrix(mat); + transform.toFloatBuffer(_transformBuffer, false); + } + + Matrix4.releaseTempInstance(mat); + _transformBuffer.rewind(); + + return _transformBuffer; + } + + /** + * Returns the number of meshes to be drawn this batch. This function is only valid after the apply call (internal + * use only) + */ + public int getPrimitiveCount() { + return _primCount; + } + + /** + * Split the batch in multiple batches if number of visible meshes exceeds this amount. Using larger batches will + * lead to better performance, although you might overflow the uniform space of the shader/videocard (crashes) + * + * @return maximum batch size + */ + public int getMaxBatchSize() { + return _maxBatchSize; + } + + /** + * Split the batch in multiple batches if number of visible meshes exceeds this amount. Using larger batches will + * lead to better performance, although you might overflow the uniform space of the shader/videocard (crashes) + * + * @param maxBatchSize + * maximum batch size + */ + public void setMaxBatchSize(final int maxBatchSize) { + _maxBatchSize = maxBatchSize; + } + + public boolean isAddedToRenderQueue() { + return _meshesToDraw > 0; + } + + /** + * Applies all instancing info to the mesh and returns if the current render call is allowed to continue + * + * @param mesh + * @param renderer + * @param shader + * @return continue rendering or skip rendering all together + */ + public boolean apply(final Mesh mesh, final Renderer renderer, final GLSLShaderObjectsState shader) { + final RenderContext context = ContextManager.getCurrentContext(); + final ContextCapabilities caps = context.getCapabilities(); + + if (!caps.isGeometryInstancingSupported()) { + throw new Ardor3dException("Geometry instancing not supported for current graphics configuration"); + } + + if (_meshesToDraw <= 0) { + // reset for next draw call + _primCount = -1; + shader.setUniform("nrOfInstances", -1); + _visibleMeshes.clear(); + return false; + } + + shader.setUniform("transforms", fillTransformBuffer(), 4); + shader.setUniform("nrOfInstances", getPrimitiveCount()); + return true; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java new file mode 100644 index 0000000..4e4cf04 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.IntBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Simple data class storing a buffer of ints + */ +public class IntBufferData extends IndexBufferData<IntBuffer> implements Savable { + + /** + * Instantiates a new IntBufferData. + */ + public IntBufferData() {} + + /** + * Instantiates a new IntBufferData with a buffer of the given size. + */ + public IntBufferData(final int size) { + this(BufferUtils.createIntBuffer(size)); + } + + /** + * Creates a new IntBufferData. + * + * @param buffer + * Buffer holding the data. Must not be null. + */ + public IntBufferData(final IntBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("Buffer can not be null!"); + } + + _buffer = buffer; + } + + public Class<? extends IntBufferData> getClassTag() { + return getClass(); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _buffer = capsule.readIntBuffer("buffer", null); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_buffer, "buffer", null); + } + + @Override + public int get() { + return _buffer.get(); + } + + @Override + public int get(final int index) { + return _buffer.get(index); + } + + @Override + public IntBufferData put(final int value) { + if (value < 0) { + throw new IllegalArgumentException("Invalid value passed to int buffer: " + value); + } + _buffer.put(value); + return this; + } + + @Override + public IntBufferData put(final int index, final int value) { + if (value < 0) { + throw new IllegalArgumentException("Invalid value passed to int buffer: " + value); + } + _buffer.put(index, value); + return this; + } + + @Override + public void put(final IndexBufferData<?> buf) { + if (buf instanceof IntBufferData) { + _buffer.put((IntBuffer) buf.getBuffer()); + } else { + while (buf.getBuffer().hasRemaining()) { + put(buf.get()); + } + } + } + + @Override + public int getByteCount() { + return 4; + } + + @Override + public IntBuffer getBuffer() { + return _buffer; + } + + @Override + public IntBuffer asIntBuffer() { + final IntBuffer source = getBuffer().duplicate(); + source.rewind(); + final IntBuffer buff = BufferUtils.createIntBufferOnHeap(source.limit()); + buff.put(source); + buff.flip(); + return buff; + } + + @Override + public IntBufferData makeCopy() { + final IntBufferData copy = new IntBufferData(); + copy._buffer = BufferUtils.clone(_buffer); + copy._vboAccessMode = _vboAccessMode; + return copy; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java new file mode 100644 index 0000000..b1cfaae --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class Line extends Mesh { + + private float _lineWidth = 1.0f; + private short _stipplePattern = (short) 0xFFFF; + private int _stippleFactor = 1; + private boolean _antialiased = false; + + public Line() { + this("line"); + } + + /** + * Constructs a new line with the given name. By default, the line has no information. + * + * @param name + * The name of the line. + */ + public Line(final String name) { + super(name); + + _meshData.setIndexMode(IndexMode.Lines); + } + + /** + * Constructor instantiates a new <code>Line</code> object with a given set of data. Any data can be null except for + * the vertex list. If vertices are null an exception will be thrown. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + * @param vertex + * the vertices that make up the lines. + * @param normal + * the normals of the lines. + * @param color + * the color of each point of the lines. + * @param coords + * the texture coordinates of the lines. + */ + public Line(final String name, final FloatBuffer vertex, final FloatBuffer normal, final FloatBuffer color, + final FloatBufferData coords) { + super(name); + setupData(vertex, normal, color, coords); + _meshData.setIndexMode(IndexMode.Lines); + } + + /** + * Constructor instantiates a new <code>Line</code> object with a given set of data. Any data can be null except for + * the vertex list. If vertices are null an exception will be thrown. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + * @param vertex + * the vertices that make up the lines. + * @param normal + * the normals of the lines. + * @param color + * the color of each point of the lines. + * @param texture + * the texture coordinates of the lines. + */ + public Line(final String name, final ReadOnlyVector3[] vertex, final ReadOnlyVector3[] normal, + final ReadOnlyColorRGBA[] color, final ReadOnlyVector2[] texture) { + super(name); + setupData(BufferUtils.createFloatBuffer(vertex), BufferUtils.createFloatBuffer(normal), + BufferUtils.createFloatBuffer(color), FloatBufferDataUtil.makeNew(texture)); + _meshData.setIndexMode(IndexMode.Lines); + } + + /** + * Initialize the meshdata object with data. + * + * @param vertices + * @param normals + * @param colors + * @param coords + */ + private void setupData(final FloatBuffer vertices, final FloatBuffer normals, final FloatBuffer colors, + final FloatBufferData coords) { + _meshData.setVertexBuffer(vertices); + _meshData.setNormalBuffer(normals); + _meshData.setColorBuffer(colors); + _meshData.setTextureCoords(coords, 0); + _meshData.setIndices(null); + } + + /** + * Puts a circle into vertex and normal buffer at the current buffer position. The buffers are enlarged and copied + * if they are too small. + * + * @param radius + * radius of the circle + * @param x + * x coordinate of circle center + * @param y + * y coordinate of circle center + * @param segments + * number of line segments the circle is built from + * @param insideOut + * false for normal winding (ccw), true for clockwise winding + */ + public void appendCircle(final double radius, final double x, final double y, final int segments, + final boolean insideOut) { + final int requiredFloats = segments * 2 * 3; + final FloatBuffer verts = BufferUtils.ensureLargeEnough(_meshData.getVertexBuffer(), requiredFloats); + _meshData.setVertexBuffer(verts); + final FloatBuffer normals = BufferUtils.ensureLargeEnough(_meshData.getNormalBuffer(), requiredFloats); + _meshData.setNormalBuffer(normals); + double angle = 0; + final double step = MathUtils.PI * 2 / segments; + for (int i = 0; i < segments; i++) { + final double dx = MathUtils.cos(insideOut ? -angle : angle) * radius; + final double dy = MathUtils.sin(insideOut ? -angle : angle) * radius; + if (i > 0) { + verts.put((float) (dx + x)).put((float) (dy + y)).put(0); + normals.put((float) dx).put((float) dy).put(0); + } + verts.put((float) (dx + x)).put((float) (dy + y)).put(0); + normals.put((float) dx).put((float) dy).put(0); + angle += step; + } + verts.put((float) (radius + x)).put((float) y).put(0); + normals.put((float) radius).put(0).put(0); + } + + /** + * @return true if points are to be drawn antialiased + */ + public boolean isAntialiased() { + return _antialiased; + } + + /** + * Sets whether the point should be antialiased. May decrease performance. If you want to enabled antialiasing, you + * should also use an alphastate with a source of SourceFunction.SourceAlpha and a destination of + * DB_ONE_MINUS_SRC_ALPHA or DB_ONE. + * + * @param antialiased + * true if the line should be antialiased. + */ + public void setAntialiased(final boolean antialiased) { + _antialiased = antialiased; + } + + /** + * @return the width of this line. + */ + public float getLineWidth() { + return _lineWidth; + } + + /** + * Sets the width of the line when drawn. Non anti-aliased line widths are rounded to the nearest whole number by + * opengl. + * + * @param lineWidth + * The lineWidth to set. + */ + public void setLineWidth(final float lineWidth) { + _lineWidth = lineWidth; + } + + /** + * @return the set stipplePattern. 0xFFFF means no stipple. + */ + public short getStipplePattern() { + return _stipplePattern; + } + + /** + * The stipple or pattern to use when drawing this line. 0xFFFF is a solid line. + * + * @param stipplePattern + * a 16bit short whose bits describe the pattern to use when drawing this line + */ + public void setStipplePattern(final short stipplePattern) { + _stipplePattern = stipplePattern; + } + + /** + * @return the set stippleFactor. + */ + public int getStippleFactor() { + return _stippleFactor; + } + + /** + * @param stippleFactor + * magnification factor to apply to the stipple pattern. + */ + public void setStippleFactor(final int stippleFactor) { + _stippleFactor = stippleFactor; + } + + @Override + public Line makeCopy(final boolean shareGeometricData) { + final Line lineCopy = (Line) super.makeCopy(shareGeometricData); + lineCopy.setAntialiased(_antialiased); + lineCopy.setLineWidth(_lineWidth); + lineCopy.setStippleFactor(_stippleFactor); + lineCopy.setStipplePattern(_stipplePattern); + return lineCopy; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_lineWidth, "lineWidth", 1); + capsule.write(_stipplePattern, "stipplePattern", (short) 0xFFFF); + capsule.write(_antialiased, "antialiased", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _lineWidth = capsule.readFloat("lineWidth", 1); + _stipplePattern = capsule.readShort("stipplePattern", (short) 0xFFFF); + _antialiased = capsule.readBoolean("antialiased", false); + } + + @Override + public void render(final Renderer renderer) { + renderer.setupLineParameters(getLineWidth(), getStippleFactor(), getStipplePattern(), isAntialiased()); + + super.render(renderer); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java new file mode 100644 index 0000000..d97c844 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java @@ -0,0 +1,777 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.bounding.BoundingSphere; +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.bounding.CollisionTree; +import com.ardor3d.bounding.CollisionTreeManager; +import com.ardor3d.intersection.IntersectionRecord; +import com.ardor3d.intersection.Pickable; +import com.ardor3d.intersection.PrimitiveKey; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Ray3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.renderer.state.LightState; +import com.ardor3d.renderer.state.LightUtil; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.event.DirtyType; +import com.ardor3d.scenegraph.hint.DataMode; +import com.ardor3d.scenegraph.hint.NormalsMode; +import com.ardor3d.util.Constants; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.scenegraph.RenderDelegate; +import com.ardor3d.util.stat.StatCollector; +import com.ardor3d.util.stat.StatType; +import com.google.common.collect.Lists; + +/** + * A Mesh is a spatial describing a renderable geometric object. Data about the mesh is stored locally using MeshData. + */ +public class Mesh extends Spatial implements Renderable, Pickable { + + public static boolean RENDER_VERTEX_ONLY = false; + + /** Actual buffer representation of the mesh */ + protected MeshData _meshData = new MeshData(); + + /** Local model bounding volume */ + protected BoundingVolume _modelBound = new BoundingSphere(Double.POSITIVE_INFINITY, Vector3.ZERO); + + /** + * The compiled list of renderstates for this mesh, taking into account ancestors states - updated with + * updateRenderStates() + */ + protected final EnumMap<RenderState.StateType, RenderState> _states = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + /** The compiled lightState for this mesh */ + protected transient LightState _lightState; + + /** Default color to use when no per vertex colors are set */ + protected ColorRGBA _defaultColor = new ColorRGBA(ColorRGBA.WHITE); + + /** Visibility setting that can be used after the scenegraph hierarchical culling */ + protected boolean _isVisible = true; + + /** + * Constructs a new Mesh. + */ + public Mesh() { + super(); + } + + /** + * Constructs a new <code>Mesh</code> with a given name. + * + * @param name + * the name of the mesh. This is required for identification purposes. + */ + public Mesh(final String name) { + super(name); + } + + /** + * Retrieves the mesh data object used by this mesh. + * + * @return the mesh data object + */ + public MeshData getMeshData() { + return _meshData; + } + + /** + * Sets the mesh data object for this mesh. + * + * @param meshData + * the mesh data object + */ + public void setMeshData(final MeshData meshData) { + // invalidate collision tree cache + CollisionTreeManager.INSTANCE.removeCollisionTree(this); + _meshData = meshData; + } + + /** + * Retrieves the local bounding volume for this mesh. + * + * @param store + * the bounding volume + */ + public BoundingVolume getModelBound() { + return _modelBound; + } + + /** + * Retrieves a copy of the local bounding volume for this mesh. + * + * @param store + * the bounding volume + */ + public BoundingVolume getModelBound(final BoundingVolume store) { + if (_modelBound == null) { + return null; + } + return _modelBound.clone(store); + } + + /** + * Sets the local bounding volume for this mesh to the given bounds, updated to fit the vertices of this Mesh. Marks + * the spatial as having dirty world bounds. + * + * @param modelBound + * the bounding volume - only type is used, actual values are replaced. + */ + public void setModelBound(final BoundingVolume modelBound) { + setModelBound(modelBound, true); + } + + /** + * Sets the local bounding volume for this mesh to the given bounding volume. If autoCompute is true (default, if + * not given) then we will modify the given modelBound to fit the current vertices of this mesh. This will also mark + * the spatial as having dirty world bounds. + * + * @param modelBound + * the bounding volume + * @param autoCompute + * if true, update the given modelBound to fit the vertices of this Mesh. + */ + public void setModelBound(final BoundingVolume modelBound, final boolean autoCompute) { + _modelBound = modelBound != null ? modelBound.clone(_modelBound) : null; + if (autoCompute) { + updateModelBound(); + } + markDirty(DirtyType.Bounding); + } + + /** + * Recalculate the local bounding volume of this Mesh to fit its vertices. + */ + public void updateModelBound() { + if (_modelBound != null && _meshData.getVertexBuffer() != null) { + // using duplicate to allow walking through buffer without altering current position, etc. + // NB: this maintains a measure of thread safety when using shared meshdata. + _modelBound.computeFromPoints(_meshData.getVertexBuffer().duplicate()); + markDirty(DirtyType.Bounding); + } + } + + @Override + public void updateWorldBound(final boolean recurse) { + if (_modelBound != null) { + _worldBound = _modelBound.transform(_worldTransform, _worldBound); + } else { + _worldBound = null; + } + clearDirty(DirtyType.Bounding); + } + + /** + * translates/rotates and scales the vectors of this Mesh to world coordinates based on its world settings. The + * results are stored in the given FloatBuffer. If given FloatBuffer is null, one is created. + * + * @param store + * the FloatBuffer to store the results in, or null if you want one created. + * @return store or new FloatBuffer if store == null. + */ + public FloatBuffer getWorldVectors(FloatBuffer store) { + final FloatBuffer vertBuf = _meshData.getVertexBuffer(); + if (store == null || store.capacity() != vertBuf.limit()) { + store = BufferUtils.createFloatBuffer(vertBuf.limit()); + } + + final Vector3 compVect = Vector3.fetchTempInstance(); + for (int v = 0, vSize = store.capacity() / 3; v < vSize; v++) { + BufferUtils.populateFromBuffer(compVect, vertBuf, v); + _worldTransform.applyForward(compVect); + BufferUtils.setInBuffer(compVect, store, v); + } + Vector3.releaseTempInstance(compVect); + return store; + } + + /** + * rotates the normals of this Mesh to world normals based on its world settings. The results are stored in the + * given FloatBuffer. If given FloatBuffer is null, one is created. + * + * @param store + * the FloatBuffer to store the results in, or null if you want one created. + * @return store or new FloatBuffer if store == null. + */ + public FloatBuffer getWorldNormals(FloatBuffer store) { + final FloatBuffer normBuf = _meshData.getNormalBuffer(); + if (store == null || store.capacity() != normBuf.limit()) { + store = BufferUtils.createFloatBuffer(normBuf.limit()); + } + + final Vector3 compVect = Vector3.fetchTempInstance(); + for (int v = 0, vSize = store.capacity() / 3; v < vSize; v++) { + BufferUtils.populateFromBuffer(compVect, normBuf, v); + _worldTransform.applyForwardVector(compVect); + BufferUtils.setInBuffer(compVect, store, v); + } + Vector3.releaseTempInstance(compVect); + return store; + } + + public void render(final Renderer renderer) { + if (isVisible()) { + render(renderer, getMeshData()); + } + } + + public void render(final Renderer renderer, final MeshData meshData) { + // Set up MeshData in GLSLShaderObjectsState if necessary + // XXX: considered a hack until we settle on our shader model. + final GLSLShaderObjectsState glsl = (GLSLShaderObjectsState) renderer.getProperRenderState( + RenderState.StateType.GLSLShader, _states.get(RenderState.StateType.GLSLShader)); + + if (glsl != null && glsl.getShaderDataLogic() != null) { + glsl.setMesh(this); + glsl.setNeedsRefresh(true); + } + + final InstancingManager instancing = glsl != null ? meshData.getInstancingManager() : null; + + final RenderContext context = ContextManager.getCurrentContext(); + final ContextCapabilities caps = context.getCapabilities(); + + // Apply fixed function states before mesh transforms for proper function + for (final StateType type : StateType.values) { + if (type != StateType.GLSLShader && type != StateType.FragmentProgram && type != StateType.VertexProgram) { + renderer.applyState(type, _states.get(type)); + } + } + + final boolean useVBO = (getSceneHints().getDataMode() == DataMode.VBO || getSceneHints().getDataMode() == DataMode.VBOInterleaved) + && caps.isVBOSupported(); + + if (instancing == null) { + final boolean transformed = renderer.doTransforms(_worldTransform); + + // Apply shader states here for the ability to retrieve mesh matrices + renderer.applyState(StateType.GLSLShader, _states.get(StateType.GLSLShader)); + renderer.applyState(StateType.FragmentProgram, _states.get(StateType.FragmentProgram)); + renderer.applyState(StateType.VertexProgram, _states.get(StateType.VertexProgram)); + + if (useVBO) { + renderVBO(renderer, meshData, -1); + } else { + renderArrays(renderer, meshData, -1, caps); + } + + if (transformed) { + renderer.undoTransforms(_worldTransform); + } + + } else { + while (instancing.apply(this, renderer, glsl)) { + // Apply shader states here for the ability to retrieve mesh matrices + renderer.applyState(StateType.GLSLShader, _states.get(StateType.GLSLShader)); + renderer.applyState(StateType.FragmentProgram, _states.get(StateType.FragmentProgram)); + renderer.applyState(StateType.VertexProgram, _states.get(StateType.VertexProgram)); + + if (useVBO) { + renderVBO(renderer, meshData, instancing.getPrimitiveCount()); + } else { + renderArrays(renderer, meshData, instancing.getPrimitiveCount(), caps); + } + } + } + } + + protected void renderArrays(final Renderer renderer, final MeshData meshData, final int primcount, + final ContextCapabilities caps) { + // Use arrays + if (caps.isVBOSupported()) { + renderer.unbindVBO(); + } + + if (RENDER_VERTEX_ONLY) { + renderer.applyNormalsMode(NormalsMode.Off, null); + renderer.setupNormalData(null); + renderer.applyDefaultColor(null); + renderer.setupColorData(null); + renderer.setupTextureData(null); + } else { + renderer.applyNormalsMode(getSceneHints().getNormalsMode(), _worldTransform); + if (getSceneHints().getNormalsMode() != NormalsMode.Off) { + renderer.setupNormalData(meshData.getNormalCoords()); + } else { + renderer.setupNormalData(null); + } + + if (meshData.getColorCoords() != null) { + renderer.setupColorData(meshData.getColorCoords()); + } else { + renderer.applyDefaultColor(_defaultColor); + renderer.setupColorData(null); + } + + renderer.setupTextureData(meshData.getTextureCoords()); + } + renderer.setupVertexData(meshData.getVertexCoords()); + + if (meshData.getIndexBuffer() != null) { + renderer.drawElements(meshData.getIndices(), meshData.getIndexLengths(), meshData.getIndexModes(), + primcount); + } else { + renderer.drawArrays(meshData.getVertexCoords(), meshData.getIndexLengths(), meshData.getIndexModes(), + primcount); + } + + if (Constants.stats) { + StatCollector.addStat(StatType.STAT_VERTEX_COUNT, meshData.getVertexCount()); + StatCollector.addStat(StatType.STAT_MESH_COUNT, 1); + } + } + + protected void renderVBO(final Renderer renderer, final MeshData meshData, final int primcount) { + if (getSceneHints().getDataMode() == DataMode.VBOInterleaved) { + if (meshData.getColorCoords() == null) { + renderer.applyDefaultColor(_defaultColor); + } + renderer.applyNormalsMode(getSceneHints().getNormalsMode(), _worldTransform); + // Make sure we have a FBD to hold our id. + if (meshData.getInterleavedData() == null) { + final FloatBufferData interleaved = new FloatBufferData(FloatBuffer.allocate(0), 1); + meshData.setInterleavedData(interleaved); + } + renderer.setupInterleavedDataVBO(meshData.getInterleavedData(), meshData.getVertexCoords(), + meshData.getNormalCoords(), meshData.getColorCoords(), meshData.getTextureCoords()); + } else { + if (RENDER_VERTEX_ONLY) { + renderer.applyNormalsMode(NormalsMode.Off, null); + renderer.setupNormalDataVBO(null); + renderer.applyDefaultColor(null); + renderer.setupColorDataVBO(null); + renderer.setupTextureDataVBO(null); + } else { + renderer.applyNormalsMode(getSceneHints().getNormalsMode(), _worldTransform); + if (getSceneHints().getNormalsMode() != NormalsMode.Off) { + renderer.setupNormalDataVBO(meshData.getNormalCoords()); + } else { + renderer.setupNormalDataVBO(null); + } + + if (meshData.getColorCoords() != null) { + renderer.setupColorDataVBO(meshData.getColorCoords()); + } else { + renderer.applyDefaultColor(_defaultColor); + renderer.setupColorDataVBO(null); + } + + renderer.setupTextureDataVBO(meshData.getTextureCoords()); + } + renderer.setupVertexDataVBO(meshData.getVertexCoords()); + } + + if (meshData.getIndexBuffer() != null) { + // TODO: Maybe ask for the IndexBuffer's dynamic/static type and fall back to arrays for indices? + renderer.drawElementsVBO(meshData.getIndices(), meshData.getIndexLengths(), meshData.getIndexModes(), + primcount); + } else { + renderer.drawArrays(meshData.getVertexCoords(), meshData.getIndexLengths(), meshData.getIndexModes(), + primcount); + } + + if (Constants.stats) { + StatCollector.addStat(StatType.STAT_VERTEX_COUNT, meshData.getVertexCount()); + StatCollector.addStat(StatType.STAT_MESH_COUNT, 1); + } + } + + @Override + protected void applyWorldRenderStates(final boolean recurse, final RenderState.StateStack stack) { + // start with a blank slate + _states.clear(); + + // Go through each state stack and apply to our states list. + stack.extract(_states, this); + } + + public boolean isVisible() { + return _isVisible; + } + + public void setVisible(final boolean isVisible) { + _isVisible = isVisible; + } + + /** + * + */ + @Override + public void draw(final Renderer r) { + if (!r.isProcessingQueue()) { + if (r.checkAndAdd(this)) { + return; + } + } + + final RenderDelegate delegate = getCurrentRenderDelegate(); + if (delegate == null) { + r.draw((Renderable) this); + } else { + delegate.render(this, r); + } + } + + /** + * Sorts the lights based on distance to mesh bounding volume + */ + @Override + public void sortLights() { + if (_lightState != null && _lightState.getLightList().size() > LightState.MAX_LIGHTS_ALLOWED) { + LightUtil.sort(this, _lightState.getLightList()); + } + } + + public LightState getLightState() { + return _lightState; + } + + public void setLightState(final LightState lightState) { + _lightState = lightState; + } + + /** + * <code>setDefaultColor</code> sets the color to be used if no per vertex color buffer is set. + * + * @param color + */ + public void setDefaultColor(final ReadOnlyColorRGBA color) { + _defaultColor.set(color); + } + + /** + * + * @param store + * @return + */ + public ReadOnlyColorRGBA getDefaultColor() { + return _defaultColor; + } + + /** + * @param type + * StateType of RenderState we want to grab + * @return the compiled RenderState for this Mesh, either from RenderStates applied locally or those inherited from + * this Mesh's ancestors. May be null if a state of the given type was never applied in either place. + */ + public RenderState getWorldRenderState(final StateType type) { + return _states.get(type); + } + + /** + * <code>setSolidColor</code> sets the color array of this geometry to a single color. For greater efficiency, try + * setting the the ColorBuffer to null and using DefaultColor instead. + * + * @param color + * the color to set. + */ + public void setSolidColor(final ReadOnlyColorRGBA color) { + FloatBuffer colorBuf = _meshData.getColorBuffer(); + if (colorBuf == null) { + colorBuf = BufferUtils.createColorBuffer(_meshData.getVertexCount()); + _meshData.setColorBuffer(colorBuf); + } + + colorBuf.rewind(); + for (int x = 0, cLength = colorBuf.remaining(); x < cLength; x += 4) { + colorBuf.put(color.getRed()); + colorBuf.put(color.getGreen()); + colorBuf.put(color.getBlue()); + colorBuf.put(color.getAlpha()); + } + colorBuf.flip(); + } + + /** + * Sets every color of this geometry's color array to a random color. + */ + public void setRandomColors() { + FloatBuffer colorBuf = _meshData.getColorBuffer(); + if (colorBuf == null) { + colorBuf = BufferUtils.createColorBuffer(_meshData.getVertexCount()); + _meshData.setColorBuffer(colorBuf); + } else { + colorBuf.rewind(); + } + + for (int x = 0, cLength = colorBuf.limit(); x < cLength; x += 4) { + colorBuf.put(MathUtils.nextRandomFloat()); + colorBuf.put(MathUtils.nextRandomFloat()); + colorBuf.put(MathUtils.nextRandomFloat()); + colorBuf.put(1); + } + colorBuf.flip(); + } + + // PICKABLE INTERFACE + + @Override + public boolean supportsBoundsIntersectionRecord() { + return true; + } + + @Override + public boolean supportsPrimitivesIntersectionRecord() { + return true; + } + + @Override + public boolean intersectsWorldBound(final Ray3 ray) { + // should throw NPE if no bound. + return getWorldBound().intersects(ray); + } + + @Override + public IntersectionRecord intersectsWorldBoundsWhere(final Ray3 ray) { + // should throw NPE if no bound. + return getWorldBound().intersectsWhere(ray); + } + + @Override + public IntersectionRecord intersectsPrimitivesWhere(final Ray3 ray) { + final List<PrimitiveKey> primitives = Lists.newArrayList(); + + // What about Lines and Points? + final CollisionTree ct = CollisionTreeManager.getInstance().getCollisionTree(this); + if (ct != null) { + ct.getBounds().transform(getWorldTransform(), ct.getWorldBounds()); + ct.intersect(ray, primitives); + } + + if (primitives.isEmpty()) { + return null; + } + + Vector3[] vertices = null; + final double[] distances = new double[primitives.size()]; + for (int i = 0; i < primitives.size(); i++) { + final PrimitiveKey key = primitives.get(i); + vertices = getMeshData().getPrimitiveVertices(key.getPrimitiveIndex(), key.getSection(), vertices); + // convert to world coord space + final int max = getMeshData().getIndexMode(key.getSection()).getVertexCount(); + for (int j = 0; j < max; j++) { + if (vertices[j] != null) { + getWorldTransform().applyForward(vertices[j]); + } + } + final double triDistanceSq = ray.getDistanceToPrimitive(vertices); + distances[i] = triDistanceSq; + } + + // FIXME: optimize! ugly bubble sort for now + boolean sorted = false; + while (!sorted) { + sorted = true; + for (int sort = 0; sort < distances.length - 1; sort++) { + if (distances[sort] > distances[sort + 1]) { + // swap + sorted = false; + final double temp = distances[sort + 1]; + distances[sort + 1] = distances[sort]; + distances[sort] = temp; + + // swap primitives too + final PrimitiveKey temp2 = primitives.get(sort + 1); + primitives.set(sort + 1, primitives.get(sort)); + primitives.set(sort, temp2); + } + } + } + + final Vector3[] positions = new Vector3[distances.length]; + for (int i = 0; i < distances.length; i++) { + positions[i] = ray.getDirection().multiply(distances[i], new Vector3()).addLocal(ray.getOrigin()); + } + return new IntersectionRecord(distances, positions, primitives); + } + + @Override + public Mesh makeCopy(final boolean shareGeometricData) { + // get copy of basic spatial info + final Mesh mesh = (Mesh) super.makeCopy(shareGeometricData); + + // if we are sharing, just reuse meshdata + if (shareGeometricData) { + mesh.setMeshData(_meshData); + } else { + // make a copy of our data + mesh.setMeshData(_meshData.makeCopy()); + } + + // copy our basic properties + mesh.setModelBound(_modelBound != null ? _modelBound.clone(null) : null); + mesh.setDefaultColor(_defaultColor); + mesh.setVisible(_isVisible); + + // return + return mesh; + } + + @Override + public Mesh makeInstanced() { + final Mesh mesh = (Mesh) super.makeInstanced(); + if (_meshData.getInstancingManager() == null) { + _meshData.setInstancingManager(new InstancingManager()); + } + mesh.setMeshData(_meshData); + mesh.setModelBound(_modelBound != null ? _modelBound.clone(null) : null); + mesh._defaultColor = _defaultColor; + mesh.setVisible(_isVisible); + return mesh; + } + + /** + * Let this mesh know we want to change its indices to the provided new order. Override this to provide extra + * functionality for sub types as needed. + * + * @param newIndices + * the IntBufferData to switch to. + * @param modes + * the new segment modes to use. + * @param lengths + * the new lengths to use. + */ + public void reorderIndices(final IndexBufferData<?> newIndices, final IndexMode[] modes, final int[] lengths) { + _meshData.setIndices(newIndices); + _meshData.setIndexModes(modes); + _meshData.setIndexLengths(lengths); + } + + /** + * Swap around the order of the vertex data in this Mesh. This is usually called by a tool that has attempted to + * determine a more optimal order for vertex data. + * + * @param newVertexOrder + * a mapping to the desired new order, where the current location of a vertex is the index into this + * array and the value at that location in the array is the new location to store the vertex data. + */ + public void reorderVertexData(final int[] newVertexOrder) { + reorderVertexData(newVertexOrder, _meshData); + } + + /** + * Swap around the order of the vertex data in the given MeshData. Override to provide specific behavior to the Mesh + * object. + * + * @param newVertexOrder + * a mapping to the desired new order, where the current location of a vertex is the index into this + * array and the value at that location in the array is the new location to store the vertex data. + * @param meshData + * the meshData object to work against. + */ + protected void reorderVertexData(final int[] newVertexOrder, final MeshData meshData) { + // must be non-null + final FloatBufferData verts = meshData.getVertexCoords().makeCopy(); + + final FloatBufferData norms = meshData.getNormalBuffer() != null ? meshData.getVertexCoords().makeCopy() : null; + final FloatBufferData colors = meshData.getColorBuffer() != null ? meshData.getColorCoords().makeCopy() : null; + final FloatBufferData fogs = meshData.getFogBuffer() != null ? meshData.getFogCoords().makeCopy() : null; + final FloatBufferData tangents = meshData.getTangentBuffer() != null ? meshData.getTangentCoords().makeCopy() + : null; + final FloatBufferData[] uvs = new FloatBufferData[meshData.getNumberOfUnits()]; + for (int k = 0; k < uvs.length; k++) { + final FloatBufferData tex = meshData.getTextureCoords(k); + if (tex != null) { + uvs[k] = tex.makeCopy(); + } + } + + int vert; + for (int i = 0; i < meshData.getVertexCount(); i++) { + vert = newVertexOrder[i]; + if (vert == -1) { + vert = i; + } + BufferUtils.copy(meshData.getVertexBuffer(), i * verts.getValuesPerTuple(), verts.getBuffer(), + vert * verts.getValuesPerTuple(), verts.getValuesPerTuple()); + if (norms != null) { + BufferUtils.copy(meshData.getNormalBuffer(), i * norms.getValuesPerTuple(), norms.getBuffer(), vert + * norms.getValuesPerTuple(), norms.getValuesPerTuple()); + } + if (colors != null) { + BufferUtils.copy(meshData.getColorBuffer(), i * colors.getValuesPerTuple(), colors.getBuffer(), vert + * colors.getValuesPerTuple(), colors.getValuesPerTuple()); + } + if (fogs != null) { + BufferUtils.copy(meshData.getFogBuffer(), i * fogs.getValuesPerTuple(), fogs.getBuffer(), + vert * fogs.getValuesPerTuple(), fogs.getValuesPerTuple()); + } + if (tangents != null) { + BufferUtils.copy(meshData.getTangentBuffer(), i * tangents.getValuesPerTuple(), tangents.getBuffer(), + vert * tangents.getValuesPerTuple(), tangents.getValuesPerTuple()); + } + for (int k = 0; k < uvs.length; k++) { + if (uvs[k] != null) { + BufferUtils.copy(meshData.getTextureBuffer(k), i * uvs[k].getValuesPerTuple(), uvs[k].getBuffer(), + vert * uvs[k].getValuesPerTuple(), uvs[k].getValuesPerTuple()); + } + } + } + meshData.setVertexCoords(verts); + meshData.setNormalCoords(norms); + meshData.setColorCoords(colors); + meshData.setFogCoords(fogs); + meshData.setTangentCoords(tangents); + for (int k = 0; k < uvs.length; k++) { + if (uvs[k] != null) { + meshData.setTextureCoords(uvs[k], k); + } + } + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Mesh> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_meshData, "meshData", null); + capsule.write(_modelBound, "modelBound", null); + capsule.write(_defaultColor, "defaultColor", new ColorRGBA(ColorRGBA.WHITE)); + capsule.write(_isVisible, "visible", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _meshData = (MeshData) capsule.readSavable("meshData", null); + _modelBound = (BoundingVolume) capsule.readSavable("modelBound", null); + _defaultColor = (ColorRGBA) capsule.readSavable("defaultColor", new ColorRGBA(ColorRGBA.WHITE)); + _isVisible = capsule.readBoolean("visible", true); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java new file mode 100644 index 0000000..b98c7ec --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java @@ -0,0 +1,1253 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; + +/** + * MeshData contains all the commonly used buffers for rendering a mesh. + */ +public class MeshData implements Savable { + + /** The Constant logger. */ + private static final Logger logger = Logger.getLogger(MeshData.class.getName()); + + /** A cache of ids for interleaved use. */ + private transient Map<Object, Integer> _vboIdCache = null; + + /** Number of vertices represented by this data. */ + protected int _vertexCount; + + /** Number of primitives represented by this data. */ + protected transient int[] _primitiveCounts = new int[1]; + + /** Buffer data holding buffers and number of coordinates per vertex */ + protected FloatBufferData _vertexCoords; + protected FloatBufferData _normalCoords; + protected FloatBufferData _colorCoords; + protected FloatBufferData _fogCoords; + protected FloatBufferData _tangentCoords; + protected List<FloatBufferData> _textureCoords = Lists.newArrayListWithCapacity(1); + + /** Interleaved data (for VBO id use). */ + protected FloatBufferData _interleaved; + + /** Index data. */ + protected IndexBufferData<?> _indexBuffer; + protected int[] _indexLengths; + protected IndexMode[] _indexModes = new IndexMode[] { IndexMode.Triangles }; + + private InstancingManager _instancingManager; + + /** + * Gets the vertex count. + * + * @return the vertex count + */ + public int getVertexCount() { + return _vertexCount; + } + + /** + * Gets the vertex buffer. + * + * @return the vertex buffer + */ + public FloatBuffer getVertexBuffer() { + if (_vertexCoords == null) { + return null; + } + return _vertexCoords.getBuffer(); + } + + /** + * Sets the vertex buffer. + * + * @param vertexBuffer + * the new vertex buffer + */ + public void setVertexBuffer(final FloatBuffer vertexBuffer) { + if (vertexBuffer == null) { + setVertexCoords(null); + } else { + setVertexCoords(new FloatBufferData(vertexBuffer, 3)); + } + refreshInterleaved(); + } + + /** + * Gets the vertex coords. + * + * @return the vertex coords + */ + public FloatBufferData getVertexCoords() { + return _vertexCoords; + } + + private void refreshInterleaved() { + if (_interleaved != null) { + _interleaved.setNeedsRefresh(true); + } + } + + /** + * Sets the vertex coords. + * + * @param bufferData + * the new vertex coords + */ + public void setVertexCoords(final FloatBufferData bufferData) { + _vertexCoords = bufferData; + updateVertexCount(); + refreshInterleaved(); + } + + /** + * Gets the normal buffer. + * + * @return the normal buffer + */ + public FloatBuffer getNormalBuffer() { + if (_normalCoords == null) { + return null; + } + return _normalCoords.getBuffer(); + } + + /** + * Sets the normal buffer. + * + * @param normalBuffer + * the new normal buffer + */ + public void setNormalBuffer(final FloatBuffer normalBuffer) { + if (normalBuffer == null) { + _normalCoords = null; + } else { + _normalCoords = new FloatBufferData(normalBuffer, 3); + } + refreshInterleaved(); + } + + /** + * Gets the normal coords. + * + * @return the normal coords + */ + public FloatBufferData getNormalCoords() { + return _normalCoords; + } + + /** + * Sets the normal coords. + * + * @param bufferData + * the new normal coords + */ + public void setNormalCoords(final FloatBufferData bufferData) { + _normalCoords = bufferData; + refreshInterleaved(); + } + + /** + * Gets the color buffer. + * + * @return the color buffer + */ + public FloatBuffer getColorBuffer() { + if (_colorCoords == null) { + return null; + } + return _colorCoords.getBuffer(); + } + + /** + * Sets the color buffer. + * + * @param colorBuffer + * the new color buffer + */ + public void setColorBuffer(final FloatBuffer colorBuffer) { + if (colorBuffer == null) { + _colorCoords = null; + } else { + _colorCoords = new FloatBufferData(colorBuffer, 4); + } + refreshInterleaved(); + } + + /** + * Gets the color coords. + * + * @return the color coords + */ + public FloatBufferData getColorCoords() { + return _colorCoords; + } + + /** + * Sets the color coords. + * + * @param bufferData + * the new color coords + */ + public void setColorCoords(final FloatBufferData bufferData) { + _colorCoords = bufferData; + refreshInterleaved(); + } + + /** + * Gets the fog buffer. + * + * @return the fog buffer + */ + public FloatBuffer getFogBuffer() { + if (_fogCoords == null) { + return null; + } + return _fogCoords.getBuffer(); + } + + /** + * Sets the fog buffer. + * + * @param fogBuffer + * the new fog buffer + */ + public void setFogBuffer(final FloatBuffer fogBuffer) { + if (fogBuffer == null) { + _fogCoords = null; + } else { + _fogCoords = new FloatBufferData(fogBuffer, 3); + } + } + + /** + * Gets the fog coords. + * + * @return the fog coords + */ + public FloatBufferData getFogCoords() { + return _fogCoords; + } + + /** + * Sets the fog coords. + * + * @param bufferData + * the new fog coords + */ + public void setFogCoords(final FloatBufferData bufferData) { + _fogCoords = bufferData; + } + + /** + * Gets the tangent buffer. + * + * @return the tangent buffer + */ + public FloatBuffer getTangentBuffer() { + if (_tangentCoords == null) { + return null; + } + return _tangentCoords.getBuffer(); + } + + /** + * Sets the tangent buffer. + * + * @param tangentBuffer + * the new tangent buffer + */ + public void setTangentBuffer(final FloatBuffer tangentBuffer) { + if (tangentBuffer == null) { + _tangentCoords = null; + } else { + _tangentCoords = new FloatBufferData(tangentBuffer, 3); + } + } + + /** + * Gets the tangent coords. + * + * @return the tangent coords + */ + public FloatBufferData getTangentCoords() { + return _tangentCoords; + } + + /** + * Sets the tangent coords. + * + * @param bufferData + * the new tangent coords + */ + public void setTangentCoords(final FloatBufferData bufferData) { + _tangentCoords = bufferData; + } + + /** + * Gets the FloatBuffer of the FloatBufferData set on a given texture unit. + * + * @param index + * the unit index + * + * @return the texture buffer for the given index, or null if none was set. + */ + public FloatBuffer getTextureBuffer(final int index) { + if (_textureCoords.size() <= index) { + return null; + } + final FloatBufferData textureCoord = _textureCoords.get(index); + if (textureCoord == null) { + return null; + } + return textureCoord.getBuffer(); + } + + /** + * Sets the texture buffer for a given texture unit index. Interprets it as a 2 component float buffer data. If you + * need other sizes, use setTextureCoords instead. + * + * @param textureBuffer + * the texture buffer + * @param index + * the unit index + * @see #setTextureCoords(FloatBufferData, int) + */ + public void setTextureBuffer(final FloatBuffer textureBuffer, final int index) { + while (_textureCoords.size() <= index) { + _textureCoords.add(null); + } + _textureCoords.set(index, new FloatBufferData(textureBuffer, 2)); + refreshInterleaved(); + } + + /** + * Gets the texture coords. + * + * @return the texture coords + */ + public List<FloatBufferData> getTextureCoords() { + return _textureCoords; + } + + /** + * Gets the texture coords assigned to a specific texture unit index of this MeshData. + * + * @param index + * the texture unit index + * + * @return the texture coords + */ + public FloatBufferData getTextureCoords(final int index) { + if (_textureCoords.size() <= index) { + return null; + } + return _textureCoords.get(index); + } + + /** + * Sets all texture coords on this MeshData. + * + * @param textureCoords + * the new texture coords + */ + public void setTextureCoords(final List<FloatBufferData> textureCoords) { + _textureCoords = textureCoords; + refreshInterleaved(); + } + + /** + * Sets the texture coords of a specific texture unit index to the given FloatBufferData. + * + * @param textureCoords + * the texture coords + * @param index + * the unit index + */ + public void setTextureCoords(final FloatBufferData textureCoords, final int index) { + while (_textureCoords.size() <= index) { + _textureCoords.add(null); + } + _textureCoords.set(index, textureCoords); + refreshInterleaved(); + } + + /** + * Retrieves the interleaved buffer, if set or created through packInterleaved. + * + * @return the interleaved buffer + */ + public FloatBuffer getInterleavedBuffer() { + if (_interleaved == null) { + return null; + } + return _interleaved.getBuffer(); + } + + /** + * Gets the interleaved data. + * + * @return the interleaved data + */ + public FloatBufferData getInterleavedData() { + return _interleaved; + } + + /** + * Sets the interleaved buffer. + * + * @param interleavedBuffer + * the interleaved buffer + */ + public void setInterleavedData(final FloatBufferData interleavedData) { + _interleaved = interleavedData; + refreshInterleaved(); + } + + /** + * Update the vertex count based on the current limit of the vertex buffer. + */ + public void updateVertexCount() { + if (_vertexCoords == null) { + _vertexCount = 0; + } else { + _vertexCount = _vertexCoords.getTupleCount(); + } + // update primitive count if we are using arrays + if (_indexBuffer == null) { + updatePrimitiveCounts(); + } + } + + /** + * <code>copyTextureCoords</code> copies the texture coordinates of a given texture unit to another location. If the + * texture unit is not valid, then the coordinates are ignored. Coords are multiplied by the given factor. + * + * @param fromIndex + * the coordinates to copy. + * @param toIndex + * the texture unit to set them to. Must not be the same as the fromIndex. + * @param factor + * a multiple to apply when copying + */ + public void copyTextureCoordinates(final int fromIndex, final int toIndex, final float factor) { + if (_textureCoords == null) { + return; + } + + if (fromIndex < 0 || fromIndex >= _textureCoords.size() || _textureCoords.get(fromIndex) == null) { + return; + } + + if (toIndex < 0 || toIndex == fromIndex) { + return; + } + + // make sure we are big enough + while (toIndex >= _textureCoords.size()) { + _textureCoords.add(null); + } + + FloatBufferData dest = _textureCoords.get(toIndex); + final FloatBufferData src = _textureCoords.get(fromIndex); + if (dest == null || dest.getBuffer().capacity() != src.getBuffer().limit()) { + dest = new FloatBufferData(BufferUtils.createFloatBuffer(src.getBuffer().capacity()), + src.getValuesPerTuple()); + _textureCoords.set(toIndex, dest); + } + dest.getBuffer().clear(); + final int oldLimit = src.getBuffer().limit(); + src.getBuffer().clear(); + for (int i = 0, len = dest.getBuffer().capacity(); i < len; i++) { + dest.getBuffer().put(factor * src.getBuffer().get()); + } + src.getBuffer().limit(oldLimit); + dest.getBuffer().limit(oldLimit); + } + + /** + * <code>copyTextureCoords</code> copies the texture coordinates of a given texture unit to another location. If the + * texture unit is not valid, then the coordinates are ignored. Coords are multiplied by the given S and T factors. + * + * @param fromIndex + * the coordinates to copy. + * @param toIndex + * the texture unit to set them to. Must not be the same as the fromIndex. + * @param factorS + * a multiple to apply to the S channel when copying + * @param factorT + * a multiple to apply to the T channel when copying + */ + public void copyTextureCoordinates(final int fromIndex, final int toIndex, final float factorS, final float factorT) { + if (_textureCoords == null) { + return; + } + + if (fromIndex < 0 || fromIndex >= _textureCoords.size() || _textureCoords.get(fromIndex) == null) { + return; + } + + if (toIndex < 0 || toIndex == fromIndex) { + return; + } + + // make sure we are big enough + while (toIndex >= _textureCoords.size()) { + _textureCoords.add(null); + } + + FloatBufferData dest = _textureCoords.get(toIndex); + final FloatBufferData src = _textureCoords.get(fromIndex); + if (dest == null || dest.getBuffer().capacity() != src.getBuffer().limit()) { + dest = new FloatBufferData(BufferUtils.createFloatBuffer(src.getBuffer().capacity()), + src.getValuesPerTuple()); + _textureCoords.set(toIndex, dest); + } + dest.getBuffer().clear(); + final int oldLimit = src.getBuffer().limit(); + src.getBuffer().clear(); + for (int i = 0, len = dest.getBuffer().capacity(); i < len; i++) { + if (i % 2 == 0) { + dest.getBuffer().put(factorS * src.getBuffer().get()); + } else { + dest.getBuffer().put(factorT * src.getBuffer().get()); + } + } + src.getBuffer().limit(oldLimit); + dest.getBuffer().limit(oldLimit); + } + + /** + * <code>getNumberOfUnits</code> returns the number of texture units this geometry is currently using. + * + * @return the number of texture units in use. + */ + public int getNumberOfUnits() { + if (_textureCoords == null) { + return 0; + } + return _textureCoords.size(); + } + + /** + * Gets the index buffer. + * + * @return the index buffer + */ + public Buffer getIndexBuffer() { + if (_indexBuffer == null) { + return null; + } + return _indexBuffer.getBuffer(); + } + + /** + * Sets the index buffer. + * + * @param indices + * the new index buffer + */ + public void setIndexBuffer(final IntBuffer indices) { + if (indices == null) { + _indexBuffer = null; + } else { + _indexBuffer = new IntBufferData(indices); + } + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Sets the index buffer. + * + * @param indices + * the new index buffer + */ + public void setIndexBuffer(final ShortBuffer indices) { + if (indices == null) { + _indexBuffer = null; + } else { + _indexBuffer = new ShortBufferData(indices); + } + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Sets the index buffer. + * + * @param indices + * the new index buffer + */ + public void setIndexBuffer(final ByteBuffer indices) { + if (indices == null) { + _indexBuffer = null; + } else { + _indexBuffer = new ByteBufferData(indices); + } + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Gets the indices. + * + * @return the indices + */ + public IndexBufferData<?> getIndices() { + return _indexBuffer; + } + + /** + * Sets the indices + * + * @param bufferData + * the new indices + */ + public void setIndices(final IndexBufferData<?> bufferData) { + _indexBuffer = bufferData; + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Gets the index mode. + * + * @return the IndexMode of the first section of this MeshData. + * @deprecated Please switch to {@link #getIndexMode(int)} + */ + @Deprecated + public IndexMode getIndexMode() { + return getIndexMode(0); + } + + /** + * Sets the index mode. + * + * @param indexMode + * the new IndexMode to use for the first section of this MeshData. + */ + public void setIndexMode(final IndexMode indexMode) { + _indexModes[0] = indexMode; + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Gets the index lengths. + * + * @return the index lengths + */ + public int[] getIndexLengths() { + return _indexLengths; + } + + /** + * Sets the index lengths. + * + * @param indexLengths + * the new index lengths + */ + public void setIndexLengths(final int[] indexLengths) { + _indexLengths = indexLengths; + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Gets the index modes. + * + * @return the index modes + */ + public IndexMode[] getIndexModes() { + return _indexModes; + } + + /** + * Gets the index mode. + * + * @param sectionIndex + * the section index + * + * @return the index mode + */ + public IndexMode getIndexMode(final int sectionIndex) { + if (sectionIndex < 0 || sectionIndex >= getSectionCount()) { + throw new IllegalArgumentException("invalid section index: " + sectionIndex); + } + return _indexModes.length > sectionIndex ? _indexModes[sectionIndex] : _indexModes[_indexModes.length - 1]; + } + + /** + * Note: Also updates primitive counts. + * + * @param indexModes + * the index modes to use for this MeshData. + */ + public void setIndexModes(final IndexMode[] indexModes) { + _indexModes = indexModes; + updatePrimitiveCounts(); + refreshInterleaved(); + } + + /** + * Gets the section count. + * + * @return the number of sections (lengths, indexModes, etc.) this MeshData contains. + */ + public int getSectionCount() { + return _indexLengths != null ? _indexLengths.length : 1; + } + + /** + * Gets the total primitive count. + * + * @return the sum of the primitive counts on all sections of this mesh data. + */ + public int getTotalPrimitiveCount() { + int count = 0; + for (int i = 0; i < _primitiveCounts.length; i++) { + count += _primitiveCounts[i]; + } + return count; + } + + /** + * Gets the primitive count. + * + * @param section + * the section + * + * @return the number of primitives (triangles, quads, lines, points, etc.) on a given section of this mesh data. + */ + public int getPrimitiveCount(final int section) { + return _primitiveCounts[section]; + } + + /** + * Returns the vertex indices of a specified primitive. + * + * @param primitiveIndex + * which triangle, quad, etc + * @param section + * which section to pull from (corresponds to array position in indexmodes and lengths) + * @param store + * an int array to store the results in. if null, or the length < the size of the primitive, a new array + * is created and returned. + * + * @return the primitive's vertex indices as an array + * + * @throws IndexOutOfBoundsException + * if primitiveIndex is outside of range [0, count-1] where count is the number of primitives in the + * given section. + * @throws ArrayIndexOutOfBoundsException + * if section is out of range [0, N-1] where N is the number of sections in this MeshData object. + */ + public int[] getPrimitiveIndices(final int primitiveIndex, final int section, final int[] store) { + final int count = getPrimitiveCount(section); + if (primitiveIndex >= count || primitiveIndex < 0) { + throw new IndexOutOfBoundsException("Invalid primitiveIndex '" + primitiveIndex + "'. Count is " + count); + } + + final IndexMode mode = getIndexMode(section); + final int rSize = mode.getVertexCount(); + + int[] result = store; + if (result == null || result.length < rSize) { + result = new int[rSize]; + } + + for (int i = 0; i < rSize; i++) { + if (getIndexBuffer() != null) { + result[i] = getIndices().get(getVertexIndex(primitiveIndex, i, section)); + } else { + result[i] = getVertexIndex(primitiveIndex, i, section); + } + } + + return result; + } + + /** + * Gets the vertices that make up the given primitive. + * + * @param primitiveIndex + * the primitive index + * @param section + * the section + * @param store + * the store. If null or the wrong size, we'll make a new array and return that instead. + * + * @return the primitive + */ + public Vector3[] getPrimitiveVertices(final int primitiveIndex, final int section, final Vector3[] store) { + final int count = getPrimitiveCount(section); + if (primitiveIndex >= count || primitiveIndex < 0) { + throw new IndexOutOfBoundsException("Invalid primitiveIndex '" + primitiveIndex + "'. Count is " + count); + } + + final IndexMode mode = getIndexMode(section); + final int rSize = mode.getVertexCount(); + Vector3[] result = store; + if (result == null || result.length < rSize) { + result = new Vector3[rSize]; + } + + for (int i = 0; i < rSize; i++) { + if (result[i] == null) { + result[i] = new Vector3(); + } + if (getIndexBuffer() != null) { + // indexed geometry + BufferUtils.populateFromBuffer(result[i], getVertexBuffer(), + getIndices().get(getVertexIndex(primitiveIndex, i, section))); + } else { + // non-indexed geometry + BufferUtils + .populateFromBuffer(result[i], getVertexBuffer(), getVertexIndex(primitiveIndex, i, section)); + } + } + + return result; + } + + /** + * Gets the vertex index. + * + * @param primitiveIndex + * which triangle, quad, etc. + * @param point + * which point on the triangle, quad, etc. (triangle has three points, so this would be 0-2, etc.) + * @param section + * which section to pull from (corresponds to array position in indexmodes and lengths) + * + * @return the position you would expect to find the given point in the index buffer + */ + public int getVertexIndex(final int primitiveIndex, final int point, final int section) { + int index = 0; + // move our offset up to the beginning of our section + for (int i = 0; i < section; i++) { + index += _indexLengths[i]; + } + + // Ok, now pull primitive index based on indexmode. + switch (getIndexMode(section)) { + case Triangles: + index += (primitiveIndex * 3) + point; + break; + case TriangleStrip: + // XXX: Do we need to flip point 0 and 1 on odd primitiveIndex values? + // if (point < 2 && primitiveIndex % 2 == 1) { + // index += primitiveIndex + (point == 0 ? 1 : 0); + // } else { + index += primitiveIndex + point; + // } + break; + case TriangleFan: + if (point == 0) { + index += 0; + } else { + index += primitiveIndex + point; + } + break; + case Quads: + index += (primitiveIndex * 4) + point; + break; + case QuadStrip: + index += (primitiveIndex * 2) + point; + break; + case Points: + index += primitiveIndex; + break; + case Lines: + index += (primitiveIndex * 2) + point; + break; + case LineStrip: + case LineLoop: + index += primitiveIndex + point; + break; + default: + logger.warning("unimplemented index mode: " + getIndexMode(0)); + return -1; + } + return index; + } + + /** + * Random vertex. + * + * @param store + * the vector object to store the result in. if null, a new one is created. + * + * @return a random vertex from the vertices stored in this MeshData. null is returned if there are no vertices. + */ + public Vector3 randomVertex(final Vector3 store) { + if (_vertexCoords == null) { + return null; + } + + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + final int i = MathUtils.nextRandomInt(0, getVertexCount() - 1); + BufferUtils.populateFromBuffer(result, _vertexCoords.getBuffer(), i); + + return result; + } + + /** + * Random point on primitives. + * + * @param store + * the vector object to store the result in. if null, a new one is created. + * + * @return a random point from the surface of a primitive stored in this MeshData. null is returned if there are no + * vertices or indices. + */ + public Vector3 randomPointOnPrimitives(final Vector3 store) { + if (_vertexCoords == null || _indexBuffer == null) { + return null; + } + + Vector3 result = store; + if (result == null) { + result = new Vector3(); + } + + // randomly pick a section (if there are more than 1) + final int section = MathUtils.nextRandomInt(0, getSectionCount() - 1); + + // randomly pick a primitive in that section + final int primitiveIndex = MathUtils.nextRandomInt(0, getPrimitiveCount(section) - 1); + + // Now, based on IndexMode, pick a point on that primitive + final IndexMode mode = getIndexMode(section); + final boolean hasIndices = getIndexBuffer() != null; + switch (mode) { + case Triangles: + case TriangleFan: + case TriangleStrip: + case Quads: + case QuadStrip: { + int pntA = getVertexIndex(primitiveIndex, 0, section); + int pntB = getVertexIndex(primitiveIndex, 1, section); + int pntC = getVertexIndex(primitiveIndex, 2, section); + + if (hasIndices) { + pntA = getIndices().get(pntA); + pntB = getIndices().get(pntB); + pntC = getIndices().get(pntC); + } + + double b = MathUtils.nextRandomDouble(); + double c = MathUtils.nextRandomDouble(); + + if (mode != IndexMode.Quads && mode != IndexMode.QuadStrip) { + // keep it in the triangle by reflecting it across the center diagonal BC + if (b + c > 1) { + b = 1 - b; + c = 1 - c; + } + } + + final double a = 1 - b - c; + + final Vector3 work = Vector3.fetchTempInstance(); + BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntA); + work.multiplyLocal(a); + result.set(work); + + BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntB); + work.multiplyLocal(b); + result.addLocal(work); + + BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntC); + work.multiplyLocal(c); + result.addLocal(work); + Vector3.releaseTempInstance(work); + break; + } + case Points: { + int pnt = getVertexIndex(primitiveIndex, 0, section); + if (hasIndices) { + pnt = getIndices().get(pnt); + } + BufferUtils.populateFromBuffer(result, getVertexBuffer(), pnt); + break; + } + case Lines: + case LineLoop: + case LineStrip: { + int pntA = getVertexIndex(primitiveIndex, 0, section); + int pntB = getVertexIndex(primitiveIndex, 1, section); + if (hasIndices) { + pntA = getIndices().get(pntA); + pntB = getIndices().get(pntB); + } + + final Vector3 work = Vector3.fetchTempInstance(); + BufferUtils.populateFromBuffer(result, getVertexBuffer(), pntA); + BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntB); + Vector3.lerp(result, work, MathUtils.nextRandomDouble(), result); + Vector3.releaseTempInstance(work); + break; + } + } + + return result; + } + + /** + * Translate points. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + */ + public void translatePoints(final double x, final double y, final double z) { + translatePoints(new Vector3(x, y, z)); + } + + /** + * Translate points. + * + * @param amount + * the amount + */ + public void translatePoints(final Vector3 amount) { + for (int x = 0; x < _vertexCount; x++) { + BufferUtils.addInBuffer(amount, _vertexCoords.getBuffer(), x); + } + } + + public void transformVertices(final Transform transform) { + final Vector3 store = new Vector3(); + for (int x = 0; x < _vertexCount; x++) { + BufferUtils.populateFromBuffer(store, _vertexCoords.getBuffer(), x); + transform.applyForward(store, store); + BufferUtils.setInBuffer(store, _vertexCoords.getBuffer(), x); + } + } + + public void transformNormals(final Transform transform, final boolean normalize) { + final Vector3 store = new Vector3(); + for (int x = 0; x < _vertexCount; x++) { + BufferUtils.populateFromBuffer(store, _normalCoords.getBuffer(), x); + transform.applyForwardVector(store, store); + if (normalize) { + store.normalizeLocal(); + } + BufferUtils.setInBuffer(store, _normalCoords.getBuffer(), x); + } + } + + /** + * Rotate points. + * + * @param rotate + * the rotate + */ + public void rotatePoints(final Quaternion rotate) { + final Vector3 store = new Vector3(); + for (int x = 0; x < _vertexCount; x++) { + BufferUtils.populateFromBuffer(store, _vertexCoords.getBuffer(), x); + rotate.apply(store, store); + BufferUtils.setInBuffer(store, _vertexCoords.getBuffer(), x); + } + } + + /** + * Rotate normals. + * + * @param rotate + * the rotate + */ + public void rotateNormals(final Quaternion rotate) { + final Vector3 store = new Vector3(); + for (int x = 0; x < _vertexCount; x++) { + BufferUtils.populateFromBuffer(store, _normalCoords.getBuffer(), x); + rotate.apply(store, store); + BufferUtils.setInBuffer(store, _normalCoords.getBuffer(), x); + } + } + + /** + * Update primitive counts. + */ + private void updatePrimitiveCounts() { + final int maxIndex = _indexBuffer != null ? _indexBuffer.getBufferLimit() : _vertexCount; + final int maxSection = _indexLengths != null ? _indexLengths.length : 1; + if (_primitiveCounts.length != maxSection) { + _primitiveCounts = new int[maxSection]; + } + for (int i = 0; i < maxSection; i++) { + final int size = _indexLengths != null ? _indexLengths[i] : maxIndex; + final int count = IndexMode.getPrimitiveCount(getIndexMode(i), size); + _primitiveCounts[i] = count; + } + + } + + /** + * @param glContext + * the object representing the OpenGL context a vbo belongs to. See + * {@link RenderContext#getGlContextRep()} + * @return the vbo id of a vbo in the given context. If the vbo is not found in the given context, 0 is returned. + */ + public int getVBOInterleavedID(final Object glContext) { + if (_vboIdCache != null && _vboIdCache.containsKey(glContext)) { + return _vboIdCache.get(glContext); + } + return 0; + } + + /** + * Sets the id for a vbo based on interleaving this MeshData's buffer, in regards to the given OpenGL context. + * + * @param glContext + * the object representing the OpenGL context a vbo belongs to. See + * {@link RenderContext#getGlContextRep()} + * @param vboId + * the vbo id of a vbo. To be valid, this must be != 0. + * @throws IllegalArgumentException + * if vboId is equal to 0. + */ + public void setVBOInterleavedID(final Object glContext, final int vboId) { + if (vboId == 0) { + throw new IllegalArgumentException("vboId must != 0"); + } + + if (_vboIdCache == null) { + _vboIdCache = new MapMaker().initialCapacity(1).weakKeys().makeMap(); + } + _vboIdCache.put(glContext, vboId); + } + + public MeshData makeCopy() { + final MeshData data = new MeshData(); + data._vertexCount = _vertexCount; + data._primitiveCounts = new int[_primitiveCounts.length]; + System.arraycopy(_primitiveCounts, 0, data._primitiveCounts, 0, _primitiveCounts.length); + + if (_vertexCoords != null) { + data._vertexCoords = _vertexCoords.makeCopy(); + } + if (_normalCoords != null) { + data._normalCoords = _normalCoords.makeCopy(); + } + if (_colorCoords != null) { + data._colorCoords = _colorCoords.makeCopy(); + } + if (_fogCoords != null) { + data._fogCoords = _fogCoords.makeCopy(); + } + if (_tangentCoords != null) { + data._tangentCoords = _tangentCoords.makeCopy(); + } + + for (final FloatBufferData tCoord : _textureCoords) { + if (tCoord != null) { + data._textureCoords.add(tCoord.makeCopy()); + } else { + data._textureCoords.add(null); + } + } + + if (_indexBuffer != null) { + data._indexBuffer = _indexBuffer.makeCopy(); + } + + if (_indexLengths != null) { + data._indexLengths = new int[_indexLengths.length]; + System.arraycopy(_indexLengths, 0, data._indexLengths, 0, _indexLengths.length); + } + data._indexModes = new IndexMode[_indexModes.length]; + System.arraycopy(_indexModes, 0, data._indexModes, 0, _indexModes.length); + + return data; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public Class<? extends MeshData> getClassTag() { + return this.getClass(); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_vertexCount, "vertexCount", 0); + capsule.write(_vertexCoords, "vertexBuffer", null); + capsule.write(_normalCoords, "normalBuffer", null); + capsule.write(_colorCoords, "colorBuffer", null); + capsule.write(_fogCoords, "fogBuffer", null); + capsule.write(_tangentCoords, "tangentBuffer", null); + capsule.writeSavableList(_textureCoords, "textureCoords", new ArrayList<FloatBufferData>(1)); + capsule.write((Savable) _indexBuffer, "indexBuffer", null); + capsule.write(_interleaved, "interleaved", null); + capsule.write(_indexLengths, "indexLengths", null); + capsule.write(_indexModes, "indexModes"); + } + + public void read(final InputCapsule capsule) throws IOException { + _vertexCount = capsule.readInt("vertexCount", 0); + _vertexCoords = (FloatBufferData) capsule.readSavable("vertexBuffer", null); + _normalCoords = (FloatBufferData) capsule.readSavable("normalBuffer", null); + _colorCoords = (FloatBufferData) capsule.readSavable("colorBuffer", null); + _fogCoords = (FloatBufferData) capsule.readSavable("fogBuffer", null); + _tangentCoords = (FloatBufferData) capsule.readSavable("tangentBuffer", null); + _textureCoords = capsule.readSavableList("textureCoords", new ArrayList<FloatBufferData>(1)); + _indexBuffer = (IndexBufferData<?>) capsule.readSavable("indexBuffer", null); + _interleaved = (FloatBufferData) capsule.readSavable("interleaved", null); + _indexLengths = capsule.readIntArray("indexLengths", null); + _indexModes = capsule.readEnumArray("indexModes", IndexMode.class, new IndexMode[] { IndexMode.Triangles }); + + updatePrimitiveCounts(); + } + + public InstancingManager getInstancingManager() { + return _instancingManager; + } + + public void setInstancingManager(final InstancingManager info) { + _instancingManager = info; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java new file mode 100644 index 0000000..f03c447 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java @@ -0,0 +1,515 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.scenegraph.event.DirtyType; +import com.ardor3d.scenegraph.visitor.Visitor; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.scenegraph.RenderDelegate; + +/** + * Node defines an internal node of a scene graph. The internal node maintains a collection of children and handles + * merging said children into a single bound to allow for very fast culling of multiple nodes. Node allows for any + * number of children to be attached. + */ +public class Node extends Spatial { + private static final Logger logger = Logger.getLogger(Node.class.getName()); + + /** This node's children. */ + protected final List<Spatial> _children; + + /** + * Constructs a new Spatial. + */ + public Node() { + super(); + _children = Collections.synchronizedList(new ArrayList<Spatial>(1)); + } + + /** + * Constructs a new <code>Node</code> with a given name. + * + * @param name + * the name of the node. This is required for identification purposes. + */ + public Node(final String name) { + this(name, Collections.synchronizedList(new ArrayList<Spatial>(1))); + } + + /** + * Constructs a new <code>Node</code> with a given name. + * + * @param name + * the name of the node. This is required for identification purposes. + * @param children + * the list to use for storing children. Defaults to a synchronized ArrayList, but using this + * constructor, you can select a different kind of list. + */ + public Node(final String name, final List<Spatial> children) { + super(name); + _children = children; + } + + /** + * + * <code>attachChild</code> attaches a child to this node. This node becomes the child's parent. The current number + * of children maintained is returned. <br> + * If the child already had a parent it is detached from that former parent. + * + * @param child + * the child to attach to this node. + * @return the number of children maintained by this node. + */ + public int attachChild(final Spatial child) { + if (child != null) { + if (child == this || (child instanceof Node && hasAncestor((Node) child))) { + throw new IllegalArgumentException("Child is already part of this hierarchy."); + } + if (child.getParent() != this) { + if (child.getParent() != null) { + child.getParent().detachChild(child); + } + child.setParent(this); + _children.add(child); + child.markDirty(DirtyType.Attached); + if (logger.isLoggable(Level.FINE)) { + logger.fine("Child (" + child.getName() + ") attached to this" + " node (" + getName() + ")"); + } + } + } + + return _children.size(); + } + + /** + * + * <code>attachChildAt</code> attaches a child to this node at an index. This node becomes the child's parent. The + * current number of children maintained is returned. <br> + * If the child already had a parent it is detached from that former parent. + * + * @param child + * the child to attach to this node. + * @return the number of children maintained by this node. + */ + public int attachChildAt(final Spatial child, final int index) { + if (child != null) { + if (child == this || (child instanceof Node && hasAncestor((Node) child))) { + throw new IllegalArgumentException("Child is already part of this hierarchy."); + } + if (child.getParent() != this) { + if (child.getParent() != null) { + child.getParent().detachChild(child); + } + child.setParent(this); + _children.add(index, child); + child.markDirty(DirtyType.Attached); + if (logger.isLoggable(Level.FINE)) { + logger.fine("Child (" + child.getName() + ") attached to this" + " node (" + getName() + ")"); + } + } + } + + return _children.size(); + } + + /** + * <code>detachChild</code> removes a given child from the node's list. This child will no longe be maintained. + * + * @param child + * the child to remove. + * @return the index the child was at. -1 if the child was not in the list. + */ + public int detachChild(final Spatial child) { + if (child == null) { + return -1; + } + if (child.getParent() == this) { + final int index = _children.indexOf(child); + if (index != -1) { + detachChildAt(index); + } + return index; + } + + return -1; + } + + /** + * <code>detachChild</code> removes a given child from the node's list. This child will no longe be maintained. Only + * the first child with a matching name is removed. + * + * @param childName + * the child to remove. + * @return the index the child was at. -1 if the child was not in the list. + */ + public int detachChildNamed(final String childName) { + if (childName == null) { + return -1; + } + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial child = _children.get(i); + if (childName.equals(child.getName())) { + detachChildAt(i); + return i; + } + } + return -1; + } + + /** + * + * <code>detachChildAt</code> removes a child at a given index. That child is returned for saving purposes. + * + * @param index + * the index of the child to be removed. + * @return the child at the supplied index. + */ + public Spatial detachChildAt(final int index) { + final Spatial child = _children.remove(index); + if (child != null) { + child.setParent(null); + markDirty(child, DirtyType.Detached); + if (child.getListener() != null) { + child.setListener(null); + } + if (logger.isLoggable(Level.INFO)) { + logger.fine("Child removed."); + } + } + return child; + } + + /** + * + * <code>detachAllChildren</code> removes all children attached to this node. + */ + public void detachAllChildren() { + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + detachChildAt(i); + } + logger.fine("All children removed."); + } + + /** + * Get the index of the specified spatial. + * + * @param sp + * spatial to retrieve index for. + * @return the index + */ + public int getChildIndex(final Spatial sp) { + return _children.indexOf(sp); + } + + /** + * Returns all children to this node. + * + * @return a list containing all children to this node + */ + public List<Spatial> getChildren() { + return _children; + } + + /** + * Swaps two children. + * + * @param index1 + * @param index2 + */ + public void swapChildren(final int index1, final int index2) { + final Spatial c2 = _children.get(index2); + final Spatial c1 = _children.remove(index1); + _children.add(index1, c2); + _children.remove(index2); + _children.add(index2, c1); + } + + @Override + protected void updateChildren(final double time) { + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial pkChild = getChild(i); + if (pkChild != null) { + pkChild.updateGeometricState(time, false); + } + } + } + + /** + * + * <code>getChild</code> returns a child at a given index. + * + * @param i + * the index to retrieve the child from. + * @return the child at a specified index. + */ + public Spatial getChild(final int i) { + return _children.get(i); + } + + /** + * <code>getChild</code> returns the first child found with exactly the given name (case sensitive.) If our children + * are Nodes, we will search their children as well. + * + * @param name + * the name of the child to retrieve. If null, we'll return null. + * @return the child if found, or null. + */ + public Spatial getChild(final String name) { + if (name == null) { + return null; + } + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial child = _children.get(i); + if (name.equals(child.getName())) { + return child; + } else if (child instanceof Node) { + final Spatial out = ((Node) child).getChild(name); + if (out != null) { + return out; + } + } + } + return null; + } + + /** + * determines if the provided Spatial is contained in the children list of this node. + * + * @param spat + * the child object to look for. + * @return true if the object is contained, false otherwise. + */ + public boolean hasChild(final Spatial spat) { + if (_children.contains(spat)) { + return true; + } + + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial child = _children.get(i); + if (child instanceof Node && ((Node) child).hasChild(spat)) { + return true; + } + } + + return false; + } + + /** + * + * <code>getNumberOfChildren</code> returns the number of children this node maintains. + * + * @return the number of children this node maintains. + */ + public int getNumberOfChildren() { + return _children.size(); + } + + @Override + protected void propagateDirtyDown(final EnumSet<DirtyType> dirtyTypes) { + super.propagateDirtyDown(dirtyTypes); + + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial child = _children.get(i); + + child.propagateDirtyDown(dirtyTypes); + } + } + + @Override + public void updateWorldTransform(final boolean recurse) { + super.updateWorldTransform(recurse); + + if (recurse) { + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + _children.get(i).updateWorldTransform(true); + } + } + } + + @Override + protected void updateWorldRenderStates(final boolean recurse, final RenderState.StateStack stack) { + super.updateWorldRenderStates(recurse, stack); + + if (recurse) { + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + _children.get(i).updateWorldRenderStates(true, stack); + } + } + } + + /** + * <code>draw</code> calls the onDraw method for each child maintained by this node. + * + * @see com.ardor3d.scenegraph.Spatial#draw(com.ardor3d.renderer.Renderer) + * @param r + * the renderer to draw to. + */ + @Override + public void draw(final Renderer r) { + + final RenderDelegate delegate = getCurrentRenderDelegate(); + if (delegate == null) { + Spatial child; + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + child = _children.get(i); + if (child != null) { + child.onDraw(r); + } + } + } else { + // Queue as needed + if (!r.isProcessingQueue()) { + if (r.checkAndAdd(this)) { + return; + } + } + + delegate.render(this, r); + } + } + + /** + * <code>updateWorldBound</code> merges the bounds of all the children maintained by this node. This will allow for + * faster culling operations. + * + * @see com.ardor3d.scenegraph.Spatial#updateWorldBound(boolean) + */ + @Override + public void updateWorldBound(final boolean recurse) { + BoundingVolume worldBound = null; + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial child = _children.get(i); + if (child != null) { + if (recurse) { + child.updateWorldBound(true); + } + if (worldBound != null) { + // merge current world bound with child world bound + worldBound.mergeLocal(child.getWorldBound()); + + // simple check to catch NaN issues + if (!Vector3.isValid(worldBound.getCenter())) { + throw new Ardor3dException("WorldBound center is invalid after merge between " + this + " and " + + child); + } + } else { + // set world bound to first non-null child world bound + if (child.getWorldBound() != null) { + worldBound = child.getWorldBound().clone(_worldBound); + } + } + } + } + _worldBound = worldBound; + clearDirty(DirtyType.Bounding); + } + + @Override + public void acceptVisitor(final Visitor visitor, final boolean preexecute) { + if (preexecute) { + visitor.visit(this); + } + + Spatial child; + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + child = _children.get(i); + if (child != null) { + child.acceptVisitor(visitor, preexecute); + } + } + + if (!preexecute) { + visitor.visit(this); + } + } + + @Override + public void sortLights() { + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial pkChild = getChild(i); + if (pkChild != null) { + pkChild.sortLights(); + } + } + } + + @Override + public Node makeCopy(final boolean shareGeometricData) { + // get copy of basic spatial info + final Node node = (Node) super.makeCopy(shareGeometricData); + + // add copy of children + for (final Spatial child : getChildren()) { + final Spatial copy = child.makeCopy(shareGeometricData); + node.attachChild(copy); + } + + // return + return node; + } + + @Override + public Node makeInstanced() { + // get copy of basic spatial info + final Node node = (Node) super.makeInstanced(); + // add copy of children + for (final Spatial child : getChildren()) { + final Spatial copy = child.makeInstanced(); + node.attachChild(copy); + } + return node; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends Node> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.writeSavableList(new ArrayList<Spatial>(_children), "children", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + final List<Spatial> cList = capsule.readSavableList("children", null); + _children.clear(); + if (cList != null) { + _children.addAll(cList); + } + + // go through children and set parent to this node + for (int i = getNumberOfChildren() - 1; i >= 0; i--) { + final Spatial child = _children.get(i); + child._parent = this; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java new file mode 100644 index 0000000..70d04ef --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java @@ -0,0 +1,320 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Point</code> defines a collection of vertices that are rendered as single points or textured sprites depending + * on PointType. + */ +public class Point extends Mesh { + + public enum PointType { + Point, PointSprite; + }; + + private PointType _pointType; + + private float _pointSize = 1.0f; + private boolean _antialiased = false; + + /** + * Distance Attenuation fields. + */ + // XXX: LWJGL requires 4 floats, but only 3 coefficients are mentioned in the specification? + // JOGL works fine with 3. + private final FloatBuffer _attenuationCoefficients = BufferUtils.createFloatBuffer(new float[] { 0.0f, 0f, + 0.000004f, 0f }); + private float _minPointSize = 1.0f; + private float _maxPointSize = 64.0f; + private boolean _useDistanceAttenuation = false; + + public Point() { + this("point", null, null, null, (FloatBufferData) null); + } + + public Point(final PointType type) { + this(); + _pointType = type; + } + + /** + * Constructor instantiates a new <code>Point</code> object with a given set of data. Any data may be null, except + * the vertex array. If this is null an exception is thrown. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + * @param vertex + * the vertices or points. + * @param normal + * the normals of the points. + * @param color + * the color of the points. + * @param texture + * the texture coordinates of the points. + */ + public Point(final String name, final ReadOnlyVector3[] vertex, final ReadOnlyVector3[] normal, + final ReadOnlyColorRGBA[] color, final ReadOnlyVector2[] texture) { + super(name); + setupData(BufferUtils.createFloatBuffer(vertex), BufferUtils.createFloatBuffer(normal), + BufferUtils.createFloatBuffer(color), FloatBufferDataUtil.makeNew(texture)); + _meshData.setIndexMode(IndexMode.Points); + } + + /** + * Constructor instantiates a new <code>Point</code> object with a given set of data. Any data may be null, except + * the vertex array. If this is null an exception is thrown. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + * @param vertex + * the vertices or points. + * @param normal + * the normals of the points. + * @param color + * the color of the points. + * @param coords + * the texture coordinates of the points. + */ + public Point(final String name, final FloatBuffer vertex, final FloatBuffer normal, final FloatBuffer color, + final FloatBufferData coords) { + super(name); + setupData(vertex, normal, color, coords); + _meshData.setIndexMode(IndexMode.Points); + } + + /** + * Initialize the meshdata object with data. + * + * @param vertices + * @param normals + * @param colors + * @param coords + */ + private void setupData(final FloatBuffer vertices, final FloatBuffer normals, final FloatBuffer colors, + final FloatBufferData coords) { + _meshData.setVertexBuffer(vertices); + _meshData.setNormalBuffer(normals); + _meshData.setColorBuffer(colors); + _meshData.setTextureCoords(coords, 0); + } + + public boolean isPointSprite() { + return _pointType == PointType.PointSprite; + } + + /** + * Default attenuation coefficient is calculated to work best with pointSize = 1. + * + * @param bool + */ + public void enableDistanceAttenuation(final boolean bool) { + _useDistanceAttenuation = bool; + } + + /** + * Distance Attenuation Equation:<br> + * x = distance from the eye<br> + * Derived Size = clamp( pointSize * sqrt( attenuation(x) ) )<br> + * attenuation(x) = 1 / (a + b*x + c*x^2) + * <p> + * Default coefficients used are(they should work best with pointSize=1):<br> + * a = 0, b = 0, c = 0.000004f<br> + * This should give(without taking regards to the max,min pointSize clamping):<br> + * 1. A size of 1 pixel at distance of 500 units.<br> + * Derived Size = 1/(0.000004*500^2) = 1<br> + * 2. A size of 25 pixel at distance of 100 units.<br> + * 3. A size of 2500 at a distance of 10 units.<br> + * + * @see <a href="http://www.opengl.org/registry/specs/ARB/point_parameters.txt">OpenGL specification</a> + * @param a + * constant term in the attenuation equation + * @param b + * linear term in the attenuation equation + * @param c + * quadratic term in the attenuation equation + */ + public void setDistanceAttenuationCoefficients(final float a, final float b, final float c) { + _attenuationCoefficients.put(0, a); + _attenuationCoefficients.put(1, b); + _attenuationCoefficients.put(2, c); + } + + /** + * @return true if points are to be drawn antialiased + */ + public boolean isAntialiased() { + return _antialiased; + } + + /** + * Sets whether the point should be antialiased. May decrease performance. If you want to enabled antialiasing, you + * should also use an alphastate with a source of SourceFunction.SourceAlpha and a destination of + * DB_ONE_MINUS_SRC_ALPHA or DB_ONE. + * + * @param antialiased + * true if the line should be antialiased. + */ + public void setAntialiased(final boolean antialiased) { + _antialiased = antialiased; + } + + public PointType getPointType() { + return _pointType; + } + + public void setPointType(final PointType pointType) { + _pointType = pointType; + } + + /** + * @return the pixel size of each point. + */ + public float getPointSize() { + return _pointSize; + } + + /** + * Sets the pixel width of the point when drawn. Non anti-aliased point sizes are rounded to the nearest whole + * number by opengl. + * + * @param size + * The size to set. + */ + public void setPointSize(final float size) { + _pointSize = size; + } + + /** + * When DistanceAttenuation is enabled, the points maximum size will get clamped to this value. + * + * @param maxSize + */ + public void setMaxPointSize(final float maxSize) { + _maxPointSize = maxSize; + } + + /** + * When DistanceAttenuation is enabled, the points maximum size will get clamped to this value. + * + * @param maxSize + */ + public float getMaxPointSize() { + return _maxPointSize; + } + + /** + * When DistanceAttenuation is enabled, the points minimum size will get clamped to this value. + * + * @param maxSize + */ + public void setMinPointSize(final float minSize) { + _minPointSize = minSize; + } + + /** + * When DistanceAttenuation is enabled, the points minimum size will get clamped to this value. + * + * @param maxSize + */ + public float getMinPointSize() { + return _minPointSize; + } + + /** + * Used with Serialization. Do not call this directly. + * + * @param s + * @throws IOException + * @see java.io.Serializable + */ + private void writeObject(final java.io.ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + } + + /** + * Used with Serialization. Do not call this directly. + * + * @param s + * @throws IOException + * @throws ClassNotFoundException + * @see java.io.Serializable + */ + private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { + s.defaultReadObject(); + } + + @Override + public Point makeCopy(final boolean shareGeometricData) { + final Point pointCopy = (Point) super.makeCopy(shareGeometricData); + pointCopy.setAntialiased(_antialiased); + pointCopy.setDistanceAttenuationCoefficients(_attenuationCoefficients.get(0), _attenuationCoefficients.get(1), + _attenuationCoefficients.get(2)); + pointCopy.setMaxPointSize(_maxPointSize); + pointCopy.setMinPointSize(_minPointSize); + pointCopy.setPointSize(_pointSize); + pointCopy.setPointType(_pointType); + return pointCopy; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_pointSize, "pointSize", 1); + capsule.write(_antialiased, "antialiased", false); + capsule.write(_pointType, "pointType", PointType.Point); + capsule.write(_useDistanceAttenuation, "useDistanceAttenuation", false); + capsule.write(_attenuationCoefficients, "attenuationCoefficients", null); + capsule.write(_minPointSize, "minPointSize", 1); + capsule.write(_maxPointSize, "maxPointSize", 64); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _pointSize = capsule.readFloat("pointSize", 1); + _antialiased = capsule.readBoolean("antialiased", false); + _pointType = capsule.readEnum("pointType", PointType.class, PointType.Point); + _useDistanceAttenuation = capsule.readBoolean("useDistanceAttenuation", false); + final FloatBuffer coef = capsule.readFloatBuffer("attenuationCoefficients", null); + if (coef == null) { + _attenuationCoefficients.clear(); + _attenuationCoefficients.put(new float[] { 0.0f, 0f, 0.000004f, 0f }); + } else { + _attenuationCoefficients.clear(); + _attenuationCoefficients.put(coef); + } + _minPointSize = capsule.readFloat("minPointSize", 1); + _maxPointSize = capsule.readFloat("maxPointSize", 64); + + } + + @Override + public void render(final Renderer renderer) { + renderer.setupPointParameters(_pointSize, isAntialiased(), isPointSprite(), _useDistanceAttenuation, + _attenuationCoefficients, _minPointSize, _maxPointSize); + + super.render(renderer); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java new file mode 100644 index 0000000..cfc8d8b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java @@ -0,0 +1,25 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import com.ardor3d.renderer.Renderer;
+
+/**
+ * Renderable is the interface for objects that can be rendered.
+ */
+public interface Renderable {
+ /**
+ * Render the object using the supplied renderer instance.
+ *
+ * @param renderer
+ */
+ void render(Renderer renderer);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java new file mode 100644 index 0000000..95016bf --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Simple data class storing a buffer of shorts + */ +public class ShortBufferData extends IndexBufferData<ShortBuffer> implements Savable { + + /** + * Instantiates a new ShortBufferData. + */ + public ShortBufferData() {} + + /** + * Instantiates a new ShortBufferData with a buffer of the given size. + */ + public ShortBufferData(final int size) { + this(BufferUtils.createShortBuffer(size)); + } + + /** + * Creates a new ShortBufferData. + * + * @param buffer + * Buffer holding the data. Must not be null. + */ + public ShortBufferData(final ShortBuffer buffer) { + if (buffer == null) { + throw new IllegalArgumentException("Buffer can not be null!"); + } + + _buffer = buffer; + } + + public Class<? extends ShortBufferData> getClassTag() { + return getClass(); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _buffer = capsule.readShortBuffer("buffer", null); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_buffer, "buffer", null); + } + + @Override + public int get() { + return _buffer.get() & 0xFFFF; + } + + @Override + public int get(final int index) { + return _buffer.get(index) & 0xFFFF; + } + + @Override + public ShortBufferData put(final int value) { + if (value < 0 || value >= 65536) { + throw new IllegalArgumentException("Invalid value passed to short buffer: " + value); + } + _buffer.put((short) value); + return this; + } + + @Override + public ShortBufferData put(final int index, final int value) { + if (value < 0 || value >= 65536) { + throw new IllegalArgumentException("Invalid value passed to short buffer: " + value); + } + _buffer.put(index, (short) value); + return this; + } + + @Override + public void put(final IndexBufferData<?> buf) { + if (buf instanceof ShortBufferData) { + _buffer.put((ShortBuffer) buf.getBuffer()); + } else { + while (buf.getBuffer().hasRemaining()) { + put(buf.get()); + } + } + } + + @Override + public int getByteCount() { + return 2; + } + + @Override + public ShortBuffer getBuffer() { + return _buffer; + } + + @Override + public IntBuffer asIntBuffer() { + final ShortBuffer source = getBuffer().duplicate(); + source.rewind(); + final IntBuffer buff = BufferUtils.createIntBufferOnHeap(source.limit()); + for (int i = 0, max = source.limit(); i < max; i++) { + buff.put(source.get() & 0xFFFF); + } + buff.flip(); + return buff; + } + + @Override + public ShortBufferData makeCopy() { + final ShortBufferData copy = new ShortBufferData(); + copy._buffer = BufferUtils.clone(_buffer); + copy._vboAccessMode = _vboAccessMode; + return copy; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java new file mode 100644 index 0000000..14d8da4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java @@ -0,0 +1,1423 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.annotation.SavableFactory; +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Transform; +import com.ardor3d.math.ValidatingTransform; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyQuaternion; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateStack; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.controller.SpatialController; +import com.ardor3d.scenegraph.event.DirtyEventListener; +import com.ardor3d.scenegraph.event.DirtyType; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.Hintable; +import com.ardor3d.scenegraph.hint.SceneHints; +import com.ardor3d.scenegraph.visitor.Visitor; +import com.ardor3d.util.Constants; +import com.ardor3d.util.ReadOnlyTimer; +import com.ardor3d.util.export.CapsuleUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.scenegraph.RenderDelegate; +import com.google.common.collect.MapMaker; + +/** + * Base class for all scenegraph objects. + */ +public abstract class Spatial implements Savable, Hintable { + private static final Logger logger = Logger.getLogger(Spatial.class.getName()); + + /** This spatial's name. */ + protected String _name; + + /** Spatial's transform relative to its parent. */ + protected final Transform _localTransform; + + /** Spatial's absolute transform. */ + protected final Transform _worldTransform; + + /** Spatial's world bounding volume. */ + protected BoundingVolume _worldBound; + + /** Spatial's parent, or null if it has none. */ + protected Node _parent; + + /** ArrayList of controllers for this spatial. */ + protected List<SpatialController<?>> _controllers; + + /** The render states of this spatial. */ + protected EnumMap<RenderState.StateType, RenderState> _renderStateList = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + /** Listener for dirty events. */ + protected DirtyEventListener _listener; + + /** Field for accumulating dirty marks. */ + protected EnumSet<DirtyType> _dirtyMark = EnumSet + .of(DirtyType.Bounding, DirtyType.RenderState, DirtyType.Transform); + + /** Field for user data. Note: If this object is not explicitly of type Savable, it will be ignored during save. */ + protected Object _userData = null; + + /** Keeps track of the current frustum intersection state of this Spatial. */ + protected Camera.FrustumIntersect _frustumIntersects = Camera.FrustumIntersect.Intersects; + + /** The hints for Ardor3D's use when evaluating and rendering this spatial. */ + protected SceneHints _sceneHints; + + /** The render delegates to use for this Spatial, mapped by glContext reference. */ + protected transient Map<Object, RenderDelegate> _delegateMap = null; + + public transient double _queueDistance = Double.NEGATIVE_INFINITY; + + /** The default delegate reference to use if none provided. */ + private static final Object defaultDelegateRef = new Object(); + + protected static final EnumSet<DirtyType> ON_DIRTY_TRANSFORM = EnumSet.of(DirtyType.Bounding, DirtyType.Transform); + protected static final EnumSet<DirtyType> ON_DIRTY_RENDERSTATE = EnumSet.of(DirtyType.RenderState); + protected static final EnumSet<DirtyType> ON_DIRTY_BOUNDING = EnumSet.of(DirtyType.Bounding); + protected static final EnumSet<DirtyType> ON_DIRTY_ATTACHED = EnumSet.of(DirtyType.Transform, + DirtyType.RenderState, DirtyType.Bounding); + + /** + * Constructs a new Spatial. Initializes the transform fields. + */ + public Spatial() { + _localTransform = Constants.useValidatingTransform ? new ValidatingTransform() : new Transform(); + _worldTransform = Constants.useValidatingTransform ? new ValidatingTransform() : new Transform(); + _sceneHints = new SceneHints(this); + } + + /** + * Constructs a new <code>Spatial</code> with a given name. + * + * @param name + * the name of the spatial. This is required for identification purposes. + */ + public Spatial(final String name) { + this(); + _name = name; + } + + /** + * Returns the name of this spatial. + * + * @return This spatial's name. + */ + public String getName() { + return _name; + } + + /** + * Sets the name of this Spatial. + * + * @param name + * new name + */ + public void setName(final String name) { + _name = name; + } + + /** + * Sets the render delegate. + * + * @param delegate + * the new delegate, or null for default behavior + * @param glContextRef + * if null, the delegate is set as the default render delegate for this spatial. Otherwise, the delegate + * is used when this Spatial is rendered in a RenderContext tied to the given glContextRef. + */ + public void setRenderDelegate(final RenderDelegate delegate, final Object glContextRef) { + if (_delegateMap == null) { + if (delegate == null) { + return; + } else { + _delegateMap = new MapMaker().weakKeys().makeMap(); + } + } + if (delegate != null) { + if (glContextRef == null) { + _delegateMap.put(defaultDelegateRef, delegate); + } else { + _delegateMap.put(glContextRef, delegate); + } + } else { + if (glContextRef == null) { + _delegateMap.remove(defaultDelegateRef); + } else { + _delegateMap.remove(glContextRef); + } + if (_delegateMap.isEmpty()) { + _delegateMap = null; + } + } + } + + /** + * Gets the render delegate. + * + * @param glContextRef + * if null, retrieve the default render delegate for this spatial. Otherwise, retrieve the delegate used + * when this Spatial is rendered in a RenderContext tied to the given glContextRef. + * @return delegate as described. + */ + public RenderDelegate getRenderDelegate(final Object glContextRef) { + if (_delegateMap == null) { + return null; + } + if (glContextRef == null) { + return _delegateMap.get(defaultDelegateRef); + } else { + return _delegateMap.get(glContextRef); + } + } + + /** + * <code>getParent</code> retrieve's this node's parent. If the parent is null this is the root node. + * + * @return the parent of this node. + */ + public Node getParent() { + return _parent; + } + + /** + * Called by {@link Node#attachChild(Spatial)} and {@link Node#detachChild(Spatial)} - don't call directly. + * <code>setParent</code> sets the parent of this node. + * + * @param parent + * the parent of this node. + */ + protected void setParent(final Node parent) { + _parent = parent; + } + + /** + * <code>removeFromParent</code> removes this Spatial from it's parent. + * + * @return true if it has a parent and performed the remove. + */ + public boolean removeFromParent() { + if (_parent != null) { + _parent.detachChild(this); + return true; + } + return false; + } + + /** + * determines if the provided Node is the parent, or parent's parent, etc. of this Spatial. + * + * @param ancestor + * the ancestor object to look for. + * @return true if the ancestor is found, false otherwise. + */ + public boolean hasAncestor(final Node ancestor) { + if (_parent == null) { + return false; + } else if (_parent.equals(ancestor)) { + return true; + } else { + return _parent.hasAncestor(ancestor); + } + } + + /** + * @see Hintable#getParentHintable() + */ + public Hintable getParentHintable() { + return _parent; + } + + /** + * Gets the scene hints. + * + * @return the scene hints set on this Spatial + */ + public SceneHints getSceneHints() { + return _sceneHints; + } + + /** + * Returns the listener for dirty events on this node, if set. + * + * @return the listener + */ + public DirtyEventListener getListener() { + return _listener; + } + + /** + * Sets the listener for dirty events on this node. + * + * @param listener + * listener to use. + */ + public void setListener(final DirtyEventListener listener) { + _listener = listener; + } + + /** + * Mark this node as dirty. Can be marked as Transform, Bounding, Attached, Detached, Destroyed or RenderState + * + * @param dirtyType + * the dirty type + */ + public void markDirty(final DirtyType dirtyType) { + markDirty(this, dirtyType); + } + + /** + * Mark this node as dirty. Can be marked as Transform, Bounding, Attached, Detached, Destroyed or RenderState + * + * @param caller + * the spatial where the marking was initiated + * @param dirtyType + * the dirty type + */ + protected void markDirty(final Spatial caller, final DirtyType dirtyType) { + switch (dirtyType) { + case Transform: + propagateDirtyDown(ON_DIRTY_TRANSFORM); + if (_parent != null) { + _parent.propagateDirtyUp(ON_DIRTY_BOUNDING); + } + break; + case RenderState: + propagateDirtyDown(ON_DIRTY_RENDERSTATE); + break; + case Bounding: + // not just _parent here, on purpose + propagateDirtyUp(ON_DIRTY_BOUNDING); + break; + case Attached: + propagateDirtyDown(ON_DIRTY_ATTACHED); + if (_parent != null) { + _parent.propagateDirtyUp(ON_DIRTY_BOUNDING); + } + break; + case Detached: + case Destroyed: + if (_parent != null) { + _parent.propagateDirtyUp(ON_DIRTY_BOUNDING); + } + break; + default: + break; + } + + propageEventUp(caller, dirtyType, true); + } + + /** + * Test if this spatial is marked as dirty in respect to the supplied DirtyType. + * + * @param dirtyType + * dirty type to test against + * @return true if spatial marked dirty against the supplied dirty type + */ + public boolean isDirty(final DirtyType dirtyType) { + return _dirtyMark.contains(dirtyType); + } + + /** + * Clears the dirty flag set at this spatial for the supplied dirty type. + * + * @param dirtyType + * dirty type to clear flag for + */ + public void clearDirty(final DirtyType dirtyType) { + clearDirty(this, dirtyType); + } + + /** + * Clears the dirty flag set at this spatial for the supplied dirty type. + * + * @param caller + * the spatial where the clearing was initiated + * @param dirtyType + * dirty type to clear flag for + */ + public void clearDirty(final Spatial caller, final DirtyType dirtyType) { + _dirtyMark.remove(dirtyType); + + propageEventUp(caller, dirtyType, false); + } + + /** + * Propagate the dirty mark up the tree hierarchy. + * + * @param dirtyTypes + * the dirty types + */ + protected void propagateDirtyUp(final EnumSet<DirtyType> dirtyTypes) { + _dirtyMark.addAll(dirtyTypes); + + if (_parent != null) { + _parent.propagateDirtyUp(dirtyTypes); + } + } + + /** + * Propagate the dirty mark down the tree hierarchy. + * + * @param dirtyTypes + * the dirty types + */ + protected void propagateDirtyDown(final EnumSet<DirtyType> dirtyTypes) { + _dirtyMark.addAll(dirtyTypes); + } + + /** + * Propagate the dirty event up the hierarchy. If a listener is found on the spatial the event is fired and the + * propagation is stopped. + * + * @param spatial + * the spatial + * @param dirtyType + * the dirty type + * @param dirty + * if true, propogate a dirty event, else propogate a clean event + */ + protected void propageEventUp(final Spatial spatial, final DirtyType dirtyType, final boolean dirty) { + boolean consumed = false; + if (_listener != null) { + if (dirty) { + consumed = _listener.spatialDirty(spatial, dirtyType); + } else { + consumed = _listener.spatialClean(spatial, dirtyType); + } + } + + if (!consumed && _parent != null) { + _parent.propageEventUp(spatial, dirtyType, dirty); + } + } + + /** + * Gets the local rotation matrix. + * + * @return the rotation + */ + public ReadOnlyMatrix3 getRotation() { + return _localTransform.getMatrix(); + } + + /** + * Gets the local scale vector. + * + * @return the scale + */ + public ReadOnlyVector3 getScale() { + return _localTransform.getScale(); + } + + /** + * Gets the local translation vector. + * + * @return the translation + */ + public ReadOnlyVector3 getTranslation() { + return _localTransform.getTranslation(); + } + + /** + * Gets the local transform. + * + * @return the transform + */ + public ReadOnlyTransform getTransform() { + return _localTransform; + } + + /** + * Sets the local transform. + * + * @param transform + * the new transform + */ + public void setTransform(final ReadOnlyTransform transform) { + _localTransform.set(transform); + markDirty(DirtyType.Transform); + } + + /** + * Sets the world rotation matrix. + * + * @param rotation + * the new world rotation + */ + public void setWorldRotation(final ReadOnlyMatrix3 rotation) { + _worldTransform.setRotation(rotation); + } + + /** + * Sets the world rotation quaternion. + * + * @param rotation + * the new world rotation + */ + public void setWorldRotation(final ReadOnlyQuaternion rotation) { + _worldTransform.setRotation(rotation); + } + + /** + * Sets the world scale. + * + * @param scale + * the new world scale vector + */ + public void setWorldScale(final ReadOnlyVector3 scale) { + _worldTransform.setScale(scale); + } + + /** + * Sets the world scale. + * + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param z + * the z coordinate + */ + public void setWorldScale(final double x, final double y, final double z) { + _worldTransform.setScale(x, y, z); + } + + /** + * Sets the world scale. + * + * @param scale + * the new world scale + */ + public void setWorldScale(final double scale) { + _worldTransform.setScale(scale); + } + + /** + * Sets the world translation vector. + * + * @param translation + * the new world translation + */ + public void setWorldTranslation(final ReadOnlyVector3 translation) { + _worldTransform.setTranslation(translation); + } + + /** + * Sets the world translation. + * + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param z + * the z coordinate + */ + public void setWorldTranslation(final double x, final double y, final double z) { + _worldTransform.setTranslation(x, y, z); + } + + /** + * Sets the world transform. + * + * @param transform + * the new world transform + */ + public void setWorldTransform(final ReadOnlyTransform transform) { + _worldTransform.set(transform); + } + + /** + * Sets the rotation of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param rotation + * the new rotation of this spatial + * @see Transform#setRotation(Matrix3) + */ + public void setRotation(final ReadOnlyMatrix3 rotation) { + _localTransform.setRotation(rotation); + markDirty(DirtyType.Transform); + } + + /** + * Sets the rotation of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param rotation + * the new rotation of this spatial + * @see Transform#setRotation(Quaternion) + */ + public void setRotation(final ReadOnlyQuaternion rotation) { + _localTransform.setRotation(rotation); + markDirty(DirtyType.Transform); + } + + /** + * <code>setScale</code> sets the scale of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param scale + * the new scale of this spatial + */ + public void setScale(final ReadOnlyVector3 scale) { + _localTransform.setScale(scale); + markDirty(DirtyType.Transform); + } + + /** + * <code>setScale</code> sets the scale of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param scale + * the new scale of this spatial + */ + public void setScale(final double scale) { + _localTransform.setScale(scale); + markDirty(DirtyType.Transform); + } + + /** + * sets the scale of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param x + * the x scale factor + * @param y + * the y scale factor + * @param z + * the z scale factor + */ + public void setScale(final double x, final double y, final double z) { + _localTransform.setScale(x, y, z); + markDirty(DirtyType.Transform); + } + + /** + * <code>setTranslation</code> sets the translation of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param translation + * the new translation of this spatial + */ + public void setTranslation(final ReadOnlyVector3 translation) { + _localTransform.setTranslation(translation); + markDirty(DirtyType.Transform); + } + + /** + * sets the translation of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param z + * the z coordinate + */ + public void setTranslation(final double x, final double y, final double z) { + _localTransform.setTranslation(x, y, z); + markDirty(DirtyType.Transform); + } + + /** + * <code>addTranslation</code> adds the given translation to the translation of this spatial. This marks the spatial + * as DirtyType.Transform. + * + * @param translation + * the translation vector + */ + public void addTranslation(final ReadOnlyVector3 translation) { + addTranslation(translation.getX(), translation.getY(), translation.getZ()); + } + + /** + * adds to the current translation of this spatial. This marks the spatial as DirtyType.Transform. + * + * @param x + * the x amount + * @param y + * the y amount + * @param z + * the z amount + */ + public void addTranslation(final double x, final double y, final double z) { + _localTransform.translate(x, y, z); + markDirty(DirtyType.Transform); + } + + /** + * Gets the world rotation matrix. + * + * @return the world rotation + */ + public ReadOnlyMatrix3 getWorldRotation() { + return _worldTransform.getMatrix(); + } + + /** + * Gets the world scale vector. + * + * @return the world scale + */ + public ReadOnlyVector3 getWorldScale() { + return _worldTransform.getScale(); + } + + /** + * Gets the world translation vector. + * + * @return the world translation + */ + public ReadOnlyVector3 getWorldTranslation() { + return _worldTransform.getTranslation(); + } + + /** + * Gets the world transform. + * + * @return the world transform + */ + public ReadOnlyTransform getWorldTransform() { + return _worldTransform; + } + + /** + * <code>getWorldBound</code> retrieves the world bound at this level. + * + * @return the world bound at this level. + */ + public BoundingVolume getWorldBound() { + return _worldBound; + } + + /** + * <code>onDraw</code> checks the spatial with the camera to see if it should be culled, if not, the node's draw + * method is called. + * <p> + * This method is called by the renderer. Usually it should not be called directly. + * + * @param r + * the renderer used for display. + */ + public void onDraw(final Renderer r) { + final CullHint cm = _sceneHints.getCullHint(); + if (cm == CullHint.Always) { + setLastFrustumIntersection(Camera.FrustumIntersect.Outside); + return; + } else if (cm == CullHint.Never) { + setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); + draw(r); + return; + } + + final Camera camera = Camera.getCurrentCamera(); + final int state = camera.getPlaneState(); + + // check to see if we can cull this node + _frustumIntersects = ((_parent != null && _parent.getWorldBound() != null) ? _parent._frustumIntersects + : Camera.FrustumIntersect.Intersects); + + if (cm == CullHint.Dynamic && _frustumIntersects == Camera.FrustumIntersect.Intersects) { + _frustumIntersects = camera.contains(_worldBound); + } + + if (_frustumIntersects != Camera.FrustumIntersect.Outside) { + draw(r); + } + camera.setPlaneState(state); + } + + /** + * <code>draw</code> abstract method that handles drawing data to the renderer if it is geometry and passing the + * call to it's children if it is a node. + * + * @param renderer + * the renderer used for display. + */ + public abstract void draw(final Renderer renderer); + + /** + * Grab the render delegate for this spatial based on the currently set RenderContext. + * + * @return the delegate or null if a delegate was not found. + */ + protected RenderDelegate getCurrentRenderDelegate() { + // short circuit... ignore if no delegates at all. + if (_delegateMap == null || _delegateMap.isEmpty()) { + return null; + } + + // otherwise... grab our current context + final RenderContext context = ContextManager.getCurrentContext(); + + // get the delegate for this context + RenderDelegate delegate = getRenderDelegate(context.getGlContextRep()); + // if none, check for a default delegate. + if (delegate == null) { + delegate = getRenderDelegate(null); + } + + return delegate; + } + + /** + * Update geometric state. + * + * @param time + * The time in seconds between the last two consecutive frames (time per frame). See + * {@link ReadOnlyTimer#getTimePerFrame()} + * @see #updateGeometricState(double, boolean) + */ + public void updateGeometricState(final double time) { + updateGeometricState(time, true); + } + + /** + * <code>updateGeometricState</code> updates all the geometry information for the node. + * + * @param time + * The time in seconds between the last two consecutive frames (time per frame). See + * {@link ReadOnlyTimer#getTimePerFrame()} + * @param initiator + * true if this node started the update process. + */ + public void updateGeometricState(final double time, final boolean initiator) { + updateControllers(time); + + if (_dirtyMark.isEmpty()) { + updateChildren(time); + } else { + if (isDirty(DirtyType.Transform)) { + updateWorldTransform(false); + } + + if (isDirty(DirtyType.RenderState)) { + updateWorldRenderStates(false); + clearDirty(DirtyType.RenderState); + } + + updateChildren(time); + + if (isDirty(DirtyType.Bounding)) { + updateWorldBound(false); + if (initiator) { + propagateBoundToRoot(); + } + } + } + } + + /** + * Override to allow objects like Node to update their children. + * + * @param time + * The time in seconds between the last two consecutive frames (time per frame). See + * {@link ReadOnlyTimer#getTimePerFrame()} + */ + protected void updateChildren(final double time) {} + + /** + * Update all controllers set on this spatial. + * + * @param time + * The time in seconds between the last two consecutive frames (time per frame). See + * {@link ReadOnlyTimer#getTimePerFrame()} + */ + @SuppressWarnings("unchecked") + public void updateControllers(final double time) { + if (_controllers != null) { + for (int i = 0, gSize = _controllers.size(); i < gSize; i++) { + try { + final SpatialController<Spatial> controller = (SpatialController<Spatial>) _controllers.get(i); + if (controller != null) { + controller.update(time, this); + } + } catch (final IndexOutOfBoundsException e) { + // a controller was removed in SpatialController.update (note: this + // may skip one controller) + break; + } + } + } + } + + /** + * Updates the worldTransform. + * + * @param recurse + * usually false when updating the tree. Set to true when you just want to update the world transforms + * for a branch without updating geometric state. + */ + public void updateWorldTransform(final boolean recurse) { + if (_parent != null) { + _parent._worldTransform.multiply(_localTransform, _worldTransform); + } else { + _worldTransform.set(_localTransform); + } + clearDirty(DirtyType.Transform); + } + + /** + * Convert a vector (in) from this spatial's local coordinate space to world coordinate space. + * + * @param in + * vector to read from + * @param store + * where to write the result (null to create a new vector, may be same as in) + * @return the result (store) + */ + public Vector3 localToWorld(final ReadOnlyVector3 in, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + + return _worldTransform.applyForward(in, store); + } + + /** + * Convert a vector (in) from world coordinate space to this spatial's local coordinate space. + * + * @param in + * vector to read from + * @param store + * where to write the result (null to create a new vector, may be same as in) + * @return the result (store) + */ + public Vector3 worldToLocal(final ReadOnlyVector3 in, Vector3 store) { + if (store == null) { + store = new Vector3(); + } + + return _worldTransform.applyInverse(in, store); + } + + /** + * Updates the render state values of this Spatial and and children it has. Should be called whenever render states + * change. + * + * @param recurse + * true to recurse down the scenegraph tree + */ + public void updateWorldRenderStates(final boolean recurse) { + updateWorldRenderStates(recurse, null); + } + + /** + * Called internally. Updates the render states of this Spatial. The stack contains parent render states. + * + * @param recurse + * true to recurse down the scenegraph tree + * @param stateStack + * The parent render states, or null if we are starting at this point in the scenegraph. + */ + protected void updateWorldRenderStates(final boolean recurse, final RenderState.StateStack stateStack) { + if (stateStack == null) { + // grab all states from root to here. + final RenderState.StateStack stack = RenderState.StateStack.fetchTempInstance(); + propagateStatesFromRoot(stack); + + applyWorldRenderStates(recurse, stack); + + RenderState.StateStack.releaseTempInstance(stack); + } else { + for (final RenderState state : _renderStateList.values()) { + stateStack.push(state); + } + + applyWorldRenderStates(recurse, stateStack); + + for (final RenderState state : _renderStateList.values()) { + stateStack.pop(state); + } + } + } + + /** + * The method actually implements how the render states are applied to this spatial and (if recurse is true) any + * children it may have. By default, this function does nothing. + * + * @param recurse + * true to recurse down the scenegraph tree + * @param stack + * The stack for each state + */ + protected void applyWorldRenderStates(final boolean recurse, final RenderState.StateStack stack) {} + + /** + * Sort the ligts on this spatial. + */ + public void sortLights() {} + + /** + * Retrieves the complete renderstate list. + * + * @return the list of renderstates + */ + public EnumMap<StateType, RenderState> getLocalRenderStates() { + return _renderStateList; + } + + /** + * <code>setRenderState</code> sets a render state for this node. Note, there can only be one render state per type + * per node. That is, there can only be a single BlendState a single TextureState, etc. If there is already a render + * state for a type set the old render state will be returned. Otherwise, null is returned. + * + * @param rs + * the render state to add. + * @return the old render state. + */ + public RenderState setRenderState(final RenderState rs) { + if (rs == null) { + return null; + } + + final RenderState.StateType type = rs.getType(); + final RenderState oldState = _renderStateList.get(type); + _renderStateList.put(type, rs); + + markDirty(DirtyType.RenderState); + + return oldState; + } + + /** + * Returns the requested RenderState that this Spatial currently has set or null if none is set. + * + * @param type + * the state type to retrieve + * @return a render state at the given position or null + */ + public RenderState getLocalRenderState(final RenderState.StateType type) { + return _renderStateList.get(type); + } + + /** + * Clears a given render state index by setting it to null. + * + * @param type + * The type of RenderState to clear + */ + public void clearRenderState(final RenderState.StateType type) { + _renderStateList.remove(type); + markDirty(DirtyType.RenderState); + } + + /** + * Called during updateRenderState(Stack[]), this function goes up the scene graph tree until the parent is null and + * pushes RenderStates onto the states Stack array. + * + * @param stack + * The stack to push any parent states onto. + */ + public void propagateStatesFromRoot(final StateStack stack) { + // traverse to root to allow downward state propagation + if (_parent != null) { + _parent.propagateStatesFromRoot(stack); + } + + // push states onto current render state stack + for (final RenderState state : _renderStateList.values()) { + stack.push(state); + } + } + + /** + * updates the bounding volume of the world. Abstract, geometry transforms the bound while node merges the + * children's bound. In most cases, users will want to call updateModelBound() and let this function be called + * automatically during updateGeometricState(). + * + * @param recurse + * true to recurse down the scenegraph tree + */ + public abstract void updateWorldBound(boolean recurse); + + /** + * passes the new world bound up the tree to the root. + */ + public void propagateBoundToRoot() { + if (_parent != null) { + _parent.updateWorldBound(false); + _parent.propagateBoundToRoot(); + } + } + + /** + * Gets the Spatial specific user data. + * + * @return the user data + */ + public Object getUserData() { + return _userData; + } + + /** + * Sets the Spatial specific user data. + * + * @param userData + * Some Spatial specific user data. Note: If this object is not explicitly of type Savable, it will be + * ignored during read/write. + */ + public void setUserData(final Object userData) { + _userData = userData; + } + + /** + * Adds a SpatialController to this Spatial's list of controllers. + * + * @param controller + * The SpatialController to add + * @see com.ardor3d.scenegraph.controller.SpatialController + */ + public void addController(final SpatialController<?> controller) { + if (_controllers == null) { + _controllers = new ArrayList<SpatialController<?>>(1); + } + _controllers.add(controller); + } + + /** + * Removes a SpatialController from this Spatial's list of controllers, if it exist. + * + * @param controller + * The SpatialController to remove + * @return True if the SpatialController was in the list to remove. + * @see com.ardor3d.scenegraph.controller.SpatialController + */ + public boolean removeController(final SpatialController<?> controller) { + if (_controllers == null) { + return false; + } + return _controllers.remove(controller); + } + + /** + * Removes a SpatialController from this Spatial's list of controllers by index. + * + * @param index + * The index of the controller to remove + * @return The SpatialController removed or null if nothing was removed. + * @see com.ardor3d.scenegraph.controller.SpatialController + */ + public SpatialController<?> removeController(final int index) { + if (_controllers == null) { + return null; + } + return _controllers.remove(index); + } + + /** + * Removes all Controllers from this Spatial's list of controllers. + * + * @see com.ardor3d.scenegraph.controller.SpatialController + */ + public void clearControllers() { + if (_controllers != null) { + _controllers.clear(); + } + } + + /** + * Returns the controller in this list of controllers at index i. + * + * @param i + * The index to get a controller from. + * @return The controller at index i. + * @see com.ardor3d.scenegraph.controller.SpatialController + */ + public SpatialController<?> getController(final int i) { + if (_controllers == null) { + _controllers = new ArrayList<SpatialController<?>>(1); + } + return _controllers.get(i); + } + + /** + * Returns the ArrayList that contains this spatial's SpatialControllers. + * + * @return This spatial's _controllers. + */ + public List<SpatialController<?>> getControllers() { + if (_controllers == null) { + _controllers = new ArrayList<SpatialController<?>>(1); + } + return _controllers; + } + + /** + * Gets the controller count. + * + * @return the number of controllers set on this Spatial. + */ + public int getControllerCount() { + if (_controllers == null) { + return 0; + } + return _controllers.size(); + } + + /** + * Returns this spatial's last frustum intersection result. This int is set when a check is made to determine if the + * bounds of the object fall inside a camera's frustum. If a parent is found to fall outside the frustum, the value + * for this spatial will not be updated. + * + * @return The spatial's last frustum intersection result. + */ + public Camera.FrustumIntersect getLocalLastFrustumIntersection() { + return _frustumIntersects; + } + + /** + * Tries to find the most accurate last frustum intersection for this spatial by checking the parent for possible + * Outside value. + * + * @return Outside, if this, or any ancestor was Outside, otherwise the local intersect value. + */ + public Camera.FrustumIntersect getLastFrustumIntersection() { + if (_parent != null && _frustumIntersects != Camera.FrustumIntersect.Outside) { + final Camera.FrustumIntersect parentIntersect = _parent.getLastFrustumIntersection(); + if (parentIntersect == Camera.FrustumIntersect.Outside) { + return Camera.FrustumIntersect.Outside; + } + } + return _frustumIntersects; + } + + /** + * Overrides the last intersection result. This is useful for operations that want to start rendering at the middle + * of a scene tree and don't want the parent of that node to influence culling. (See texture renderer code for + * example.) + * + * @param frustumIntersects + * the new frustum intersection value + */ + public void setLastFrustumIntersection(final Camera.FrustumIntersect frustumIntersects) { + _frustumIntersects = frustumIntersects; + } + + /** + * Execute the given Visitor on this Spatial, and any Spatials managed by this Spatial as appropriate. + * + * @param visitor + * the Visitor object to use. + * @param preexecute + * if true, we will visit <i>this</i> Spatial before any Spatials we manage (such as children of a Node.) + * If false, we will visit them first, then ourselves. + */ + public void acceptVisitor(final Visitor visitor, final boolean preexecute) { + visitor.visit(this); + } + + /** + * Returns the Spatial's name followed by the class of the spatial <br> + * Example: "MyNode (com.ardor3d.scene.Spatial) + * + * @return Spatial's name followed by the class of the Spatial + */ + @Override + public String toString() { + return _name + " (" + this.getClass().getName() + ')'; + } + + /** + * Create a copy of this spatial. + * + * @param shareGeometricData + * if true, reuse any data fields describing the geometric shape of the spatial, as applicable. + * @return the copy as described. + */ + public Spatial makeCopy(final boolean shareGeometricData) { + final Spatial spat = duplicate(); + + // copy basic spatial info + spat.setName(getName()); + spat.getSceneHints().set(_sceneHints); + spat.setTransform(_localTransform); + + // copy local render states + for (final StateType type : _renderStateList.keySet()) { + final RenderState state = _renderStateList.get(type); + if (state != null) { + spat.setRenderState(state); + } + } + + // copy controllers + if (_controllers != null) { + for (final SpatialController<?> sc : _controllers) { + spat.addController(sc); + } + } + + return spat; + } + + private Spatial duplicate() { + Spatial spat = null; + final Class<? extends Spatial> clazz = getClass(); + try { + final SavableFactory ann = clazz.getAnnotation(SavableFactory.class); + if (ann == null) { + spat = clazz.newInstance(); + } else { + spat = (Spatial) clazz.getMethod(ann.factoryMethod(), (Class<?>[]) null).invoke(null, (Object[]) null); + } + } catch (final InstantiationException e) { + logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e); + throw new RuntimeException(e); + } catch (final IllegalAccessException e) { + logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e); + throw new RuntimeException(e); + } catch (final NoSuchMethodException e) { + logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e); + throw new RuntimeException(e); + } catch (final IllegalArgumentException e) { + logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e); + throw new RuntimeException(e); + } catch (final SecurityException e) { + logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e); + throw new RuntimeException(e); + } catch (final InvocationTargetException e) { + logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e); + throw new RuntimeException(e); + } + return spat; + } + + /** + * Creates and returns a new instance of this spatial. Used for instanced rendering. All instances visible on the + * screen will be drawn in one draw call. The new instance will share all data (meshData and renderStates) with the + * current mesh and all other instances created from this spatial. + * + * @return an instanced copy of this node + */ + public Spatial makeInstanced() { + + final Spatial spat = duplicate(); + + // copy basic spatial info + spat.setName(getName()); + spat._sceneHints = _sceneHints; + spat.setTransform(_localTransform); + + // copy local render states + spat._renderStateList = _renderStateList; + + // copy controllers + if (_controllers != null) { + for (final SpatialController<?> sc : _controllers) { + spat.addController(sc); + } + } + + return spat; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + /** + * @see Savable#getClassTag() + */ + public Class<? extends Spatial> getClassTag() { + return this.getClass(); + } + + /** + * @param capsule + * the input capsule + * @throws IOException + * Signals that an I/O exception has occurred. + * @see Savable#read(InputCapsule) + */ + public void read(final InputCapsule capsule) throws IOException { + _name = capsule.readString("name", null); + + final RenderState[] states = CapsuleUtils.asArray(capsule.readSavableArray("renderStateList", null), + RenderState.class); + _renderStateList.clear(); + if (states != null) { + for (final RenderState state : states) { + _renderStateList.put(state.getType(), state); + } + } + + _localTransform.set((Transform) capsule.readSavable("localTransform", new Transform(Transform.IDENTITY))); + _worldTransform.set((Transform) capsule.readSavable("worldTransform", new Transform(Transform.IDENTITY))); + + final Savable userData = capsule.readSavable("userData", null); + // only override set userdata if we have something in the capsule. + if (userData != null) { + _userData = userData; + } + + final List<Savable> list = capsule.readSavableList("controllers", null); + if (list != null) { + for (final Savable s : list) { + if (s instanceof SpatialController<?>) { + addController((SpatialController<?>) s); + } + } + } + } + + /** + * @param capsule + * the capsule + * @throws IOException + * Signals that an I/O exception has occurred. + * @see Savable#write(OutputCapsule) + */ + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_name, "name", null); + + capsule.write(_renderStateList.values().toArray(new RenderState[0]), "renderStateList", null); + + capsule.write(_localTransform, "localTransform", new Transform(Transform.IDENTITY)); + capsule.write(_worldTransform, "worldTransform", new Transform(Transform.IDENTITY)); + + if (_userData instanceof Savable) { + capsule.write((Savable) _userData, "userData", null); + } + + if (_controllers != null) { + final List<Savable> list = new ArrayList<Savable>(); + for (final SpatialController<?> sc : _controllers) { + if (sc instanceof Savable) { + list.add((Savable) sc); + } + } + capsule.writeSavableList(list, "controllers", null); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java new file mode 100644 index 0000000..909a004 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.controller; + +import java.io.IOException; +import java.io.Serializable; +import java.util.HashMap; + +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * <code>ComplexSpatialController</code> provides a base class for creation of controllers to modify nodes and render + * states over time. The base controller provides a repeat type, min and max time, as well as speed. Subclasses of this + * will provide the update method that takes the time between the last call and the current one and modifies an object + * in a application specific way. + */ +public abstract class ComplexSpatialController<T extends Spatial> implements SpatialController<T>, Serializable, + Savable { + + private static final long serialVersionUID = 1; + + public enum RepeatType { + /** + * A clamped repeat type signals that the controller should look like its final state when it's done <br> + * Example: 0 1 5 8 9 10 10 10 10 10 10 10 10 10 10 10... + */ + CLAMP, + + /** + * A wrapped repeat type signals that the controller should start back at the begining when it's final state is + * reached <br> + * Example: 0 1 5 8 9 10 0 1 5 8 9 10 0 1 5 .... + */ + WRAP, + + /** + * A cycled repeat type signals that the controller should cycle it's states forwards and backwards <br> + * Example: 0 1 5 8 9 10 9 8 5 1 0 1 5 8 9 10 9 .... + */ + CYCLE; + } + + /** + * Defines how this controller should repeat itself. Default is {@link RepeatType#CLAMP}. + */ + private RepeatType _repeatType = RepeatType.CLAMP; + + /** + * The controller's minimum cycle time + */ + private double _minTime; + + /** + * The controller's maximum cycle time + */ + private double _maxTime; + + /** + * The 'speed' of this Controller. Generically speaking, less than 1 is slower, more than 1 is faster, and 1 + * represents the base speed + */ + private double _speed = 1; + + /** + * True if this controller is active, false otherwise + */ + private boolean _active = true; + + /** + * @return The speed of this controller. Speed is 1 by default. + */ + public double getSpeed() { + return _speed; + } + + /** + * Sets the speed of this controller + * + * @param speed + * The new speed + */ + public void setSpeed(final double speed) { + _speed = speed; + } + + /** + * Returns the current maximum time for this controller. + * + * @return This controller's maximum time. + */ + public double getMaxTime() { + return _maxTime; + } + + /** + * Sets the maximum time for this controller + * + * @param maxTime + * The new maximum time + */ + public void setMaxTime(final double maxTime) { + _maxTime = maxTime; + } + + /** + * Returns the current minimum time of this controller + * + * @return This controller's minimum time + */ + public double getMinTime() { + return _minTime; + } + + /** + * Sets the minimum time of this controller + * + * @param minTime + * The new minimum time. + */ + public void setMinTime(final double minTime) { + _minTime = minTime; + } + + /** + * Returns the current repeat type of this controller. + * + * @return The current repeat type + */ + public RepeatType getRepeatType() { + return _repeatType; + } + + /** + * Sets the repeat type of this controller. The default is {@link RepeatType#CLAMP}. + * + * @param repeatType + * The new repeat type, can not be <code>null</code>. + */ + public void setRepeatType(final RepeatType repeatType) { + if (null == repeatType) { + throw new IllegalArgumentException("repeatType can not be null!"); + } + + _repeatType = repeatType; + } + + /** + * Sets the active flag of this controller. Note: updates on controllers are still called even if this flag is set + * to false. It is the responsibility of the extending class to check isActive if it wishes to be turn-off-able. + * + * @param active + * The new active state. + */ + public void setActive(final boolean active) { + _active = active; + } + + /** + * Returns if this Controller is active or not. + * + * @return True if this controller is set to active, false if not. + */ + public boolean isActive() { + return _active; + } + + /** + * @return <code>true</code> if the {@link #getRepeatType() repeat type} is {@link RepeatType#CLAMP clamp}, + * <code>false</code> otherwise. + */ + public boolean isRepeatTypeClamp() { + return RepeatType.CLAMP.equals(getRepeatType()); + } + + /** + * @return <code>true</code> if the {@link #getRepeatType() repeat type} is {@link RepeatType#WRAP wrap}, + * <code>false</code> otherwise. + */ + public boolean isRepeatTypeWrap() { + return RepeatType.WRAP.equals(getRepeatType()); + } + + /** + * @return <code>true</code> if the {@link #getRepeatType() repeat type} is {@link RepeatType#CYCLE cycle}, + * <code>false</code> otherwise. + */ + public boolean isRepeatTypeCycle() { + return RepeatType.CYCLE.equals(getRepeatType()); + } + + public abstract void update(double time, T caller); + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_repeatType, "repeatType", RepeatType.CLAMP); + capsule.write(_minTime, "minTime", 0); + capsule.write(_maxTime, "maxTime", 0); + capsule.write(_speed, "speed", 1); + capsule.write(_active, "active", true); + } + + public void read(final InputCapsule capsule) throws IOException { + _repeatType = capsule.readEnum("repeatType", RepeatType.class, RepeatType.CLAMP); + _minTime = capsule.readDouble("minTime", 0); + _maxTime = capsule.readDouble("maxTime", 0); + _speed = capsule.readDouble("speed", 1); + _active = capsule.readBoolean("active", true); + } + + @SuppressWarnings("rawtypes") + public Class<? extends ComplexSpatialController> getClassTag() { + return this.getClass(); + } + + public void getControllerValues(final HashMap<String, Object> store) { + + } + + public void setControllerValues(final HashMap<String, Object> values) { + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java new file mode 100644 index 0000000..7a3881e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.controller; + +import com.ardor3d.scenegraph.Spatial; + +public interface SpatialController<T extends Spatial> { + + /** + * @param time + * The time in seconds between the last call to update and the current one + * @param caller + * The spatial currently executing this controller. + */ + public void update(double time, T caller); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java new file mode 100644 index 0000000..cd04c05 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java @@ -0,0 +1,286 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.controller.interpolation; + +import java.util.logging.Logger; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.controller.ComplexSpatialController; +import com.ardor3d.spline.ArcLengthTable; +import com.ardor3d.spline.Curve; +import com.ardor3d.spline.Spline; + +/** + * CurveInterpolationController class interpolates a {@link Spatial}s vectors using a {@link Curve}. + * <p> + * This class is stateful and can not be used by more than one controller at a time. + * </p> + */ +public class CurveInterpolationController extends Vector3InterpolationController { + + /** Serial UID */ + private static final long serialVersionUID = 1L; + + /** Classes logger */ + private static final Logger LOGGER = Logger.getLogger(CurveInterpolationController.class.getName()); + + /** @see #setCurve(Curve) */ + private Curve _curve; + + /** Look up table of arc lengths (used for constant speed) */ + private ArcLengthTable _arcLengths; + + /** Look up table of arc lengths (used for constant speed when travelling in reverse (repeat type = cycle)) */ + private ArcLengthTable _arcLengthsReverse; + + /** Distance travelled between control points (used for constant speed) */ + private double _distance = 0.0; + + /* + * Overrides to handle constant speed updating + */ + @Override + protected double incrementDelta(final double by) { + double delta; + + /* + * If constant speed we need to also check we aren't clamped at max index before we call lookup in the arc + * length table because there would be no point + */ + if (isConstantSpeed()) { + _distance += by; + + if (isCycleForward()) { + assert (null != _arcLengths) : "You need to call generateArcLengths(x, false) to create the required arc length table!"; + + delta = _arcLengths.getDelta(getIndex(), _distance); + } else { + assert (null != _arcLengthsReverse) : "You need to call generateArcLengths(x, true) to create the required reverse arc length table!"; + + delta = _arcLengthsReverse.getDelta(getIndex(), _distance); + } + + setDelta(delta); + } else { + delta = super.incrementDelta(by); + } + + return delta; + } + + /* + * Overrides to handle updating the travelled distance correctly (used during constant speed mode) + */ + @Override + protected int decrementIndex() { + assert (null != _arcLengthsReverse) : "You need to call generateArcLengths() to create the required arc length tables!"; + + _distance -= _arcLengthsReverse.getLength(getIndex()); + + return super.decrementIndex(); + } + + /* + * Overrides to handle updating the travelled distance correctly (used during constant speed mode) + */ + @Override + protected int incrementIndex() { + assert (null != _arcLengths) : "You need to call generateArcLengths() to create the required arc length tables!"; + + _distance -= _arcLengths.getLength(getIndex()); + + return super.incrementIndex(); + } + + @Override + protected Vector3 interpolateVectors(final ReadOnlyVector3 from, final ReadOnlyVector3 to, final double delta, + final Vector3 target) { + + assert (null != from) : "parameter 'from' can not be null"; + assert (null != to) : "parameter 'to' can not be null"; + + final ReadOnlyVector3 p0 = getControlPointStart(); + final ReadOnlyVector3 p3 = getCotnrolPointEnd(); + + final Spline spline = getCurve().getSpline(); + + return spline.interpolate(p0, from, to, p3, delta, target); + } + + /** + * @return The initial control point, will not be <code>null</code>. + */ + protected ReadOnlyVector3 getControlPointStart() { + ReadOnlyVector3 control = null; + + final int fromIndex = getIndex(); + + switch (getRepeatType()) { + case CLAMP: + control = getControls().get(fromIndex - 1); + break; + + case CYCLE: + if (isCycleForward()) { + control = getControls().get(fromIndex - 1); + } else { + control = getControls().get(fromIndex + 1); + } + break; + + case WRAP: + control = getControls().get(fromIndex - 1); + break; + } + + return control; + } + + /** + * @return The final control point, will not be <code>null</code>. + */ + protected ReadOnlyVector3 getCotnrolPointEnd() { + ReadOnlyVector3 control = null; + + final int toIndex = getIndex(); + + switch (getRepeatType()) { + case CLAMP: + control = getControls().get(toIndex + 2); + break; + + case CYCLE: + if (isCycleForward()) { + control = getControls().get(toIndex + 2); + } else { + control = getControls().get(toIndex - 2); + } + break; + + case WRAP: + control = getControls().get(toIndex + 2); + break; + } + + return control; + } + + /** + * Setting a new curve will automatically update the control points. + * + * @param curve + * The new curve to follow, can not be <code>null</code>. + * @see #getCurve() + */ + public void setCurve(final Curve curve) { + if (null == curve) { + throw new IllegalArgumentException("curve can not be null!"); + } + + _curve = curve; + + setControls(_curve.getControlPoints()); + + if (isConstantSpeed()) { + LOGGER + .warning("Constant speed is set to true, you will need to call generateArcLengths() to avoid errors during update."); + } + } + + /** + * @return The curve being followed, will not <code>null</code>. + * @see #setCurve(Curve) + */ + public Curve getCurve() { + assert (null != _curve) : "curve was null, it must be set before use!"; + + return _curve; + } + + /* + * Overrides to provide a warning about generating arc lengths if constant speed is set to true and they haven't + * been generated yet. + */ + @Override + public void setConstantSpeed(final boolean constantSpeed) { + super.setConstantSpeed(constantSpeed); + + if (isConstantSpeed() && null == _arcLengths) { + LOGGER + .warning("Constant speed was set to true, you will need to call generateArcLengths() to avoid errors during update."); + } + } + + /** + * Generates the arc lengths, generates the reverse table if the + * {@link #setRepeatType(com.ardor3d.scenegraph.controller.ComplexSpatialController.RepeatType) repeat type} is set + * to {@link ComplexSpatialController.RepeatType#CYCLE cycle} + * + * @param step + * 'See Also:' method for more info. + * @see #generateArcLengths(int, boolean) + */ + public void generateArcLengths(final int step) { + generateArcLengths(step, RepeatType.CYCLE.equals(getRepeatType())); + } + + /** + * Generates arc lengths which are required if you wish to have {@link #setConstantSpeed(boolean) constant speed} + * interpolation. + * + * @param step + * 'See Also:' method for more info. + * @param reverse + * <code>true</code> to also generate a reverse look up table. This is only required if you plan to use + * the {@link ComplexSpatialController.RepeatType#CYCLE} repeat type. + * @see ArcLengthTable#generate(int, boolean) + */ + public void generateArcLengths(final int step, final boolean reverse) { + _arcLengths = new ArcLengthTable(getCurve()); + _arcLengths.generate(step, false); + + if (reverse) { + _arcLengthsReverse = new ArcLengthTable(getCurve()); + _arcLengthsReverse.generate(step, true); + } + } + + /** + * Since splines require at least 4 points to interpolate correctly the default maximum value is overridden to 1 + * less than normal. + */ + @Override + protected int getMaximumIndex() { + return super.getMaximumIndex() - 1; + } + + /** + * Since splines require at least 4 points to interpolate correctly the default minimum value is overridden to 1 + * more than normal. + */ + @Override + protected int getMinimumIndex() { + return super.getMinimumIndex() + 1; + } + + /* + * Overrides to also reset the distance. + */ + @Override + public void reset() { + super.reset(); + + _distance = 0.0; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java new file mode 100644 index 0000000..30b595b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java @@ -0,0 +1,139 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+/**
+ *
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import java.io.Serializable;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.SpatialController;
+
+/**
+ * CurveLookAtController class rotates a spatial to 'look at' a curve.
+ * <p>
+ * This class assumes the given delegate curve interpolation controller is already added to a spatial and is getting
+ * automatically updated as part of the main loop. Therefore this class doesn't call update on the delegate controller.
+ * </p>
+ */
+public class CurveLookAtController implements SpatialController<Spatial>, Serializable {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** The world up vector to use in matrix look at */
+ private ReadOnlyVector3 _worldUp;
+
+ /** The curve interpolation controller that does the work of finding the correct position to look at */
+ private final CurveInterpolationController _curveController;
+
+ /** The previous location of the spatial on the curve */
+ private final Vector3 _previous;
+
+ /** @see #setLocalRotation(boolean) */
+ private boolean _localRotation = true;
+
+ /**
+ * Creates a new instance of <code>CurveLookAtController</code>, with {@link Vector3#UNIT_Y} as the world up vector.
+ *
+ * @param curveController
+ * The curve interpolation controller that does the work of finding the correct position to look at, can
+ * not be <code>null</code>.
+ */
+ public CurveLookAtController(final CurveInterpolationController curveController) {
+ this(curveController, Vector3.UNIT_Y);
+ }
+
+ /**
+ * Creates a new instance of <code>CurveLookAtController</code>.
+ *
+ * @param curveController
+ * The curve interpolation controller that does the work of finding the correct position to look at, can
+ * not be <code>null</code>.
+ * @param worldUp
+ * The world up vector, can not be <code>null</code>.
+ */
+ public CurveLookAtController(final CurveInterpolationController curveController, final ReadOnlyVector3 worldUp) {
+ super();
+
+ if (null == curveController) {
+ throw new IllegalArgumentException("curveController can not be null!");
+ }
+
+ _curveController = curveController;
+
+ _previous = new Vector3(_curveController.getControlFrom());
+
+ setWorldUp(worldUp);
+ }
+
+ @Override
+ public void update(final double time, final Spatial caller) {
+ if (null == caller) {
+ throw new IllegalArgumentException("caller can not be null!");
+ }
+
+ final Vector3 interpolated = Vector3.fetchTempInstance();
+ final Matrix3 rotation = Matrix3.fetchTempInstance();
+
+ _curveController.interpolateVectors(_curveController.getControlFrom(), _curveController.getControlTo(),
+ _curveController.getDelta(), interpolated);
+
+ MathUtils.matrixLookAt(_previous, interpolated, _worldUp, rotation);
+
+ if (isLocalRotation()) {
+ caller.setRotation(rotation);
+ } else {
+ caller.setWorldRotation(rotation);
+ }
+
+ _previous.set(interpolated);
+
+ Matrix3.releaseTempInstance(rotation);
+ Vector3.releaseTempInstance(interpolated);
+ }
+
+ /**
+ * @param worldUp
+ * The world up vector, can not be <code>null</code>.
+ */
+ public void setWorldUp(final ReadOnlyVector3 worldUp) {
+ if (null == worldUp) {
+ throw new IllegalArgumentException("worldUp can not be null!");
+ }
+
+ _worldUp = worldUp;
+ }
+
+ /**
+ * @param localRotation
+ * <code>true</code> to update local rotation, <code>false</code> to update world rotation.
+ * @see #isLocalRotation()
+ */
+ public void setLocalRotation(final boolean localRotation) {
+ _localRotation = localRotation;
+ }
+
+ /**
+ * @return <code>true</code> if the local rotation is being updated, <code>false</code> if the world rotation is.
+ * @see #setLocalRotation(boolean)
+ */
+ public boolean isLocalRotation() {
+ return _localRotation;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java new file mode 100644 index 0000000..378e5b2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java @@ -0,0 +1,52 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+
+/**
+ * ColorRGBAInterpolationController class interpolates the {@link Mesh#getDefaultColor() default colour} of a mesh using
+ * {@link ReadOnlyColorRGBA}s.
+ * <p>
+ * Note: The default colour only works if a {@link MeshData#getColorBuffer() colour buffer} has NOT been set on the
+ * mesh.
+ * </p>
+ */
+public class DefaultColorInterpolationController extends InterpolationController<ReadOnlyColorRGBA, Mesh> {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Interpolates between the given colors using the
+ * {@link ColorRGBA#lerpLocal(ReadOnlyColorRGBA, ReadOnlyColorRGBA, float)} method.
+ */
+ @Override
+ protected void interpolate(final ReadOnlyColorRGBA from, final ReadOnlyColorRGBA to, final double delta,
+ final Mesh caller) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+ assert (null != caller) : "parameter 'caller' can not be null";
+
+ final ColorRGBA color = ColorRGBA.fetchTempInstance().set(caller.getDefaultColor());
+
+ color.lerpLocal(from, to, (float) delta);
+
+ caller.setDefaultColor(color);
+
+ ColorRGBA.releaseTempInstance(color);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java new file mode 100644 index 0000000..0baf1d5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java @@ -0,0 +1,420 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.controller.interpolation; + +import java.util.Arrays; +import java.util.List; + +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.controller.ComplexSpatialController; + +/** + * InterpolationController class is an abstract class containing all the stuff common to controllers interpolating + * things. + * <p> + * Implementation note: This class is comprised of quite a few protected methods, this is mainly to allow maximum + * flexibility for overriding classes. + * </p> + * + * @param <C> + * The control 'points' being interpolated, for example Vectors or Quaternions. + * @param <T> + * The object this controller will perform the interpolation on, for example Spatials. + */ +public abstract class InterpolationController<C, T extends Spatial> extends ComplexSpatialController<T> { + + /** Serial UID */ + private static final long serialVersionUID = 1L; + + /** The minimum allowed delta */ + public static final double DELTA_MIN = 0.0; + + /** The maximum allowed delta */ + public static final double DELTA_MAX = 1.0; + + /** @see #setControls(List) */ + private List<C> _controls = null; + + /** @see #setIndex(int) */ + private int _index = getMinimumIndex(); + + /** @see #setDelta(double) */ + private double _delta = DELTA_MIN; + + /** @see #setCycleForward(boolean) */ + private boolean _cycleForward = true; + + /** + * This method is automatically called by {@link #update(double, Spatial)} from this controller. + * + * @param from + * The control to interpolate from. + * @param to + * The control to interpolate to. + * @param delta + * The distance between <code>from</code> and <code>to</code>, will be between <code>0.0</code> and + * <code>1.0</code> (inclusive). + * @param caller + * The object to interpolate, will not be <code>null</code>. + */ + protected abstract void interpolate(C from, C to, double delta, T caller); + + /** + * Interpolates on the set {@link #getControls() controls}. + * <p> + * It will only update the given object if this controller is {@link #isActive() active}, caller isn't + * <code>null</code> and it's {@link #getSpeed() speed} is greater than zero. + * </p> + * + * @param time + * The passed since this controller was last called. + * @param caller + * The object to update, if this is <code>null</code> nothing will be updated. + */ + @Override + public void update(final double time, final T caller) { + if (shouldUpdate(time, caller)) { + + updateDeltaAndIndex(time); + + assert (getDelta() >= DELTA_MIN) : "delta is less than " + DELTA_MIN + + ", updateDeltaAndIndex() has probably been overriden incorrectly"; + assert (getDelta() <= DELTA_MAX) : "delta is greater than " + DELTA_MAX + + ", updateDeltaAndIndex() has probably been overriden incorrectly"; + + clampIndex(); + + assert (getIndex() < getControls().size()) : "_index was greater than the number of controls, clampIndex() has probably been overriden incorrectly"; + assert (getIndex() >= 0) : "_index was negative, clampIndex() has probably been overriden incorrectly"; + + interpolate(getControlFrom(), getControlTo(), getDelta(), caller); + } + } + + private boolean shouldUpdate(final double time, final T caller) { + return isActive() && null != caller && time > 0.0 && getSpeed() > 0.0 && !isClamped(); + } + + /** + * @return The minimum allowed index. By default it returns 0. + */ + protected int getMinimumIndex() { + return 0; + } + + /** + * @return The maximum allowed index. By default it returns the last index in the {@link #getControls() control} + * list. + */ + protected int getMaximumIndex() { + return getControls().size() - 1; + } + + /** + * @param controls + * The new controls to set, can not be <code>null</code> or empty. + * @see #getControls() + */ + public void setControls(final List<C> controls) { + if (null == controls) { + throw new IllegalArgumentException("controls can not be null!"); + } + if (controls.isEmpty()) { + throw new IllegalArgumentException("controls can not be empty!"); + } + + _controls = controls; + } + + /** + * @param controlArray + * The new values to set, can not be <code>null</code> or size 0. + * @see #getControls() + */ + public void setControls(final C... controlArray) { + if (null == controlArray) { + throw new IllegalArgumentException("controlArray can not be null!"); + } + + setControls(Arrays.<C> asList(controlArray)); + } + + /** + * @return The controls getting interpolated between, will not be <code>null</code> or empty. + * @see #setControls(List) + */ + public List<C> getControls() { + assert (null != _controls) : "_controls was null, it must be set before use!"; + assert (!_controls.isEmpty()) : "_controls was empty, it must contain at least 1 element for this class to work!"; + + return _controls; + } + + /** + * Updates the {@link #getDelta() delta} and {@link #getIndex() index}. + * + * @param time + * The raw time since this was last called. + */ + protected void updateDeltaAndIndex(final double time) { + incrementDelta(getSpeed() * time); + + /* If >= DELTA_MAX then we need to start interpolating between next set of points */ + while (getDelta() >= DELTA_MAX) { + /* Adjust delta for new set of points */ + decrementDelta(DELTA_MAX); + + /* Increment/decrement current index based on whether we are cycling forward or backwards */ + if (isCycleForward()) { + incrementIndex(); + } else { + decrementIndex(); + } + } + } + + /** + * Clamps the {@link #getIndex() index} to ensure its not out of bounds. + * <p> + * This is called automatically from {@link #update(double, Spatial)} and shouldn't be called manually. It only + * really exists as a separate method to allow sub classes maximum flexibility. Also of note is the fact that if + * this method is overridden then {@link #getControlFrom()} and {@link #getControlTo()} methods will also probably + * need to be overridden as they rely on this method clamping the index correctly before they get called. + * </p> + */ + protected void clampIndex() { + switch (getRepeatType()) { + case CLAMP: + if (getIndex() >= getMaximumIndex()) { + /* Clamp these just to be on the safe side (overflow) */ + setIndex(getMaximumIndex()); + setDelta(DELTA_MAX); + } + break; + + case CYCLE: + if (isCycleForward()) { + if (getIndex() >= getMaximumIndex()) { + setIndex(getMaximumIndex()); + setCycleForward(false); + } + } else { + if (getIndex() <= getMinimumIndex()) { + setIndex(getMinimumIndex()); + setCycleForward(true); + } + } + break; + + case WRAP: + if (getIndex() >= getMaximumIndex()) { + setIndex(getMinimumIndex()); + } + break; + } + } + + /** + * This method assumes the {@link #getIndex() index} has already been {@link #clampIndex() clamped} correctly. + * + * @return The control to interpolate from, will not be <code>null</code>. + * @see #getControlTo() + */ + protected C getControlFrom() { + C from = null; + + switch (getRepeatType()) { + case CLAMP: + if (getIndex() > getMaximumIndex()) { + from = getControls().get(getMaximumIndex()); + } else { + from = getControls().get(getIndex()); + } + break; + + case CYCLE: + from = getControls().get(getIndex()); + break; + + case WRAP: + from = getControls().get(getIndex()); + break; + } + + return from; + } + + /** + * This method assumes the {@link #getIndex() index} has already been {@link #clampIndex() clamped} correctly. + * + * @return The control to interpolate to, will not be <code>null</code>. + * @see #getControlFrom() + */ + protected C getControlTo() { + C to = null; + + switch (getRepeatType()) { + case CLAMP: + if (getIndex() >= getMaximumIndex()) { + to = getControls().get(getMaximumIndex()); + } else { + to = getControls().get(getIndex() + 1); + } + break; + + case CYCLE: + if (isCycleForward()) { + to = getControls().get(getIndex() + 1); + } else { + to = getControls().get(getIndex() - 1); + } + break; + + case WRAP: + to = getControls().get(getIndex() + 1); + break; + } + + return to; + } + + /** + * Increments the index by 1. + * + * @return The new index value as a convenience. + */ + protected int incrementIndex() { + return ++_index; + } + + /** + * Decrements the index by 1. + * + * @return The new index value as a convenience. + */ + protected int decrementIndex() { + return --_index; + } + + /** + * @param index + * The new index value. + * @see #getIndex() + */ + protected void setIndex(final int index) { + _index = index; + } + + /** + * @return The index of the {@link #getControls() control} to interpolate from. + * @see #setIndex(int) + */ + protected int getIndex() { + return _index; + } + + /** + * @param by + * The amount to increment by, if this is negative it will actually decrement the delta. + * @return The new delta value as a convenience. + * @see #decrementDelta(double) + */ + protected double incrementDelta(final double by) { + _delta += by; + + return _delta; + } + + /** + * @param by + * The amount to decrement by, if this is negative it will actually increment the delta. + * @return The new delta value as a convenience. + * @see #incrementDelta(double) + */ + protected double decrementDelta(final double by) { + _delta -= by; + + return _delta; + } + + /** + * @param delta + * The new distance between the {@link #getControlFrom() from control} and {@link #getControlTo() to + * control} , should be between <code>0.0</code> and <code>1.0</code> (inclusive). + * @see #getDelta() + */ + protected void setDelta(final double delta) { + _delta = delta; + } + + /** + * @return The distance between the {@link #getControlFrom() from control} and {@link #getControlTo() to control} , + * will be between <code>0.0</code> and <code>1.0</code> (inclusive). + * @see #setDelta(double) + */ + protected double getDelta() { + return _delta; + } + + /** + * @param cycleForward + * <code>true</code> to interpolate the controls forwards (1, 2, 3 ...), <code>false</code> to + * interpolate the controls backwards (3, 2, 1 ...) + * @see #isCycleForward() + */ + protected void setCycleForward(final boolean cycleForward) { + _cycleForward = cycleForward; + } + + /** + * @return <code>true</code> if interpolating the controls forwards (1, 2, 3 ...), <code>false</code> if + * interpolating the controls backwards (3, 2, 1 ...) + * @see #setCycleForward(boolean) + */ + protected boolean isCycleForward() { + return _cycleForward; + } + + /** + * Also {@link #reset() resets} this controller for safety, because changing the repeat type part way through an + * interpolation can cause problems. + * + * @param repeatType + * The new repeat type to use. + */ + @Override + public void setRepeatType(final RepeatType repeatType) { + if (getRepeatType() != repeatType) { + /* Reset for safety */ + reset(); + } + + super.setRepeatType(repeatType); + } + + /** + * Resets the internal state, namely the cycle direction, delta and index variables. + */ + public void reset() { + setCycleForward(true); + setDelta(DELTA_MIN); + setIndex(getMinimumIndex()); + } + + /** + * @return <code>true</code> if this controllers {@link #getRepeatType() repeat type} is + * {@link ComplexSpatialController.RepeatType#CLAMP clamp} and its currently clamped at the maximum index. + */ + public boolean isClamped() { + return isRepeatTypeClamp() && getIndex() == getMaximumIndex(); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java new file mode 100644 index 0000000..176383a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java @@ -0,0 +1,36 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * LinearVector3InterpolationController class interpolates a {@link Spatial}s vectors using
+ * {@link Vector3#lerpLocal(ReadOnlyVector3, ReadOnlyVector3, double)}
+ */
+public class LinearVector3InterpolationController extends Vector3InterpolationController {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected Vector3 interpolateVectors(final ReadOnlyVector3 from, final ReadOnlyVector3 to, final double delta,
+ final Vector3 target) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+
+ return target.lerpLocal(from, to, delta);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java new file mode 100644 index 0000000..dfc5c2c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java @@ -0,0 +1,70 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.type.ReadOnlyQuaternion;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * QuaternionInterpolationController class interpolates a {@link Spatial}s rotation using {@link Quaternion}s.
+ */
+public class QuaternionInterpolationController extends InterpolationController<ReadOnlyQuaternion, Spatial> {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** @see #setLocalRotation(boolean) */
+ private boolean _localRotation = true;
+
+ /**
+ * Interpolates between the given quaternions using the
+ * {@link Quaternion#slerpLocal(ReadOnlyQuaternion, ReadOnlyQuaternion, double)} method.
+ */
+ @Override
+ protected void interpolate(final ReadOnlyQuaternion from, final ReadOnlyQuaternion to, final double delta,
+ final Spatial caller) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+ assert (null != caller) : "parameter 'caller' can not be null";
+
+ final Quaternion tempQuat = Quaternion.fetchTempInstance();
+
+ tempQuat.slerpLocal(from, to, delta);
+
+ if (isLocalRotation()) {
+ caller.setRotation(tempQuat);
+ } else {
+ caller.setWorldRotation(tempQuat);
+ }
+
+ Quaternion.releaseTempInstance(tempQuat);
+ }
+
+ /**
+ * @param localRotation
+ * <code>true</code> to update local rotation, <code>false</code> to update world rotation.
+ * @see #isLocalRotation()
+ */
+ public void setLocalRotation(final boolean localRotation) {
+ _localRotation = localRotation;
+ }
+
+ /**
+ * @return <code>true</code> if the local rotation is being updated, <code>false</code> if the world rotation is.
+ * @see #setLocalRotation(boolean)
+ */
+ public boolean isLocalRotation() {
+ return _localRotation;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java new file mode 100644 index 0000000..44ad32a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.controller.interpolation; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.Spatial; + +/** + * Vector3InterpolationController class is a base class for controllers that can interpolate on vectors. + */ +public abstract class Vector3InterpolationController extends InterpolationController<ReadOnlyVector3, Spatial> { + + /** Serial UID */ + private static final long serialVersionUID = 1L; + + /** @see #setConstantSpeed(boolean) */ + private boolean _constantSpeed; + + /** @see #setUpdateField(UpdateField) */ + private UpdateField _updateField = UpdateField.LOCAL_TRANSLATION; + + /** + * Implemented by sub classes to perform the actual interpolation. + * + * @param from + * The vector to interpolate from. + * @param to + * The vector to interpolate to. + * @param delta + * The distance between <code>from</code> and <code>to</code>, will be between <code>0.0</code> and + * <code>1.0</code> (inclusive). + * @param target + * The vector to actually interpolate. + * @return The interpolated vector, should not be <code>null</code>. + */ + protected abstract Vector3 interpolateVectors(ReadOnlyVector3 from, ReadOnlyVector3 to, double delta, Vector3 target); + + /** + * Interpolates between the given vectors using the + * {@link #interpolateVectors(ReadOnlyVector3, ReadOnlyVector3, double, Vector3)} to perform the actual + * interpolation. + */ + @Override + protected void interpolate(final ReadOnlyVector3 from, final ReadOnlyVector3 to, final double delta, + final Spatial caller) { + + assert (null != from) : "parameter 'from' can not be null"; + assert (null != to) : "parameter 'to' can not be null"; + assert (null != caller) : "parameter 'caller' can not be null"; + + final Vector3 target = Vector3.fetchTempInstance(); + + final ReadOnlyVector3 interpolated = interpolateVectors(from, to, delta, target); + + switch (getUpdateField()) { + case LOCAL_SCALE: + caller.setScale(interpolated); + break; + case LOCAL_TRANSLATION: + caller.setTranslation(interpolated); + break; + case WORLD_SCALE: + caller.setWorldScale(interpolated); + break; + case WORLD_TRANSLATION: + caller.setWorldTranslation(interpolated); + break; + default: + caller.setTranslation(interpolated); + } + + Vector3.releaseTempInstance(target); + + } + + /** + * @param constantSpeed + * <code>true</code> to interpolate between vectors at a constant speed, <code>false</code> to + * interpolate at a constant time. + * @see #isConstantSpeed() + */ + public void setConstantSpeed(final boolean constantSpeed) { + _constantSpeed = constantSpeed; + } + + /** + * See the setters Javadoc for more information. + * + * @return <code>true</code> if interpolating at a constant speed, <code>false</code> otherwise. + * @see #setConstantSpeed(boolean) + */ + public boolean isConstantSpeed() { + return _constantSpeed; + } + + /** + * @param updateField + * The new field to update. + * @see #getUpdateField() + */ + public void setUpdateField(final UpdateField updateField) { + _updateField = updateField; + } + + /** + * @return The field being updated. + * @see #setUpdateField(UpdateField) + */ + public UpdateField getUpdateField() { + return _updateField; + } + + /** + * Specifies which field on the spatial to update. + */ + public enum UpdateField { + /** @see Spatial#getTranslation() */ + LOCAL_TRANSLATION, + /** @see Spatial#getWorldTranslation() */ + WORLD_TRANSLATION, + /** @see Spatial#getScale() */ + LOCAL_SCALE, + /** @see Spatial#getWorldScale() */ + WORLD_SCALE; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java new file mode 100644 index 0000000..004c7a5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java @@ -0,0 +1,34 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.event;
+
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * DirtyEventListener is the interface for objects interested in updates when spatials get marked dirty / clean
+ * (updated).
+ */
+public interface DirtyEventListener {
+
+ /**
+ * spatialDirty is called when a spatial is changed in respect to transform, bounding, attach/dettach or renderstate
+ *
+ * @return true if the event should be consumed and not continue up the scenegraph.
+ */
+ boolean spatialDirty(Spatial spatial, DirtyType dirtyType);
+
+ /**
+ * spatialClean is called when a spatial is changed in respect to transform, bounding, attach/dettach or renderstate
+ *
+ * @return true if the event should be consumed and not continue up the scenegraph.
+ */
+ boolean spatialClean(Spatial spatial, DirtyType dirtyType);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java new file mode 100644 index 0000000..3a4c91b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java @@ -0,0 +1,18 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.event;
+
+/**
+ * DirtyType contains the types of update that can occur on a spatial.
+ */
+public enum DirtyType {
+ Transform, Bounding, Attached, Detached, Destroyed, RenderState
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java new file mode 100644 index 0000000..9c9d95e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java @@ -0,0 +1,63 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * SceneGraphManager is a convenience class for use when you want to have multiple listeners on a particular node.
+ */
+public class SceneGraphManager implements DirtyEventListener {
+ private static SceneGraphManager sceneGraphManagerInstance;
+
+ private final List<DirtyEventListener> _listeners;
+
+ private SceneGraphManager() {
+ _listeners = new ArrayList<DirtyEventListener>();
+ }
+
+ public static SceneGraphManager getSceneGraphManager() {
+ if (sceneGraphManagerInstance == null) {
+ sceneGraphManagerInstance = new SceneGraphManager();
+ }
+
+ return sceneGraphManagerInstance;
+ }
+
+ public void listenOnSpatial(final Spatial spatial) {
+ spatial.setListener(this);
+ }
+
+ public void addDirtyEventListener(final DirtyEventListener listener) {
+ _listeners.add(listener);
+ }
+
+ public void removeDirtyEventListener(final DirtyEventListener listener) {
+ _listeners.remove(listener);
+ }
+
+ public boolean spatialDirty(final Spatial spatial, final DirtyType dirtyType) {
+ for (final DirtyEventListener listener : _listeners) {
+ listener.spatialDirty(spatial, dirtyType);
+ }
+ return false;
+ }
+
+ public boolean spatialClean(final Spatial spatial, final DirtyType dirtyType) {
+ for (final DirtyEventListener listener : _listeners) {
+ listener.spatialClean(spatial, dirtyType);
+ }
+ return false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java new file mode 100644 index 0000000..8a3cad5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java @@ -0,0 +1,277 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.extension; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>BillboardNode</code> defines a node that always orients towards the camera. However, it does not tilt up/down + * as the camera rises. This keep geometry from appearing to fall over if the camera rises or lowers. + * <code>BillboardNode</code> is useful to contain a single quad that has a image applied to it for lowest detail + * models. This quad, with the texture, will appear to be a full model at great distances, and save on rendering and + * memory. It is important to note that for AXIAL mode, the billboards orientation will always be up (0,1,0). This means + * that a "standard" ardor3d camera with up (0,1,0) is the only camera setting compatible with AXIAL mode. + */ +public class BillboardNode extends Node { + + private double _lastTime; + + private final Matrix3 _orient = new Matrix3(Matrix3.IDENTITY); + + private final Vector3 _look = new Vector3(Vector3.ZERO); + + private final Vector3 _left = new Vector3(Vector3.ZERO); + + public enum BillboardAlignment { + ScreenAligned, CameraAligned, AxialY, AxialZ + } + + private BillboardAlignment _alignment; + + public BillboardNode() {} + + /** + * Constructor instantiates a new <code>BillboardNode</code>. The name of the node is supplied during construction. + * + * @param name + * the name of the node. + */ + public BillboardNode(final String name) { + super(name); + _alignment = BillboardAlignment.ScreenAligned; + } + + @Override + public void updateWorldTransform(final boolean recurse) { + _lastTime = 0; // time + super.updateWorldTransform(recurse); + } + + /** + * <code>draw</code> updates the billboards orientation then renders the billboard's children. + * + * @param r + * the renderer used to draw. + * @see com.ardor3d.scenegraph.Spatial#draw(com.ardor3d.renderer.Renderer) + */ + @Override + public void draw(final Renderer r) { + rotateBillboard(); + + super.draw(r); + } + + /** + * rotate the billboard based on the type set + * + * @param cam + * Camera + */ + public void rotateBillboard() { + // get the scale, translation and rotation of the node in world space + updateWorldTransform(false); + + switch (_alignment) { + case ScreenAligned: + rotateScreenAligned(); + break; + case CameraAligned: + rotateCameraAligned(); + break; + case AxialY: + rotateAxial(new Vector3(Vector3.UNIT_Y)); + break; + case AxialZ: + rotateAxial(new Vector3(Vector3.UNIT_Z)); + break; + } + + if (_children == null) { + return; + } + + propagateDirtyDown(ON_DIRTY_TRANSFORM); + for (int i = 0, cSize = getNumberOfChildren(); i < cSize; i++) { + final Spatial child = getChild(i); + if (child != null) { + child.updateGeometricState(_lastTime, false); + } + } + } + + /** + * Aligns this Billboard Node so that it points to the camera position. + * + * @param camera + * Camera + */ + private void rotateCameraAligned() { + final Camera camera = Camera.getCurrentCamera(); + _look.set(camera.getLocation()).subtractLocal(_worldTransform.getTranslation()); + // coopt left for our own purposes. + final Vector3 xzp = _left; + // The xzp vector is the projection of the look vector on the xz plane + xzp.set(_look.getX(), 0, _look.getZ()); + + // check for undefined rotation... + if (xzp.equals(Vector3.ZERO)) { + return; + } + + _look.normalizeLocal(); + xzp.normalizeLocal(); + final double cosp = _look.dot(xzp); + + // compute the local orientation matrix for the billboard + _orient.setValue(0, 0, xzp.getZ()); + _orient.setValue(0, 1, xzp.getX() * -_look.getY()); + _orient.setValue(0, 2, xzp.getX() * cosp); + _orient.setValue(1, 0, 0); + _orient.setValue(1, 1, cosp); + _orient.setValue(1, 2, _look.getY()); + _orient.setValue(2, 0, -xzp.getX()); + _orient.setValue(2, 1, xzp.getZ() * -_look.getY()); + _orient.setValue(2, 2, xzp.getZ() * cosp); + + // The billboard must be oriented to face the camera before it is + // transformed into the world. + final Matrix3 mat = Matrix3.fetchTempInstance().set(_worldTransform.getMatrix()).multiplyLocal(_orient); + _worldTransform.setRotation(mat); + Matrix3.releaseTempInstance(mat); + } + + /** + * Rotate the billboard so it points directly opposite the direction the camera's facing + * + * @param camera + * Camera + */ + private void rotateScreenAligned() { + final Camera camera = Camera.getCurrentCamera(); + // coopt diff for our in direction: + _look.set(camera.getDirection()).negateLocal(); + // coopt loc for our left direction: + _left.set(camera.getLeft()).negateLocal(); + _orient.fromAxes(_left, camera.getUp(), _look); + _worldTransform.setRotation(_orient); + } + + /** + * Rotate the billboard towards the camera, but keeping a given axis fixed. + * + * @param camera + * Camera + */ + private void rotateAxial(final Vector3 axis) { + final Camera camera = Camera.getCurrentCamera(); + // Compute the additional rotation required for the billboard to face + // the camera. To do this, the camera must be inverse-transformed into + // the model space of the billboard. + _look.set(camera.getLocation()).subtractLocal(_worldTransform.getTranslation()); + final Matrix3 worldMatrix = Matrix3.fetchTempInstance().set(_worldTransform.getMatrix()); + worldMatrix.applyPost(_look, _left); // coopt left for our own purposes. + final ReadOnlyVector3 scale = _worldTransform.getScale(); + _left.divideLocal(scale); + + // squared length of the camera projection in the xz-plane + final double lengthSquared = _left.getX() * _left.getX() + _left.getZ() * _left.getZ(); + if (lengthSquared < MathUtils.EPSILON) { + // camera on the billboard axis, rotation not defined + return; + } + + // unitize the projection + final double invLength = 1.0 / Math.sqrt(lengthSquared); + if (axis.getY() == 1) { + _left.setX(_left.getX() * invLength); + _left.setY(0.0); + _left.setZ(_left.getZ() * invLength); + + // compute the local orientation matrix for the billboard + _orient.setValue(0, 0, _left.getZ()); + _orient.setValue(0, 1, 0); + _orient.setValue(0, 2, _left.getX()); + _orient.setValue(1, 0, 0); + _orient.setValue(1, 1, 1); + _orient.setValue(1, 2, 0); + _orient.setValue(2, 0, -_left.getX()); + _orient.setValue(2, 1, 0); + _orient.setValue(2, 2, _left.getZ()); + } else if (axis.getZ() == 1) { + _left.setX(_left.getX() * invLength); + _left.setY(_left.getY() * invLength); + _left.setZ(0.0); + + // compute the local orientation matrix for the billboard + _orient.setValue(0, 0, _left.getY()); + _orient.setValue(0, 1, _left.getX()); + _orient.setValue(0, 2, 0); + _orient.setValue(1, 0, -_left.getY()); + _orient.setValue(1, 1, _left.getX()); + _orient.setValue(1, 2, 0); + _orient.setValue(2, 0, 0); + _orient.setValue(2, 1, 0); + _orient.setValue(2, 2, 1); + } + + // The billboard must be oriented to face the camera before it is + // transformed into the world. + worldMatrix.multiplyLocal(_orient); + _worldTransform.setRotation(worldMatrix); + Matrix3.releaseTempInstance(worldMatrix); + } + + /** + * Returns the alignment this BillboardNode is set too. + * + * @return The alignment of rotation, ScreenAligned, CameraAligned, AxialY or AxialZ. + */ + public BillboardAlignment getAlignment() { + return _alignment; + } + + /** + * Sets the type of rotation this BillboardNode will have. The alignment can be ScreenAligned, CameraAligned, AxialY + * or AxialZ. Invalid alignments will assume no billboard rotation. + */ + public void setAlignment(final BillboardAlignment alignment) { + _alignment = alignment; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_orient, "orient", new Matrix3()); + capsule.write(_look, "look", new Vector3(Vector3.ZERO)); + capsule.write(_left, "left", new Vector3(Vector3.ZERO)); + capsule.write(_alignment, "alignment", BillboardAlignment.ScreenAligned); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _orient.set((Matrix3) capsule.readSavable("orient", new Matrix3(Matrix3.IDENTITY))); + _look.set((Vector3) capsule.readSavable("look", new Vector3(Vector3.ZERO))); + _left.set((Vector3) capsule.readSavable("left", new Vector3(Vector3.ZERO))); + _alignment = capsule.readEnum("alignment", BillboardAlignment.class, BillboardAlignment.ScreenAligned); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java new file mode 100644 index 0000000..76e1628 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.extension; + +import java.io.IOException; + +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * Defines a node that manages a {@link Camera} object, allowing it to be part of a scenegraph. The updateWorldTransform + * method is overridden to adjust the managed camera's location and orientation using this Node's world translation and + * the world rotation. The column 0 of the world rotation matrix is used for the camera left vector, column 1 is used + * for the camera up vector, column 2 is used for the camera direction vector. + */ +public class CameraNode extends Node { + + private Camera _camera; + + public CameraNode() {} + + /** + * Constructor instantiates a new <code>CameraNode</code> object setting the camera to use for the frame reference. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + * @param camera + * the camera this node controls. + */ + public CameraNode(final String name, final Camera camera) { + super(name); + _camera = camera; + } + + /** + * Forces rotation and translation of this node to be sync'd with the attached camera. (Assumes the node is in world + * space.) + */ + public void updateFromCamera() { + final ReadOnlyVector3 camLeft = _camera.getLeft(); + final ReadOnlyVector3 camUp = _camera.getUp(); + final ReadOnlyVector3 camDir = _camera.getDirection(); + final ReadOnlyVector3 camLoc = _camera.getLocation(); + + final Matrix3 rotation = Matrix3.fetchTempInstance(); + rotation.fromAxes(camLeft, camUp, camDir); + + setRotation(rotation); + setTranslation(camLoc); + + Matrix3.releaseTempInstance(rotation); + } + + /** + * <code>setCamera</code> sets the camera that this node controls. + * + * @param camera + * the camera that this node controls. + */ + public void setCamera(final Camera camera) { + _camera = camera; + } + + /** + * <code>getCamera</code> retrieves the camera object that this node controls. + * + * @return the camera this node controls. + */ + public Camera getCamera() { + return _camera; + } + + /** + * <code>updateWorldTransform</code> updates the rotation and translation of this node, and sets the camera's frame + * buffer to reflect the current view. + */ + @Override + public void updateWorldTransform(final boolean recurse) { + super.updateWorldTransform(recurse); + if (_camera != null) { + final ReadOnlyVector3 worldTranslation = getWorldTranslation(); + final ReadOnlyMatrix3 worldRotation = getWorldRotation(); + _camera.setFrame(worldTranslation, worldRotation); + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_camera, "camera", null); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _camera = (Camera) capsule.readSavable("camera", null); + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java new file mode 100644 index 0000000..8d2377d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.extension; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class PassNode extends Node { + + private List<PassNodeState> _passNodeStates = new ArrayList<PassNodeState>(); + + public PassNode(final String name) { + super(name); + } + + public PassNode() { + super(); + } + + @Override + public void draw(final Renderer r) { + if (_children == null) { + return; + } + + final RenderContext context = ContextManager.getCurrentContext(); + r.getQueue().pushBuckets(); + for (final PassNodeState pass : _passNodeStates) { + if (!pass.isEnabled()) { + continue; + } + + pass.applyPassNodeStates(context); + + Spatial child; + for (int i = 0, cSize = _children.size(); i < cSize; i++) { + child = _children.get(i); + if (child != null) { + child.onDraw(r); + } + } + r.renderBuckets(); + + context.popEnforcedStates(); + } + r.getQueue().popBuckets(); + } + + public void addPass(final PassNodeState toAdd) { + _passNodeStates.add(toAdd); + } + + public void insertPass(final PassNodeState toAdd, final int index) { + _passNodeStates.add(index, toAdd); + } + + public boolean containsPass(final PassNodeState s) { + return _passNodeStates.contains(s); + } + + public boolean removePass(final PassNodeState toRemove) { + return _passNodeStates.remove(toRemove); + } + + public PassNodeState getPass(final int index) { + return _passNodeStates.get(index); + } + + public int nrPasses() { + return _passNodeStates.size(); + } + + public void clearAll() { + _passNodeStates.clear(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.writeSavableList(_passNodeStates, "passNodeStates", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _passNodeStates = capsule.readSavableList("passNodeStates", null); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java new file mode 100644 index 0000000..c34d4f9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.extension; + +import java.io.IOException; +import java.io.Serializable; +import java.util.EnumMap; + +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.util.export.CapsuleUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +public class PassNodeState implements Savable, Serializable { + + private static final long serialVersionUID = 1L; + + /** if false, pass will not be updated or rendered. */ + protected boolean _enabled = true; + + /** + * RenderStates registered with this pass - if a given state is not null it overrides the corresponding state set + * during rendering. + */ + protected final EnumMap<RenderState.StateType, RenderState> _passStates = new EnumMap<RenderState.StateType, RenderState>( + RenderState.StateType.class); + + /** + * Applies all currently set renderstates to the supplied context + * + * @param context + */ + public void applyPassNodeStates(final RenderContext context) { + context.pushEnforcedStates(); + context.enforceStates(_passStates); + } + + /** + * Enforce a particular state. In other words, the given state will override any state of the same type set on a + * scene object. Remember to clear the state when done enforcing. Very useful for multipass techniques where + * multiple sets of states need to be applied to a scenegraph drawn multiple times. + * + * @param state + * state to enforce + */ + public void setPassState(final RenderState state) { + _passStates.put(state.getType(), state); + } + + /** + * @param type + * the type to query + * @return the state enforced for a give state type, or null if none. + */ + public RenderState getPassState(final StateType type) { + return _passStates.get(type); + } + + /** + * Clears an enforced render state index by setting it to null. This allows object specific states to be used. + * + * @param type + * The type of RenderState to clear enforcement on. + */ + public void clearPassState(final StateType type) { + _passStates.remove(type); + } + + /** + * sets all enforced states to null. + * + * @see RenderContext#clearEnforcedState(int) + */ + public void clearPassStates() { + _passStates.clear(); + } + + /** @return Returns the enabled. */ + public boolean isEnabled() { + return _enabled; + } + + /** + * @param enabled + * The enabled to set. + */ + public void setEnabled(final boolean enabled) { + _enabled = enabled; + } + + public Class<? extends PassNodeState> getClassTag() { + return this.getClass(); + } + + public void write(final OutputCapsule capsule) throws IOException { + final OutputCapsule oc = capsule; + oc.write(_enabled, "enabled", true); + oc.write(_passStates.values().toArray(new RenderState[0]), "passStates", null); + } + + public void read(final InputCapsule capsule) throws IOException { + final InputCapsule ic = capsule; + _enabled = ic.readBoolean("enabled", true); + final RenderState[] states = CapsuleUtils.asArray(ic.readSavableArray("passStates", null), RenderState.class); + _passStates.clear(); + if (states != null) { + for (final RenderState state : states) { + _passStates.put(state.getType(), state); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java new file mode 100644 index 0000000..1500c5d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java @@ -0,0 +1,406 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.extension; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.bounding.BoundingBox; +import com.ardor3d.bounding.BoundingSphere; +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.renderer.TextureRendererFactory; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.util.Timer; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * QuadImposterNode + */ +public class QuadImposterNode extends Node { + + protected TextureRenderer _tRenderer; + + protected Texture2D _texture; + + protected Node _targetScene; + + protected Quad _imposterQuad; + + protected double _redrawRate; + protected double _elapsed; + protected double _cameraAngleThreshold; + protected double _cameraDistanceThreshold = Double.MAX_VALUE; + protected boolean _haveDrawn; + + protected Vector3 _worldUpVector = new Vector3(0, 1, 0); + + protected boolean _doUpdate = true; + + protected Camera _cam; + + protected int _twidth, _theight; + protected int _depth, _samples; + + protected final Vector3 _lastCamDir = new Vector3(); + protected double _lastCamDist; + + protected Vector3[] _corners = new Vector3[8]; + protected final Vector3 _center = new Vector3(); + protected final Vector3 _extents = new Vector3(); + protected final Vector2 _minScreenPos = new Vector2(); + protected final Vector2 _maxScreenPos = new Vector2(); + protected final Vector2 _minMaxScreenPos = new Vector2(); + protected final Vector2 _maxMinScreenPos = new Vector2(); + protected final Vector3 _tempVec = new Vector3(); + protected double _minZ; + protected double _nearPlane; + protected double _farPlane; + protected Timer _timer; + + public QuadImposterNode() { + this(null, 64, 64); + } + + public QuadImposterNode(final String name, final int twidth, final int theight) { + this(name, twidth, theight, null); + } + + public QuadImposterNode(final String name, final int twidth, final int theight, final Timer timer) { + this(name, twidth, theight, 8, 0, timer); + } + + public QuadImposterNode(final String name, final int twidth, final int theight, final int depth, final int samples, + final Timer timer) { + super(name); + + _twidth = twidth; + _theight = theight; + _depth = depth; + _samples = samples; + + _timer = timer; + + _texture = new Texture2D(); + + _imposterQuad = new Quad("ImposterQuad"); + _imposterQuad.resize(1, 1); + _imposterQuad.setModelBound(new BoundingBox()); + _imposterQuad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + _imposterQuad.getSceneHints().setLightCombineMode(LightCombineMode.Off); + super.attachChild(_imposterQuad); + + getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + + _targetScene = new Node(); + super.attachChild(_targetScene); + + for (int i = 0; i < _corners.length; i++) { + _corners[i] = new Vector3(); + } + + if (timer != null) { + _redrawRate = _elapsed = 0.05; // 20x per sec + } else { + setCameraAngleThreshold(10.0); + setCameraDistanceThreshold(0.2); + } + _haveDrawn = false; + } + + @Override + public int attachChild(final Spatial child) { + return _targetScene.attachChild(child); + } + + @Override + public int attachChildAt(final Spatial child, final int index) { + return _targetScene.attachChildAt(child, index); + } + + @Override + public void detachAllChildren() { + _targetScene.detachAllChildren(); + } + + @Override + public int detachChild(final Spatial child) { + return _targetScene.detachChild(child); + } + + @Override + public Spatial detachChildAt(final int index) { + return _targetScene.detachChildAt(index); + } + + @Override + public int detachChildNamed(final String childName) { + return _targetScene.detachChildNamed(childName); + } + + private void init(final Renderer renderer) { + _tRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(_twidth, _theight, _depth, _samples, + renderer, ContextManager.getCurrentContext().getCapabilities()); + + _tRenderer.setBackgroundColor(new ColorRGBA(0, 0, 0, 0)); + resetTexture(); + } + + @Override + public void draw(final Renderer r) { + if (_timer != null && _redrawRate > 0) { + _elapsed += _timer.getTimePerFrame(); + } + + if (_tRenderer == null) { + init(r); + } + if (_cam == null) { + _cam = Camera.getCurrentCamera(); + + _tRenderer.getCamera().setFrustum(_cam.getFrustumNear(), _cam.getFrustumFar(), _cam.getFrustumLeft(), + _cam.getFrustumRight(), _cam.getFrustumTop(), _cam.getFrustumBottom()); + _tRenderer.getCamera().setFrame(_cam.getLocation(), _cam.getLeft(), _cam.getUp(), _cam.getDirection()); + } + + if (_doUpdate && (!_haveDrawn || shouldDoUpdate(_cam)) && _targetScene.getWorldBound() != null) { + final BoundingVolume b = _targetScene.getWorldBound(); + _center.set(b.getCenter()); + + updateCameraLookat(); + + calculateImposter(); + + updateCameraLookat(); + updateCameraFrustum(); + + renderImposter(); + + _haveDrawn = true; + } + + _imposterQuad.draw(r); + } + + @Override + protected void updateChildren(final double time) { + _imposterQuad.updateGeometricState(time, false); + if (_doUpdate && (!_haveDrawn || shouldDoUpdate(_cam))) { + _targetScene.updateGeometricState(time, false); + } + } + + private void calculateImposter() { + final BoundingVolume worldBound = _targetScene.getWorldBound(); + _center.set(worldBound.getCenter()); + + for (int i = 0; i < _corners.length; i++) { + _corners[i].set(_center); + } + + if (worldBound instanceof BoundingBox) { + final BoundingBox bbox = (BoundingBox) worldBound; + bbox.getExtent(_extents); + } else if (worldBound instanceof BoundingSphere) { + final BoundingSphere bsphere = (BoundingSphere) worldBound; + _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius()); + } + + _corners[0].addLocal(_extents.getX(), _extents.getY(), -_extents.getZ()); + _corners[1].addLocal(-_extents.getX(), _extents.getY(), -_extents.getZ()); + _corners[2].addLocal(_extents.getX(), -_extents.getY(), -_extents.getZ()); + _corners[3].addLocal(-_extents.getX(), -_extents.getY(), -_extents.getZ()); + _corners[4].addLocal(_extents.getX(), _extents.getY(), _extents.getZ()); + _corners[5].addLocal(-_extents.getX(), _extents.getY(), _extents.getZ()); + _corners[6].addLocal(_extents.getX(), -_extents.getY(), _extents.getZ()); + _corners[7].addLocal(-_extents.getX(), -_extents.getY(), _extents.getZ()); + + for (int i = 0; i < _corners.length; i++) { + _tRenderer.getCamera().getScreenCoordinates(_corners[i], _corners[i]); + } + + _minScreenPos.set(Double.MAX_VALUE, Double.MAX_VALUE); + _maxScreenPos.set(-Double.MAX_VALUE, -Double.MAX_VALUE); + _minZ = Double.MAX_VALUE; + for (int i = 0; i < _corners.length; i++) { + _minScreenPos.setX(Math.min(_corners[i].getX(), _minScreenPos.getX())); + _minScreenPos.setY(Math.min(_corners[i].getY(), _minScreenPos.getY())); + + _maxScreenPos.setX(Math.max(_corners[i].getX(), _maxScreenPos.getX())); + _maxScreenPos.setY(Math.max(_corners[i].getY(), _maxScreenPos.getY())); + + _minZ = Math.min(_corners[i].getZ(), _minZ); + } + _maxMinScreenPos.set(_maxScreenPos.getX(), _minScreenPos.getY()); + _minMaxScreenPos.set(_minScreenPos.getX(), _maxScreenPos.getY()); + + _tRenderer.getCamera().getWorldCoordinates(_maxScreenPos, _minZ, _corners[0]); + _tRenderer.getCamera().getWorldCoordinates(_maxMinScreenPos, _minZ, _corners[1]); + _tRenderer.getCamera().getWorldCoordinates(_minScreenPos, _minZ, _corners[2]); + _tRenderer.getCamera().getWorldCoordinates(_minMaxScreenPos, _minZ, _corners[3]); + _center.set(_corners[0]).addLocal(_corners[1]).addLocal(_corners[2]).addLocal(_corners[3]).multiplyLocal(0.25); + + _lastCamDir.set(_center).subtractLocal(_tRenderer.getCamera().getLocation()); + _lastCamDist = _nearPlane = _lastCamDir.length(); + _farPlane = _nearPlane + _extents.length() * 2.0; + _lastCamDir.normalizeLocal(); + + final FloatBuffer vertexBuffer = _imposterQuad.getMeshData().getVertexBuffer(); + BufferUtils.setInBuffer(_corners[0], vertexBuffer, 3); + BufferUtils.setInBuffer(_corners[1], vertexBuffer, 2); + BufferUtils.setInBuffer(_corners[2], vertexBuffer, 1); + BufferUtils.setInBuffer(_corners[3], vertexBuffer, 0); + + _imposterQuad.updateModelBound(); + } + + private void updateCameraLookat() { + _tRenderer.getCamera().setLocation(_cam.getLocation()); + _tRenderer.getCamera().lookAt(_center, _worldUpVector); + } + + private void updateCameraFrustum() { + final double width = _corners[2].subtractLocal(_corners[1]).length() / 2.0; + final double height = _corners[1].subtractLocal(_corners[0]).length() / 2.0; + + _tRenderer.getCamera().setFrustum(_nearPlane, _farPlane, -width, width, height, -height); + } + + private boolean shouldDoUpdate(final Camera cam) { + if (_redrawRate > 0 && _elapsed >= _redrawRate) { + _elapsed = _elapsed % _redrawRate; + return true; + } + + if (_cameraAngleThreshold > 0) { + _tempVec.set(_center).subtractLocal(cam.getLocation()); + + final double currentDist = _tempVec.length(); + if (_lastCamDist != 0 && Math.abs(currentDist - _lastCamDist) / _lastCamDist > _cameraDistanceThreshold) { + return true; + } + + _tempVec.normalizeLocal(); + final double angle = _tempVec.smallestAngleBetween(_lastCamDir); + if (angle > _cameraAngleThreshold) { + return true; + } + } + return false; + } + + public void setRedrawRate(final double rate) { + _redrawRate = _elapsed = rate; + } + + public double getCameraDistanceThreshold() { + return _cameraDistanceThreshold; + } + + public void setCameraDistanceThreshold(final double cameraDistanceThreshold) { + _cameraDistanceThreshold = cameraDistanceThreshold; + } + + public double getCameraAngleThreshold() { + return _cameraAngleThreshold; + } + + public void setCameraAngleThreshold(final double cameraAngleThreshold) { + _cameraAngleThreshold = cameraAngleThreshold; + } + + public void resetTexture() { + _texture.setWrap(Texture.WrapMode.EdgeClamp); + _texture.setMinificationFilter(Texture.MinificationFilter.BilinearNoMipMaps); + _texture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear); + _texture.setTextureStoreFormat(TextureStoreFormat.RGBA8); + _tRenderer.setupTexture(_texture); + final TextureState ts = new TextureState(); + ts.setEnabled(true); + ts.setTexture(_texture, 0); + _imposterQuad.setRenderState(ts); + + // Add a blending mode... This is so the background of the texture is + // transparent. + final BlendState as1 = new BlendState(); + as1.setBlendEnabled(true); + as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + as1.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + as1.setTestEnabled(true); + as1.setTestFunction(BlendState.TestFunction.GreaterThan); + as1.setEnabled(true); + _imposterQuad.setRenderState(as1); + } + + public void renderImposter() { + _tRenderer.render(_targetScene, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + } + + public Vector3 getWorldUpVector() { + return _worldUpVector; + } + + public void setWorldUpVector(final Vector3 worldUpVector) { + _worldUpVector = worldUpVector; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_texture, "texture", null); + capsule.write(_targetScene, "targetScene", null); + capsule.write(_imposterQuad, "standIn", new Quad("ImposterQuad")); + capsule.write(_redrawRate, "redrawRate", 0.05f); + capsule.write(_cameraAngleThreshold, "cameraThreshold", 0); + capsule.write(_worldUpVector, "worldUpVector", new Vector3(Vector3.UNIT_Y)); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _texture = (Texture2D) capsule.readSavable("texture", null); + _targetScene = (Node) capsule.readSavable("targetScene", null); + _imposterQuad = (Quad) capsule.readSavable("standIn", new Quad("ImposterQuad")); + _redrawRate = capsule.readFloat("redrawRate", 0.05f); + _cameraAngleThreshold = capsule.readFloat("cameraThreshold", 0); + _worldUpVector = (Vector3) capsule.readSavable("worldUpVector", new Vector3(Vector3.UNIT_Y)); + } + + public Texture getTexture() { + return _texture; + } + + public void setDoUpdate(final boolean doUpdate) { + _doUpdate = doUpdate; + } + + public boolean isDoUpdate() { + return _doUpdate; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java new file mode 100644 index 0000000..c396e0b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java @@ -0,0 +1,257 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.extension; + +import java.io.IOException; + +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture.WrapMode; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.FogState; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.util.export.CapsuleUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * A Box made of textured quads that simulate having a sky, horizon and so forth around your scene. Either attach to a + * camera node or update on each frame to set this skybox at the camera's position. + */ +public class Skybox extends Node { + + public enum Face { + /** The +Z side of the skybox. */ + North, + /** The -Z side of the skybox. */ + South, + /** The -X side of the skybox. */ + East, + /** The +X side of the skybox. */ + West, + /** The +Y side of the skybox. */ + Up, + /** The -Y side of the skybox. */ + Down; + } + + private float _xExtent; + private float _yExtent; + private float _zExtent; + + private Quad[] _skyboxQuads; + + public Skybox() {} + + /** + * Creates a new skybox. The size of the skybox and name is specified here. By default, no textures are set. + * + * @param name + * The name of the skybox. + * @param xExtent + * The x size of the skybox in both directions from the center. + * @param yExtent + * The y size of the skybox in both directions from the center. + * @param zExtent + * The z size of the skybox in both directions from the center. + */ + public Skybox(final String name, final float xExtent, final float yExtent, final float zExtent) { + super(name); + + _xExtent = xExtent; + _yExtent = yExtent; + _zExtent = zExtent; + + initialize(); + } + + /** + * Set the texture to be displayed on the given face of the skybox. Replaces any existing texture on that face. + * + * @param face + * the face to set + * @param texture + * The texture for that side to assume. + * @throws IllegalArgumentException + * if face is null. + */ + public void setTexture(final Face face, final Texture texture) { + if (face == null) { + throw new IllegalArgumentException("Face can not be null."); + } + + _skyboxQuads[face.ordinal()].clearRenderState(RenderState.StateType.Texture); + setTexture(face, texture, 0); + } + + /** + * Set the texture to be displayed on the given side of the skybox. Only replaces the texture at the index specified + * by textureUnit. + * + * @param face + * the face to set + * @param texture + * The texture for that side to assume. + * @param textureUnit + * The texture unite of the given side's TextureState the texture will assume. + */ + public void setTexture(final Face face, final Texture texture, final int textureUnit) { + // Validate + if (face == null) { + throw new IllegalArgumentException("Face can not be null."); + } + + TextureState ts = (TextureState) _skyboxQuads[face.ordinal()] + .getLocalRenderState(RenderState.StateType.Texture); + if (ts == null) { + ts = new TextureState(); + } + + // Initialize the texture state + ts.setTexture(texture, textureUnit); + ts.setEnabled(true); + + texture.setWrap(WrapMode.EdgeClamp); + + // Set the texture to the quad + _skyboxQuads[face.ordinal()].setRenderState(ts); + + return; + } + + public Texture getTexture(final Face face) { + if (face == null) { + throw new IllegalArgumentException("Face can not be null."); + } + return ((TextureState) _skyboxQuads[face.ordinal()].getLocalRenderState(RenderState.StateType.Texture)) + .getTexture(); + } + + public void initialize() { + + // Skybox consists of 6 sides + _skyboxQuads = new Quad[6]; + + // Create each of the quads + _skyboxQuads[Face.North.ordinal()] = new Quad("north", _xExtent * 2, _yExtent * 2); + _skyboxQuads[Face.North.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(180), 0)); + _skyboxQuads[Face.North.ordinal()].setTranslation(new Vector3(0, 0, _zExtent)); + _skyboxQuads[Face.South.ordinal()] = new Quad("south", _xExtent * 2, _yExtent * 2); + _skyboxQuads[Face.South.ordinal()].setTranslation(new Vector3(0, 0, -_zExtent)); + _skyboxQuads[Face.East.ordinal()] = new Quad("east", _zExtent * 2, _yExtent * 2); + _skyboxQuads[Face.East.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(90), 0)); + _skyboxQuads[Face.East.ordinal()].setTranslation(new Vector3(-_xExtent, 0, 0)); + _skyboxQuads[Face.West.ordinal()] = new Quad("west", _zExtent * 2, _yExtent * 2); + _skyboxQuads[Face.West.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(270), 0)); + _skyboxQuads[Face.West.ordinal()].setTranslation(new Vector3(_xExtent, 0, 0)); + _skyboxQuads[Face.Up.ordinal()] = new Quad("up", _xExtent * 2, _zExtent * 2); + _skyboxQuads[Face.Up.ordinal()] + .setRotation(new Matrix3().fromAngles(Math.toRadians(90), Math.toRadians(270), 0)); + _skyboxQuads[Face.Up.ordinal()].setTranslation(new Vector3(0, _yExtent, 0)); + _skyboxQuads[Face.Down.ordinal()] = new Quad("down", _xExtent * 2, _zExtent * 2); + _skyboxQuads[Face.Down.ordinal()].setRotation(new Matrix3().fromAngles(Math.toRadians(270), + Math.toRadians(270), 0)); + _skyboxQuads[Face.Down.ordinal()].setTranslation(new Vector3(0, -_yExtent, 0)); + + // We don't want the light to effect our skybox + getSceneHints().setLightCombineMode(LightCombineMode.Off); + + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + + final ZBufferState zbuff = new ZBufferState(); + zbuff.setEnabled(false); + setRenderState(zbuff); + + final FogState fs = new FogState(); + fs.setEnabled(false); + setRenderState(fs); + + // We don't want it making our skybox disapear, so force view + getSceneHints().setCullHint(CullHint.Never); + + for (int i = 0; i < 6; i++) { + // Make sure texture is only what is set. + _skyboxQuads[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + + // Make sure no lighting on the skybox + _skyboxQuads[i].getSceneHints().setLightCombineMode(LightCombineMode.Off); + + // Make sure the quad is viewable + _skyboxQuads[i].getSceneHints().setCullHint(CullHint.Never); + + // Add to the prebucket. + _skyboxQuads[i].getSceneHints().setRenderBucketType(RenderBucketType.PreBucket); + + // And attach the skybox as a child + attachChild(_skyboxQuads[i]); + } + } + + /** + * Retrieve the quad indicated by the given side. + * + * @param face + * One of Skybox.Face.North, Skybox.Face.South, and so on... + * @return The Quad that makes up that side of the Skybox. + */ + public Quad getFace(final Face face) { + return _skyboxQuads[face.ordinal()]; + } + + public void preloadTexture(final Face face, final Renderer r) { + final TextureState ts = (TextureState) _skyboxQuads[face.ordinal()] + .getLocalRenderState(RenderState.StateType.Texture); + if (ts != null) { + r.applyState(StateType.Texture, ts); + } + } + + /** + * Force all of the textures to load. This prevents pauses later during the application as you pan around the world. + */ + public void preloadTextures(final Renderer r) { + for (int x = 0; x < 6; x++) { + final TextureState ts = (TextureState) _skyboxQuads[x].getLocalRenderState(RenderState.StateType.Texture); + if (ts != null) { + r.applyState(StateType.Texture, ts); + } + } + + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_xExtent, "xExtent", 0); + capsule.write(_yExtent, "yExtent", 0); + capsule.write(_zExtent, "zExtent", 0); + capsule.write(_skyboxQuads, "skyboxQuads", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _xExtent = capsule.readFloat("xExtent", 0); + _yExtent = capsule.readFloat("yExtent", 0); + _zExtent = capsule.readFloat("zExtent", 0); + _skyboxQuads = CapsuleUtils.asArray(capsule.readSavableArray("skyboxQuads", null), Quad.class); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java new file mode 100644 index 0000000..532dace --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java @@ -0,0 +1,142 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.util.BitSet;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+
+public class SwitchNode extends Node {
+
+ protected BitSet _childMask = new BitSet();
+
+ public SwitchNode() {
+ this("SwitchNode");
+ }
+
+ public SwitchNode(final String name) {
+ super(name);
+
+ _childMask.set(0);
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ if (_children == null) {
+ return;
+ }
+ for (int i = 0, max = Math.min(_childMask.length(), _children.size()); i < max; i++) {
+ if (_childMask.get(i)) {
+ final Spatial child = _children.get(i);
+ if (child != null) {
+ child.onDraw(r);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void updateChildren(final double time) {
+ if (_children == null) {
+ return;
+ }
+ for (int i = 0, max = Math.min(_childMask.length(), _children.size()); i < max; i++) {
+ if (_childMask.get(i)) {
+ final Spatial child = _children.get(i);
+ if (child != null) {
+ child.updateGeometricState(time, false);
+ }
+ }
+ }
+ }
+
+ public void setAllNonVisible() {
+ _childMask.clear();
+ }
+
+ public void setAllVisible() {
+ _childMask.set(0, getNumberOfChildren());
+ }
+
+ public void flipAllVisible() {
+ _childMask.flip(0, getNumberOfChildren());
+ }
+
+ public boolean getVisible(final int bitIndex) {
+ return _childMask.get(bitIndex);
+ }
+
+ public BitSet getVisible() {
+ return _childMask;
+ }
+
+ public void setVisible(final BitSet set) {
+ _childMask = set;
+ }
+
+ public void setVisible(final int bitIndex, final boolean value) {
+ _childMask.set(bitIndex, value);
+ }
+
+ public void setVisible(final int fromIndex, final int toIndex, final boolean value) {
+ _childMask.set(fromIndex, toIndex, value);
+ }
+
+ public void setSingleVisible(final int bitIndex) {
+ _childMask.clear();
+ _childMask.set(bitIndex);
+ }
+
+ public int getNextNonVisible(final int fromIndex) {
+ return _childMask.nextClearBit(fromIndex);
+ }
+
+ public int getNextVisible(final int fromIndex) {
+ return _childMask.nextSetBit(fromIndex);
+ }
+
+ public void shiftVisibleRight() {
+ final int nrChildren = getNumberOfChildren();
+ if (nrChildren == 0) {
+ return;
+ }
+
+ final boolean lastVal = _childMask.get(nrChildren - 1);
+ for (int i = nrChildren - 1; i > 0; i--) {
+ _childMask.set(i, _childMask.get(i - 1));
+ }
+ _childMask.set(0, lastVal);
+ }
+
+ public void shiftVisibleLeft() {
+ final int nrChildren = getNumberOfChildren();
+ if (nrChildren == 0) {
+ return;
+ }
+
+ final boolean firstVal = _childMask.get(0);
+ for (int i = 0; i < nrChildren - 1; i++) {
+ _childMask.set(i, _childMask.get(i + 1));
+ }
+ _childMask.set(getNumberOfChildren() - 1, firstVal);
+ }
+
+ public void flipVisible(final int fromIndex, final int toIndex) {
+ _childMask.flip(fromIndex, toIndex);
+ }
+
+ public void flipVisible(final int bitIndex) {
+ _childMask.flip(bitIndex);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java new file mode 100644 index 0000000..74a9399 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +/** + * + */ + +package com.ardor3d.scenegraph.hint; + +/** + * Describes how a scene object interacts with Ardor3D's frustum culling. + */ +public enum CullHint { + + /** + * Do whatever our parent does. If no parent, we'll default to dynamic. + */ + Inherit, + + /** + * Do not draw if we are not at least partially within the view frustum of the renderer's camera. + */ + Dynamic, + + /** + * Always cull this from view. + */ + Always, + + /** + * Never cull this from view. Note that we will still get culled if our parent is culled. + */ + Never; +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java new file mode 100644 index 0000000..ba4673d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.hint; + +/** + * Enum used to describe how we prefer data to be sent to the card. + */ +public enum DataMode { + /** + * Use our parent's DataMode. If we do not have a parent, Arrays will be used. + */ + Inherit, + + /** + * Send each data buffer to the card using vertex arrays. + */ + Arrays, + + /** + * Send each data buffer to the card using vertex buffer objects. This is usually faster than Arrays, but may not be + * supported on older cards. If not supported, Arrays is used by the Renderer. + */ + VBO, + + /** + * Send each data buffer to the card using a combined vertex buffer object(s). Usually this is done by combining all + * FloatBufferData buffers in one buffer sequentially, but if their types are different, multiple buffers might be + * used instead. This is usually a bit faster than just VBO. If not supported, Arrays is used by the Renderer. + */ + VBOInterleaved; +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java new file mode 100644 index 0000000..0df2795 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.hint; + +/** + * Interface used for describing objects that deal with the SceneHints class. This interface allows non-specific access + * to object hierarchy. + */ +public interface Hintable { + + /** + * @return a hierarchical parent to be used for determining inheritance of certain SceneHint fields. + */ + Hintable getParentHintable(); + + /** + * @return the SceneHints object used by this Hintable. + */ + SceneHints getSceneHints(); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java new file mode 100644 index 0000000..a8e4f18 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java @@ -0,0 +1,34 @@ + +package com.ardor3d.scenegraph.hint; + +/** + * Describes how to combine lights from ancestor LightStates. + */ +public enum LightCombineMode { + /** When updating render states, turn off lighting for this spatial. */ + Off, + + /** + * Combine lights starting from the root node and working towards the given Spatial. Ignore disabled states. Stop + * combining when lights == MAX_LIGHTS_ALLOWED + */ + CombineFirst, + + /** + * Combine lights starting from the given Spatial and working up towards the root. Ignore disabled states. Stop + * combining when lights == MAX_LIGHTS_ALLOWED + */ + CombineClosest, + + /** + * Similar to CombineClosest, but if a disabled state is encountered, it will stop combining at that point. Stop + * combining when lights == MAX_LIGHTS_ALLOWED + */ + CombineClosestEnabled, + + /** Inherit mode from parent. */ + Inherit, + + /** Do not combine lights, just use the most recent light state. */ + Replace; +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java new file mode 100644 index 0000000..a6873c0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java @@ -0,0 +1,39 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+public enum NormalsMode {
+
+ /**
+ * Do whatever our parent does. If no parent, we'll default to NormalizeIfScaled.
+ */
+ Inherit,
+
+ /**
+ * Send through the normals currently set as-is.
+ */
+ UseProvided,
+
+ /**
+ * Tell the card to normalize any normals data we might give it.
+ */
+ AlwaysNormalize,
+
+ /**
+ * If a scale other than 1,1,1 is being used then tell the card to normalize any normals data we might give it.
+ */
+ NormalizeIfScaled,
+
+ /**
+ * Do not send normal data to the card, even if we have some.
+ */
+ Off;
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java new file mode 100644 index 0000000..54e4098 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +/** + * + */ + +package com.ardor3d.scenegraph.hint; + +public enum PickingHint { + /** + * Scene object can be included in results from pick operations. + */ + Pickable, + + /** + * Scene object can be included in results from collision checks. + */ + Collidable +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java new file mode 100644 index 0000000..36a239c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java @@ -0,0 +1,493 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.hint; + +import java.io.IOException; +import java.util.EnumSet; + +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * SceneHints encapsulates various rendering and interaction preferences for a scene object. + */ +public class SceneHints implements Savable { + + /** + * How we want the data for a subset of the scenegraph to be sent to the card. + */ + protected DataMode _dataMode = DataMode.Inherit; + + /** + * A flag indicating how normals should be treated by the renderer. + */ + protected NormalsMode _normalsMode = NormalsMode.Inherit; + + /** + * A flag indicating if scene culling should be done on this object by inheritance, dynamically, never, or always. + */ + protected CullHint _cullHint = CullHint.Inherit; + + /** + * Flag signaling how lights are combined for this node. By default set to INHERIT. + */ + protected LightCombineMode _lightCombineMode = LightCombineMode.Inherit; + + /** + * Flag signaling how textures are combined for this node. By default set to INHERIT. + */ + protected TextureCombineMode _textureCombineMode = TextureCombineMode.Inherit; + + /** + * RenderBucketType for this spatial + */ + protected RenderBucketType _renderBucketType = RenderBucketType.Inherit; + + /** + * Draw order to use when drawing in ortho mode. + */ + protected int _orthoOrder = 0; + + /** + * Field for setting whether the Spatial is enabled for Picking or Collision. Both are enabled by default. + */ + protected final EnumSet<PickingHint> _pickingHints = EnumSet.allOf(PickingHint.class); + + /** + * The source for this SceneHints. + */ + private final Hintable _source; + + /** + * Type of transparency to do. + */ + private TransparencyType _transpType = TransparencyType.Inherit; + + /** + * Hint for shadow implementations + */ + protected boolean _castsShadows = true; + + public SceneHints(final Hintable source) { + _source = source; + } + + public void set(final SceneHints sceneHints) { + _dataMode = sceneHints._dataMode; + _normalsMode = sceneHints._normalsMode; + _cullHint = sceneHints._cullHint; + _lightCombineMode = sceneHints._lightCombineMode; + _textureCombineMode = sceneHints._textureCombineMode; + _renderBucketType = sceneHints._renderBucketType; + _orthoOrder = sceneHints._orthoOrder; + _pickingHints.clear(); + _pickingHints.addAll(sceneHints._pickingHints); + _castsShadows = sceneHints._castsShadows; + _transpType = sceneHints._transpType; + } + + /** + * Returns the normals mode. If the mode is set to inherit, then we get its normals mode from the given source's + * hintable parent. If no parent, we'll default to NormalizeIfScaled. + * + * @return The normals mode to use. + */ + public DataMode getDataMode() { + if (_dataMode != DataMode.Inherit) { + return _dataMode; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getDataMode(); + } + + return DataMode.Arrays; + } + + /** + * @return the exact data mode set. + */ + public DataMode getLocalDataMode() { + return _dataMode; + } + + /** + * @param type + * the new data mode to set on this SceneHints + */ + public void setDataMode(final DataMode type) { + _dataMode = type; + } + + /** + * Returns the normals mode. If the mode is set to inherit, then we get its normals mode from the given source's + * hintable parent. If no parent, we'll default to NormalizeIfScaled. + * + * @return The normals mode to use. + */ + public NormalsMode getNormalsMode() { + if (_normalsMode != NormalsMode.Inherit) { + return _normalsMode; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getNormalsMode(); + } + + return NormalsMode.NormalizeIfScaled; + } + + /** + * @return the exact normals mode set. + */ + public NormalsMode getLocalNormalsMode() { + return _normalsMode; + } + + /** + * @param mode + * the new normals mode to set on this SceneHints + */ + public void setNormalsMode(final NormalsMode mode) { + _normalsMode = mode; + } + + /** + * @see #setCullHint(CullHint) + * @return the cull mode of this spatial, or if set to INHERIT, the cullmode of its parent. + */ + public CullHint getCullHint() { + if (_cullHint != CullHint.Inherit) { + return _cullHint; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getCullHint(); + } + + return CullHint.Dynamic; + } + + /** + * @return the cullmode set on this Spatial + */ + public CullHint getLocalCullHint() { + return _cullHint; + } + + /** + * <code>setCullHint</code> sets how scene culling should work on this spatial during drawing. CullHint.Dynamic: + * Determine via the defined Camera planes whether or not this Spatial should be culled. CullHint.Always: Always + * throw away this object and any children during draw commands. CullHint.Never: Never throw away this object + * (always draw it) CullHint.Inherit: Look for a non-inherit parent and use its cull mode. NOTE: You must set this + * AFTER attaching to a parent or it will be reset with the parent's cullMode value. + * + * @param hint + * one of CullHint.Dynamic, CullHint.Always, CullHint.Inherit or CullHint.Never + */ + public void setCullHint(final CullHint hint) { + _cullHint = hint; + } + + /** + * Returns this spatial's texture combine mode. If the mode is set to inherit, then the spatial gets its combine + * mode from its parent. + * + * @return The spatial's texture current combine mode. + */ + public TextureCombineMode getTextureCombineMode() { + if (_textureCombineMode != TextureCombineMode.Inherit) { + return _textureCombineMode; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getTextureCombineMode(); + } + + return TextureCombineMode.CombineClosest; + } + + /** + * @return the textureCombineMode set on this Spatial + */ + public TextureCombineMode getLocalTextureCombineMode() { + return _textureCombineMode; + } + + /** + * Sets how textures from parents should be combined for this Spatial. + * + * @param mode + * The new texture combine mode for this spatial. + * @throws IllegalArgumentException + * if mode is null + */ + public void setTextureCombineMode(final TextureCombineMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + _textureCombineMode = mode; + } + + /** + * Returns this spatial's light combine mode. If the mode is set to inherit, then the spatial gets its combine mode + * from its parent. + * + * @return The spatial's light current combine mode. + */ + public LightCombineMode getLightCombineMode() { + if (_lightCombineMode != LightCombineMode.Inherit) { + return _lightCombineMode; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getLightCombineMode(); + } + + return LightCombineMode.CombineFirst; + } + + /** + * @return the lightCombineMode set on this Spatial + */ + public LightCombineMode getLocalLightCombineMode() { + return _lightCombineMode; + } + + /** + * Sets how lights from parents should be combined for this spatial. + * + * @param mode + * The light combine mode for this spatial + * @throws IllegalArgumentException + * if mode is null + */ + public void setLightCombineMode(final LightCombineMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode can not be null."); + } + _lightCombineMode = mode; + } + + /** + * Get the render bucket type used to determine which "phase" of the rendering process this Spatial will rendered + * in. + * <p> + * This method returns the effective bucket type that is used for rendering. If the type is set to + * {@link com.ardor3d.renderer.queue.RenderBucketType#Inherit Inherit} then the bucket type from the spatial's + * parent will be used during rendering. If no parent, then + * {@link com.ardor3d.renderer.queue.RenderBucketType#Opaque Opaque} is used. + * + * @return the render queue mode used for this spatial. + * @see com.ardor3d.renderer.queue.RenderBucketType + */ + public RenderBucketType getRenderBucketType() { + if (_renderBucketType != RenderBucketType.Inherit) { + return _renderBucketType; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getRenderBucketType(); + } + + return RenderBucketType.Opaque; + } + + /** + * Get the render bucket type used to determine which "phase" of the rendering process this Spatial will rendered + * in. + * <p> + * This method returns the actual bucket type that is set on this spatial, if the type is set to + * {@link com.ardor3d.renderer.queue.RenderBucketType#Inherit Inherit} then the bucket type from the spatial's + * parent will be used during rendering. If no parent, then + * {@link com.ardor3d.renderer.queue.RenderBucketType#Opaque Opaque} is used. + * + * @return the render queue mode set on this spatial. + * @see com.ardor3d.renderer.queue.RenderBucketType + */ + public RenderBucketType getLocalRenderBucketType() { + return _renderBucketType; + } + + /** + * Set the render bucket type used to determine which "phase" of the rendering process this Spatial will rendered + * in. + * + * @param renderBucketType + * the render bucket type to use for this spatial. + * @see com.ardor3d.renderer.queue.RenderBucketType + */ + public void setRenderBucketType(final RenderBucketType renderBucketType) { + _renderBucketType = renderBucketType; + } + + /** + * Returns whether a certain pick hint is set on this spatial. + * + * @param pickingHint + * Pick hint to test for + * @return Enabled or disabled + */ + public boolean isPickingHintEnabled(final PickingHint pickingHint) { + return _pickingHints.contains(pickingHint); + } + + /** + * Enable or disable a picking hint for this Spatial + * + * @param pickingHint + * PickingHint to set. Pickable or Collidable + * @param enabled + * Enable or disable + */ + public void setPickingHint(final PickingHint pickingHint, final boolean enabled) { + if (enabled) { + _pickingHints.add(pickingHint); + } else { + _pickingHints.remove(pickingHint); + } + } + + /** + * Enable or disable all picking hints for this Spatial + * + * @param enabled + * Enable or disable + */ + public void setAllPickingHints(final boolean enabled) { + if (enabled) { + _pickingHints.addAll(EnumSet.allOf(PickingHint.class)); + } else { + _pickingHints.clear(); + } + } + + /** + * @return a number representing z ordering when used in the Ortho bucket. Higher values are + * "further into the screen" and lower values are "closer". Or in other words, if you draw two quads, one + * with a zorder of 1 and the other with a zorder of 2, the quad with zorder of 2 will be "under" the other + * quad. + */ + public int getOrthoOrder() { + return _orthoOrder; + } + + /** + * @param orthoOrder + */ + public void setOrthoOrder(final int orthoOrder) { + _orthoOrder = orthoOrder; + } + + /** + * Returns the transparency rendering type. If the mode is set to inherit, then we get its type from the given + * source's hintable parent. If no parent, we'll default to OnePass. + * + * @return The transparency rendering type to use. + */ + public TransparencyType getTransparencyType() { + if (_transpType != TransparencyType.Inherit) { + return _transpType; + } + + final Hintable parent = _source.getParentHintable(); + if (parent != null) { + return parent.getSceneHints().getTransparencyType(); + } + + return TransparencyType.OnePass; + } + + /** + * @return the exact transparency rendering type set. + */ + public TransparencyType getLocalTransparencyType() { + return _transpType; + } + + /** + * @param type + * the new transparency rendering type to set on this SceneHints + */ + public void setTransparencyType(final TransparencyType type) { + _transpType = type; + } + + /** + * @return true if this object should cast shadows + */ + public boolean isCastsShadows() { + return _castsShadows; + } + + /** + * @param castsShadows + * set if this object should cast shadows + */ + public void setCastsShadows(final boolean castsShadows) { + _castsShadows = castsShadows; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public Class<? extends SceneHints> getClassTag() { + return this.getClass(); + } + + public void read(final InputCapsule capsule) throws IOException { + _orthoOrder = capsule.readInt("orthoOrder", 0); + _cullHint = capsule.readEnum("cullMode", CullHint.class, CullHint.Inherit); + final String bucketTypeName = capsule.readString("renderBucketType", RenderBucketType.Inherit.name()); + _renderBucketType = RenderBucketType.getRenderBucketType(bucketTypeName); + _lightCombineMode = capsule.readEnum("lightCombineMode", LightCombineMode.class, LightCombineMode.Inherit); + _textureCombineMode = capsule.readEnum("textureCombineMode", TextureCombineMode.class, + TextureCombineMode.Inherit); + _normalsMode = capsule.readEnum("normalsMode", NormalsMode.class, NormalsMode.Inherit); + _dataMode = capsule.readEnum("dataMode", DataMode.class, DataMode.Inherit); + _transpType = capsule.readEnum("transpType", TransparencyType.class, TransparencyType.Inherit); + _castsShadows = capsule.readBoolean("castsShadows", true); + final PickingHint[] pickHints = capsule.readEnumArray("pickingHints", PickingHint.class, null); + _pickingHints.clear(); + if (pickHints != null) { + for (final PickingHint hint : pickHints) { + _pickingHints.add(hint); + } + } else { + // default is all values set. + for (final PickingHint hint : PickingHint.values()) { + _pickingHints.add(hint); + } + } + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_orthoOrder, "orthoOrder", 0); + capsule.write(_cullHint, "cullMode", CullHint.Inherit); + capsule.write(_renderBucketType.name(), "renderBucketType", RenderBucketType.Inherit.name()); + capsule.write(_lightCombineMode, "lightCombineMode", LightCombineMode.Inherit); + capsule.write(_textureCombineMode, "textureCombineMode", TextureCombineMode.Inherit); + capsule.write(_normalsMode, "normalsMode", NormalsMode.Inherit); + capsule.write(_dataMode, "dataMode", DataMode.Inherit); + capsule.write(_pickingHints.toArray(new PickingHint[] {}), "pickingHints"); + capsule.write(_transpType, "transpType", TransparencyType.Inherit); + capsule.write(_castsShadows, "castsShadows", true); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java new file mode 100644 index 0000000..49e3fa9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java @@ -0,0 +1,31 @@ + +package com.ardor3d.scenegraph.hint; + +/** + * Describes how to combine textures from ancestor TextureStates. + */ +public enum TextureCombineMode { + /** When updating render states, turn off texturing for this spatial. */ + Off, + + /** + * Combine textures starting from the root node and working towards the given Spatial. Ignore disabled states. + */ + CombineFirst, + + /** + * Combine textures starting from the given Spatial and working towards the root. Ignore disabled states. (Default) + */ + CombineClosest, + + /** + * Similar to CombineClosest, but if a disabled state is encountered, it will stop combining at that point. + */ + CombineClosestEnabled, + + /** Inherit mode from parent. */ + Inherit, + + /** Do not combine textures, just use the most recent texture state. */ + Replace; +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java new file mode 100644 index 0000000..71520a4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.hint; + +public enum TransparencyType { + + /** + * Do whatever our parent does. If no parent, we'll default to OnePass. + */ + Inherit, + + /** + * Single pass. Best for most circumstances. + */ + OnePass, + + /** + * Two passes, one with CullState enforced to Front and another with it enforced to Back. The back face pass will + * not write to depth buffer. The front face will use the ZBufferState from the scene or enforced on the context. + */ + TwoPass; + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java new file mode 100644 index 0000000..e1e8e7d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>Arrow</code> is basically a cylinder with a pyramid on top. + */ +public class Arrow extends Node { + + protected double _length = 1; + protected double _width = .25; + + protected static final Quaternion rotator = new Quaternion().applyRotationX(MathUtils.HALF_PI); + + public Arrow() {} + + public Arrow(final String name) { + super(name); + } + + public Arrow(final String name, final double length, final double width) { + super(name); + _length = length; + _width = width; + + buildArrow(); + } + + public void buildArrow() { + // Start with cylinders: + final Cylinder base = new Cylinder("base", 4, 16, _width * .75, _length); + base.getMeshData().rotatePoints(rotator); + base.getMeshData().rotateNormals(rotator); + attachChild(base); + base.updateModelBound(); + + final Pyramid tip = new Pyramid("tip", 2 * _width, _length / 2f); + tip.getMeshData().translatePoints(0, _length * .75, 0); + attachChild(tip); + tip.updateModelBound(); + } + + public double getLength() { + return _length; + } + + public void setLength(final double length) { + _length = length; + } + + public double getWidth() { + return _width; + } + + public void setWidth(final double width) { + _width = width; + } + + public void setSolidColor(final ReadOnlyColorRGBA color) { + for (int x = 0; x < getNumberOfChildren(); x++) { + if (getChild(x) instanceof Mesh) { + ((Mesh) getChild(x)).setSolidColor(color); + } + } + } + + public void setDefaultColor(final ReadOnlyColorRGBA color) { + for (int x = 0; x < getNumberOfChildren(); x++) { + if (getChild(x) instanceof Mesh) { + ((Mesh) getChild(x)).setDefaultColor(color); + } + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_length, "length", 1); + capsule.write(_width, "width", .25); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _length = capsule.readDouble("length", 1); + _width = capsule.readDouble("width", .25); + + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java new file mode 100644 index 0000000..3d2914b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>AxisRods</code> is a convenience shape representing three axes in space. + */ +public class AxisRods extends Node { + + protected static final ColorRGBA xAxisColor = new ColorRGBA(1, 0, 0, .4f); + protected static final ColorRGBA yAxisColor = new ColorRGBA(0, 1, 0, .25f); + protected static final ColorRGBA zAxisColor = new ColorRGBA(0, 0, 1, .4f); + + protected double length; + protected double width; + protected boolean rightHanded; + + protected Arrow xAxis; + protected Arrow yAxis; + protected Arrow zAxis; + + public AxisRods() {} + + public AxisRods(final String name) { + this(name, true, 1); + } + + public AxisRods(final String name, final boolean rightHanded, final double baseScale) { + this(name, rightHanded, baseScale, baseScale * 0.125); + } + + public AxisRods(final String name, final boolean rightHanded, final double length, final double width) { + super(name); + this.length = length; + this.width = width; + this.rightHanded = rightHanded; + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Off); + + buildAxis(); + } + + protected void buildAxis() { + xAxis = new Arrow("_xAxis", length, width); + xAxis.setDefaultColor(xAxisColor); + xAxis.setRotation(new Matrix3().fromAngles(0, 0, -90 * MathUtils.DEG_TO_RAD)); + xAxis.setTranslation(length * .5, 0, 0); + attachChild(xAxis); + + yAxis = new Arrow("yAxis", length, width); + yAxis.setDefaultColor(yAxisColor); + yAxis.setTranslation(0, length * .5, 0); + attachChild(yAxis); + + zAxis = new Arrow("zAxis", length, width); + zAxis.setDefaultColor(zAxisColor); + if (rightHanded) { + zAxis.setRotation(new Matrix3().fromAngles(90 * MathUtils.DEG_TO_RAD, 0, 0)); + zAxis.setTranslation(0, 0, length * .5); + } else { + zAxis.setRotation(new Matrix3().fromAngles(-90 * MathUtils.DEG_TO_RAD, 0, 0)); + zAxis.setTranslation(0, 0, -length * .5); + } + attachChild(zAxis); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(length, "length", 1); + capsule.write(width, "width", 0.125); + capsule.write(rightHanded, "rightHanded", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + length = capsule.readDouble("length", 1); + width = capsule.readDouble("width", 0.125); + rightHanded = capsule.readBoolean("rightHanded", true); + buildAxis(); + } + + public double getLength() { + return length; + } + + public void setLength(final double length) { + this.length = length; + } + + public double getWidth() { + return width; + } + + public void setWidth(final double width) { + this.width = width; + } + + public Arrow getxAxis() { + return xAxis; + } + + public Arrow getyAxis() { + return yAxis; + } + + public Arrow getzAxis() { + return zAxis; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java new file mode 100644 index 0000000..4b4bfdd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java @@ -0,0 +1,329 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Box</code> is an axis-aligned rectangular prism defined by a center point and x, y, and z extents from that + * center (essentially radii.) + */ +public class Box extends Mesh { + + private double _xExtent, _yExtent, _zExtent; + + private final Vector3 _center = new Vector3(0, 0, 0); + + /** + * Constructs a new 1x1x1 <code>Box</code>. + */ + public Box() { + this("unnamed Box"); + } + + /** + * Constructs a new 1x1x1 <code>Box</code> with the given name. + * + * @param name + * the name to give this new box. This is required for identification and comparison purposes. + */ + public Box(final String name) { + super(name); + setData(Vector3.ZERO, 0.5, 0.5, 0.5); + } + + /** + * Constructs a new <code>Box</code> object using the given two points as opposite corners of the box. These two + * points may be in any order. + * + * @param name + * the name to give this new box. This is required for identification and comparison purposes. + * @param pntA + * the first point + * @param pntB + * the second point. + */ + public Box(final String name, final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) { + super(name); + setData(pntA, pntB); + } + + /** + * Constructs a new <code>Box</code> object using the given center and extents. Since the extents represent the + * distance from the center of the box to the edge, the full length of a side is actually 2 * extent. + * + * @param name + * the name to give this new box. This is required for identification and comparison purposes. + * @param center + * Center of the box. + * @param xExtent + * x extent of the box + * @param yExtent + * y extent of the box + * @param zExtent + * z extent of the box + */ + public Box(final String name, final ReadOnlyVector3 center, final double xExtent, final double yExtent, + final double zExtent) { + super(name); + setData(center, xExtent, yExtent, zExtent); + } + + /** + * @return the current center of this box. + */ + public ReadOnlyVector3 getCenter() { + return _center; + } + + /** + * @return the current X extent of this box. + */ + public double getXExtent() { + return _xExtent; + } + + /** + * @return the current Y extent of this box. + */ + public double getYExtent() { + return _yExtent; + } + + /** + * @return the current Z extent of this box. + */ + public double getZExtent() { + return _zExtent; + } + + /** + * Updates the center point and extents of this box to match an axis-aligned box defined by the two given opposite + * corners. + * + * @param pntA + * the first point + * @param pntB + * the second point. + */ + public void setData(final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) { + _center.set(pntB).addLocal(pntA).multiplyLocal(0.5); + + final double x = Math.abs(pntB.getX() - _center.getX()); + final double y = Math.abs(pntB.getY() - _center.getY()); + final double z = Math.abs(pntB.getZ() - _center.getZ()); + setData(_center, x, y, z); + } + + /** + * Updates the center point and extents of this box using the defined values. + * + * @param center + * The center of the box. + * @param xExtent + * x extent of the box + * @param yExtent + * y extent of the box + * @param zExtent + * z extent of the box + */ + public void setData(final ReadOnlyVector3 center, final double xExtent, final double yExtent, final double zExtent) { + if (center != null) { + _center.set(center); + } + + _xExtent = xExtent; + _yExtent = yExtent; + _zExtent = zExtent; + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + + } + + /** + * <code>setVertexData</code> sets the vertex positions that define the box using the center point and defined + * extents. + */ + protected void setVertexData() { + if (_meshData.getVertexBuffer() == null) { + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(24)); + } + + final Vector3[] vert = computeVertices(); // returns 8 + + // Back + BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 0); + BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 1); + BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 2); + BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 3); + + // Right + BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 4); + BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 5); + BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 6); + BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 7); + + // Front + BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 8); + BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 9); + BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 10); + BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 11); + + // Left + BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 12); + BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 13); + BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 14); + BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 15); + + // Top + BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 16); + BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 17); + BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 18); + BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 19); + + // Bottom + BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 20); + BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 21); + BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 22); + BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 23); + } + + /** + * <code>setNormalData</code> sets the normals of each of the box's planes. + */ + private void setNormalData() { + if (_meshData.getNormalBuffer() == null) { + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(24)); + + // back + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(0).put(-1); + } + + // right + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(1).put(0).put(0); + } + + // front + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(0).put(1); + } + + // left + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(-1).put(0).put(0); + } + + // top + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(1).put(0); + } + + // bottom + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(-1).put(0); + } + } + } + + /** + * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one ratio, where + * each plane of the box has it's own copy of the texture. That is, the texture is repeated one time for each six + * faces. + */ + private void setTextureData() { + if (_meshData.getTextureCoords(0) == null) { + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(24), 0); + final FloatBuffer tex = _meshData.getTextureBuffer(0); + + for (int i = 0; i < 6; i++) { + tex.put(1).put(0); + tex.put(0).put(0); + tex.put(0).put(1); + tex.put(1).put(1); + } + } + } + + /** + * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the + * box. + */ + private void setIndexData() { + if (_meshData.getIndexBuffer() == null) { + final byte[] indices = { 2, 1, 0, 3, 2, 0, 6, 5, 4, 7, 6, 4, 10, 9, 8, 11, 10, 8, 14, 13, 12, 15, 14, 12, + 18, 17, 16, 19, 18, 16, 22, 21, 20, 23, 22, 20 }; + final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length); + buf.put(indices); + buf.rewind(); + _meshData.setIndexBuffer(buf); + } + } + + /** + * <code>clone</code> creates a new Box object containing the same data as this one. + * + * @return the new Box + */ + @Override + public Box clone() { + return new Box(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent); + } + + /** + * @return a size 8 array of Vectors representing the 8 points of the box. + */ + public Vector3[] computeVertices() { + + final Vector3 rVal[] = new Vector3[8]; + rVal[0] = _center.add(-_xExtent, -_yExtent, -_zExtent, null); + rVal[1] = _center.add(_xExtent, -_yExtent, -_zExtent, null); + rVal[2] = _center.add(_xExtent, _yExtent, -_zExtent, null); + rVal[3] = _center.add(-_xExtent, _yExtent, -_zExtent, null); + rVal[4] = _center.add(_xExtent, -_yExtent, _zExtent, null); + rVal[5] = _center.add(-_xExtent, -_yExtent, _zExtent, null); + rVal[6] = _center.add(_xExtent, _yExtent, _zExtent, null); + rVal[7] = _center.add(-_xExtent, _yExtent, _zExtent, null); + return rVal; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_xExtent, "xExtent", 0); + capsule.write(_yExtent, "yExtent", 0); + capsule.write(_zExtent, "zExtent", 0); + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _xExtent = capsule.readDouble("xExtent", 0); + _yExtent = capsule.readDouble("yExtent", 0); + _zExtent = capsule.readDouble("zExtent", 0); + _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO))); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java new file mode 100644 index 0000000..fb184d4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java @@ -0,0 +1,333 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Capsule</code> provides an extension of <code>Mesh</code>. A <code>Capsule</code> is defined by a height and a + * radius. The center of the Cylinder is the origin. + */ +public class Capsule extends Mesh { + + private int axisSamples, radialSamples, sphereSamples; + private double radius, height; + + public Capsule() {} + + /** + * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better + * looking cylinder, but at the cost of more vertex information. <br> + * If the cylinder is closed the texture is split into axisSamples parts: top most and bottom most part is used for + * top and bottom of the cylinder, rest of the texture for the cylinder wall. The middle of the top is mapped to + * texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need a suited distorted texture. + * + * @param name + * The name of this Cylinder. + * @param axisSamples + * Number of triangle samples along the axis. + * @param radialSamples + * Number of triangle samples along the radial. + * @param radius + * The radius of the cylinder. + * @param height + * The cylinder's height. + */ + public Capsule(final String name, final int axisSamples, final int radialSamples, final int sphereSamples, + final double radius, final double height) { + + super(name); + + this.axisSamples = axisSamples; + this.sphereSamples = sphereSamples; + this.radialSamples = radialSamples; + this.radius = radius; + this.height = height; + + recreateBuffers(); + } + + /** + * @return Returns the height. + */ + public double getHeight() { + return height; + } + + /** + * @param height + * The height to set. + */ + public void setHeight(final double height) { + this.height = height; + recreateBuffers(); + } + + /** + * @return Returns the radius. + */ + public double getRadius() { + return radius; + } + + /** + * Change the radius of this cylinder. + * + * @param radius + * The radius to set. + */ + public void setRadius(final double radius) { + this.radius = radius; + setGeometryData(); + } + + private void recreateBuffers() { + // determine vert quantity - first the sphere caps + final int sampleLines = (2 * sphereSamples - 1 + axisSamples); + final int verts = (radialSamples + 1) * sampleLines + 2; + + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), verts)); + + // allocate normals + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts)); + + // allocate texture coordinates + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + + // determine tri quantity + final int tris = 2 * radialSamples * sampleLines; + + if (_meshData.getIndices() == null || _meshData.getIndices().getBufferLimit() != 3 * tris) { + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + } + + setGeometryData(); + setIndexData(); + } + + private void setGeometryData() { + final FloatBuffer verts = _meshData.getVertexBuffer(); + final FloatBuffer norms = _meshData.getNormalBuffer(); + final FloatBuffer texs = _meshData.getTextureBuffer(0); + verts.rewind(); + norms.rewind(); + texs.rewind(); + + // generate geometry + final double inverseRadial = 1.0 / radialSamples; + final double inverseSphere = 1.0 / sphereSamples; + final double halfHeight = 0.5 * height; + + // Generate points on the unit circle to be used in computing the mesh + // points on a cylinder slice. + final double[] sin = new double[radialSamples + 1]; + final double[] cos = new double[radialSamples + 1]; + + for (int radialCount = 0; radialCount < radialSamples; radialCount++) { + final double angle = MathUtils.TWO_PI * inverseRadial * radialCount; + cos[radialCount] = MathUtils.cos(angle); + sin[radialCount] = MathUtils.sin(angle); + } + sin[radialSamples] = sin[0]; + cos[radialSamples] = cos[0]; + + final Vector3 tempA = new Vector3(); + + // top point. + verts.put(0).put((float) (radius + halfHeight)).put(0); + norms.put(0).put(1).put(0); + texs.put(1).put(1); + + // generating the top dome. + for (int i = 0; i < sphereSamples; i++) { + final double center = radius * (1 - (i + 1) * (inverseSphere)); + final double lengthFraction = (center + height + radius) / (height + 2 * radius); + + // compute radius of slice + final double fSliceRadius = Math.sqrt(Math.abs(radius * radius - center * center)); + + for (int j = 0; j <= radialSamples; j++) { + final Vector3 kRadial = tempA.set(cos[j], 0, sin[j]); + kRadial.multiplyLocal(fSliceRadius); + verts.put(kRadial.getXf()).put((float) (center + halfHeight)).put(kRadial.getZf()); + kRadial.setY(center); + kRadial.normalizeLocal(); + norms.put(kRadial.getXf()).put(kRadial.getYf()).put(kRadial.getZf()); + final double radialFraction = 1 - (j * inverseRadial); // in [0,1) + texs.put((float) radialFraction).put((float) lengthFraction); + } + } + + // generate cylinder... but no need to add points for first and last + // samples as they are already part of domes. + for (int i = 1; i < axisSamples; i++) { + final double center = halfHeight - (i * height / axisSamples); + final double lengthFraction = (center + halfHeight + radius) / (height + 2 * radius); + + for (int j = 0; j <= radialSamples; j++) { + final Vector3 kRadial = tempA.set(cos[j], 0, sin[j]); + kRadial.multiplyLocal(radius); + verts.put(kRadial.getXf()).put((float) center).put(kRadial.getZf()); + kRadial.normalizeLocal(); + norms.put(kRadial.getXf()).put(kRadial.getYf()).put(kRadial.getZf()); + final double radialFraction = 1 - (j * inverseRadial); // in [0,1) + texs.put((float) radialFraction).put((float) lengthFraction); + } + + } + + // generating the bottom dome. + for (int i = 0; i < sphereSamples; i++) { + final double center = i * (radius / sphereSamples); + final double lengthFraction = (radius - center) / (height + 2 * radius); + + // compute radius of slice + final double fSliceRadius = Math.sqrt(Math.abs(radius * radius - center * center)); + + for (int j = 0; j <= radialSamples; j++) { + final Vector3 kRadial = tempA.set(cos[j], 0, sin[j]); + kRadial.multiplyLocal(fSliceRadius); + verts.put(kRadial.getXf()).put((float) (-center - halfHeight)).put(kRadial.getZf()); + kRadial.setY(-center); + kRadial.normalizeLocal(); + norms.put(kRadial.getXf()).put(kRadial.getYf()).put(kRadial.getZf()); + final double radialFraction = 1 - (j * inverseRadial); // in [0,1) + texs.put((float) radialFraction).put((float) lengthFraction); + } + } + + // bottom point. + verts.put(0).put((float) (-radius - halfHeight)).put(0); + norms.put(0).put(-1).put(0); + texs.put(0).put(0); + + } + + private void setIndexData() { + _meshData.getIndexBuffer().rewind(); + + // start with top of top dome. + for (int samples = 1; samples <= radialSamples; samples++) { + _meshData.getIndices().put(samples + 1); + _meshData.getIndices().put(samples); + _meshData.getIndices().put(0); + } + + for (int plane = 1; plane < (sphereSamples); plane++) { + final int topPlaneStart = plane * (radialSamples + 1); + final int bottomPlaneStart = (plane - 1) * (radialSamples + 1); + for (int sample = 1; sample <= radialSamples; sample++) { + _meshData.getIndices().put(bottomPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + } + } + + int start = sphereSamples * (radialSamples + 1); + + // add cylinder + for (int plane = 0; plane < (axisSamples); plane++) { + final int topPlaneStart = start + plane * (radialSamples + 1); + final int bottomPlaneStart = start + (plane - 1) * (radialSamples + 1); + for (int sample = 1; sample <= radialSamples; sample++) { + _meshData.getIndices().put(bottomPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + } + } + + start += ((axisSamples - 1) * (radialSamples + 1)); + + // Add most of the bottom dome triangles. + for (int plane = 1; plane < (sphereSamples); plane++) { + final int topPlaneStart = start + plane * (radialSamples + 1); + final int bottomPlaneStart = start + (plane - 1) * (radialSamples + 1); + for (int sample = 1; sample <= radialSamples; sample++) { + _meshData.getIndices().put(bottomPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + } + } + + start += ((sphereSamples - 1) * (radialSamples + 1)); + // Finally the bottom of bottom dome. + for (int samples = 1; samples <= radialSamples; samples++) { + _meshData.getIndices().put(start + samples); + _meshData.getIndices().put(start + samples + 1); + _meshData.getIndices().put(start + radialSamples + 2); + } + } + + public void reconstruct(final Vector3 top, final Vector3 bottom, final double radius) { + // our temp vars + final Vector3 localTranslation = Vector3.fetchTempInstance(); + final Vector3 capsuleUp = Vector3.fetchTempInstance(); + + // first make the capsule the right shape + height = top.distance(bottom); + this.radius = radius; + setGeometryData(); + + // now orient it in space. + localTranslation.set(_localTransform.getTranslation()); + top.add(bottom, localTranslation).multiplyLocal(.5); + + // rotation that takes us from 0,1,0 to the unit vector described by top/center. + top.subtract(localTranslation, capsuleUp).normalizeLocal(); + final Matrix3 rotation = Matrix3.fetchTempInstance(); + rotation.fromStartEndLocal(Vector3.UNIT_Y, capsuleUp); + _localTransform.setRotation(rotation); + + Vector3.releaseTempInstance(localTranslation); + Vector3.releaseTempInstance(capsuleUp); + Matrix3.releaseTempInstance(rotation); + + updateWorldTransform(false); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(axisSamples, "axisSamples", 0); + capsule.write(radialSamples, "radialSamples", 0); + capsule.write(sphereSamples, "sphereSamples", 0); + capsule.write(radius, "radius", 0); + capsule.write(height, "height", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + axisSamples = capsule.readInt("circleSamples", 0); + radialSamples = capsule.readInt("radialSamples", 0); + sphereSamples = capsule.readInt("sphereSamples", 0); + radius = capsule.readDouble("radius", 0); + height = capsule.readDouble("height", 0); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java new file mode 100644 index 0000000..1e5b6c4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +public class Cone extends Cylinder { + + public Cone() {} + + public Cone(final String name, final int axisSamples, final int radialSamples, final float radius, + final float height) { + this(name, axisSamples, radialSamples, radius, height, true); + } + + public Cone(final String name, final int axisSamples, final int radialSamples, final float radius, + final float height, final boolean closed) { + super(name, axisSamples, radialSamples, radius, height, closed); + setRadius2(0); + } + + public void setHalfAngle(final float radians) { + setRadius1(Math.tan(radians)); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java new file mode 100644 index 0000000..5b4c7f7 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java @@ -0,0 +1,399 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Cylinder</code> provides an extension of <code>Mesh</code>. A <code>Cylinder</code> is defined by a height and + * radius. The center of the Cylinder is the origin. + */ +public class Cylinder extends Mesh { + + private int _axisSamples; + + private int _radialSamples; + + private double _radius; + private double _radius2; + + private double _height; + private boolean _closed; + private boolean _inverted; + + public Cylinder() {} + + /** + * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better + * looking cylinder, but at the cost of more vertex information. + * + * @param name + * The name of this Cylinder. + * @param axisSamples + * Number of triangle samples along the axis. + * @param radialSamples + * Number of triangle samples along the radial. + * @param radius + * The radius of the cylinder. + * @param height + * The cylinder's height. + */ + public Cylinder(final String name, final int axisSamples, final int radialSamples, final double radius, + final double height) { + this(name, axisSamples, radialSamples, radius, height, false); + } + + /** + * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better + * looking cylinder, but at the cost of more vertex information. <br> + * If the cylinder is closed the texture is split into axisSamples parts: top most and bottom most part is used for + * top and bottom of the cylinder, rest of the texture for the cylinder wall. The middle of the top is mapped to + * texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need a suited distorted texture. + * + * @param name + * The name of this Cylinder. + * @param axisSamples + * Number of triangle samples along the axis. + * @param radialSamples + * Number of triangle samples along the radial. + * @param radius + * The radius of the cylinder. + * @param height + * The cylinder's height. + * @param closed + * true to create a cylinder with top and bottom surface + */ + public Cylinder(final String name, final int axisSamples, final int radialSamples, final double radius, + final double height, final boolean closed) { + this(name, axisSamples, radialSamples, radius, height, closed, false); + } + + /** + * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better + * looking cylinder, but at the cost of more vertex information. <br> + * If the cylinder is closed the texture is split into axisSamples parts: top most and bottom most part is used for + * top and bottom of the cylinder, rest of the texture for the cylinder wall. The middle of the top is mapped to + * texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need a suited distorted texture. + * + * @param name + * The name of this Cylinder. + * @param axisSamples + * Number of triangle samples along the axis. + * @param radialSamples + * Number of triangle samples along the radial. + * @param radius + * The radius of the cylinder. + * @param height + * The cylinder's height. + * @param closed + * true to create a cylinder with top and bottom surface + * @param inverted + * true to create a cylinder that is meant to be viewed from the interior. + */ + public Cylinder(final String name, final int axisSamples, final int radialSamples, final double radius, + final double height, final boolean closed, final boolean inverted) { + + super(name); + + _axisSamples = axisSamples + (closed ? 2 : 0); + _radialSamples = radialSamples; + setRadius(radius); + _height = height; + _closed = closed; + _inverted = inverted; + + allocateVertices(); + } + + /** + * @return Returns the height. + */ + public double getHeight() { + return _height; + } + + /** + * @param height + * The height to set. + */ + public void setHeight(final double height) { + _height = height; + allocateVertices(); + } + + /** + * @return Returns the radius. + */ + public double getRadius() { + return _radius; + } + + /** + * Change the radius of this cylinder. This resets any second radius. + * + * @param radius + * The radius to set. + */ + public void setRadius(final double radius) { + _radius = radius; + _radius2 = radius; + allocateVertices(); + } + + /** + * Set the top radius of the 'cylinder' to differ from the bottom radius. + * + * @param radius + * The first radius to set. + * @see com.ardor3d.extension.shape.Cone + */ + public void setRadius1(final double radius) { + _radius = radius; + allocateVertices(); + } + + /** + * Set the bottom radius of the 'cylinder' to differ from the top radius. This makes the Mesh be a frustum of + * pyramid, or if set to 0, a cone. + * + * @param radius + * The second radius to set. + * @see com.ardor3d.extension.shape.Cone + */ + public void setRadius2(final double radius) { + _radius2 = radius; + allocateVertices(); + } + + /** + * @return the number of samples along the cylinder axis + */ + public int getAxisSamples() { + return _axisSamples; + } + + /** + * @return true if end caps are used. + */ + public boolean isClosed() { + return _closed; + } + + /** + * @return true if normals and uvs are created for interior use + */ + public boolean isInverted() { + return _inverted; + } + + /** + * @return number of samples around cylinder + */ + public int getRadialSamples() { + return _radialSamples; + } + + private void allocateVertices() { + // allocate vertices + final int verts = _axisSamples * (_radialSamples + 1) + (_closed ? 2 : 0); + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), verts)); + + // allocate normals if requested + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts)); + + // allocate texture coordinates + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + + final int count = ((_closed ? 2 : 0) + 2 * (_axisSamples - 1)) * _radialSamples; + + if (_meshData.getIndices() == null || _meshData.getIndices().getBufferLimit() != 3 * count) { + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * count, verts - 1)); + } + + setGeometryData(); + setIndexData(); + } + + private void setGeometryData() { + // generate geometry + final double inverseRadial = 1.0 / _radialSamples; + final double inverseAxisLess = 1.0 / (_closed ? _axisSamples - 3 : _axisSamples - 1); + final double inverseAxisLessTexture = 1.0 / (_axisSamples - 1); + final double halfHeight = 0.5 * _height; + + // Generate points on the unit circle to be used in computing the mesh + // points on a cylinder slice. + final double[] sin = new double[_radialSamples + 1]; + final double[] cos = new double[_radialSamples + 1]; + + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final double angle = MathUtils.TWO_PI * inverseRadial * radialCount; + cos[radialCount] = MathUtils.cos(angle); + sin[radialCount] = MathUtils.sin(angle); + } + sin[_radialSamples] = sin[0]; + cos[_radialSamples] = cos[0]; + + // generate the cylinder itself + final Vector3 tempNormal = new Vector3(); + for (int axisCount = 0, i = 0; axisCount < _axisSamples; axisCount++) { + double axisFraction; + double axisFractionTexture; + int topBottom = 0; + if (!_closed) { + axisFraction = axisCount * inverseAxisLess; // in [0,1] + axisFractionTexture = axisFraction; + } else { + if (axisCount == 0) { + topBottom = -1; // bottom + axisFraction = 0; + axisFractionTexture = inverseAxisLessTexture; + } else if (axisCount == _axisSamples - 1) { + topBottom = 1; // top + axisFraction = 1; + axisFractionTexture = 1 - inverseAxisLessTexture; + } else { + axisFraction = (axisCount - 1) * inverseAxisLess; + axisFractionTexture = axisCount * inverseAxisLessTexture; + } + } + final double z = -halfHeight + _height * axisFraction; + + // compute center of slice + final Vector3 sliceCenter = new Vector3(0, 0, z); + + // compute slice vertices with duplication at end point + final int save = i; + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final double radialFraction = radialCount * inverseRadial; // in [0,1) + tempNormal.set(cos[radialCount], sin[radialCount], 0); + if (topBottom == 0) { + if (!_inverted) { + _meshData.getNormalBuffer().put(tempNormal.getXf()).put(tempNormal.getYf()) + .put(tempNormal.getZf()); + } else { + _meshData.getNormalBuffer().put(-tempNormal.getXf()).put(-tempNormal.getYf()) + .put(-tempNormal.getZf()); + } + } else { + _meshData.getNormalBuffer().put(0).put(0).put(topBottom * (_inverted ? -1 : 1)); + } + + tempNormal.multiplyLocal((_radius - _radius2) * axisFraction + _radius2).addLocal(sliceCenter); + _meshData.getVertexBuffer().put(tempNormal.getXf()).put(tempNormal.getYf()).put(tempNormal.getZf()); + + _meshData.getTextureCoords(0).getBuffer() + .put((float) (_inverted ? 1 - radialFraction : radialFraction)) + .put((float) axisFractionTexture); + i++; + } + + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), save, i); + BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), save, i); + + _meshData.getTextureCoords(0).getBuffer().put((_inverted ? 0.0f : 1.0f)).put((float) axisFractionTexture); + + i++; + } + + if (_closed) { + _meshData.getVertexBuffer().put(0).put(0).put((float) -halfHeight); // bottom center + _meshData.getNormalBuffer().put(0).put(0).put(-1 * (_inverted ? -1 : 1)); + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0); + _meshData.getVertexBuffer().put(0).put(0).put((float) halfHeight); // top center + _meshData.getNormalBuffer().put(0).put(0).put(1 * (_inverted ? -1 : 1)); + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(1); + } + } + + private void setIndexData() { + _meshData.getIndexBuffer().rewind(); + + // generate connectivity + for (int axisCount = 0, axisStart = 0; axisCount < _axisSamples - 1; axisCount++) { + int i0 = axisStart; + int i1 = i0 + 1; + axisStart += _radialSamples + 1; + int i2 = axisStart; + int i3 = i2 + 1; + for (int i = 0; i < _radialSamples; i++) { + if (_closed && axisCount == 0) { + if (!_inverted) { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(_meshData.getVertexCount() - 2); + _meshData.getIndices().put(i1++); + } else { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(_meshData.getVertexCount() - 2); + } + } else if (_closed && axisCount == _axisSamples - 2) { + if (!_inverted) { + _meshData.getIndices().put(i2++); + _meshData.getIndices().put(i3++); + _meshData.getIndices().put(_meshData.getVertexCount() - 1); + } else { + _meshData.getIndices().put(i2++); + _meshData.getIndices().put(_meshData.getVertexCount() - 1); + _meshData.getIndices().put(i3++); + } + } else { + if (!_inverted) { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i1); + _meshData.getIndices().put(i2); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(i3++); + _meshData.getIndices().put(i2++); + } else { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i2); + _meshData.getIndices().put(i1); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(i2++); + _meshData.getIndices().put(i3++); + } + } + } + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_axisSamples, "axisSamples", 0); + capsule.write(_radialSamples, "radialSamples", 0); + capsule.write(_radius, "radius", 0); + capsule.write(_radius2, "radius2", 0); + capsule.write(_height, "height", 0); + capsule.write(_closed, "closed", false); + capsule.write(_inverted, "inverted", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _axisSamples = capsule.readInt("axisSamples", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + _radius = capsule.readDouble("radius", 0); + _radius2 = capsule.readDouble("radius2", 0); + _height = capsule.readDouble("height", 0); + _closed = capsule.readBoolean("closed", false); + _inverted = capsule.readBoolean("inverted", false); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java new file mode 100644 index 0000000..3ada7d3 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * An approximations of a flat circle. It is simply defined with a radius. It starts out flat along the Z, with center + * at the origin. + */ +public class Disk extends Mesh { + + private int _shellSamples; + + private int _radialSamples; + + private double _radius; + + public Disk() {} + + /** + * Creates a flat disk (circle) at the origin flat along the Z. Usually, a higher sample number creates a better + * looking cylinder, but at the cost of more vertex information. + * + * @param name + * The name of the disk. + * @param shellSamples + * The number of shell samples. + * @param radialSamples + * The number of radial samples. + * @param radius + * The radius of the disk. + */ + public Disk(final String name, final int shellSamples, final int radialSamples, final double radius) { + super(name); + + _shellSamples = shellSamples; + _radialSamples = radialSamples; + _radius = radius; + + final int radialless = radialSamples - 1; + final int shellLess = shellSamples - 1; + // allocate vertices + final int verts = 1 + radialSamples * shellLess; + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts)); + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + + final int tris = radialSamples * (2 * shellLess - 1); + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + + setGeometryData(shellLess); + setIndexData(radialless, shellLess); + + } + + private void setGeometryData(final int shellLess) { + // generate geometry + // center of disk + _meshData.getVertexBuffer().put(0).put(0).put(0); + + for (int x = 0; x < _meshData.getVertexCount(); x++) { + _meshData.getNormalBuffer().put(0).put(0).put(1); + } + + _meshData.getTextureCoords(0).getBuffer().put(.5f).put(.5f); + + final double inverseShellLess = 1.0 / shellLess; + final double inverseRadial = 1.0 / _radialSamples; + final Vector3 radialFraction = new Vector3(); + final Vector2 texCoord = new Vector2(); + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final double angle = MathUtils.TWO_PI * inverseRadial * radialCount; + final double cos = MathUtils.cos(angle); + final double sin = MathUtils.sin(angle); + final Vector3 radial = new Vector3(cos, sin, 0); + + for (int shellCount = 1; shellCount < _shellSamples; shellCount++) { + final double fraction = inverseShellLess * shellCount; // in (0,R] + radialFraction.set(radial).multiplyLocal(fraction); + final int i = shellCount + shellLess * radialCount; + texCoord.setX(0.5 * (1.0 + radialFraction.getX())); + texCoord.setY(0.5 * (1.0 + radialFraction.getY())); + BufferUtils.setInBuffer(texCoord, _meshData.getTextureCoords(0).getBuffer(), i); + + radialFraction.multiplyLocal(_radius); + BufferUtils.setInBuffer(radialFraction, _meshData.getVertexBuffer(), i); + } + } + } + + private void setIndexData(final int radialless, final int shellLess) { + // generate connectivity + for (int radialCount0 = radialless, radialCount1 = 0; radialCount1 < _radialSamples; radialCount0 = radialCount1++) { + _meshData.getIndices().put(0); + _meshData.getIndices().put(1 + shellLess * radialCount0); + _meshData.getIndices().put(1 + shellLess * radialCount1); + for (int iS = 1; iS < shellLess; iS++) { + final int i00 = iS + shellLess * radialCount0; + final int i01 = iS + shellLess * radialCount1; + final int i10 = i00 + 1; + final int i11 = i01 + 1; + _meshData.getIndices().put(i00); + _meshData.getIndices().put(i10); + _meshData.getIndices().put(i11); + _meshData.getIndices().put(i00); + _meshData.getIndices().put(i11); + _meshData.getIndices().put(i01); + } + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_shellSamples, "shellSamples", 0); + capsule.write(_radialSamples, "radialSamples", 0); + capsule.write(_radius, "radius", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _shellSamples = capsule.readInt("shellSamples", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + _radius = capsule.readDouble("radius", 0); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java new file mode 100644 index 0000000..11e00e9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class Dodecahedron extends Mesh { + + private static final int NUM_POINTS = 20; + private static final int NUM_TRIS = 36; + + private double _sideLength; + + public Dodecahedron() {} + + /** + * Creates an Dodecahedron (think of 12-sided dice) with center at the origin. The length of the sides will be as + * specified in sideLength. + * + * @param name + * The name of the octahedron. + * @param sideLength + * The length of each side of the octahedron. + */ + public Dodecahedron(final String name, final double sideLength) { + super(name); + _sideLength = sideLength; + // allocate vertices + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0); + + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * NUM_TRIS, NUM_POINTS - 1)); + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + + } + + private void setIndexData() { + final ByteBuffer indices = (ByteBuffer) _meshData.getIndexBuffer(); + indices.rewind(); + indices.put((byte) 0).put((byte) 8).put((byte) 9); + indices.put((byte) 0).put((byte) 9).put((byte) 4); + indices.put((byte) 0).put((byte) 4).put((byte) 16); + indices.put((byte) 0).put((byte) 12).put((byte) 13); + indices.put((byte) 0).put((byte) 13).put((byte) 1); + indices.put((byte) 0).put((byte) 1).put((byte) 8); + indices.put((byte) 0).put((byte) 16).put((byte) 17); + indices.put((byte) 0).put((byte) 17).put((byte) 2); + indices.put((byte) 0).put((byte) 2).put((byte) 12); + indices.put((byte) 8).put((byte) 1).put((byte) 18); + indices.put((byte) 8).put((byte) 18).put((byte) 5); + indices.put((byte) 8).put((byte) 5).put((byte) 9); + indices.put((byte) 12).put((byte) 2).put((byte) 10); + indices.put((byte) 12).put((byte) 10).put((byte) 3); + indices.put((byte) 12).put((byte) 3).put((byte) 13); + indices.put((byte) 16).put((byte) 4).put((byte) 14); + indices.put((byte) 16).put((byte) 14).put((byte) 6); + indices.put((byte) 16).put((byte) 6).put((byte) 17); + indices.put((byte) 9).put((byte) 5).put((byte) 15); + indices.put((byte) 9).put((byte) 15).put((byte) 14); + indices.put((byte) 9).put((byte) 14).put((byte) 4); + indices.put((byte) 6).put((byte) 11).put((byte) 10); + indices.put((byte) 6).put((byte) 10).put((byte) 2); + indices.put((byte) 6).put((byte) 2).put((byte) 17); + indices.put((byte) 3).put((byte) 19).put((byte) 18); + indices.put((byte) 3).put((byte) 18).put((byte) 1); + indices.put((byte) 3).put((byte) 1).put((byte) 13); + indices.put((byte) 7).put((byte) 15).put((byte) 5); + indices.put((byte) 7).put((byte) 5).put((byte) 18); + indices.put((byte) 7).put((byte) 18).put((byte) 19); + indices.put((byte) 7).put((byte) 11).put((byte) 6); + indices.put((byte) 7).put((byte) 6).put((byte) 14); + indices.put((byte) 7).put((byte) 14).put((byte) 15); + indices.put((byte) 7).put((byte) 19).put((byte) 3); + indices.put((byte) 7).put((byte) 3).put((byte) 10); + indices.put((byte) 7).put((byte) 10).put((byte) 11); + } + + private void setTextureData() { + final Vector2 tex = new Vector2(); + final Vector3 vert = new Vector3(); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.populateFromBuffer(vert, _meshData.getVertexBuffer(), i); + if (Math.abs(vert.getZ()) < _sideLength) { + tex.setX(0.5 * (1.0 + Math.atan2(vert.getY(), vert.getX()) * MathUtils.INV_PI)); + } else { + tex.setX(0.5); + } + tex.setY(Math.acos(vert.getZ() / _sideLength) * MathUtils.INV_PI); + _meshData.getTextureCoords(0).getBuffer().put((float) tex.getX()).put((float) tex.getY()); + } + } + + private void setNormalData() { + final Vector3 norm = new Vector3(); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.populateFromBuffer(norm, _meshData.getVertexBuffer(), i); + norm.normalizeLocal(); + BufferUtils.setInBuffer(norm, _meshData.getNormalBuffer(), i); + } + } + + private void setVertexData() { + double fA = 1.0 / Math.sqrt(3.0f); + double fB = Math.sqrt((3.0 - Math.sqrt(5.0)) / 6.0); + double fC = Math.sqrt((3.0 + Math.sqrt(5.0)) / 6.0); + fA *= _sideLength; + fB *= _sideLength; + fC *= _sideLength; + + final FloatBuffer vbuf = _meshData.getVertexBuffer(); + vbuf.rewind(); + vbuf.put((float) fA).put((float) fA).put((float) fA); + vbuf.put((float) fA).put((float) fA).put((float) -fA); + vbuf.put((float) fA).put((float) -fA).put((float) fA); + vbuf.put((float) fA).put((float) -fA).put((float) -fA); + vbuf.put((float) -fA).put((float) fA).put((float) fA); + vbuf.put((float) -fA).put((float) fA).put((float) -fA); + vbuf.put((float) -fA).put((float) -fA).put((float) fA); + vbuf.put((float) -fA).put((float) -fA).put((float) -fA); + vbuf.put((float) fB).put((float) fC).put(0.0f); + vbuf.put((float) -fB).put((float) fC).put(0.0f); + vbuf.put((float) fB).put((float) -fC).put(0.0f); + vbuf.put((float) -fB).put((float) -fC).put(0.0f); + vbuf.put((float) fC).put(0.0f).put((float) fB); + vbuf.put((float) fC).put(0.0f).put((float) -fB); + vbuf.put((float) -fC).put(0.0f).put((float) fB); + vbuf.put((float) -fC).put(0.0f).put((float) -fB); + vbuf.put(0.0f).put((float) fB).put((float) fC); + vbuf.put(0.0f).put((float) -fB).put((float) fC); + vbuf.put(0.0f).put((float) fB).put((float) -fC); + vbuf.put(0.0f).put((float) -fB).put((float) -fC); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_sideLength, "sideLength", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _sideLength = capsule.readInt("sideLength", 0); + + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java new file mode 100644 index 0000000..121f33c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java @@ -0,0 +1,299 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * A half sphere. + */ +public class Dome extends Mesh { + + private int _planes; + + private int _radialSamples; + + /** The radius of the dome */ + private double _radius; + + public Dome() {} + + /** + * Constructs a dome. By default the dome has not geometry data or center. + * + * @param name + * The name of the dome. + */ + public Dome(final String name) { + super(name); + } + + /** + * Constructs a dome with center at the origin. For details, see the other constructor. + * + * @param name + * Name of dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The samples along the radial. + * @param radius + * Radius of the dome. + * @see #Dome(java.lang.String, com.ardor3d.math.Vector3, int, int, double) + */ + public Dome(final String name, final int planes, final int radialSamples, final double radius) { + this(name, new Vector3(0, 0, 0), planes, radialSamples, radius); + } + + /** + * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase + * the quality of the generated dome. + * + * @param name + * Name of the dome. + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the dome. + */ + public Dome(final String name, final Vector3 center, final int planes, final int radialSamples, final double radius) { + + super(name); + setData(center, planes, radialSamples, radius, true, true); + } + + /** + * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase + * the quality of the generated dome. + * + * @param name + * Name of the dome. + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the dome. + * @param outsideView + * If true, the triangles will be connected for a view outside of the dome. + */ + public Dome(final String name, final Vector3 center, final int planes, final int radialSamples, + final double radius, final boolean outsideView) { + super(name); + setData(center, planes, radialSamples, radius, true, outsideView); + } + + /** + * Changes the information of the dome into the given values. The boolean at the end signals if buffer data should + * be updated as well. If the dome is to be rendered, then that value should be true. + * + * @param center + * The new center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The new number of radial samples of the dome. + * @param radius + * The new radius of the dome. + * @param updateBuffers + * If true, buffer information is updated as well. + * @param outsideView + * If true, the triangles will be connected for a view outside of the dome. + */ + public void setData(final Vector3 center, final int planes, final int radialSamples, final double radius, + final boolean updateBuffers, final boolean outsideView) { + _planes = planes; + _radialSamples = radialSamples; + _radius = radius; + + if (updateBuffers) { + setGeometryData(outsideView, center); + setIndexData(); + } + } + + /** + * Generates the vertices of the dome + * + * @param outsideView + * If the dome should be viewed from the outside (if not zbuffer is used) + * @param center + */ + private void setGeometryData(final boolean outsideView, final Vector3 center) { + final Vector3 tempVa = Vector3.fetchTempInstance(); + final Vector3 tempVb = Vector3.fetchTempInstance(); + final Vector3 tempVc = Vector3.fetchTempInstance(); + + // allocate vertices, we need one extra in each radial to get the + // correct texture coordinates + final int verts = ((_planes - 1) * (_radialSamples + 1)) + 1; + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate normals + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate texture coordinates + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + + // generate geometry + final double fInvRS = 1.0 / _radialSamples; + final double fYFactor = 1.0 / (_planes - 1); + + // Generate points on the unit circle to be used in computing the mesh + // points on a dome slice. + final double[] afSin = new double[(_radialSamples)]; + final double[] afCos = new double[(_radialSamples)]; + for (int iR = 0; iR < _radialSamples; iR++) { + final double fAngle = MathUtils.TWO_PI * fInvRS * iR; + afCos[iR] = MathUtils.cos(fAngle); + afSin[iR] = MathUtils.sin(fAngle); + } + + // generate the dome itself + int i = 0; + for (int iY = 0; iY < (_planes - 1); iY++) { + final double fYFraction = fYFactor * iY; // in (0,1) + final double fY = _radius * fYFraction; + // compute center of slice + final Vector3 kSliceCenter = tempVb.set(center); + kSliceCenter.addLocal(0, fY, 0); + + // compute radius of slice + final double fSliceRadius = Math.sqrt(Math.abs(_radius * _radius - fY * fY)); + + // compute slice vertices + Vector3 kNormal; + final int iSave = i; + for (int iR = 0; iR < _radialSamples; iR++) { + final double fRadialFraction = iR * fInvRS; // in [0,1) + final Vector3 kRadial = tempVc.set(afCos[iR], 0, afSin[iR]); + kRadial.multiply(fSliceRadius, tempVa); + _meshData.getVertexBuffer().put((float) (kSliceCenter.getX() + tempVa.getX())) + .put((float) (kSliceCenter.getY() + tempVa.getY())) + .put((float) (kSliceCenter.getZ() + tempVa.getZ())); + + BufferUtils.populateFromBuffer(tempVa, _meshData.getVertexBuffer(), i); + kNormal = tempVa.subtractLocal(center); + kNormal.normalizeLocal(); + if (outsideView) { + _meshData.getNormalBuffer().put((float) kNormal.getX()).put((float) kNormal.getY()) + .put((float) kNormal.getZ()); + } else { + _meshData.getNormalBuffer().put((float) -kNormal.getX()).put((float) -kNormal.getY()) + .put((float) -kNormal.getZ()); + } + + _meshData.getTextureCoords(0).getBuffer().put((float) fRadialFraction).put((float) fYFraction); + + i++; + } + + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i); + BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iSave, i); + + _meshData.getTextureCoords(0).getBuffer().put(1.0f).put((float) fYFraction); + + i++; + } + + // pole + _meshData.getVertexBuffer().put((float) center.getX()).put((float) (center.getY() + _radius)) + .put((float) center.getZ()); + + if (outsideView) { + _meshData.getNormalBuffer().put(0).put(1).put(0); + } else { + _meshData.getNormalBuffer().put(0).put(-1).put(0); + } + + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(1.0f); + + Vector3.releaseTempInstance(tempVa); + Vector3.releaseTempInstance(tempVb); + Vector3.releaseTempInstance(tempVc); + } + + /** + * Generates the connections + */ + private void setIndexData() { + // allocate connectivity + final int verts = ((_planes - 1) * (_radialSamples + 1)) + 1; + final int tris = (_planes - 2) * _radialSamples * 2 + _radialSamples; + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + + // generate connectivity + // Generate only for middle planes + for (int plane = 1; plane < (_planes - 1); plane++) { + final int bottomPlaneStart = (plane - 1) * (_radialSamples + 1); + final int topPlaneStart = plane * (_radialSamples + 1); + for (int sample = 0; sample < _radialSamples; sample++) { + _meshData.getIndices().put(bottomPlaneStart + sample); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(topPlaneStart + sample + 1); + } + } + + // pole triangles + final int bottomPlaneStart = (_planes - 2) * (_radialSamples + 1); + for (int samples = 0; samples < _radialSamples; samples++) { + _meshData.getIndices().put(bottomPlaneStart + samples); + _meshData.getIndices().put(_meshData.getVertexCount() - 1); + _meshData.getIndices().put(bottomPlaneStart + samples + 1); + } + } + + public int getPlanes() { + return _planes; + } + + public int getRadialSamples() { + return _radialSamples; + } + + public double getRadius() { + return _radius; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_planes, "planes", 0); + capsule.write(_radialSamples, "radialSamples", 0); + capsule.write(_radius, "radius", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _planes = capsule.readInt("planes", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + _radius = capsule.readDouble("radius", 0); + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java new file mode 100644 index 0000000..c6c2c13 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java @@ -0,0 +1,360 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.geom.BufferUtils; + +/** + * An extrusion of a 2D object ({@link Line}) along a path (List of Vector3). Either a convenience constructor can be + * used or the {@link #updateGeometry} method. It is also capable of doing a cubic spline interpolation for a list of + * supporting points + */ +public class Extrusion extends Mesh { + + /** + * Default Constructor. Creates an empty Extrusion. + * + * @see #updateGeometry(Line, List, Vector3) + * @see #updateGeometry(Line, List, boolean, Vector3) + * @see #updateGeometry(Line, List, int, Vector3) + * @see #updateGeometry(Line, List, int, boolean, Vector3) + */ + public Extrusion() {} + + /** + * Creates an empty named Extrusion. + * + * @param name + * name + * @see #updateGeometry(Line, List, Vector3) + * @see #updateGeometry(Line, List, boolean, Vector3) + * @see #updateGeometry(Line, List, int, Vector3) + * @see #updateGeometry(Line, List, int, boolean, Vector3) + */ + public Extrusion(final String name) { + super(name); + } + + /** + * Convenience constructor. Calls {@link #updateGeometry(Line, List, Vector3)}. + * + * @param shape + * see {@link #updateGeometry(Line, List, Vector3)} + * @param path + * see {@link #updateGeometry(Line, List, Vector3)} + * @param up + * up vector + */ + public Extrusion(final Line shape, final List<ReadOnlyVector3> path, final ReadOnlyVector3 up) { + updateGeometry(shape, path, up); + } + + /** + * Convenience constructor. Sets the name and calls {@link #updateGeometry(Line, List, Vector3)}. + * + * @param name + * name + * @param shape + * see {@link #updateGeometry(Line, List, Vector3)} + * @param path + * see {@link #updateGeometry(Line, List, Vector3)} + * @param up + * up vector + */ + public Extrusion(final String name, final Line shape, final List<ReadOnlyVector3> path, final ReadOnlyVector3 up) { + super(name); + updateGeometry(shape, path, up); + } + + /** + * Update vertex, color, index and texture buffers (0) to contain an extrusion of shape along path. + * + * @param shape + * an instance of Line that describes the 2D shape + * @param path + * a list of vectors that describe the path the shape should be extruded + * @param up + * up vector + */ + public void updateGeometry(final Line shape, final List<ReadOnlyVector3> path, final ReadOnlyVector3 up) { + updateGeometry(shape, path, false, up); + } + + /** + * Update vertex, color, index and texture buffers (0) to contain an extrusion of shape along path. + * + * @param shape + * an instance of Line that describes the 2D shape + * @param path + * a list of vectors that describe the path the shape should be extruded + * @param closed + * true to connect first and last point + * @param up + * up vector + */ + public void updateGeometry(final Line shape, final List<ReadOnlyVector3> path, final boolean closed, + final ReadOnlyVector3 up) { + final FloatBuffer shapeBuffer = shape.getMeshData().getVertexBuffer(); + final FloatBuffer shapeNormalBuffer = shape.getMeshData().getNormalBuffer(); + + final int numVertices = path.size() * shapeBuffer.limit(); + + FloatBuffer vertices; + if (_meshData.getVertexBuffer() != null && _meshData.getVertexBuffer().limit() == numVertices) { + vertices = _meshData.getVertexBuffer(); + vertices.rewind(); + } else { + vertices = BufferUtils.createFloatBuffer(numVertices); + } + + FloatBuffer normals = null; + if (shapeNormalBuffer != null) { + if (_meshData.getNormalBuffer() != null && _meshData.getNormalBuffer().limit() == numVertices) { + normals = _meshData.getNormalBuffer(); + normals.rewind(); + } else { + normals = BufferUtils.createFloatBuffer(numVertices); + } + } + + final int numIndices = (path.size() - 1) * 2 * shapeBuffer.limit(); + IndexBufferData<?> indices; + if (_meshData.getIndexBuffer() != null && _meshData.getIndexBuffer().limit() == numIndices) { + indices = _meshData.getIndices(); + indices.getBuffer().rewind(); + } else { + indices = BufferUtils.createIndexBufferData(numIndices, numVertices - 1); + } + + final IndexMode indexMode = shape.getMeshData().getIndexMode(0); + + final int shapeVertices = shapeBuffer.limit() / 3; + final Vector3 vector = new Vector3(); + final Vector3 direction = new Vector3(); + final Quaternion rotation = new Quaternion(); + + for (int i = 0; i < path.size(); i++) { + final ReadOnlyVector3 point = path.get(i); + shapeBuffer.rewind(); + if (shapeNormalBuffer != null) { + shapeNormalBuffer.rewind(); + } + int shapeVertice = 0; + do { + final ReadOnlyVector3 nextPoint = i < path.size() - 1 ? path.get(i + 1) : closed ? path.get(0) : null; + final ReadOnlyVector3 lastPoint = i > 0 ? path.get(i - 1) : null; + if (nextPoint != null) { + direction.set(nextPoint).subtractLocal(point); + } else { + direction.set(point).subtractLocal(lastPoint); + } + rotation.lookAt(direction, up); + + if (shapeNormalBuffer != null && normals != null) { + vector.set(shapeNormalBuffer.get(), shapeNormalBuffer.get(), shapeNormalBuffer.get()); + rotation.apply(vector, vector); + normals.put(vector.getXf()); + normals.put(vector.getYf()); + normals.put(vector.getZf()); + } + + vector.set(shapeBuffer.get(), shapeBuffer.get(), shapeBuffer.get()); + rotation.apply(vector, vector); + vector.addLocal(point); + vertices.put(vector.getXf()); + vertices.put(vector.getYf()); + vertices.put(vector.getZf()); + + if (indexMode != IndexMode.Lines || (shapeVertice & 1) == 0) { + if (i < path.size() - 1) { + if (shapeBuffer.hasRemaining()) { + indices.put(i * shapeVertices + shapeVertice); + indices.put(i * shapeVertices + shapeVertice + 1); + indices.put((i + 1) * shapeVertices + shapeVertice); + + indices.put((i + 1) * shapeVertices + shapeVertice + 1); + indices.put((i + 1) * shapeVertices + shapeVertice); + indices.put(i * shapeVertices + shapeVertice + 1); + } else if (indexMode == IndexMode.LineLoop) { + indices.put(i * shapeVertices + shapeVertice); + indices.put(i * shapeVertices + shapeVertice + 1); + indices.put((i + 1) * shapeVertices + shapeVertice); + + indices.put(i * shapeVertices + shapeVertice); + indices.put((i - 1) * shapeVertices + shapeVertice + 1); + indices.put(i * shapeVertices + shapeVertice + 1); + } + } else if (closed) { + indices.put(i * shapeVertices + shapeVertice); + indices.put(i * shapeVertices + shapeVertice + 1); + indices.put(0 + shapeVertice); + + indices.put(0 + shapeVertice + 1); + indices.put(0 + shapeVertice); + indices.put(i * shapeVertices + shapeVertice + 1); + } + } + shapeVertice++; + } while (shapeBuffer.hasRemaining()); + } + + _meshData.setVertexBuffer(vertices); + if (normals != null) { + _meshData.setNormalBuffer(normals); + } + _meshData.setIndices(indices); + } + + /** + * Performs cubic spline interpolation to find a path through the supporting points where the second derivative is + * zero. Then calls {@link #updateGeometry(Line, List, Vector3)} with this path. + * + * @param shape + * an instance of Line that describes the 2D shape + * @param points + * a list of supporting points for the spline interpolation + * @param segments + * number of resulting path segments per supporting point + * @param up + * up vector + */ + public void updateGeometry(final Line shape, final List<ReadOnlyVector3> points, final int segments, + final ReadOnlyVector3 up) { + updateGeometry(shape, points, segments, false, up); + } + + /** + * Performs cubic spline interpolation to find a path through the supporting points where the second derivative is + * zero. Then calls {@link #updateGeometry(Line, List, boolean, Vector3)} with this path. + * + * @param shape + * an instance of Line that describes the 2D shape + * @param points + * a list of supporting points for the spline interpolation + * @param segments + * number of resulting path segments per supporting point + * @param closed + * true to close the shape (connect last and first point) + * @param up + * up vector + */ + public void updateGeometry(final Line shape, final List<ReadOnlyVector3> points, final int segments, + final boolean closed, final ReadOnlyVector3 up) { + int np = points.size(); // number of points + if (closed) { + np = np + 3; + } + final double d[][] = new double[3][np]; // Newton form coefficients + final double x[] = new double[np]; // x-coordinates of nodes + + final List<ReadOnlyVector3> path = new ArrayList<ReadOnlyVector3>(); + + for (int i = 0; i < np; i++) { + ReadOnlyVector3 p; + if (!closed) { + p = points.get(i); + } else { + if (i == 0) { + p = points.get(points.size() - 1); + } else if (i >= np - 2) { + p = points.get(i - np + 2); + } else { + p = points.get(i - 1); + } + } + x[i] = i; + d[0][i] = p.getX(); + d[1][i] = p.getY(); + d[2][i] = p.getZ(); + } + + if (np > 1) { + final double[][] a = new double[3][np]; + final double h[] = new double[np]; + for (int i = 1; i <= np - 1; i++) { + h[i] = x[i] - x[i - 1]; + } + if (np > 2) { + final double sub[] = new double[np - 1]; + final double diag[] = new double[np - 1]; + final double sup[] = new double[np - 1]; + + for (int i = 1; i <= np - 2; i++) { + diag[i] = (h[i] + h[i + 1]) / 3; + sup[i] = h[i + 1] / 6; + sub[i] = h[i] / 6; + for (int dim = 0; dim < 3; dim++) { + a[dim][i] = (d[dim][i + 1] - d[dim][i]) / h[i + 1] - (d[dim][i] - d[dim][i - 1]) / h[i]; + } + } + for (int dim = 0; dim < 3; dim++) { + solveTridiag(sub.clone(), diag.clone(), sup.clone(), a[dim], np - 2); + } + } + // note that a[0]=a[np-1]=0 + // draw + if (!closed) { + path.add(new Vector3(d[0][0], d[1][0], d[2][0])); + } + final double[] point = new double[3]; + for (int i = closed ? 2 : 1; i <= np - 2; i++) { // loop over + // intervals + // between nodes + for (int j = 1; j <= segments; j++) { + for (int dim = 0; dim < 3; dim++) { + final double t1 = (h[i] * j) / segments; + final double t2 = h[i] - t1; + final double v = ((-a[dim][i - 1] / 6 * (t2 + h[i]) * t1 + d[dim][i - 1]) * t2 + (-a[dim][i] + / 6 * (t1 + h[i]) * t2 + d[dim][i]) + * t1) + / h[i]; + // float t = x[i - 1] + t1; + point[dim] = v; + } + path.add(new Vector3(point[0], point[1], point[2])); + } + } + } + + this.updateGeometry(shape, path, closed, up); + } + + /* + * solve linear system with tridiagonal n by n matrix a using Gaussian elimination without pivoting where a(i,i-1) = + * sub[i] for 2<=i<=n a(i,i) = diag[i] for 1<=i<=n a(i,i+1) = sup[i] for 1<=i<=n-1 (the values sub[1], sup[n] are + * ignored) right hand side vector b[1:n] is overwritten with solution NOTE: 1...n is used in all arrays, 0 is + * unused + */ + private static void solveTridiag(final double sub[], final double diag[], final double sup[], final double b[], + final int n) { + // factorization and forward substitution + for (int i = 2; i <= n; i++) { + sub[i] = sub[i] / diag[i - 1]; + diag[i] = diag[i] - sub[i] * sup[i - 1]; + b[i] = b[i] - sub[i] * b[i - 1]; + } + b[n] = b[n] / diag[n]; + for (int i = n - 1; i >= 1; i--) { + b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i]; + } + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java new file mode 100644 index 0000000..eaa2580 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java @@ -0,0 +1,366 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.nio.FloatBuffer; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.geom.BufferUtils; + +/** + * GeoSphere - generate a polygon mesh approximating a sphere by recursive subdivision. First approximation is an + * octahedron; each level of refinement increases the number of polygons by a factor of 4. + * <p/> + * Shared vertices are not retained, so numerical errors may produce cracks between polygons at high subdivision levels. + * <p/> + * Initial idea and text from C-Sourcecode by Jon Leech 3/24/89 + */ + +public class GeoSphere extends Mesh { + + public enum TextureMode { + Original, Projected; + } + + private int _maxlevels; + private boolean _usingIcosahedron = true; + private TextureMode _textureMode = TextureMode.Original; + private double _radius; + + /** + * @param name + * name of the spatial + * @param useIcosahedron + * true to start with a 20 triangle mesh, false to start with a 8 triangle mesh + * @param radius + * the radius of this sphere + * @param maxlevels + * an integer >= 1 setting the recursion level + * @param textureMode + * the texture mode to use when generating texture coordinates + */ + public GeoSphere(final String name, final boolean useIcosahedron, final double radius, final int maxlevels, + final TextureMode textureMode) { + super(name); + _maxlevels = maxlevels; + _radius = radius; + _maxlevels = maxlevels; + _usingIcosahedron = useIcosahedron; + _textureMode = textureMode; + updateGeometry(); + } + + /** + * Default Constructor for Savable use. + */ + public GeoSphere() {} + + public double getRadius() { + return _radius; + } + + public boolean isUsingIcosahedron() { + return _usingIcosahedron; + } + + public void setTextureMode(final TextureMode textureMode) { + if (textureMode != _textureMode) { + _textureMode = textureMode; + updateGeometry(); + } + } + + public TextureMode getTextureMode() { + return _textureMode; + } + + private void updateGeometry() { + final int initialTriangleCount = _usingIcosahedron ? 20 : 8; + final int initialVertexCount = _usingIcosahedron ? 12 : 6; + // number of triangles = initialTriangleCount * 4^(maxlevels-1) + final int tris = initialTriangleCount << ((_maxlevels - 1) * 2); + + // number of vertBuf = (initialVertexCount + initialTriangleCount*4 + + // initialTriangleCount*4*4 + ...) + // = initialTriangleCount*(((4^maxlevels)-1)/(4-1)-1) + + // initialVertexCount + final int verts = initialTriangleCount * (((1 << (_maxlevels * 2)) - 1) / (4 - 1) - 1) + initialVertexCount + + calculateBorderTriangles(_maxlevels); + + FloatBuffer vertBuf = _meshData.getVertexBuffer(); + _meshData.setVertexBuffer(vertBuf = BufferUtils.createVector3Buffer(vertBuf, verts)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts)); + final FloatBufferData textureCoords = _meshData.getTextureCoords(0); + _meshData.setTextureCoords( + new FloatBufferData(BufferUtils.createVector2Buffer(textureCoords != null ? textureCoords.getBuffer() + : null, verts), 2), 0); + + int pos = 0; + + Triangle[] old; + if (_usingIcosahedron) { + final int[] indices = new int[] { pos + 0, pos + 1, pos + 2, pos + 0, pos + 2, pos + 3, pos + 0, pos + 3, + pos + 4, pos + 0, pos + 4, pos + 5, pos + 0, pos + 5, pos + 1, pos + 1, pos + 10, pos + 6, pos + 2, + pos + 6, pos + 7, pos + 3, pos + 7, pos + 8, pos + 4, pos + 8, pos + 9, pos + 5, pos + 9, pos + 10, + pos + 6, pos + 2, pos + 1, pos + 7, pos + 3, pos + 2, pos + 8, pos + 4, pos + 3, pos + 9, pos + 5, + pos + 4, pos + 10, pos + 1, pos + 5, pos + 11, pos + 7, pos + 6, pos + 11, pos + 8, pos + 7, + pos + 11, pos + 9, pos + 8, pos + 11, pos + 10, pos + 9, pos + 11, pos + 6, pos + 10 }; + final double y = 0.4472 * _radius; + final double a = 0.8944 * _radius; + final double b = 0.2764 * _radius; + final double c = 0.7236 * _radius; + final double d = 0.8507 * _radius; + final double e = 0.5257 * _radius; + pos++; + put(new Vector3(0, _radius, 0)); + pos++; + put(new Vector3(a, y, 0)); + pos++; + put(new Vector3(b, y, -d)); + pos++; + put(new Vector3(-c, y, -e)); + pos++; + put(new Vector3(-c, y, e)); + pos++; + put(new Vector3(b, y, d)); + pos++; + put(new Vector3(c, -y, -e)); + pos++; + put(new Vector3(-b, -y, -d)); + pos++; + put(new Vector3(-a, -y, 0)); + pos++; + put(new Vector3(-b, -y, d)); + pos++; + put(new Vector3(c, -y, e)); + pos++; + put(new Vector3(0, -_radius, 0)); + final Triangle[] ikosaedron = new Triangle[indices.length / 3]; + for (int i = 0; i < ikosaedron.length; i++) { + final Triangle triangle = ikosaedron[i] = new Triangle(); + triangle.pt[0] = indices[i * 3]; + triangle.pt[1] = indices[i * 3 + 1]; + triangle.pt[2] = indices[i * 3 + 2]; + } + + old = ikosaedron; + } else { + /* Six equidistant points lying on the unit sphere */ + final Vector3 XPLUS = new Vector3(_radius, 0, 0); /* X */ + final Vector3 XMIN = new Vector3(-_radius, 0, 0); /* -X */ + final Vector3 YPLUS = new Vector3(0, _radius, 0); /* Y */ + final Vector3 YMIN = new Vector3(0, -_radius, 0); /* -Y */ + final Vector3 ZPLUS = new Vector3(0, 0, _radius); /* Z */ + final Vector3 ZMIN = new Vector3(0, 0, -_radius); /* -Z */ + + final int xplus = pos++; + put(XPLUS); + final int xmin = pos++; + put(XMIN); + final int yplus = pos++; + put(YPLUS); + final int ymin = pos++; + put(YMIN); + final int zplus = pos++; + put(ZPLUS); + final int zmin = pos++; + put(ZMIN); + + final Triangle[] octahedron = new Triangle[] { new Triangle(yplus, zplus, xplus), + new Triangle(xmin, zplus, yplus), new Triangle(ymin, zplus, xmin), + new Triangle(xplus, zplus, ymin), new Triangle(zmin, yplus, xplus), + new Triangle(zmin, xmin, yplus), new Triangle(zmin, ymin, xmin), new Triangle(zmin, xplus, ymin) }; + + old = octahedron; + } + + final Vector3 pt0 = new Vector3(); + final Vector3 pt1 = new Vector3(); + final Vector3 pt2 = new Vector3(); + + /* Subdivide each starting triangle (maxlevels - 1) times */ + for (int level = 1; level < _maxlevels; level++) { + /* Allocate a next triangle[] */ + final Triangle[] next = new Triangle[old.length * 4]; + for (int i = 0; i < next.length; i++) { + next[i] = new Triangle(); + } + + /** + * Subdivide each polygon in the old approximation and normalize the next points thus generated to lie on + * the surface of the unit sphere. Each input triangle with vertBuf labeled [0,1,2] as shown below will be + * turned into four next triangles: + * + * <pre> + * Make next points + * a = (0+2)/2 + * b = (0+1)/2 + * c = (1+2)/2 + * + * 1 /\ Normalize a, b, c + * / \ + * b /____\ c + * + * Construct next triangles + * + * /\ /\ [0,b,a] + * / \ / \ [b,1,c] + * /____\/____\ [a,b,c] + * 0 a 2 [a,c,2] + * </pre> + */ + for (int i = 0; i < old.length; i++) { + int newi = i * 4; + final Triangle oldt = old[i]; + Triangle newt = next[newi]; + + BufferUtils.populateFromBuffer(pt0, vertBuf, oldt.pt[0]); + BufferUtils.populateFromBuffer(pt1, vertBuf, oldt.pt[1]); + BufferUtils.populateFromBuffer(pt2, vertBuf, oldt.pt[2]); + final Vector3 av = createMidpoint(pt0, pt2).normalizeLocal().multiplyLocal(_radius); + final Vector3 bv = createMidpoint(pt0, pt1).normalizeLocal().multiplyLocal(_radius); + final Vector3 cv = createMidpoint(pt1, pt2).normalizeLocal().multiplyLocal(_radius); + final int a = pos++; + put(av); + final int b = pos++; + put(bv); + final int c = pos++; + put(cv); + + newt.pt[0] = oldt.pt[0]; + newt.pt[1] = b; + newt.pt[2] = a; + newt = next[++newi]; + + newt.pt[0] = b; + newt.pt[1] = oldt.pt[1]; + newt.pt[2] = c; + newt = next[++newi]; + + newt.pt[0] = a; + newt.pt[1] = b; + newt.pt[2] = c; + newt = next[++newi]; + + newt.pt[0] = a; + newt.pt[1] = c; + newt.pt[2] = oldt.pt[2]; + } + + /* Continue subdividing next triangles */ + old = next; + } + + final IndexBufferData<?> indexBuffer = BufferUtils.createIndexBufferData(tris * 3, verts - 1); + _meshData.setIndices(indexBuffer); + + int carryIntIndex = _meshData.getVertexBuffer().position() / 3; + for (final Triangle triangle : old) { + for (final int aPt : triangle.pt) { + final Vector3 point = new Vector3(); + BufferUtils.populateFromBuffer(point, _meshData.getVertexBuffer(), aPt); + if (point.getX() > 0 && point.getY() == 0) { + // Find out which 'y' side the triangle is on + final double yCenter = (_meshData.getVertexBuffer().get(triangle.pt[0] * 3 + 1) + + _meshData.getVertexBuffer().get(triangle.pt[1] * 3 + 1) + _meshData.getVertexBuffer() + .get(triangle.pt[2] * 3 + 1)) / 3.0; + if (yCenter > 0.0) { + put(point, true); + indexBuffer.put(carryIntIndex++); + continue; + } + } + indexBuffer.put(aPt); + } + } + } + + private void put(final Vector3 vec) { + put(vec, false); + } + + private void put(final Vector3 vec, final boolean begining) { + final FloatBuffer vertBuf = _meshData.getVertexBuffer(); + vertBuf.put((float) vec.getX()); + vertBuf.put((float) vec.getY()); + vertBuf.put((float) vec.getZ()); + + final double length = vec.length(); + final FloatBuffer normBuf = _meshData.getNormalBuffer(); + final double xNorm = vec.getX() / length; + normBuf.put((float) xNorm); + final double yNorm = vec.getY() / length; + normBuf.put((float) yNorm); + final double zNorm = vec.getZ() / length; + normBuf.put((float) zNorm); + + final FloatBuffer texBuf = _meshData.getTextureCoords(0).getBuffer(); + if (vec.getX() > 0.0 && vec.getY() == 0.0) { + if (begining) { + texBuf.put(0); + } else { + texBuf.put(1); + } + } else { + texBuf.put((float) ((Math.atan2(yNorm, xNorm) / (2 * Math.PI) + 1) % 1)); + } + + double vPos = 0; + switch (_textureMode) { + case Original: + vPos = .5 * (zNorm + 1); + break; + case Projected: + vPos = MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(zNorm)); + break; + } + texBuf.put((float) vPos); + } + + private int calculateBorderTriangles(int levels) { + int current = 108; + // Pattern starts at 4 + levels -= 4; + while (levels-- > 0) { + current = 2 * current + 12; + } + return current; + } + + /** + * Compute the average of two vectors. + * + * @param a + * first vector + * @param b + * second vector + * @return the average of two points + */ + protected Vector3 createMidpoint(final Vector3 a, final Vector3 b) { + return new Vector3((a.getX() + b.getX()) * 0.5, (a.getY() + b.getY()) * 0.5, (a.getZ() + b.getZ()) * 0.5); + } + + static class Triangle { + int[] pt = new int[3]; /* Vertices of triangle */ + + public Triangle() {} + + public Triangle(final int pt0, final int pt1, final int pt2) { + pt[0] = pt0; + pt[1] = pt1; + pt[2] = pt2; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java new file mode 100644 index 0000000..361fbb1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * A regular hexagon with each triangle having side length that is given in the constructor. + */ +public class Hexagon extends Mesh { + + private static final int NUM_POINTS = 7; + + private static final int NUM_TRIS = 6; + + private float _sideLength; + + public Hexagon() {} + + /** + * Hexagon Constructor instantiates a new Hexagon. This element is center on 0,0,0 with all normals pointing up. The + * user must move and rotate for positioning. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + * @param sideLength + * The length of all the sides of the triangles + */ + public Hexagon(final String name, final float sideLength) { + super(name); + _sideLength = sideLength; + // allocate vertices + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0); + + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * NUM_TRIS, NUM_POINTS - 1)); + + setVertexData(); + setIndexData(); + setTextureData(); + setNormalData(); + + } + + /** + * Vertexes are set up like this: 0__1 / \ / \ 5/__\6/__\2 \ / \ / \ /___\ / 4 3 All lines on this diagram are + * sideLength long. Therefore, the width of the hexagon is sideLength * 2, and the height is 2 * the height of one + * equalateral triangle with all side = sideLength which is .866 + */ + private void setVertexData() { + _meshData.getVertexBuffer().put(-(_sideLength / 2)).put(_sideLength * 0.866f).put(0.0f); + _meshData.getVertexBuffer().put(_sideLength / 2).put(_sideLength * 0.866f).put(0.0f); + _meshData.getVertexBuffer().put(_sideLength).put(0.0f).put(0.0f); + _meshData.getVertexBuffer().put(_sideLength / 2).put(-_sideLength * 0.866f).put(0.0f); + _meshData.getVertexBuffer().put(-(_sideLength / 2)).put(-_sideLength * 0.866f).put(0.0f); + _meshData.getVertexBuffer().put(-_sideLength).put(0.0f).put(0.0f); + _meshData.getVertexBuffer().put(0.0f).put(0.0f).put(0.0f); + } + + /** + * Sets up the indexes of the mesh. These go in a clockwise fashion and thus only the 'up' side of the hex is lit + * properly. If you wish to have to either set two sided lighting or create two hexes back-to-back + */ + + private void setIndexData() { + _meshData.getIndexBuffer().rewind(); + // tri 1 + _meshData.getIndices().put(0); + _meshData.getIndices().put(6); + _meshData.getIndices().put(1); + // tri 2 + _meshData.getIndices().put(1); + _meshData.getIndices().put(6); + _meshData.getIndices().put(2); + // tri 3 + _meshData.getIndices().put(2); + _meshData.getIndices().put(6); + _meshData.getIndices().put(3); + // tri 4 + _meshData.getIndices().put(3); + _meshData.getIndices().put(6); + _meshData.getIndices().put(4); + // tri 5 + _meshData.getIndices().put(4); + _meshData.getIndices().put(6); + _meshData.getIndices().put(5); + // tri 6 + _meshData.getIndices().put(5); + _meshData.getIndices().put(6); + _meshData.getIndices().put(0); + } + + private void setTextureData() { + _meshData.getTextureCoords(0).getBuffer().put(0.25f).put(0); + _meshData.getTextureCoords(0).getBuffer().put(0.75f).put(0); + _meshData.getTextureCoords(0).getBuffer().put(1.0f).put(0.5f); + _meshData.getTextureCoords(0).getBuffer().put(0.75f).put(1.0f); + _meshData.getTextureCoords(0).getBuffer().put(0.25f).put(1.0f); + _meshData.getTextureCoords(0).getBuffer().put(0.0f).put(0.5f); + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.5f); + } + + /** + * Sets all the default vertex normals to 'up', +1 in the Z direction. + */ + private void setNormalData() { + final Vector3 zAxis = new Vector3(0, 0, 1); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.setInBuffer(zAxis, _meshData.getNormalBuffer(), i); + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_sideLength, "sideLength", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _sideLength = capsule.readInt("sideLength", 0); + + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java new file mode 100644 index 0000000..4725e36 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class Icosahedron extends Mesh { + + private static final int NUM_POINTS = 12; + + private double _sideLength; + + public Icosahedron() {} + + /** + * Creates an Icosahedron (think of 20-sided dice) with center at the origin. The length of the sides will be as + * specified in sideLength. + * + * @param name + * The name of the Icosahedron. + * @param sideLength + * The length of each side of the Icosahedron. + */ + public Icosahedron(final String name, final double sideLength) { + super(name); + _sideLength = sideLength; + + // allocate vertices + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0); + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + + } + + private void setIndexData() { + final byte[] indices = { 0, 8, 4, 0, 5, 10, 2, 4, 9, 2, 11, 5, 1, 6, 8, 1, 10, 7, 3, 9, 6, 3, 7, 11, 0, 10, 8, + 1, 8, 10, 2, 9, 11, 3, 11, 9, 4, 2, 0, 5, 0, 2, 6, 1, 3, 7, 3, 1, 8, 6, 4, 9, 4, 6, 10, 5, 7, 11, 7, 5 }; + final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length); + buf.put(indices); + buf.rewind(); + _meshData.setIndexBuffer(buf); + } + + private void setTextureData() { + final Vector2 tex = new Vector2(); + final Vector3 vert = new Vector3(); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.populateFromBuffer(vert, _meshData.getVertexBuffer(), i); + if (Math.abs(vert.getZ()) < _sideLength) { + tex.setX(0.5 * (1.0 + Math.atan2(vert.getY(), vert.getX()) * MathUtils.INV_PI)); + } else { + tex.setX(0.5); + } + tex.setY(Math.acos(vert.getZ() / _sideLength) * MathUtils.INV_PI); + + _meshData.getTextureCoords(0).getBuffer().put((float) tex.getX()).put((float) tex.getY()); + } + } + + private void setNormalData() { + final Vector3 norm = new Vector3(); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.populateFromBuffer(norm, _meshData.getVertexBuffer(), i); + norm.normalizeLocal(); + BufferUtils.setInBuffer(norm, _meshData.getNormalBuffer(), i); + } + } + + private void setVertexData() { + final double dGoldenRatio = 0.5 * (1.0 + Math.sqrt(5.0)); + final double dInvRoot = 1.0 / Math.sqrt(1.0 + dGoldenRatio * dGoldenRatio); + final float dU = (float) (dGoldenRatio * dInvRoot * _sideLength); + final float dV = (float) (dInvRoot * _sideLength); + + final FloatBuffer vbuf = _meshData.getVertexBuffer(); + vbuf.rewind(); + vbuf.put(dU).put(dV).put(0.0f); + vbuf.put(-dU).put(dV).put(0.0f); + vbuf.put(dU).put(-dV).put(0.0f); + vbuf.put(-dU).put(-dV).put(0.0f); + vbuf.put(dV).put(0.0f).put(dU); + vbuf.put(dV).put(0.0f).put(-dU); + vbuf.put(-dV).put(0.0f).put(dU); + vbuf.put(-dV).put(0.0f).put(-dU); + vbuf.put(0.0f).put(dU).put(dV); + vbuf.put(0.0f).put(-dU).put(dV); + vbuf.put(0.0f).put(dU).put(-dV); + vbuf.put(0.0f).put(-dU).put(-dV); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_sideLength, "sideLength", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _sideLength = capsule.readInt("sideLength", 0); + + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java new file mode 100644 index 0000000..4698562 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; + +public class MultiFaceBox extends Box { + + public MultiFaceBox() { + super(); + remap(); + } + + public MultiFaceBox(final String name) { + super(name); + remap(); + } + + public MultiFaceBox(final String name, final Vector3 min, final Vector3 max) { + super(name, min, max); + remap(); + } + + public MultiFaceBox(final String name, final Vector3 center, final float xExtent, final float yExtent, + final float zExtent) { + super(name, center, xExtent, yExtent, zExtent); + remap(); + } + + private void remap() { + final FloatBuffer fb = _meshData.getTextureCoords(0).getBuffer(); + fb.rewind(); + for (int i = 0; i < 6; i++) { + final float bottom = i / 8f; + final float top = (i + 1) / 8f; + final float[] tex = new float[] { 1, bottom, 0, bottom, 0, top, 1, top }; + fb.put(tex); + } + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java new file mode 100644 index 0000000..b4e1785 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * An eight faced polyhedron. It looks somewhat like two pyramids placed bottom to bottom. + */ +public class Octahedron extends Mesh { + + private static final int NUM_POINTS = 6; + + private static final int NUM_TRIS = 8; + + private double _sideLength; + + public Octahedron() {} + + /** + * Creates an octahedron with center at the origin. The lenght sides are given. + * + * @param name + * The name of the octahedron. + * @param sideLength + * The length of each side of the octahedron. + */ + public Octahedron(final String name, final double sideLength) { + super(name); + _sideLength = sideLength; + + // allocate vertices + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS)); + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0); + + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * NUM_TRIS, NUM_POINTS - 1)); + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + + } + + private void setIndexData() { + final IndexBufferData<?> indices = _meshData.getIndices(); + indices.getBuffer().rewind(); + indices.put(4).put(0).put(2); + indices.put(4).put(2).put(1); + indices.put(4).put(1).put(3); + indices.put(4).put(3).put(0); + indices.put(5).put(2).put(0); + indices.put(5).put(1).put(2); + indices.put(5).put(3).put(1); + indices.put(5).put(0).put(3); + } + + private void setTextureData() { + final Vector2 tex = new Vector2(); + final Vector3 vert = new Vector3(); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.populateFromBuffer(vert, _meshData.getVertexBuffer(), i); + if (Math.abs(vert.getZ()) < _sideLength) { + tex.setX(0.5 * (1.0 + Math.atan2(vert.getY(), vert.getX()) * MathUtils.INV_PI)); + } else { + tex.setX(0.5); + } + tex.setY(Math.acos(vert.getZ() / _sideLength) * MathUtils.INV_PI); + _meshData.getTextureCoords(0).getBuffer().put((float) tex.getX()).put((float) tex.getY()); + } + } + + private void setNormalData() { + final Vector3 norm = new Vector3(); + for (int i = 0; i < NUM_POINTS; i++) { + BufferUtils.populateFromBuffer(norm, _meshData.getVertexBuffer(), i); + norm.normalizeLocal(); + BufferUtils.setInBuffer(norm, _meshData.getNormalBuffer(), i); + } + } + + private void setVertexData() { + final float floatSideLength = (float) _sideLength; + + _meshData.getVertexBuffer().put(floatSideLength).put(0.0f).put(0.0f); + _meshData.getVertexBuffer().put(-floatSideLength).put(0.0f).put(0.0f); + _meshData.getVertexBuffer().put(0.0f).put(floatSideLength).put(0.0f); + _meshData.getVertexBuffer().put(0.0f).put(-floatSideLength).put(0.0f); + _meshData.getVertexBuffer().put(0.0f).put(0.0f).put(floatSideLength); + _meshData.getVertexBuffer().put(0.0f).put(0.0f).put(-floatSideLength); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_sideLength, "sideLength", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _sideLength = capsule.readInt("sideLength", 0); + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java new file mode 100644 index 0000000..c24b3b5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java @@ -0,0 +1,404 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.CapsuleUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * This primitive represents a box that has options to orient it according to its X/Y/Z axis. It is used to create an + * OrientedBoundingBox mostly. + */ +public class OrientedBox extends Mesh { + + /** Center of the Oriented Box. */ + protected Vector3 _center; + + /** X axis of the Oriented Box. */ + protected Vector3 _xAxis = new Vector3(1, 0, 0); + + /** Y axis of the Oriented Box. */ + protected Vector3 _yAxis = new Vector3(0, 1, 0); + + /** Z axis of the Oriented Box. */ + protected Vector3 _zAxis = new Vector3(0, 0, 1); + + /** Extents of the box along the x,y,z axis. */ + protected Vector3 _extent = new Vector3(0, 0, 0); + + /** Texture coordintae values for the corners of the box. */ + protected Vector2 _texTopRight, _texTopLeft, _texBotRight, _texBotLeft; + + /** Vector array used to store the array of 8 corners the box has. */ + public Vector3[] _vectorStore; + + /** + * If true, the box's vectorStore array correctly represnts the box's corners. + */ + public boolean _correctCorners; + + public OrientedBox() {} + + /** + * Creates a new OrientedBox with the given name. + * + * @param name + * The name of the new box. + */ + public OrientedBox(final String name) { + super(name); + _vectorStore = new Vector3[8]; + for (int i = 0; i < _vectorStore.length; i++) { + _vectorStore[i] = new Vector3(); + } + _texTopRight = new Vector2(1, 1); + _texTopLeft = new Vector2(1, 0); + _texBotRight = new Vector2(0, 1); + _texBotLeft = new Vector2(0, 0); + _center = new Vector3(0, 0, 0); + _correctCorners = false; + computeInformation(); + } + + /** + * Takes the plane and center information and creates the correct vertex,normal,color,texture,index information to + * represent the OrientedBox. + */ + public void computeInformation() { + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + } + + /** + * Sets the correct indices array for the box. + */ + private void setIndexData() { + if (_meshData.getIndexBuffer() == null) { + _meshData.setIndexBuffer(BufferUtils.createByteBuffer(36)); + + for (int i = 0; i < 6; i++) { + _meshData.getIndices().put(i * 4 + 0); + _meshData.getIndices().put(i * 4 + 1); + _meshData.getIndices().put(i * 4 + 3); + _meshData.getIndices().put(i * 4 + 1); + _meshData.getIndices().put(i * 4 + 2); + _meshData.getIndices().put(i * 4 + 3); + } + } + } + + /** + * Sets the correct texture array for the box. + */ + private void setTextureData() { + if (_meshData.getTextureBuffer(0) == null) { + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(24), 0); + + for (int x = 0; x < 6; x++) { + _meshData.getTextureCoords(0).getBuffer().put(_texTopRight.getXf()).put(_texTopRight.getYf()); + _meshData.getTextureCoords(0).getBuffer().put(_texTopLeft.getXf()).put(_texTopLeft.getYf()); + _meshData.getTextureCoords(0).getBuffer().put(_texBotLeft.getXf()).put(_texBotLeft.getYf()); + _meshData.getTextureCoords(0).getBuffer().put(_texBotRight.getXf()).put(_texBotRight.getYf()); + } + } + } + + /** + * Sets the correct normal array for the box. + */ + private void setNormalData() { + if (_meshData.getNormalBuffer() == null) { + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(24)); + } else { + _meshData.getNormalBuffer().rewind(); + } + + // top + _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf()); + _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf()); + _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf()); + _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf()); + + // right + _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf()); + _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf()); + _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf()); + _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf()); + + // left + _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf()); + _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf()); + _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf()); + _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf()); + + // bottom + _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf()); + _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf()); + _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf()); + _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf()); + + // back + _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf()); + _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf()); + _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf()); + _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf()); + + // front + _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf()); + _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf()); + _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf()); + _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf()); + } + + /** + * Sets the correct vertex information for the box. + */ + private void setVertexData() { + computeCorners(); + if (_meshData.getVertexBuffer() == null) { + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(24)); + } else { + _meshData.getVertexBuffer().rewind(); + } + + // Top + _meshData.getVertexBuffer().put(_vectorStore[0].getXf()).put(_vectorStore[0].getYf()) + .put(_vectorStore[0].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[1].getXf()).put(_vectorStore[1].getYf()) + .put(_vectorStore[1].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[5].getXf()).put(_vectorStore[5].getYf()) + .put(_vectorStore[5].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[3].getXf()).put(_vectorStore[3].getYf()) + .put(_vectorStore[3].getZf()); + + // Right + _meshData.getVertexBuffer().put(_vectorStore[0].getXf()).put(_vectorStore[0].getYf()) + .put(_vectorStore[0].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[3].getXf()).put(_vectorStore[3].getYf()) + .put(_vectorStore[3].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[6].getXf()).put(_vectorStore[6].getYf()) + .put(_vectorStore[6].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[2].getXf()).put(_vectorStore[2].getYf()) + .put(_vectorStore[2].getZf()); + + // Left + _meshData.getVertexBuffer().put(_vectorStore[5].getXf()).put(_vectorStore[5].getYf()) + .put(_vectorStore[5].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[1].getXf()).put(_vectorStore[1].getYf()) + .put(_vectorStore[1].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[4].getXf()).put(_vectorStore[4].getYf()) + .put(_vectorStore[4].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[7].getXf()).put(_vectorStore[7].getYf()) + .put(_vectorStore[7].getZf()); + + // Bottom + _meshData.getVertexBuffer().put(_vectorStore[6].getXf()).put(_vectorStore[6].getYf()) + .put(_vectorStore[6].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[7].getXf()).put(_vectorStore[7].getYf()) + .put(_vectorStore[7].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[4].getXf()).put(_vectorStore[4].getYf()) + .put(_vectorStore[4].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[2].getXf()).put(_vectorStore[2].getYf()) + .put(_vectorStore[2].getZf()); + + // Back + _meshData.getVertexBuffer().put(_vectorStore[3].getXf()).put(_vectorStore[3].getYf()) + .put(_vectorStore[3].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[5].getXf()).put(_vectorStore[5].getYf()) + .put(_vectorStore[5].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[7].getXf()).put(_vectorStore[7].getYf()) + .put(_vectorStore[7].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[6].getXf()).put(_vectorStore[6].getYf()) + .put(_vectorStore[6].getZf()); + + // Front + _meshData.getVertexBuffer().put(_vectorStore[1].getXf()).put(_vectorStore[1].getYf()) + .put(_vectorStore[1].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[4].getXf()).put(_vectorStore[4].getYf()) + .put(_vectorStore[4].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[2].getXf()).put(_vectorStore[2].getYf()) + .put(_vectorStore[2].getZf()); + _meshData.getVertexBuffer().put(_vectorStore[0].getXf()).put(_vectorStore[0].getYf()) + .put(_vectorStore[0].getZf()); + } + + /** + * Sets the vectorStore information to the 8 corners of the box. + */ + public void computeCorners() { + _correctCorners = true; + + final Vector3 tempVa = Vector3.fetchTempInstance(); + final Vector3 tempVb = Vector3.fetchTempInstance(); + final Vector3 tempVc = Vector3.fetchTempInstance(); + tempVa.set(_xAxis).multiplyLocal(_extent.getX()); + tempVb.set(_yAxis).multiplyLocal(_extent.getY()); + tempVc.set(_zAxis).multiplyLocal(_extent.getZ()); + + _vectorStore[0].set(_center).addLocal(tempVa).addLocal(tempVb).addLocal(tempVc); + _vectorStore[1].set(_center).addLocal(tempVa).subtractLocal(tempVb).addLocal(tempVc); + _vectorStore[2].set(_center).addLocal(tempVa).addLocal(tempVb).subtractLocal(tempVc); + _vectorStore[3].set(_center).subtractLocal(tempVa).addLocal(tempVb).addLocal(tempVc); + _vectorStore[4].set(_center).addLocal(tempVa).subtractLocal(tempVb).subtractLocal(tempVc); + _vectorStore[5].set(_center).subtractLocal(tempVa).subtractLocal(tempVb).addLocal(tempVc); + _vectorStore[6].set(_center).subtractLocal(tempVa).addLocal(tempVb).subtractLocal(tempVc); + _vectorStore[7].set(_center).subtractLocal(tempVa).subtractLocal(tempVb).subtractLocal(tempVc); + + Vector3.releaseTempInstance(tempVa); + Vector3.releaseTempInstance(tempVb); + Vector3.releaseTempInstance(tempVc); + } + + /** + * Returns the center of the box. + * + * @return The box's center. + */ + public Vector3 getCenter() { + return _center; + } + + /** + * Sets the box's center to the given value. Shallow copy only. + * + * @param center + * The box's new center. + */ + public void setCenter(final Vector3 center) { + _center = center; + } + + /** + * Returns the box's extent vector along the x,y,z. + * + * @return The box's extent vector. + */ + public Vector3 getExtent() { + return _extent; + } + + /** + * Sets the box's extent vector to the given value. Shallow copy only. + * + * @param extent + * The box's new extent. + */ + public void setExtent(final Vector3 extent) { + _extent = extent; + } + + /** + * Returns the x axis of this box. + * + * @return This OB's x axis. + */ + public Vector3 getxAxis() { + return _xAxis; + } + + /** + * Sets the x axis of this OB. Shallow copy. + * + * @param xAxis + * The new x axis. + */ + public void setXAxis(final Vector3 xAxis) { + _xAxis = xAxis; + } + + /** + * Gets the Y axis of this OB. + * + * @return This OB's Y axis. + */ + public Vector3 getYAxis() { + return _yAxis; + } + + /** + * Sets the Y axis of this OB. Shallow copy. + * + * @param yAxis + * The new Y axis. + */ + public void setYAxis(final Vector3 yAxis) { + _yAxis = yAxis; + } + + /** + * Returns the Z axis of this OB. + * + * @return The Z axis. + */ + public Vector3 getZAxis() { + return _zAxis; + } + + /** + * Sets the Z axis of this OB. Shallow copy. + * + * @param zAxis + * The new Z axis. + */ + public void setZAxis(final Vector3 zAxis) { + _zAxis = zAxis; + } + + /** + * Returns if the corners are set corectly. + * + * @return True if the vectorStore is correct. + */ + public boolean isCorrectCorners() { + return _correctCorners; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + capsule.write(_xAxis, "_xAxis", new Vector3(Vector3.UNIT_X)); + capsule.write(_yAxis, "yAxis", new Vector3(Vector3.UNIT_Y)); + capsule.write(_zAxis, "zAxis", new Vector3(Vector3.UNIT_Z)); + capsule.write(_extent, "extent", new Vector3(Vector3.ZERO)); + capsule.write(_texTopRight, "texTopRight", new Vector2(1, 1)); + capsule.write(_texTopLeft, "texTopLeft", new Vector2(1, 0)); + capsule.write(_texBotRight, "texBotRight", new Vector2(0, 1)); + capsule.write(_texBotLeft, "texBotLeft", new Vector2(0, 0)); + capsule.write(_vectorStore, "vectorStore", new Vector3[8]); + capsule.write(_correctCorners, "correctCorners", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _center = (Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO)); + _xAxis = (Vector3) capsule.readSavable("_xAxis", new Vector3(Vector3.UNIT_X)); + _yAxis = (Vector3) capsule.readSavable("yAxis", new Vector3(Vector3.UNIT_Y)); + _zAxis = (Vector3) capsule.readSavable("zAxis", new Vector3(Vector3.UNIT_Z)); + _extent = (Vector3) capsule.readSavable("extent", new Vector3(Vector3.ZERO)); + _texTopRight = (Vector2) capsule.readSavable("texTopRight", new Vector2(1, 1)); + _texTopLeft = (Vector2) capsule.readSavable("texTopLeft", new Vector2(1, 0)); + _texBotRight = (Vector2) capsule.readSavable("texBotRight", new Vector2(0, 1)); + _texBotLeft = (Vector2) capsule.readSavable("texBotLeft", new Vector2(0, 0)); + _vectorStore = CapsuleUtils.asArray(capsule.readSavableArray("vectorStore", new Vector3[8]), Vector3.class); + _correctCorners = capsule.readBoolean("correctCorners", false); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java new file mode 100644 index 0000000..f1380a0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * PQTorus generates the geometry of a parameterized torus, also known as a pq torus. + */ +public class PQTorus extends Mesh { + + private double _p, _q; + + private double _radius, _width; + + private int _steps, _radialSamples; + + public PQTorus() {} + + /** + * Creates a parameterized torus. Steps and radialSamples are both degree of accuracy values. + * + * @param name + * The name of the torus. + * @param p + * The x/z oscillation. + * @param q + * The y oscillation. + * @param radius + * The radius of the PQTorus. + * @param width + * The width of the torus. + * @param steps + * The steps along the torus. + * @param radialSamples + * Radial samples for the torus. + */ + public PQTorus(final String name, final double p, final double q, final double radius, final double width, + final int steps, final int radialSamples) { + super(name); + + _p = p; + _q = q; + _radius = radius; + _width = width; + _steps = steps; + _radialSamples = radialSamples; + + setGeometryData(); + setIndexData(); + } + + private void setGeometryData() { + final double THETA_STEP = (MathUtils.TWO_PI / _steps); + final double BETA_STEP = (MathUtils.TWO_PI / _radialSamples); + + final Vector3[] toruspoints = new Vector3[_steps]; + // allocate vertices + final int verts = _radialSamples * _steps; + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate normals if requested + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate texture coordinates + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + + final Vector3 pointB = Vector3.fetchTempInstance(); + final Vector3 T = Vector3.fetchTempInstance(), N = Vector3.fetchTempInstance(), B = Vector3.fetchTempInstance(); + final Vector3 tempNormA = Vector3.fetchTempInstance(); + final Vector3 tempNormB = Vector3.fetchTempInstance(); + double r, x, y, z, theta = 0.0, beta = 0.0; + + // Move along the length of the pq torus + for (int i = 0; i < _steps; i++) { + theta += THETA_STEP; + final double circleFraction = ((double) i) / (double) _steps; + + // Find the point on the torus + r = (0.5 * (2.0 + MathUtils.sin(_q * theta)) * _radius); + x = (r * MathUtils.cos(_p * theta) * _radius); + y = (r * MathUtils.sin(_p * theta) * _radius); + z = (r * MathUtils.cos(_q * theta) * _radius); + toruspoints[i] = new Vector3(x, y, z); + + // Now find a point slightly farther along the torus + r = (0.5 * (2.0 + MathUtils.sin(_q * (theta + 0.01))) * _radius); + x = (r * MathUtils.cos(_p * (theta + 0.01)) * _radius); + y = (r * MathUtils.sin(_p * (theta + 0.01)) * _radius); + z = (r * MathUtils.cos(_q * (theta + 0.01)) * _radius); + pointB.set(x, y, z); + + // Approximate the Frenet Frame + pointB.subtract(toruspoints[i], T); + toruspoints[i].add(pointB, N); + T.cross(N, B); + B.cross(T, N); + + // Normalize the two vectors before use + N.normalizeLocal(); + B.normalizeLocal(); + + // Create a circle oriented by these new vectors + beta = 0.0; + for (int j = 0; j < _radialSamples; j++) { + beta += BETA_STEP; + final double cx = MathUtils.cos(beta) * _width; + final double cy = MathUtils.sin(beta) * _width; + final double radialFraction = ((double) j) / _radialSamples; + tempNormA.setX((cx * N.getX() + cy * B.getX())); + tempNormA.setY((cx * N.getY() + cy * B.getY())); + tempNormA.setZ((cx * N.getZ() + cy * B.getZ())); + tempNormA.normalize(tempNormB); + tempNormA.addLocal(toruspoints[i]); + + _meshData.getVertexBuffer().put(tempNormA.getXf()).put(tempNormA.getYf()).put(tempNormA.getZf()); + _meshData.getNormalBuffer().put(tempNormB.getXf()).put(tempNormB.getYf()).put(tempNormB.getZf()); + _meshData.getTextureCoords(0).getBuffer().put((float) radialFraction).put((float) circleFraction); + } + } + Vector3.releaseTempInstance(tempNormA); + Vector3.releaseTempInstance(tempNormB); + Vector3.releaseTempInstance(T); + Vector3.releaseTempInstance(N); + Vector3.releaseTempInstance(B); + Vector3.releaseTempInstance(pointB); + } + + private void setIndexData() { + final IndexBufferData<?> indices = BufferUtils.createIndexBufferData(6 * _meshData.getVertexCount(), + _meshData.getVertexCount() - 1); + + for (int i = _radialSamples; i < _meshData.getVertexCount() + (_radialSamples); i++) { + indices.put(i); + indices.put(i - _radialSamples); + indices.put(i + 1); + + indices.put(i + 1); + indices.put(i - _radialSamples); + indices.put(i - _radialSamples + 1); + } + + for (int i = 0, len = indices.getBufferCapacity(); i < len; i++) { + int ind = indices.get(i); + if (ind < 0) { + ind += _meshData.getVertexCount(); + indices.put(i, ind); + } + if (ind >= _meshData.getVertexCount()) { + ind -= _meshData.getVertexCount(); + indices.put(i, ind); + } + } + indices.getBuffer().rewind(); + + _meshData.setIndices(indices); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_p, "p", 0); + capsule.write(_q, "q", 0); + capsule.write(_radius, "radius", 0); + capsule.write(_width, "width", 0); + capsule.write(_steps, "steps", 0); + capsule.write(_radialSamples, "radialSamples", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _p = capsule.readDouble("p", 0); + _q = capsule.readDouble("q", 0); + _radius = capsule.readDouble("radius", 0); + _width = capsule.readDouble("width", 0); + _steps = capsule.readInt("steps", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java new file mode 100644 index 0000000..9a008f6 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Pyramid</code> provides an extension of <code>Mesh</code>. A pyramid is defined by a width at the base and a + * height. The pyramid is a four sided pyramid with the center at (0,0). The pyramid will be axis aligned with the peak + * being on the positive y axis and the base being in the x-z plane. + */ +public class Pyramid extends Mesh { + + private double _height; + + private double _width; + + public Pyramid() {} + + /** + * Constructor instantiates a new <code>Pyramid</code> object. The base width and the height are provided. + * + * @param name + * the name of the scene element. This is required for identification and comparison purposes. + * @param width + * the base width of the pyramid. + * @param height + * the height of the pyramid from the base to the peak. + */ + public Pyramid(final String name, final double width, final double height) { + super(name); + _width = width; + _height = height; + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + } + + /** + * <code>setVertexData</code> sets the vertices that make the pyramid. Where the center of the box is the origin and + * the base and height are set during construction. + */ + private void setVertexData() { + final Vector3 peak = new Vector3(0, _height / 2, 0); + final Vector3 vert0 = new Vector3(-_width / 2, -_height / 2, -_width / 2); + final Vector3 vert1 = new Vector3(_width / 2, -_height / 2, -_width / 2); + final Vector3 vert2 = new Vector3(_width / 2, -_height / 2, _width / 2); + final Vector3 vert3 = new Vector3(-_width / 2, -_height / 2, _width / 2); + + final FloatBuffer verts = BufferUtils.createVector3Buffer(16); + + // base + verts.put(vert3.getXf()).put(vert3.getYf()).put(vert3.getZf()); + verts.put(vert2.getXf()).put(vert2.getYf()).put(vert2.getZf()); + verts.put(vert1.getXf()).put(vert1.getYf()).put(vert1.getZf()); + verts.put(vert0.getXf()).put(vert0.getYf()).put(vert0.getZf()); + + // side 1 + verts.put(vert0.getXf()).put(vert0.getYf()).put(vert0.getZf()); + verts.put(vert1.getXf()).put(vert1.getYf()).put(vert1.getZf()); + verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf()); + + // side 2 + verts.put(vert1.getXf()).put(vert1.getYf()).put(vert1.getZf()); + verts.put(vert2.getXf()).put(vert2.getYf()).put(vert2.getZf()); + verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf()); + + // side 3 + verts.put(vert2.getXf()).put(vert2.getYf()).put(vert2.getZf()); + verts.put(vert3.getXf()).put(vert3.getYf()).put(vert3.getZf()); + verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf()); + + // side 4 + verts.put(vert3.getXf()).put(vert3.getYf()).put(vert3.getZf()); + verts.put(vert0.getXf()).put(vert0.getYf()).put(vert0.getZf()); + verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf()); + + verts.rewind(); + _meshData.setVertexBuffer(verts); + } + + /** + * <code>setNormalData</code> defines the normals of each face of the pyramid. + */ + private void setNormalData() { + final FloatBuffer norms = BufferUtils.createVector3Buffer(16); + + // bottom + norms.put(0).put(-1).put(0); + norms.put(0).put(-1).put(0); + norms.put(0).put(-1).put(0); + norms.put(0).put(-1).put(0); + + // back + norms.put(0).put(0.70710677f).put(-0.70710677f); + norms.put(0).put(0.70710677f).put(-0.70710677f); + norms.put(0).put(0.70710677f).put(-0.70710677f); + + // right + norms.put(0.70710677f).put(0.70710677f).put(0); + norms.put(0.70710677f).put(0.70710677f).put(0); + norms.put(0.70710677f).put(0.70710677f).put(0); + + // front + norms.put(0).put(0.70710677f).put(0.70710677f); + norms.put(0).put(0.70710677f).put(0.70710677f); + norms.put(0).put(0.70710677f).put(0.70710677f); + + // left + norms.put(-0.70710677f).put(0.70710677f).put(0); + norms.put(-0.70710677f).put(0.70710677f).put(0); + norms.put(-0.70710677f).put(0.70710677f).put(0); + + norms.rewind(); + _meshData.setNormalBuffer(norms); + } + + /** + * <code>setTextureData</code> sets the texture that defines the look of the pyramid. The top point of the pyramid + * is the top center of the texture, with the remaining texture wrapping around it. + */ + private void setTextureData() { + final FloatBuffer texCoords = BufferUtils.createVector2Buffer(16); + + texCoords.put(1).put(0); + texCoords.put(0).put(0); + texCoords.put(0).put(1); + texCoords.put(1).put(1); + + texCoords.put(1).put(0); + texCoords.put(0.75f).put(0); + texCoords.put(0.5f).put(1); + + texCoords.put(0.75f).put(0); + texCoords.put(0.5f).put(0); + texCoords.put(0.5f).put(1); + + texCoords.put(0.5f).put(0); + texCoords.put(0.25f).put(0); + texCoords.put(0.5f).put(1); + + texCoords.put(0.25f).put(0); + texCoords.put(0).put(0); + texCoords.put(0.5f).put(1); + + texCoords.rewind(); + _meshData.setTextureBuffer(texCoords, 0); + } + + /** + * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the + * pyramid. + */ + private void setIndexData() { + final IndexBufferData<?> indices = BufferUtils.createIndexBufferData(18, 16 - 1); + indices.put(3).put(2).put(1); + indices.put(3).put(1).put(0); + indices.put(6).put(5).put(4); + indices.put(9).put(8).put(7); + indices.put(12).put(11).put(10); + indices.put(15).put(14).put(13); + + indices.getBuffer().rewind(); + _meshData.setIndices(indices); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_height, "height", 0); + capsule.write(_width, "width", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _height = capsule.readDouble("height", 0); + _width = capsule.readDouble("width", 0); + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java new file mode 100644 index 0000000..e2036fd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Quad</code> defines a four sided, two dimensional shape. The local height of the <code>Quad</code> defines it's + * size about the y-axis, while the width defines the x-axis. The z-axis will always be 0. + */ +public class Quad extends Mesh { + + protected double _width = 0; + protected double _height = 0; + + public Quad() { + + } + + /** + * Constructor creates a new <code>Quad</code> object. + * + * @param name + * the name of this <code>Quad</code>. + */ + public Quad(final String name) { + this(name, 1, 1); + } + + /** + * Constructor creates a new <code>Quade</code> object with the provided width and height. + * + * @param name + * the name of the <code>Quad</code>. + * @param width + * the width of the <code>Quad</code>. + * @param height + * the height of the <code>Quad</code>. + */ + public Quad(final String name, final double width, final double height) { + super(name); + initialize(width, height); + } + + /** + * <code>resize</code> changes the width and height of the given quad by altering its vertices. + * + * @param width + * the new width of the <code>Quad</code>. + * @param height + * the new height of the <code>Quad</code>. + */ + public void resize(final double width, final double height) { + _width = width; + _height = height; + + _meshData.getVertexBuffer().clear(); + _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (height / 2)).put(0); + _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (-height / 2)).put(0); + _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (-height / 2)).put(0); + _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (height / 2)).put(0); + } + + /** + * <code>initialize</code> builds the data for the <code>Quad</code> object. + * + * @param width + * the width of the <code>Quad</code>. + * @param height + * the height of the <code>Quad</code>. + */ + private void initialize(final double width, final double height) { + final int verts = 4; + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts)); + final FloatBuffer tbuf = BufferUtils.createVector2Buffer(verts); + _meshData.setTextureBuffer(tbuf, 0); + + _meshData.getNormalBuffer().put(0).put(0).put(1); + _meshData.getNormalBuffer().put(0).put(0).put(1); + _meshData.getNormalBuffer().put(0).put(0).put(1); + _meshData.getNormalBuffer().put(0).put(0).put(1); + + tbuf.put(0).put(1); + tbuf.put(0).put(0); + tbuf.put(1).put(0); + tbuf.put(1).put(1); + + final byte[] indices = { 0, 1, 2, 0, 2, 3 }; + final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length); + buf.put(indices); + buf.rewind(); + _meshData.setIndexBuffer(buf); + + resize(width, height); + } + + public double getWidth() { + return _width; + } + + public double getHeight() { + return _height; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java new file mode 100644 index 0000000..1f252e7 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java @@ -0,0 +1,305 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class RoundedBox extends Mesh { + + private final Vector3 _extent = new Vector3(0.5, 0.5, 0.5); + private final Vector3 _border = new Vector3(0.05, 0.05, 0.05); + private final Vector3 _slope = new Vector3(0.02, 0.02, 0.02); + + /** Creates a new instance of RoundedBox */ + public RoundedBox(final String name) { + super(name); + setData(); + } + + public RoundedBox(final String name, final Vector3 extent) { + super(name); + extent.subtract(_slope, _extent); + setData(); + } + + public RoundedBox(final String name, final Vector3 extent, final Vector3 border, final Vector3 slope) { + super(name); + _border.set(border); + _slope.set(slope); + extent.subtract(_slope, _extent); + setData(); + } + + private void setData() { + setVertexAndNormalData(); + setTextureData(); + setIndexData(); + } + + private void put(final FloatBuffer fb, final FloatBuffer nb, final Vector3 vec) { + fb.put((float) vec.getX()).put((float) vec.getY()).put((float) vec.getZ()); + final Vector3 v = vec.normalize(Vector3.fetchTempInstance()); + nb.put((float) v.getX()).put((float) v.getY()).put((float) v.getZ()); + Vector3.releaseTempInstance(v); + } + + private void setVertexAndNormalData() { + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), 48)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(48)); + final Vector3[] vert = computeVertices(); // returns 32 + final FloatBuffer vb = _meshData.getVertexBuffer(); + final FloatBuffer nb = _meshData.getNormalBuffer(); + + // bottom + put(vb, nb, vert[0]); + put(vb, nb, vert[1]); + put(vb, nb, vert[2]); + put(vb, nb, vert[3]); + put(vb, nb, vert[8]); + put(vb, nb, vert[9]); + put(vb, nb, vert[10]); + put(vb, nb, vert[11]); + + // front + put(vb, nb, vert[1]); + put(vb, nb, vert[0]); + put(vb, nb, vert[5]); + put(vb, nb, vert[4]); + put(vb, nb, vert[13]); + put(vb, nb, vert[12]); + put(vb, nb, vert[15]); + put(vb, nb, vert[14]); + + // right + put(vb, nb, vert[3]); + put(vb, nb, vert[1]); + put(vb, nb, vert[7]); + put(vb, nb, vert[5]); + put(vb, nb, vert[17]); + put(vb, nb, vert[16]); + put(vb, nb, vert[19]); + put(vb, nb, vert[18]); + + // back + put(vb, nb, vert[2]); + put(vb, nb, vert[3]); + put(vb, nb, vert[6]); + put(vb, nb, vert[7]); + put(vb, nb, vert[20]); + put(vb, nb, vert[21]); + put(vb, nb, vert[22]); + put(vb, nb, vert[23]); + + // left + put(vb, nb, vert[0]); + put(vb, nb, vert[2]); + put(vb, nb, vert[4]); + put(vb, nb, vert[6]); + put(vb, nb, vert[24]); + put(vb, nb, vert[25]); + put(vb, nb, vert[26]); + put(vb, nb, vert[27]); + + // top + put(vb, nb, vert[5]); + put(vb, nb, vert[4]); + put(vb, nb, vert[7]); + put(vb, nb, vert[6]); + put(vb, nb, vert[29]); + put(vb, nb, vert[28]); + put(vb, nb, vert[31]); + put(vb, nb, vert[30]); + } + + private void setTextureData() { + if (_meshData.getTextureCoords(0) == null) { + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(48), 0); + final FloatBuffer tex = _meshData.getTextureCoords(0).getBuffer(); + + final double[][] ratio = new double[][] { + { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()), + 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()) }, + { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()), + 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) }, + { 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()), + 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) }, + { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()), + 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) }, + { 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()), + 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) }, + { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()), + 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()) }, }; + + for (int i = 0; i < 6; i++) { + tex.put(1).put(0); + tex.put(0).put(0); + tex.put(1).put(1); + tex.put(0).put(1); + tex.put((float) (1 - ratio[i][0])).put((float) (0 + ratio[i][1])); + tex.put((float) (0 + ratio[i][0])).put((float) (0 + ratio[i][1])); + tex.put((float) (1 - ratio[i][0])).put((float) (1 - ratio[i][1])); + tex.put((float) (0 + ratio[i][0])).put((float) (1 - ratio[i][1])); + } + } + } + + private void setIndexData() { + if (_meshData.getIndexBuffer() == null) { + final IndexBufferData<?> buff = BufferUtils.createIndexBufferData(180, 48 - 1); + final int[] data = new int[] { 0, 4, 1, 1, 4, 5, 1, 5, 3, 3, 5, 7, 3, 7, 2, 2, 7, 6, 2, 6, 0, 0, 6, 4, 4, + 6, 5, 5, 6, 7 }; + for (int i = 0; i < 6; i++) { + for (int n = 0; n < 30; n++) { + buff.put(30 * i + n, 8 * i + data[n]); + } + } + _meshData.setIndices(buff); + } + } + + public Vector3[] computeVertices() { + return new Vector3[] { + // Cube + new Vector3(-_extent.getX(), -_extent.getY(), _extent.getZ()), // 0 + new Vector3(_extent.getX(), -_extent.getY(), _extent.getZ()), // 1 + new Vector3(-_extent.getX(), -_extent.getY(), -_extent.getZ()), // 2 + new Vector3(_extent.getX(), -_extent.getY(), -_extent.getZ()), // 3 + new Vector3(-_extent.getX(), _extent.getY(), _extent.getZ()), // 4 + new Vector3(_extent.getX(), _extent.getY(), _extent.getZ()), // 5 + new Vector3(-_extent.getX(), _extent.getY(), -_extent.getZ()), // 6 + new Vector3(_extent.getX(), _extent.getY(), -_extent.getZ()), // 7 + // bottom + new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() - _slope.getY(), _extent.getZ() + - _border.getZ()), // 8 (0) + new Vector3(_extent.getX() - _border.getX(), -_extent.getY() - _slope.getY(), _extent.getZ() + - _border.getZ()), // 9 + // ( + // 1 + // ) + new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() - _slope.getY(), -_extent.getZ() + + _border.getZ()), // 10 (2) + new Vector3(_extent.getX() - _border.getX(), -_extent.getY() - _slope.getY(), -_extent.getZ() + + _border.getZ()), // 11 (3) + // front + new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() + _border.getY(), _extent.getZ() + + _slope.getZ()), // 12 (0) + new Vector3(_extent.getX() - _border.getX(), -_extent.getY() + _border.getY(), _extent.getZ() + + _slope.getZ()), // 13 + // ( + // 1 + // ) + new Vector3(-_extent.getX() + _border.getX(), _extent.getY() - _border.getY(), _extent.getZ() + + _slope.getZ()), // 14 + // ( + // 4 + // ) + new Vector3(_extent.getX() - _border.getX(), _extent.getY() - _border.getY(), _extent.getZ() + + _slope.getZ()), // 15 + // ( + // 5 + // ) + // right + new Vector3(_extent.getX() + _slope.getX(), -_extent.getY() + _border.getY(), _extent.getZ() + - _border.getZ()), // 16 + // ( + // 1 + // ) + new Vector3(_extent.getX() + _slope.getX(), -_extent.getY() + _border.getY(), -_extent.getZ() + + _border.getZ()), // 17 (3) + new Vector3(_extent.getX() + _slope.getX(), _extent.getY() - _border.getY(), _extent.getZ() + - _border.getZ()), // 18 + // ( + // 5 + // ) + new Vector3(_extent.getX() + _slope.getX(), _extent.getY() - _border.getY(), -_extent.getZ() + + _border.getZ()), // 19 + // ( + // 7 + // ) + // back + new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() + _border.getY(), -_extent.getZ() + - _slope.getZ()), // 20 (2) + new Vector3(_extent.getX() - _border.getX(), -_extent.getY() + _border.getY(), -_extent.getZ() + - _slope.getZ()), // 21 (3) + new Vector3(-_extent.getX() + _border.getX(), _extent.getY() - _border.getY(), -_extent.getZ() + - _slope.getZ()), // 22 (6) + new Vector3(_extent.getX() - _border.getX(), _extent.getY() - _border.getY(), -_extent.getZ() + - _slope.getZ()), // 23 + // ( + // 7 + // ) + // left + new Vector3(-_extent.getX() - _slope.getX(), -_extent.getY() + _border.getY(), _extent.getZ() + - _border.getZ()), // 24 (0) + new Vector3(-_extent.getX() - _slope.getX(), -_extent.getY() + _border.getY(), -_extent.getZ() + + _border.getZ()), // 25 (2) + new Vector3(-_extent.getX() - _slope.getX(), _extent.getY() - _border.getY(), _extent.getZ() + - _border.getZ()), // 26 + // ( + // 4 + // ) + new Vector3(-_extent.getX() - _slope.getX(), _extent.getY() - _border.getY(), -_extent.getZ() + + _border.getZ()), // 27 (6) + // top + new Vector3(-_extent.getX() + _border.getX(), _extent.getY() + _slope.getY(), _extent.getZ() + - _border.getZ()), // 28 + // ( + // 4 + // ) + new Vector3(_extent.getX() - _border.getX(), _extent.getY() + _slope.getY(), _extent.getZ() + - _border.getZ()), // 29 + // ( + // 5 + // ) + new Vector3(-_extent.getX() + _border.getX(), _extent.getY() + _slope.getY(), -_extent.getZ() + + _border.getZ()), // 30 (6) + new Vector3(_extent.getX() - _border.getX(), _extent.getY() + _slope.getY(), -_extent.getZ() + + _border.getZ()), // 31 + // ( + // 7 + // ) + }; + } + + /** + * <code>clone</code> creates a new RoundedBox object containing the same data as this one. + * + * @return the new Box + */ + @Override + public RoundedBox clone() { + return new RoundedBox(getName() + "_clone", _extent.clone(), _border.clone(), _slope.clone()); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_extent, "extent", new Vector3(Vector3.ZERO)); + capsule.write(_border, "border", new Vector3(Vector3.ZERO)); + capsule.write(_slope, "slope", new Vector3(Vector3.ZERO)); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _extent.set((Vector3) capsule.readSavable("extent", new Vector3(Vector3.ZERO))); + _border.set((Vector3) capsule.readSavable("border", new Vector3(Vector3.ZERO))); + _slope.set((Vector3) capsule.readSavable("slope", new Vector3(Vector3.ZERO))); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java new file mode 100644 index 0000000..46e1e18 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java @@ -0,0 +1,436 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Sphere represents a 3D object with all points equi-distance from a center point. + */ +public class Sphere extends Mesh { + + public enum TextureMode { + Linear, Projected, Polar; + } + + protected int _zSamples; + + protected int _radialSamples; + + /** the distance from the center point each point falls on */ + public double _radius; + /** the center of the sphere */ + public final Vector3 _center = new Vector3(); + + protected TextureMode _textureMode = TextureMode.Linear; + + protected boolean _viewInside = false; + + public Sphere() {} + + /** + * Constructs a sphere. By default the Sphere has not geometry data or center. + * + * @param name + * The name of the sphere. + */ + public Sphere(final String name) { + super(name); + } + + /** + * Constructs a sphere with center at the origin. For details, see the other constructor. + * + * @param name + * Name of sphere. + * @param zSamples + * The samples along the Z. + * @param radialSamples + * The samples along the radial. + * @param radius + * Radius of the sphere. + * @see #Sphere(java.lang.String, com.ardor3d.math.Vector3, int, int, double) + */ + public Sphere(final String name, final int zSamples, final int radialSamples, final double radius) { + this(name, new Vector3(0, 0, 0), zSamples, radialSamples, radius); + } + + /** + * Constructs a sphere. All geometry data buffers are updated automatically. Both zSamples and radialSamples + * increase the quality of the generated sphere. + * + * @param name + * Name of the sphere. + * @param center + * Center of the sphere. + * @param zSamples + * The number of samples along the Z. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the sphere. + */ + public Sphere(final String name, final ReadOnlyVector3 center, final int zSamples, final int radialSamples, + final double radius) { + super(name); + setData(center, zSamples, radialSamples, radius); + } + + /** + * Constructs a sphere. All geometry data buffers are updated automatically. Both zSamples and radialSamples + * increase the quality of the generated sphere. + * + * @param name + * Name of the sphere. + * @param center + * Center of the sphere. + * @param zSamples + * The number of samples along the Z. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the sphere. + * @param textureMode + * the mode to use when setting uv coordinates for this Sphere. + */ + public Sphere(final String name, final ReadOnlyVector3 center, final int zSamples, final int radialSamples, + final double radius, final TextureMode textureMode) { + super(name); + _textureMode = textureMode; + setData(center, zSamples, radialSamples, radius); + } + + /** + * Changes the information of the sphere into the given values. + * + * @param center + * The new center of the sphere. + * @param zSamples + * The new number of zSamples of the sphere. + * @param radialSamples + * The new number of radial samples of the sphere. + * @param radius + * The new radius of the sphere. + */ + public void setData(final ReadOnlyVector3 center, final int zSamples, final int radialSamples, final double radius) { + _center.set(center); + _zSamples = zSamples; + _radialSamples = radialSamples; + _radius = radius; + + setGeometryData(); + setIndexData(); + } + + /** + * builds the vertices based on the radius, center and radial and zSamples. + */ + private void setGeometryData() { + // allocate vertices + final int verts = (_zSamples - 2) * (_radialSamples + 1) + 2; + final FloatBufferData vertsData = _meshData.getVertexCoords(); + if (vertsData == null) { + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + } else { + vertsData.setBuffer(BufferUtils.createVector3Buffer(vertsData.getBuffer(), verts)); + } + + // allocate normals if requested + final FloatBufferData normsData = _meshData.getNormalCoords(); + if (normsData == null) { + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts)); + } else { + normsData.setBuffer(BufferUtils.createVector3Buffer(normsData.getBuffer(), verts)); + } + + // allocate texture coordinates + final FloatBufferData texData = _meshData.getTextureCoords(0); + if (texData == null) { + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + } else { + texData.setBuffer(BufferUtils.createVector2Buffer(texData.getBuffer(), verts)); + } + + // generate geometry + final double fInvRS = 1.0 / _radialSamples; + final double fZFactor = 2.0 / (_zSamples - 1); + + // Generate points on the unit circle to be used in computing the mesh + // points on a sphere slice. + final double[] afSin = new double[(_radialSamples + 1)]; + final double[] afCos = new double[(_radialSamples + 1)]; + for (int iR = 0; iR < _radialSamples; iR++) { + final double fAngle = MathUtils.TWO_PI * fInvRS * iR; + afCos[iR] = MathUtils.cos(fAngle); + afSin[iR] = MathUtils.sin(fAngle); + } + afSin[_radialSamples] = afSin[0]; + afCos[_radialSamples] = afCos[0]; + + // generate the sphere itself + int i = 0; + final Vector3 tempVa = Vector3.fetchTempInstance(); + final Vector3 tempVb = Vector3.fetchTempInstance(); + final Vector3 tempVc = Vector3.fetchTempInstance(); + for (int iZ = 1; iZ < (_zSamples - 1); iZ++) { + final double fAFraction = MathUtils.HALF_PI * (-1.0f + fZFactor * iZ); // in (-pi/2, pi/2) + final double fZFraction = MathUtils.sin(fAFraction); // in (-1,1) + final double fZ = _radius * fZFraction; + + // compute center of slice + final Vector3 kSliceCenter = tempVb.set(_center); + kSliceCenter.setZ(kSliceCenter.getZ() + fZ); + + // compute radius of slice + final double fSliceRadius = Math.sqrt(Math.abs(_radius * _radius - fZ * fZ)); + + // compute slice vertices with duplication at end point + Vector3 kNormal; + final int iSave = i; + for (int iR = 0; iR < _radialSamples; iR++) { + final double fRadialFraction = iR * fInvRS; // in [0,1) + final Vector3 kRadial = tempVc.set(afCos[iR], afSin[iR], 0); + kRadial.multiply(fSliceRadius, tempVa); + _meshData.getVertexBuffer().put((float) (kSliceCenter.getX() + tempVa.getX())) + .put((float) (kSliceCenter.getY() + tempVa.getY())) + .put((float) (kSliceCenter.getZ() + tempVa.getZ())); + + BufferUtils.populateFromBuffer(tempVa, _meshData.getVertexBuffer(), i); + kNormal = tempVa.subtractLocal(_center); + kNormal.normalizeLocal(); + if (!_viewInside) { + _meshData.getNormalBuffer().put(kNormal.getXf()).put(kNormal.getYf()).put(kNormal.getZf()); + } else { + _meshData.getNormalBuffer().put(-kNormal.getXf()).put(-kNormal.getYf()).put(-kNormal.getZf()); + } + + if (_textureMode == TextureMode.Linear) { + _meshData.getTextureCoords(0).getBuffer().put((float) fRadialFraction) + .put((float) (0.5 * (fZFraction + 1.0))); + } else if (_textureMode == TextureMode.Projected) { + _meshData.getTextureCoords(0).getBuffer().put((float) fRadialFraction) + .put((float) (MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(fZFraction)))); + } else if (_textureMode == TextureMode.Polar) { + final double r = (MathUtils.HALF_PI - Math.abs(fAFraction)) / MathUtils.PI; + final double u = r * afCos[iR] + 0.5; + final double v = r * afSin[iR] + 0.5; + _meshData.getTextureCoords(0).getBuffer().put((float) u).put((float) v); + } + + i++; + } + + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i); + BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iSave, i); + + if (_textureMode == TextureMode.Linear) { + _meshData.getTextureCoords(0).getBuffer().put(1.0f).put((float) (0.5 * (fZFraction + 1.0))); + } else if (_textureMode == TextureMode.Projected) { + _meshData.getTextureCoords(0).getBuffer().put(1.0f) + .put((float) (MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(fZFraction)))); + } else if (_textureMode == TextureMode.Polar) { + final float r = (float) ((MathUtils.HALF_PI - Math.abs(fAFraction)) / MathUtils.PI); + _meshData.getTextureCoords(0).getBuffer().put(r + 0.5f).put(0.5f); + } + + i++; + } + + // south pole + _meshData.getVertexBuffer().position(i * 3); + _meshData.getVertexBuffer().put(_center.getXf()).put(_center.getYf()).put((float) (_center.getZ() - _radius)); + + _meshData.getNormalBuffer().position(i * 3); + if (!_viewInside) { + // TODO: allow for inner texture orientation later. + _meshData.getNormalBuffer().put(0).put(0).put(-1); + } else { + _meshData.getNormalBuffer().put(0).put(0).put(1); + } + + _meshData.getTextureCoords(0).getBuffer().position(i * 2); + if (_textureMode == TextureMode.Polar) { + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.5f); + } else { + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.0f); + } + + i++; + + // north pole + _meshData.getVertexBuffer().put(_center.getXf()).put(_center.getYf()).put((float) (_center.getZ() + _radius)); + + if (!_viewInside) { + _meshData.getNormalBuffer().put(0).put(0).put(1); + } else { + _meshData.getNormalBuffer().put(0).put(0).put(-1); + } + + if (_textureMode == TextureMode.Polar) { + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.5f); + } else { + _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(1.0f); + } + Vector3.releaseTempInstance(tempVa); + Vector3.releaseTempInstance(tempVb); + Vector3.releaseTempInstance(tempVc); + } + + /** + * sets the indices for rendering the sphere. + */ + private void setIndexData() { + // allocate connectivity + final int verts = (_zSamples - 2) * (_radialSamples + 1) + 2; + final int tris = 2 * (_zSamples - 2) * _radialSamples; + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + + // generate connectivity + for (int iZ = 0, iZStart = 0; iZ < (_zSamples - 3); iZ++) { + int i0 = iZStart; + int i1 = i0 + 1; + iZStart += (_radialSamples + 1); + int i2 = iZStart; + int i3 = i2 + 1; + for (int i = 0; i < _radialSamples; i++) { + if (!_viewInside) { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i1); + _meshData.getIndices().put(i2); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(i3++); + _meshData.getIndices().put(i2++); + } else // inside view + { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i2); + _meshData.getIndices().put(i1); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(i2++); + _meshData.getIndices().put(i3++); + } + } + } + + // south pole triangles + for (int i = 0; i < _radialSamples; i++) { + if (!_viewInside) { + _meshData.getIndices().put(i); + _meshData.getIndices().put(_meshData.getVertexCount() - 2); + _meshData.getIndices().put(i + 1); + } else // inside view + { + _meshData.getIndices().put(i); + _meshData.getIndices().put(i + 1); + _meshData.getIndices().put(_meshData.getVertexCount() - 2); + } + } + + // north pole triangles + final int iOffset = (_zSamples - 3) * (_radialSamples + 1); + for (int i = 0; i < _radialSamples; i++) { + if (!_viewInside) { + _meshData.getIndices().put(i + iOffset); + _meshData.getIndices().put(i + 1 + iOffset); + _meshData.getIndices().put(_meshData.getVertexCount() - 1); + } else // inside view + { + _meshData.getIndices().put(i + iOffset); + _meshData.getIndices().put(_meshData.getVertexCount() - 1); + _meshData.getIndices().put(i + 1 + iOffset); + } + } + } + + /** + * Returns the center of this sphere. + * + * @return The sphere's center. + */ + public Vector3 getCenter() { + return _center; + } + + /** + * + * @return true if the normals are inverted to point into the sphere so that the face is oriented for a viewer + * inside the sphere. false (the default) for exterior viewing. + */ + public boolean isViewFromInside() { + return _viewInside; + } + + /** + * + * @param viewInside + * if true, the normals are inverted to point into the sphere so that the face is oriented for a viewer + * inside the sphere. Default is false (for outside viewing) + */ + public void setViewFromInside(final boolean viewInside) { + if (viewInside != _viewInside) { + _viewInside = viewInside; + setGeometryData(); + setIndexData(); + } + } + + /** + * @return Returns the textureMode. + */ + public TextureMode getTextureMode() { + return _textureMode; + } + + /** + * @param textureMode + * The textureMode to set. + */ + public void setTextureMode(final TextureMode textureMode) { + _textureMode = textureMode; + setGeometryData(); + setIndexData(); + } + + public double getRadius() { + return _radius; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_zSamples, "zSamples", 0); + capsule.write(_radialSamples, "radialSamples", 0); + capsule.write(_radius, "radius", 0); + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + capsule.write(_textureMode, "textureMode", TextureMode.Linear); + capsule.write(_viewInside, "viewInside", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _zSamples = capsule.readInt("zSamples", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + _radius = capsule.readDouble("radius", 0); + _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO))); + _textureMode = capsule.readEnum("textureMode", TextureMode.class, TextureMode.Linear); + _viewInside = capsule.readBoolean("viewInside", false); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java new file mode 100644 index 0000000..9c98bee --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java @@ -0,0 +1,275 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class StripBox extends Mesh { + + public double _xExtent, _yExtent, _zExtent; + + public final Vector3 _center = new Vector3(0f, 0f, 0f); + + /** + * instantiates a new <code>StripBox</code> object. All information must be applies later. For internal usage only + */ + public StripBox() { + super("temp"); + } + + /** + * Constructor instantiates a new <code>StripBox</code> object. Center and vertice information must be supplied + * later. + * + * @param name + * the name of the scene element. This is required for identification and comparision purposes. + */ + public StripBox(final String name) { + super(name); + } + + /** + * Constructor instantiates a new <code>StripBox</code> object. The minimum and maximum point are provided. These + * two points define the shape and size of the box, but not it's orientation or position. You should use the + * <code>setTranslation</code> and <code>setLocalRotation</code> for those attributes. + * + * @param name + * the name of the scene element. This is required for identification and comparison purposes. + * @param min + * the minimum point that defines the box. + * @param max + * the maximum point that defines the box. + */ + public StripBox(final String name, final Vector3 min, final Vector3 max) { + super(name); + setData(min, max); + } + + /** + * Constructs a new box. The box has the given center and extends in the x, y, and z out from the center (+ and -) + * by the given amounts. So, for example, a box with extent of .5 would be the unit cube. + * + * @param name + * Name of the box. + * @param center + * Center of the box. + * @param xExtent + * x extent of the box, in both directions. + * @param yExtent + * y extent of the box, in both directions. + * @param zExtent + * z extent of the box, in both directions. + */ + public StripBox(final String name, final Vector3 center, final double xExtent, final double yExtent, + final double zExtent) { + super(name); + setData(center, xExtent, yExtent, zExtent); + } + + /** + * Changes the data of the box so that the two opposite corners are minPoint and maxPoint. The other corners are + * created from those two poitns. If update buffers is flagged as true, the vertex/normal/texture/color/index + * buffers are updated when the data is changed. + * + * @param minPoint + * The new minPoint of the box. + * @param maxPoint + * The new maxPoint of the box. + */ + public void setData(final Vector3 minPoint, final Vector3 maxPoint) { + _center.set(maxPoint).addLocal(minPoint).multiplyLocal(0.5f); + + final double x = maxPoint.getX() - _center.getX(); + final double y = maxPoint.getY() - _center.getY(); + final double z = maxPoint.getZ() - _center.getZ(); + setData(_center, x, y, z); + } + + /** + * Changes the data of the box so that its center is <code>center</code> and it extends in the x, y, and z + * directions by the given extent. Note that the actual sides will be 2x the given extent values because the box + * extends in + & - from the center for each extent. + * + * @param center + * The center of the box. + * @param xExtent + * x extent of the box, in both directions. + * @param yExtent + * y extent of the box, in both directions. + * @param zExtent + * z extent of the box, in both directions. + */ + public void setData(final Vector3 center, final double xExtent, final double yExtent, final double zExtent) { + if (center != null) { + _center.set(center); + } + + _xExtent = xExtent; + _yExtent = yExtent; + _zExtent = zExtent; + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + + } + + /** + * + * <code>setVertexData</code> sets the vertex positions that define the box. These eight points are determined from + * the minimum and maximum point. + * + */ + private void setVertexData() { + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), 8)); + final Vector3[] vert = computeVertices(); // returns 8 + _meshData.getVertexBuffer().clear(); + _meshData.getVertexBuffer().put(vert[0].getXf()).put(vert[0].getYf()).put(vert[0].getZf()); + _meshData.getVertexBuffer().put(vert[1].getXf()).put(vert[1].getYf()).put(vert[1].getZf()); + _meshData.getVertexBuffer().put(vert[2].getXf()).put(vert[2].getYf()).put(vert[2].getZf()); + _meshData.getVertexBuffer().put(vert[3].getXf()).put(vert[3].getYf()).put(vert[3].getZf()); + _meshData.getVertexBuffer().put(vert[4].getXf()).put(vert[4].getYf()).put(vert[4].getZf()); + _meshData.getVertexBuffer().put(vert[5].getXf()).put(vert[5].getYf()).put(vert[5].getZf()); + _meshData.getVertexBuffer().put(vert[6].getXf()).put(vert[6].getYf()).put(vert[6].getZf()); + _meshData.getVertexBuffer().put(vert[7].getXf()).put(vert[7].getYf()).put(vert[7].getZf()); + } + + /** + * + * <code>setNormalData</code> sets the normals of each of the box's planes. + * + * + */ + private void setNormalData() { + final Vector3[] vert = computeVertices(); // returns 8 + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), 8)); + final Vector3 norm = new Vector3(); + + _meshData.getNormalBuffer().clear(); + for (int i = 0; i < 8; i++) { + norm.set(vert[i]).normalizeLocal(); + _meshData.getNormalBuffer().put(norm.getXf()).put(norm.getYf()).put(norm.getZf()); + } + } + + /** + * + * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one ratio, where + * each plane of the box has it's own copy of the texture. That is, the texture is repeated one time for each six + * faces. + * + */ + private void setTextureData() { + if (_meshData.getTextureCoords(0) == null) { + _meshData.setTextureCoords(new FloatBufferData(BufferUtils.createVector2Buffer(8), 2), 0); + final FloatBuffer tex = _meshData.getTextureCoords(0).getBuffer(); + tex.put(1).put(0); // 0 + tex.put(0).put(0); // 1 + tex.put(0).put(1); // 2 + tex.put(1).put(1); // 3 + tex.put(1).put(0); // 4 + tex.put(0).put(0); // 5 + tex.put(1).put(1); // 6 + tex.put(0).put(1); // 7 + } + } + + /** + * + * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the + * box. + * + */ + private void setIndexData() { + _meshData.setIndexMode(IndexMode.TriangleStrip); + if (_meshData.getIndexBuffer() == null) { + final byte[] indices = new byte[] { 2, 3, 6, 7, 5, 3, 0, 2, 1, 6, 4, 5, 1, 0 }; + final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length); + buf.put(indices); + buf.rewind(); + _meshData.setIndexBuffer(buf); + } + } + + /** + * <code>clone</code> creates a new StripBox object containing the same data as this one. + * + * @return the new StripBox + */ + @Override + public StripBox clone() { + return new StripBox(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent); + } + + /** + * + * @return a size 8 array of Vectors representing the 8 points of the box. + */ + public Vector3[] computeVertices() { + + final Vector3 akEAxis[] = { Vector3.UNIT_X.multiply(_xExtent, Vector3.fetchTempInstance()), + Vector3.UNIT_Y.multiply(_yExtent, Vector3.fetchTempInstance()), + Vector3.UNIT_Z.multiply(_zExtent, Vector3.fetchTempInstance()) }; + + final Vector3 rVal[] = new Vector3[8]; + rVal[0] = _center.subtract(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).subtractLocal(akEAxis[2]); + rVal[1] = _center.add(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).subtractLocal(akEAxis[2]); + rVal[2] = _center.add(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).subtractLocal(akEAxis[2]); + rVal[3] = _center.subtract(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).subtractLocal(akEAxis[2]); + rVal[4] = _center.add(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).addLocal(akEAxis[2]); + rVal[5] = _center.subtract(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).addLocal(akEAxis[2]); + rVal[6] = _center.add(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).addLocal(akEAxis[2]); + rVal[7] = _center.subtract(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).addLocal(akEAxis[2]); + for (final Vector3 axis : akEAxis) { + Vector3.releaseTempInstance(axis); + } + return rVal; + } + + /** + * Returns the current center of the box. + * + * @return The box's center. + */ + public Vector3 getCenter() { + return _center; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_xExtent, "xExtent", 0); + capsule.write(_yExtent, "yExtent", 0); + capsule.write(_zExtent, "zExtent", 0); + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _xExtent = capsule.readDouble("xExtent", 0); + _yExtent = capsule.readDouble("yExtent", 0); + _zExtent = capsule.readDouble("zExtent", 0); + _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO))); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java new file mode 100644 index 0000000..06945f2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java @@ -0,0 +1,852 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.geom.BufferUtils; + +/** + * Teapot is the classical teapot model ready for you to use in ardor3d! If you plan to texture this shape, use wrapmode + * WM_WRAP_S_WRAP_T. + * + * @see "http://www.sjbaker.org/teapot/" + */ +public class Teapot extends Mesh { + + /** + * Instantiates a new Teapot object. + */ + public Teapot() { + super("teapot"); + resetData(); + } + + /** + * Constructor instantiates a new Teapot object. + * + * @param name + * the name of the scene element. This is required for identification and comparison purposes. + */ + public Teapot(final String name) { + super(name); + resetData(); + } + + /** + * sets up the data for the Teapot + */ + public void resetData() { + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + } + + private void setVertexData() { + final float[] verts = new float[] { 1.4403734f, 2.7980254f, 0.6128418f, 1.5613803f, 2.7980254f, 0.0f, + 1.5834712f, 2.7145221f, 0.0f, 1.4607521f, 2.7145221f, 0.62151235f, 1.4640129f, 2.8258598f, 0.6228997f, + 1.5870057f, 2.8258598f, 0.0f, 1.5121067f, 2.7980254f, 0.6433625f, 1.6391401f, 2.7980254f, 0.0f, + 1.5650915f, 2.7145221f, 0.6659062f, 1.6965764f, 2.7145221f, 0.0f, 1.10858f, 2.7980254f, 1.10858f, + 1.1242645f, 2.7145221f, 1.1242645f, 1.1267741f, 2.8258598f, 1.1267741f, 1.1637895f, 2.7980254f, + 1.1637895f, 1.2045691f, 2.7145221f, 1.2045691f, 0.6128418f, 2.7980254f, 1.4403734f, 0.62151235f, + 2.7145221f, 1.4607521f, 0.6228997f, 2.8258598f, 1.4640129f, 0.6433625f, 2.7980254f, 1.5121067f, + 0.6659062f, 2.7145221f, 1.5650915f, 0.0f, 2.7980254f, 1.5613803f, 0.0f, 2.7145221f, 1.5834712f, 0.0f, + 2.8258598f, 1.5870057f, 0.0f, 2.7980254f, 1.6391401f, 0.0f, 2.7145221f, 1.6965764f, -0.63095903f, + 2.7980254f, 1.4403734f, 0.0f, 2.7980254f, 1.5613803f, 0.0f, 2.7145221f, 1.5834712f, -0.664457f, + 2.7145221f, 1.4607521f, -0.6282678f, 2.8258598f, 1.4640129f, 0.0f, 2.8258598f, 1.5870057f, -0.6440335f, + 2.7980254f, 1.5121067f, 0.0f, 2.7980254f, 1.6391401f, -0.6659062f, 2.7145221f, 1.5650915f, 0.0f, + 2.7145221f, 1.6965764f, -1.1246843f, 2.7980254f, 1.10858f, -1.1624376f, 2.7145221f, 1.1242645f, + -1.1315457f, 2.8258598f, 1.1267741f, -1.1643858f, 2.7980254f, 1.1637895f, -1.2045691f, 2.7145221f, + 1.2045691f, -1.4464124f, 2.7980254f, 0.6128418f, -1.475067f, 2.7145221f, 0.62151235f, -1.4658021f, + 2.8258598f, 0.6228997f, -1.5123304f, 2.7980254f, 0.6433625f, -1.5650915f, 2.7145221f, 0.6659062f, + -1.5613803f, 2.7980254f, 0.0f, -1.5834712f, 2.7145221f, 0.0f, -1.5870057f, 2.8258598f, 0.0f, + -1.6391401f, 2.7980254f, 0.0f, -1.6965764f, 2.7145221f, 0.0f, -1.4403734f, 2.7980254f, -0.6128418f, + -1.5613803f, 2.7980254f, 0.0f, -1.5834712f, 2.7145221f, 0.0f, -1.4607521f, 2.7145221f, -0.62151235f, + -1.4640129f, 2.8258598f, -0.6228997f, -1.5870057f, 2.8258598f, 0.0f, -1.5121067f, 2.7980254f, + -0.6433625f, -1.6391401f, 2.7980254f, 0.0f, -1.5650915f, 2.7145221f, -0.6659062f, -1.6965764f, + 2.7145221f, 0.0f, -1.10858f, 2.7980254f, -1.10858f, -1.1242645f, 2.7145221f, -1.1242645f, -1.1267741f, + 2.8258598f, -1.1267741f, -1.1637895f, 2.7980254f, -1.1637895f, -1.2045691f, 2.7145221f, -1.2045691f, + -0.6128418f, 2.7980254f, -1.4403734f, -0.62151235f, 2.7145221f, -1.4607521f, -0.6228997f, 2.8258598f, + -1.4640129f, -0.6433625f, 2.7980254f, -1.5121067f, -0.6659062f, 2.7145221f, -1.5650915f, 0.0f, + 2.7980254f, -1.5613803f, 0.0f, 2.7145221f, -1.5834712f, 0.0f, 2.8258598f, -1.5870057f, 0.0f, + 2.7980254f, -1.6391401f, 0.0f, 2.7145221f, -1.6965764f, 0.6128418f, 2.7980254f, -1.4403734f, 0.0f, + 2.7980254f, -1.5613803f, 0.0f, 2.7145221f, -1.5834712f, 0.62151235f, 2.7145221f, -1.4607521f, + 0.6228997f, 2.8258598f, -1.4640129f, 0.0f, 2.8258598f, -1.5870057f, 0.6433625f, 2.7980254f, + -1.5121067f, 0.0f, 2.7980254f, -1.6391401f, 0.6659062f, 2.7145221f, -1.5650915f, 0.0f, 2.7145221f, + -1.6965764f, 1.10858f, 2.7980254f, -1.10858f, 1.1242645f, 2.7145221f, -1.1242645f, 1.1267741f, + 2.8258598f, -1.1267741f, 1.1637895f, 2.7980254f, -1.1637895f, 1.2045691f, 2.7145221f, -1.2045691f, + 1.4403734f, 2.7980254f, -0.6128418f, 1.4607521f, 2.7145221f, -0.62151235f, 1.4640129f, 2.8258598f, + -0.6228997f, 1.5121067f, 2.7980254f, -0.6433625f, 1.5650915f, 2.7145221f, -0.6659062f, 1.5613803f, + 2.7980254f, 0.0f, 1.5834712f, 2.7145221f, 0.0f, 1.5870057f, 2.8258598f, 0.0f, 1.6391401f, 2.7980254f, + 0.0f, 1.6965764f, 2.7145221f, 0.0f, 1.7566524f, 2.2704964f, 0.7474103f, 1.9042302f, 2.2704964f, 0.0f, + 1.6965764f, 2.7145221f, 0.0f, 1.5650915f, 2.7145221f, 0.6659062f, 1.9237585f, 1.8344232f, 0.8185097f, + 2.085375f, 1.8344232f, 0.0f, 2.0419555f, 1.4142554f, 0.86879945f, 2.2135017f, 1.4142554f, 0.0f, + 2.086789f, 1.0179458f, 0.88787496f, 2.2621017f, 1.0179458f, 0.0f, 1.3520035f, 2.2704964f, 1.3520035f, + 1.2045691f, 2.7145221f, 1.2045691f, 1.4806162f, 1.8344232f, 1.4806162f, 1.5715863f, 1.4142554f, + 1.5715863f, 1.6060922f, 1.0179458f, 1.6060922f, 0.7474103f, 2.2704964f, 1.7566524f, 0.6659062f, + 2.7145221f, 1.5650915f, 0.8185097f, 1.8344232f, 1.9237585f, 0.86879945f, 1.4142554f, 2.0419555f, + 0.88787496f, 1.0179458f, 2.086789f, 0.0f, 2.2704964f, 1.9042302f, 0.0f, 2.7145221f, 1.6965764f, 0.0f, + 1.8344232f, 2.085375f, 0.0f, 1.4142554f, 2.2135017f, 0.0f, 1.0179458f, 2.2621017f, -0.7474103f, + 2.2704964f, 1.7566524f, 0.0f, 2.2704964f, 1.9042302f, 0.0f, 2.7145221f, 1.6965764f, -0.6659062f, + 2.7145221f, 1.5650915f, -0.8185097f, 1.8344232f, 1.9237585f, 0.0f, 1.8344232f, 2.085375f, -0.86879945f, + 1.4142554f, 2.0419555f, 0.0f, 1.4142554f, 2.2135017f, -0.88787496f, 1.0179458f, 2.086789f, 0.0f, + 1.0179458f, 2.2621017f, -1.3520035f, 2.2704964f, 1.3520035f, -1.2045691f, 2.7145221f, 1.2045691f, + -1.4806162f, 1.8344232f, 1.4806162f, -1.5715863f, 1.4142554f, 1.5715863f, -1.6060922f, 1.0179458f, + 1.6060922f, -1.7566524f, 2.2704964f, 0.7474103f, -1.5650915f, 2.7145221f, 0.6659062f, -1.9237585f, + 1.8344232f, 0.8185097f, -2.0419555f, 1.4142554f, 0.86879945f, -2.086789f, 1.0179458f, 0.88787496f, + -1.9042302f, 2.2704964f, 0.0f, -1.6965764f, 2.7145221f, 0.0f, -2.085375f, 1.8344232f, 0.0f, + -2.2135017f, 1.4142554f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, -1.7566524f, 2.2704964f, -0.7474103f, + -1.9042302f, 2.2704964f, 0.0f, -1.6965764f, 2.7145221f, 0.0f, -1.5650915f, 2.7145221f, -0.6659062f, + -1.9237585f, 1.8344232f, -0.8185097f, -2.085375f, 1.8344232f, 0.0f, -2.0419555f, 1.4142554f, + -0.86879945f, -2.2135017f, 1.4142554f, 0.0f, -2.086789f, 1.0179458f, -0.88787496f, -2.2621017f, + 1.0179458f, 0.0f, -1.3520035f, 2.2704964f, -1.3520035f, -1.2045691f, 2.7145221f, -1.2045691f, + -1.4806162f, 1.8344232f, -1.4806162f, -1.5715863f, 1.4142554f, -1.5715863f, -1.6060922f, 1.0179458f, + -1.6060922f, -0.7474103f, 2.2704964f, -1.7566524f, -0.6659062f, 2.7145221f, -1.5650915f, -0.8185097f, + 1.8344232f, -1.9237585f, -0.86879945f, 1.4142554f, -2.0419555f, -0.88787496f, 1.0179458f, -2.086789f, + 0.0f, 2.2704964f, -1.9042302f, 0.0f, 2.7145221f, -1.6965764f, 0.0f, 1.8344232f, -2.085375f, 0.0f, + 1.4142554f, -2.2135017f, 0.0f, 1.0179458f, -2.2621017f, 0.7474103f, 2.2704964f, -1.7566524f, 0.0f, + 2.2704964f, -1.9042302f, 0.0f, 2.7145221f, -1.6965764f, 0.6659062f, 2.7145221f, -1.5650915f, + 0.8185097f, 1.8344232f, -1.9237585f, 0.0f, 1.8344232f, -2.085375f, 0.86879945f, 1.4142554f, + -2.0419555f, 0.0f, 1.4142554f, -2.2135017f, 0.88787496f, 1.0179458f, -2.086789f, 0.0f, 1.0179458f, + -2.2621017f, 1.3520035f, 2.2704964f, -1.3520035f, 1.2045691f, 2.7145221f, -1.2045691f, 1.4806162f, + 1.8344232f, -1.4806162f, 1.5715863f, 1.4142554f, -1.5715863f, 1.6060922f, 1.0179458f, -1.6060922f, + 1.7566524f, 2.2704964f, -0.7474103f, 1.5650915f, 2.7145221f, -0.6659062f, 1.9237585f, 1.8344232f, + -0.8185097f, 2.0419555f, 1.4142554f, -0.86879945f, 2.086789f, 1.0179458f, -0.88787496f, 1.9042302f, + 2.2704964f, 0.0f, 1.6965764f, 2.7145221f, 0.0f, 2.085375f, 1.8344232f, 0.0f, 2.2135017f, 1.4142554f, + 0.0f, 2.2621017f, 1.0179458f, 0.0f, 2.0052736f, 0.6826069f, 0.85319227f, 2.1737385f, 0.6826069f, 0.0f, + 2.2621017f, 1.0179458f, 0.0f, 2.086789f, 1.0179458f, 0.88787496f, 1.8259401f, 0.43474767f, 0.7768906f, + 1.9793389f, 0.43474767f, 0.0f, 1.6466068f, 0.26641548f, 0.7005888f, 1.7849396f, 0.26641548f, 0.0f, + 1.5650915f, 0.16965766f, 0.6659062f, 1.6965764f, 0.16965766f, 0.0f, 1.5433542f, 0.6826069f, 1.5433542f, + 1.6060922f, 1.0179458f, 1.6060922f, 1.4053307f, 0.43474767f, 1.4053307f, 1.2673072f, 0.26641548f, + 1.2673072f, 1.2045691f, 0.16965766f, 1.2045691f, 0.85319227f, 0.6826069f, 2.0052736f, 0.88787496f, + 1.0179458f, 2.086789f, 0.7768906f, 0.43474767f, 1.8259401f, 0.7005888f, 0.26641548f, 1.6466068f, + 0.6659062f, 0.16965766f, 1.5650915f, 0.0f, 0.6826069f, 2.1737385f, 0.0f, 1.0179458f, 2.2621017f, 0.0f, + 0.43474767f, 1.9793389f, 0.0f, 0.26641548f, 1.7849396f, 0.0f, 0.16965766f, 1.6965764f, -0.85319227f, + 0.6826069f, 2.0052736f, 0.0f, 0.6826069f, 2.1737385f, 0.0f, 1.0179458f, 2.2621017f, -0.88787496f, + 1.0179458f, 2.086789f, -0.7768906f, 0.43474767f, 1.8259401f, 0.0f, 0.43474767f, 1.9793389f, + -0.7005888f, 0.26641548f, 1.6466068f, 0.0f, 0.26641548f, 1.7849396f, -0.6659062f, 0.16965766f, + 1.5650915f, 0.0f, 0.16965766f, 1.6965764f, -1.5433542f, 0.6826069f, 1.5433542f, -1.6060922f, + 1.0179458f, 1.6060922f, -1.4053307f, 0.43474767f, 1.4053307f, -1.2673072f, 0.26641548f, 1.2673072f, + -1.2045691f, 0.16965766f, 1.2045691f, -2.0052736f, 0.6826069f, 0.85319227f, -2.086789f, 1.0179458f, + 0.88787496f, -1.8259401f, 0.43474767f, 0.7768906f, -1.6466068f, 0.26641548f, 0.7005888f, -1.5650915f, + 0.16965766f, 0.6659062f, -2.1737385f, 0.6826069f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, -1.9793389f, + 0.43474767f, 0.0f, -1.7849396f, 0.26641548f, 0.0f, -1.6965764f, 0.16965766f, 0.0f, -2.0052736f, + 0.6826069f, -0.85319227f, -2.1737385f, 0.6826069f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, -2.086789f, + 1.0179458f, -0.88787496f, -1.8259401f, 0.43474767f, -0.7768906f, -1.9793389f, 0.43474767f, 0.0f, + -1.6466068f, 0.26641548f, -0.7005888f, -1.7849396f, 0.26641548f, 0.0f, -1.5650915f, 0.16965766f, + -0.6659062f, -1.6965764f, 0.16965766f, 0.0f, -1.5433542f, 0.6826069f, -1.5433542f, -1.6060922f, + 1.0179458f, -1.6060922f, -1.4053307f, 0.43474767f, -1.4053307f, -1.2673072f, 0.26641548f, -1.2673072f, + -1.2045691f, 0.16965766f, -1.2045691f, -0.85319227f, 0.6826069f, -2.0052736f, -0.88787496f, 1.0179458f, + -2.086789f, -0.7768906f, 0.43474767f, -1.8259401f, -0.7005888f, 0.26641548f, -1.6466068f, -0.6659062f, + 0.16965766f, -1.5650915f, 0.0f, 0.6826069f, -2.1737385f, 0.0f, 1.0179458f, -2.2621017f, 0.0f, + 0.43474767f, -1.9793389f, 0.0f, 0.26641548f, -1.7849396f, 0.0f, 0.16965766f, -1.6965764f, 0.85319227f, + 0.6826069f, -2.0052736f, 0.0f, 0.6826069f, -2.1737385f, 0.0f, 1.0179458f, -2.2621017f, 0.88787496f, + 1.0179458f, -2.086789f, 0.7768906f, 0.43474767f, -1.8259401f, 0.0f, 0.43474767f, -1.9793389f, + 0.7005888f, 0.26641548f, -1.6466068f, 0.0f, 0.26641548f, -1.7849396f, 0.6659062f, 0.16965766f, + -1.5650915f, 0.0f, 0.16965766f, -1.6965764f, 1.5433542f, 0.6826069f, -1.5433542f, 1.6060922f, + 1.0179458f, -1.6060922f, 1.4053307f, 0.43474767f, -1.4053307f, 1.2673072f, 0.26641548f, -1.2673072f, + 1.2045691f, 0.16965766f, -1.2045691f, 2.0052736f, 0.6826069f, -0.85319227f, 2.086789f, 1.0179458f, + -0.88787496f, 1.8259401f, 0.43474767f, -0.7768906f, 1.6466068f, 0.26641548f, -0.7005888f, 1.5650915f, + 0.16965766f, -0.6659062f, 2.1737385f, 0.6826069f, 0.0f, 2.2621017f, 1.0179458f, 0.0f, 1.9793389f, + 0.43474767f, 0.0f, 1.7849396f, 0.26641548f, 0.0f, 1.6965764f, 0.16965766f, 0.0f, 1.5296324f, + 0.10736148f, 0.6508193f, 1.6581382f, 0.10736148f, 0.0f, 1.6965764f, 0.16965766f, 0.0f, 1.5650915f, + 0.16965766f, 0.6659062f, 1.3401097f, 0.053018f, 0.5701822f, 1.4526935f, 0.053018f, 0.0f, 0.87180483f, + 0.014579957f, 0.37093055f, 0.94504595f, 0.014579957f, 0.0f, 0.0f, 0.0f, 0.0f, 0.87180483f, + 0.014579957f, -0.37093055f, 1.1772782f, 0.10736148f, 1.1772782f, 1.2045691f, 0.16965766f, 1.2045691f, + 1.0314124f, 0.053018f, 1.0314124f, 0.6709826f, 0.014579957f, 0.6709826f, 0.6508193f, 0.10736148f, + 1.5296324f, 0.6659062f, 0.16965766f, 1.5650915f, 0.5701822f, 0.053018f, 1.3401097f, 0.37093055f, + 0.014579957f, 0.87180483f, 0.0f, 0.10736148f, 1.6581382f, 0.0f, 0.16965766f, 1.6965764f, 0.0f, + 0.053018f, 1.4526935f, 0.0f, 0.014579957f, 0.94504595f, -0.6508193f, 0.10736148f, 1.5296324f, 0.0f, + 0.10736148f, 1.6581382f, 0.0f, 0.16965766f, 1.6965764f, -0.6659062f, 0.16965766f, 1.5650915f, + -0.5701822f, 0.053018f, 1.3401097f, 0.0f, 0.053018f, 1.4526935f, -0.37093055f, 0.014579957f, + 0.87180483f, 0.0f, 0.014579957f, 0.94504595f, -1.1772782f, 0.10736148f, 1.1772782f, -1.2045691f, + 0.16965766f, 1.2045691f, -1.0314124f, 0.053018f, 1.0314124f, -0.6709826f, 0.014579957f, 0.6709826f, + -1.5296324f, 0.10736148f, 0.6508193f, -1.5650915f, 0.16965766f, 0.6659062f, -1.3401097f, 0.053018f, + 0.5701822f, -0.87180483f, 0.014579957f, 0.37093055f, -1.6581382f, 0.10736148f, 0.0f, -1.6965764f, + 0.16965766f, 0.0f, -1.4526935f, 0.053018f, 0.0f, -0.94504595f, 0.014579957f, 0.0f, -1.5296324f, + 0.10736148f, -0.6508193f, -1.6581382f, 0.10736148f, 0.0f, -1.6965764f, 0.16965766f, 0.0f, -1.5650915f, + 0.16965766f, -0.6659062f, -1.3401097f, 0.053018f, -0.5701822f, -1.4526935f, 0.053018f, 0.0f, + -0.87180483f, 0.014579957f, -0.37093055f, -0.94504595f, 0.014579957f, 0.0f, -1.1772782f, 0.10736148f, + -1.1772782f, -1.2045691f, 0.16965766f, -1.2045691f, -1.0314124f, 0.053018f, -1.0314124f, -0.6709826f, + 0.014579957f, -0.6709826f, -0.6508193f, 0.10736148f, -1.5296324f, -0.6659062f, 0.16965766f, + -1.5650915f, -0.5701822f, 0.053018f, -1.3401097f, -0.37093055f, 0.014579957f, -0.87180483f, 0.0f, + 0.10736148f, -1.6581382f, 0.0f, 0.16965766f, -1.6965764f, 0.0f, 0.053018f, -1.4526935f, 0.0f, + 0.014579957f, -0.94504595f, 0.6508193f, 0.10736148f, -1.5296324f, 0.0f, 0.10736148f, -1.6581382f, 0.0f, + 0.16965766f, -1.6965764f, 0.6659062f, 0.16965766f, -1.5650915f, 0.5701822f, 0.053018f, -1.3401097f, + 0.0f, 0.053018f, -1.4526935f, 0.37093055f, 0.014579957f, -0.87180483f, 0.0f, 0.014579957f, + -0.94504595f, 1.1772782f, 0.10736148f, -1.1772782f, 1.2045691f, 0.16965766f, -1.2045691f, 1.0314124f, + 0.053018f, -1.0314124f, 0.6709826f, 0.014579957f, -0.6709826f, 1.5296324f, 0.10736148f, -0.6508193f, + 1.5650915f, 0.16965766f, -0.6659062f, 1.3401097f, 0.053018f, -0.5701822f, 1.6581382f, 0.10736148f, + 0.0f, 1.6965764f, 0.16965766f, 0.0f, 1.4526935f, 0.053018f, 0.0f, 0.94504595f, 0.014579957f, 0.0f, + -2.353834f, 2.3255439f, 0.19086483f, -2.3380942f, 2.2864017f, 0.0f, -1.8096814f, 2.290378f, 0.0f, + -1.7920088f, 2.3301415f, 0.19086483f, -2.7662144f, 2.2933602f, 0.19086483f, -2.72866f, 2.2585673f, + 0.0f, -3.020204f, 2.2060049f, 0.19086483f, -2.9707758f, 2.1830165f, 0.0f, -3.1068554f, 2.0358915f, + 0.19086483f, -3.0538373f, 2.0358915f, 0.0f, -2.3884614f, 2.4116569f, 0.25448647f, -1.7531288f, + 2.4176214f, 0.25448647f, -2.8488343f, 2.369905f, 0.25448647f, -3.128946f, 2.256579f, 0.25448647f, + -3.2234948f, 2.0358915f, 0.25448647f, -2.4230888f, 2.4977696f, 0.19086483f, -1.714249f, 2.505101f, + 0.19086483f, -2.9314542f, 2.4464495f, 0.19086483f, -3.237688f, 2.3071532f, 0.19086483f, -3.3401346f, + 2.0358915f, 0.19086483f, -2.4388282f, 2.5369117f, 0.0f, -1.6965764f, 2.5448644f, 0.0f, -2.9690084f, + 2.4812427f, 0.0f, -3.2871168f, 2.3301415f, 0.0f, -3.3931527f, 2.0358915f, 0.0f, -2.4230888f, + 2.4977696f, -0.19086483f, -2.4388282f, 2.5369117f, 0.0f, -1.6965764f, 2.5448644f, 0.0f, -1.714249f, + 2.505101f, -0.19086483f, -2.9314542f, 2.4464495f, -0.19086483f, -2.9690084f, 2.4812427f, 0.0f, + -3.237688f, 2.3071532f, -0.19086483f, -3.2871168f, 2.3301415f, 0.0f, -3.3401346f, 2.0358915f, + -0.19086483f, -3.3931527f, 2.0358915f, 0.0f, -2.3884614f, 2.4116569f, -0.25448647f, -1.7531288f, + 2.4176214f, -0.25448647f, -2.8488343f, 2.369905f, -0.25448647f, -3.128946f, 2.256579f, -0.25448647f, + -3.2234948f, 2.0358915f, -0.25448647f, -2.353834f, 2.3255439f, -0.19086483f, -1.7920088f, 2.3301415f, + -0.19086483f, -2.7662144f, 2.2933602f, -0.19086483f, -3.020204f, 2.2060049f, -0.19086483f, -3.1068554f, + 2.0358915f, -0.19086483f, -2.3380942f, 2.2864017f, 0.0f, -1.8096814f, 2.290378f, 0.0f, -2.72866f, + 2.2585673f, 0.0f, -2.9707758f, 2.1830165f, 0.0f, -3.0538373f, 2.0358915f, 0.0f, -3.0578415f, + 1.7829998f, 0.19086483f, -3.0096557f, 1.8052632f, 0.0f, -3.0538373f, 2.0358915f, 0.0f, -3.1068554f, + 2.0358915f, 0.19086483f, -2.9042823f, 1.4929541f, 0.19086483f, -2.8700416f, 1.5269186f, 0.0f, + -2.6364033f, 1.2066361f, 0.19086483f, -2.6243916f, 1.2485741f, 0.0f, -2.244429f, 0.96492773f, + 0.19086483f, -2.2621017f, 1.0179458f, 0.0f, -3.1638496f, 1.7340202f, 0.25448647f, -3.2234948f, + 2.0358915f, 0.25448647f, -2.9796124f, 1.4182318f, 0.25448647f, -2.6628296f, 1.1143724f, 0.25448647f, + -2.2055492f, 0.8482882f, 0.25448647f, -3.269858f, 1.6850407f, 0.19086483f, -3.3401346f, 2.0358915f, + 0.19086483f, -3.054942f, 1.3435094f, 0.19086483f, -2.6892557f, 1.0221086f, 0.19086483f, -2.1666694f, + 0.73164856f, 0.19086483f, -3.3180435f, 1.6627773f, 0.0f, -3.3931527f, 2.0358915f, 0.0f, -3.0891826f, + 1.3095448f, 0.0f, -2.7012677f, 0.9801704f, 0.0f, -2.1489966f, 0.67863053f, 0.0f, -3.269858f, + 1.6850407f, -0.19086483f, -3.3180435f, 1.6627773f, 0.0f, -3.3931527f, 2.0358915f, 0.0f, -3.3401346f, + 2.0358915f, -0.19086483f, -3.054942f, 1.3435094f, -0.19086483f, -3.0891826f, 1.3095448f, 0.0f, + -2.6892557f, 1.0221086f, -0.19086483f, -2.7012677f, 0.9801704f, 0.0f, -2.1666694f, 0.73164856f, + -0.19086483f, -2.1489966f, 0.67863053f, 0.0f, -3.1638496f, 1.7340202f, -0.25448647f, -3.2234948f, + 2.0358915f, -0.25448647f, -2.9796124f, 1.4182318f, -0.25448647f, -2.6628296f, 1.1143724f, -0.25448647f, + -2.2055492f, 0.8482882f, -0.25448647f, -3.0578415f, 1.7829998f, -0.19086483f, -3.1068554f, 2.0358915f, + -0.19086483f, -2.9042823f, 1.4929541f, -0.19086483f, -2.6364033f, 1.2066361f, -0.19086483f, -2.244429f, + 0.96492773f, -0.19086483f, -3.0096557f, 1.8052632f, 0.0f, -3.0538373f, 2.0358915f, 0.0f, -2.8700416f, + 1.5269186f, 0.0f, -2.6243916f, 1.2485741f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, 2.5067577f, 1.6282327f, + 0.37914506f, 2.4653373f, 1.7363397f, 0.0f, 1.9227866f, 1.6117474f, 0.0f, 1.9227866f, 1.4659479f, + 0.41990262f, 2.753402f, 1.9729326f, 0.28947833f, 2.700384f, 2.0358915f, 0.0f, 2.89401f, 2.3762836f, + 0.19981161f, 2.8293946f, 2.399065f, 0.0f, 3.1598732f, 2.7145221f, 0.15905404f, 3.0538373f, 2.7145221f, + 0.0f, 2.5978825f, 1.3903973f, 0.5055267f, 1.9227866f, 1.145189f, 0.5598702f, 2.8700416f, 1.8344232f, + 0.3859711f, 3.0361648f, 2.326165f, 0.26641548f, 3.3931527f, 2.7145221f, 0.21207204f, 2.689007f, + 1.1525618f, 0.37914506f, 1.9227866f, 0.82443005f, 0.41990262f, 2.986681f, 1.6959136f, 0.28947833f, + 3.1783192f, 2.2760465f, 0.19981161f, 3.6264317f, 2.7145221f, 0.15905404f, 2.7304275f, 1.0444548f, 0.0f, + 1.9227866f, 0.67863053f, 0.0f, 3.039699f, 1.6329546f, 0.0f, 3.242935f, 2.2532656f, 0.0f, 3.7324677f, + 2.7145221f, 0.0f, 2.689007f, 1.1525618f, -0.37914506f, 2.7304275f, 1.0444548f, 0.0f, 1.9227866f, + 0.67863053f, 0.0f, 1.9227866f, 0.82443005f, -0.41990262f, 2.986681f, 1.6959136f, -0.28947833f, + 3.039699f, 1.6329546f, 0.0f, 3.1783192f, 2.2760465f, -0.19981161f, 3.242935f, 2.2532656f, 0.0f, + 3.6264317f, 2.7145221f, -0.15905404f, 3.7324677f, 2.7145221f, 0.0f, 2.5978825f, 1.3903973f, + -0.5055267f, 1.9227866f, 1.145189f, -0.5598702f, 2.8700416f, 1.8344232f, -0.3859711f, 3.0361648f, + 2.326165f, -0.26641548f, 3.3931527f, 2.7145221f, -0.21207204f, 2.5067577f, 1.6282327f, -0.37914506f, + 1.9227866f, 1.4659479f, -0.41990262f, 2.753402f, 1.9729326f, -0.28947833f, 2.89401f, 2.3762836f, + -0.19981161f, 3.1598732f, 2.7145221f, -0.15905404f, 2.4653373f, 1.7363397f, 0.0f, 1.9227866f, + 1.6117474f, 0.0f, 2.700384f, 2.0358915f, 0.0f, 2.8293946f, 2.399065f, 0.0f, 3.0538373f, 2.7145221f, + 0.0f, 3.248692f, 2.764568f, 0.14911313f, 3.1351316f, 2.7622383f, 0.0f, 3.0538373f, 2.7145221f, 0.0f, + 3.1598732f, 2.7145221f, 0.15905404f, 3.301807f, 2.7818716f, 0.12724322f, 3.1952186f, 2.7781436f, 0.0f, + 3.3033948f, 2.7655f, 0.1053733f, 3.2128913f, 2.7622383f, 0.0f, 3.237633f, 2.7145221f, 0.09543244f, + 3.1669424f, 2.7145221f, 0.0f, 3.4985259f, 2.7696939f, 0.19881752f, 3.3931527f, 2.7145221f, 0.21207204f, + 3.5363014f, 2.7900727f, 0.16965766f, 3.5025022f, 2.7726762f, 0.14049773f, 3.3931527f, 2.7145221f, + 0.12724322f, 3.7483597f, 2.7748199f, 0.14911313f, 3.6264317f, 2.7145221f, 0.15905404f, 3.7707958f, + 2.7982738f, 0.12724322f, 3.7016098f, 2.7798524f, 0.1053733f, 3.5486722f, 2.7145221f, 0.09543244f, + 3.86192f, 2.7771497f, 0.0f, 3.7324677f, 2.7145221f, 0.0f, 3.877384f, 2.802002f, 0.0f, 3.7921128f, + 2.7831142f, 0.0f, 3.6193628f, 2.7145221f, 0.0f, 3.7483597f, 2.7748199f, -0.14911313f, 3.86192f, + 2.7771497f, 0.0f, 3.7324677f, 2.7145221f, 0.0f, 3.6264317f, 2.7145221f, -0.15905404f, 3.7707958f, + 2.7982738f, -0.12724322f, 3.877384f, 2.802002f, 0.0f, 3.7016098f, 2.7798524f, -0.1053733f, 3.7921128f, + 2.7831142f, 0.0f, 3.5486722f, 2.7145221f, -0.09543244f, 3.6193628f, 2.7145221f, 0.0f, 3.4985259f, + 2.7696939f, -0.19881752f, 3.3931527f, 2.7145221f, -0.21207204f, 3.5363014f, 2.7900727f, -0.16965766f, + 3.5025022f, 2.7726762f, -0.14049773f, 3.3931527f, 2.7145221f, -0.12724322f, 3.248692f, 2.764568f, + -0.14911313f, 3.1598732f, 2.7145221f, -0.15905404f, 3.301807f, 2.7818716f, -0.12724322f, 3.3033948f, + 2.7655f, -0.1053733f, 3.237633f, 2.7145221f, -0.09543244f, 3.1351316f, 2.7622383f, 0.0f, 3.0538373f, + 2.7145221f, 0.0f, 3.1952186f, 2.7781436f, 0.0f, 3.2128913f, 2.7622383f, 0.0f, 3.1669424f, 2.7145221f, + 0.0f, 0.0f, 3.5628102f, 0.0f, 0.27389547f, 3.507141f, 0.27389547f, 0.3555404f, 3.507141f, 0.15161878f, + 0.38526422f, 3.507141f, 0.0f, 0.33922246f, 3.3719451f, 0.14463753f, 0.36759156f, 3.3719451f, 0.0f, + 0.20546299f, 3.204939f, 0.08753438f, 0.22267565f, 3.204939f, 0.0f, 0.20867887f, 3.0538373f, + 0.08878748f, 0.22621018f, 3.0538373f, 0.0f, 0.2613081f, 3.3719451f, 0.2613081f, 0.158219f, 3.204939f, + 0.158219f, 0.16060922f, 3.0538373f, 0.16060922f, 0.0f, 3.5628102f, 0.0f, 0.0f, 3.507141f, 0.38526422f, + 0.15161878f, 3.507141f, 0.3555404f, 0.14463753f, 3.3719451f, 0.33922246f, 0.08753438f, 3.204939f, + 0.20546299f, 0.08878748f, 3.0538373f, 0.20867887f, 0.0f, 3.3719451f, 0.36759156f, 0.0f, 3.204939f, + 0.22267565f, 0.0f, 3.0538373f, 0.22621018f, 0.0f, 3.5628102f, 0.0f, -0.27389547f, 3.507141f, + 0.27389547f, -0.15161878f, 3.507141f, 0.3555404f, 0.0f, 3.507141f, 0.38526422f, -0.14463753f, + 3.3719451f, 0.33922246f, 0.0f, 3.3719451f, 0.36759156f, -0.08753438f, 3.204939f, 0.20546299f, 0.0f, + 3.204939f, 0.22267565f, -0.08878748f, 3.0538373f, 0.20867887f, 0.0f, 3.0538373f, 0.22621018f, + -0.2613081f, 3.3719451f, 0.2613081f, -0.158219f, 3.204939f, 0.158219f, -0.16060922f, 3.0538373f, + 0.16060922f, 0.0f, 3.5628102f, 0.0f, -0.38526422f, 3.507141f, 0.0f, -0.3555404f, 3.507141f, + 0.15161878f, -0.33922246f, 3.3719451f, 0.14463753f, -0.20546299f, 3.204939f, 0.08753438f, -0.20867887f, + 3.0538373f, 0.08878748f, -0.36759156f, 3.3719451f, 0.0f, -0.22267565f, 3.204939f, 0.0f, -0.22621018f, + 3.0538373f, 0.0f, 0.0f, 3.5628102f, 0.0f, -0.27389547f, 3.507141f, -0.27389547f, -0.3555404f, + 3.507141f, -0.15161878f, -0.38526422f, 3.507141f, 0.0f, -0.33922246f, 3.3719451f, -0.14463753f, + -0.36759156f, 3.3719451f, 0.0f, -0.20546299f, 3.204939f, -0.08753438f, -0.22267565f, 3.204939f, 0.0f, + -0.20867887f, 3.0538373f, -0.08878748f, -0.22621018f, 3.0538373f, 0.0f, -0.2613081f, 3.3719451f, + -0.2613081f, -0.158219f, 3.204939f, -0.158219f, -0.16060922f, 3.0538373f, -0.16060922f, 0.0f, + 3.5628102f, 0.0f, 0.0f, 3.507141f, -0.38526422f, -0.15161878f, 3.507141f, -0.3555404f, -0.14463753f, + 3.3719451f, -0.33922246f, -0.08753438f, 3.204939f, -0.20546299f, -0.08878748f, 3.0538373f, + -0.20867887f, 0.0f, 3.3719451f, -0.36759156f, 0.0f, 3.204939f, -0.22267565f, 0.0f, 3.0538373f, + -0.22621018f, 0.0f, 3.5628102f, 0.0f, 0.27389547f, 3.507141f, -0.27389547f, 0.15161878f, 3.507141f, + -0.3555404f, 0.0f, 3.507141f, -0.38526422f, 0.14463753f, 3.3719451f, -0.33922246f, 0.0f, 3.3719451f, + -0.36759156f, 0.08753438f, 3.204939f, -0.20546299f, 0.0f, 3.204939f, -0.22267565f, 0.08878748f, + 3.0538373f, -0.20867887f, 0.0f, 3.0538373f, -0.22621018f, 0.2613081f, 3.3719451f, -0.2613081f, + 0.158219f, 3.204939f, -0.158219f, 0.16060922f, 3.0538373f, -0.16060922f, 0.0f, 3.5628102f, 0.0f, + 0.38526422f, 3.507141f, 0.0f, 0.3555404f, 3.507141f, -0.15161878f, 0.33922246f, 3.3719451f, + -0.14463753f, 0.20546299f, 3.204939f, -0.08753438f, 0.20867887f, 3.0538373f, -0.08878748f, 0.36759156f, + 3.3719451f, 0.0f, 0.22267565f, 3.204939f, 0.0f, 0.22621018f, 3.0538373f, 0.0f, 0.47604877f, 2.953103f, + 0.20254643f, 0.51604193f, 2.953103f, 0.0f, 0.22621018f, 3.0538373f, 0.0f, 0.20867887f, 3.0538373f, + 0.08878748f, 0.8608004f, 2.8841796f, 0.36624837f, 0.9331169f, 2.8841796f, 0.0f, 1.2064248f, 2.815256f, + 0.5133026f, 1.3077775f, 2.815256f, 0.0f, 1.3564128f, 2.7145221f, 0.5771187f, 1.4703661f, 2.7145221f, + 0.0f, 0.3663898f, 2.953103f, 0.3663898f, 0.16060922f, 3.0538373f, 0.16060922f, 0.662513f, 2.8841796f, + 0.662513f, 0.92852205f, 2.815256f, 0.92852205f, 1.0439599f, 2.7145221f, 1.0439599f, 0.20254643f, + 2.953103f, 0.47604877f, 0.08878748f, 3.0538373f, 0.20867887f, 0.36624837f, 2.8841796f, 0.8608004f, + 0.5133026f, 2.815256f, 1.2064248f, 0.5771187f, 2.7145221f, 1.3564128f, 0.0f, 2.953103f, 0.51604193f, + 0.0f, 3.0538373f, 0.22621018f, 0.0f, 2.8841796f, 0.9331169f, 0.0f, 2.815256f, 1.3077775f, 0.0f, + 2.7145221f, 1.4703661f, -0.20254643f, 2.953103f, 0.47604877f, 0.0f, 2.953103f, 0.51604193f, 0.0f, + 3.0538373f, 0.22621018f, -0.08878748f, 3.0538373f, 0.20867887f, -0.36624837f, 2.8841796f, 0.8608004f, + 0.0f, 2.8841796f, 0.9331169f, -0.5133026f, 2.815256f, 1.2064248f, 0.0f, 2.815256f, 1.3077775f, + -0.5771187f, 2.7145221f, 1.3564128f, 0.0f, 2.7145221f, 1.4703661f, -0.3663898f, 2.953103f, 0.3663898f, + -0.16060922f, 3.0538373f, 0.16060922f, -0.662513f, 2.8841796f, 0.662513f, -0.92852205f, 2.815256f, + 0.92852205f, -1.0439599f, 2.7145221f, 1.0439599f, -0.47604877f, 2.953103f, 0.20254643f, -0.20867887f, + 3.0538373f, 0.08878748f, -0.8608004f, 2.8841796f, 0.36624837f, -1.2064248f, 2.815256f, 0.5133026f, + -1.3564128f, 2.7145221f, 0.5771187f, -0.51604193f, 2.953103f, 0.0f, -0.22621018f, 3.0538373f, 0.0f, + -0.9331169f, 2.8841796f, 0.0f, -1.3077775f, 2.815256f, 0.0f, -1.4703661f, 2.7145221f, 0.0f, + -0.47604877f, 2.953103f, -0.20254643f, -0.51604193f, 2.953103f, 0.0f, -0.22621018f, 3.0538373f, 0.0f, + -0.20867887f, 3.0538373f, -0.08878748f, -0.8608004f, 2.8841796f, -0.36624837f, -0.9331169f, 2.8841796f, + 0.0f, -1.2064248f, 2.815256f, -0.5133026f, -1.3077775f, 2.815256f, 0.0f, -1.3564128f, 2.7145221f, + -0.5771187f, -1.4703661f, 2.7145221f, 0.0f, -0.3663898f, 2.953103f, -0.3663898f, -0.16060922f, + 3.0538373f, -0.16060922f, -0.662513f, 2.8841796f, -0.662513f, -0.92852205f, 2.815256f, -0.92852205f, + -1.0439599f, 2.7145221f, -1.0439599f, -0.20254643f, 2.953103f, -0.47604877f, -0.08878748f, 3.0538373f, + -0.20867887f, -0.36624837f, 2.8841796f, -0.8608004f, -0.5133026f, 2.815256f, -1.2064248f, -0.5771187f, + 2.7145221f, -1.3564128f, 0.0f, 2.953103f, -0.51604193f, 0.0f, 3.0538373f, -0.22621018f, 0.0f, + 2.8841796f, -0.9331169f, 0.0f, 2.815256f, -1.3077775f, 0.0f, 2.7145221f, -1.4703661f, 0.20254643f, + 2.953103f, -0.47604877f, 0.0f, 2.953103f, -0.51604193f, 0.0f, 3.0538373f, -0.22621018f, 0.08878748f, + 3.0538373f, -0.20867887f, 0.36624837f, 2.8841796f, -0.8608004f, 0.0f, 2.8841796f, -0.9331169f, + 0.5133026f, 2.815256f, -1.2064248f, 0.0f, 2.815256f, -1.3077775f, 0.5771187f, 2.7145221f, -1.3564128f, + 0.0f, 2.7145221f, -1.4703661f, 0.3663898f, 2.953103f, -0.3663898f, 0.16060922f, 3.0538373f, + -0.16060922f, 0.662513f, 2.8841796f, -0.662513f, 0.92852205f, 2.815256f, -0.92852205f, 1.0439599f, + 2.7145221f, -1.0439599f, 0.47604877f, 2.953103f, -0.20254643f, 0.20867887f, 3.0538373f, -0.08878748f, + 0.8608004f, 2.8841796f, -0.36624837f, 1.2064248f, 2.815256f, -0.5133026f, 1.3564128f, 2.7145221f, + -0.5771187f, 0.51604193f, 2.953103f, 0.0f, 0.22621018f, 3.0538373f, 0.0f, 0.9331169f, 2.8841796f, 0.0f, + 1.3077775f, 2.815256f, 0.0f, 1.4703661f, 2.7145221f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f }; + _meshData.setVertexBuffer(BufferUtils.createFloatBuffer(verts)); + } + + private void setNormalData() { + final float[] norms = new float[] { -0.893437f, 0.255997f, -0.369102f, -0.966824f, 0.255444f, 0.0f, -0.966742f, + -0.255753f, 0.0f, -0.893014f, -0.256343f, -0.369883f, -0.083878f, 0.995843f, -0.035507f, -0.092051f, + 0.995754f, -0.0f, 0.629723f, 0.731861f, 0.260439f, 0.682049f, 0.731306f, 0.0f, 0.803725f, 0.49337f, + 0.332584f, 0.8703f, 0.492521f, 0.0f, -0.683531f, 0.256069f, -0.683531f, -0.683407f, -0.256729f, + -0.683407f, -0.064924f, 0.995776f, -0.064924f, 0.481398f, 0.73247f, 0.481398f, 0.614804f, 0.493997f, + 0.614804f, -0.369102f, 0.255997f, -0.893437f, -0.369882f, -0.256343f, -0.893015f, -0.035507f, + 0.995843f, -0.083878f, 0.260439f, 0.731861f, 0.629723f, 0.332584f, 0.49337f, 0.803725f, -0.001923f, + 0.254737f, -0.967008f, -0.002849f, -0.257864f, -0.966177f, -2.66E-4f, 0.995734f, -0.092269f, 2.4E-5f, + 0.731296f, 0.68206f, 0.0f, 0.492521f, 0.870301f, 0.37711f, 0.149085f, -0.914091f, -0.001923f, + 0.254737f, -0.967008f, -0.002849f, -0.257864f, -0.966177f, 0.379058f, -0.359299f, -0.852772f, + 0.027503f, 0.992081f, -0.122552f, -2.66E-4f, 0.995734f, -0.092269f, -0.26101f, 0.726762f, 0.635366f, + 2.4E-5f, 0.731296f, 0.68206f, -0.332485f, 0.492547f, 0.804271f, 0.0f, 0.492521f, 0.870301f, 0.712664f, + 0.073723f, -0.697621f, 0.663548f, -0.410791f, -0.625264f, 0.099726f, 0.987509f, -0.121983f, -0.487319f, + 0.723755f, 0.488568f, -0.615242f, 0.492602f, 0.615484f, 0.917276f, 0.167113f, -0.361493f, 0.880028f, + -0.332906f, -0.338709f, 0.113585f, 0.992365f, -0.04807f, -0.63415f, 0.727509f, 0.261888f, -0.804126f, + 0.492634f, 0.332705f, 0.967442f, 0.252963f, 0.008103f, 0.966689f, -0.255739f, 0.010454f, 0.093435f, + 0.995625f, 0.001281f, -0.682166f, 0.731197f, -3.44E-4f, -0.870322f, 0.492484f, -5.4E-5f, 0.893437f, + 0.255997f, 0.369102f, 0.967442f, 0.252963f, 0.008103f, 0.966689f, -0.255739f, 0.010454f, 0.893014f, + -0.256343f, 0.369883f, 0.083878f, 0.995843f, 0.035507f, 0.093435f, 0.995625f, 0.001281f, -0.629723f, + 0.731861f, -0.260439f, -0.682166f, 0.731197f, -3.44E-4f, -0.803725f, 0.49337f, -0.332584f, -0.870322f, + 0.492484f, -5.4E-5f, 0.683531f, 0.256069f, 0.683531f, 0.683407f, -0.256729f, 0.683407f, 0.064924f, + 0.995776f, 0.064924f, -0.481398f, 0.73247f, -0.481398f, -0.614804f, 0.493997f, -0.614804f, 0.369102f, + 0.255997f, 0.893437f, 0.369882f, -0.256343f, 0.893015f, 0.035507f, 0.995843f, 0.083878f, -0.260439f, + 0.731861f, -0.629723f, -0.332584f, 0.49337f, -0.803725f, 0.0f, 0.255444f, 0.966824f, 0.0f, -0.255753f, + 0.966742f, -0.0f, 0.995754f, 0.092051f, 0.0f, 0.731306f, -0.682049f, -0.0f, 0.492521f, -0.870301f, + -0.369102f, 0.255997f, 0.893437f, 0.0f, 0.255444f, 0.966824f, 0.0f, -0.255753f, 0.966742f, -0.369883f, + -0.256343f, 0.893014f, -0.035507f, 0.995843f, 0.083878f, -0.0f, 0.995754f, 0.092051f, 0.260439f, + 0.731861f, -0.629723f, 0.0f, 0.731306f, -0.682049f, 0.332584f, 0.49337f, -0.803725f, -0.0f, 0.492521f, + -0.870301f, -0.683531f, 0.256069f, 0.683531f, -0.683407f, -0.256729f, 0.683407f, -0.064924f, 0.995776f, + 0.064924f, 0.481398f, 0.73247f, -0.481398f, 0.614804f, 0.493997f, -0.614804f, -0.893437f, 0.255997f, + 0.369102f, -0.893015f, -0.256343f, 0.369882f, -0.083878f, 0.995843f, 0.035507f, 0.629723f, 0.731861f, + -0.260439f, 0.803725f, 0.49337f, -0.332584f, -0.966824f, 0.255444f, 0.0f, -0.966742f, -0.255753f, 0.0f, + -0.092051f, 0.995754f, -0.0f, 0.682049f, 0.731306f, 0.0f, 0.8703f, 0.492521f, 0.0f, 0.845438f, + 0.403545f, 0.349835f, 0.915321f, 0.402725f, -0.0f, 0.8703f, 0.492521f, 0.0f, 0.803725f, 0.49337f, + 0.332584f, 0.869996f, 0.336859f, 0.360047f, 0.941808f, 0.336151f, -0.0f, 0.904193f, 0.205791f, + 0.37428f, 0.97869f, 0.205342f, -0.0f, 0.921879f, -0.06637f, 0.381752f, 0.997804f, -0.06624f, 0.0f, + 0.646802f, 0.404096f, 0.646802f, 0.614804f, 0.493997f, 0.614804f, 0.665655f, 0.337351f, 0.665655f, + 0.691923f, 0.20612f, 0.691923f, 0.705542f, -0.06648f, 0.705543f, 0.349835f, 0.403546f, 0.845438f, + 0.332584f, 0.49337f, 0.803725f, 0.360047f, 0.336858f, 0.869996f, 0.37428f, 0.205791f, 0.904193f, + 0.381752f, -0.06637f, 0.921879f, 0.0f, 0.402725f, 0.915321f, 0.0f, 0.492521f, 0.870301f, 0.0f, + 0.336151f, 0.941808f, 0.0f, 0.205342f, 0.97869f, -0.0f, -0.06624f, 0.997804f, -0.349835f, 0.403545f, + 0.845438f, 0.0f, 0.402725f, 0.915321f, 0.0f, 0.492521f, 0.870301f, -0.332485f, 0.492547f, 0.804271f, + -0.360047f, 0.336859f, 0.869996f, 0.0f, 0.336151f, 0.941808f, -0.37428f, 0.205791f, 0.904193f, 0.0f, + 0.205342f, 0.97869f, -0.381752f, -0.06637f, 0.921879f, -0.0f, -0.06624f, 0.997804f, -0.646802f, + 0.404096f, 0.646802f, -0.615242f, 0.492602f, 0.615484f, -0.665655f, 0.337351f, 0.665655f, -0.691923f, + 0.20612f, 0.691923f, -0.705543f, -0.06648f, 0.705542f, -0.845438f, 0.403546f, 0.349835f, -0.804126f, + 0.492634f, 0.332705f, -0.869996f, 0.336858f, 0.360047f, -0.904193f, 0.205791f, 0.37428f, -0.921879f, + -0.06637f, 0.381752f, -0.915321f, 0.402725f, 0.0f, -0.870322f, 0.492484f, -5.4E-5f, -0.941808f, + 0.336151f, 0.0f, -0.97869f, 0.205342f, 0.0f, -0.997804f, -0.06624f, -0.0f, -0.845438f, 0.403545f, + -0.349835f, -0.915321f, 0.402725f, 0.0f, -0.870322f, 0.492484f, -5.4E-5f, -0.803725f, 0.49337f, + -0.332584f, -0.869996f, 0.336859f, -0.360047f, -0.941808f, 0.336151f, 0.0f, -0.904193f, 0.205791f, + -0.37428f, -0.97869f, 0.205342f, 0.0f, -0.921879f, -0.06637f, -0.381752f, -0.997804f, -0.06624f, -0.0f, + -0.646802f, 0.404096f, -0.646802f, -0.614804f, 0.493997f, -0.614804f, -0.665655f, 0.337351f, + -0.665655f, -0.691923f, 0.20612f, -0.691923f, -0.705542f, -0.06648f, -0.705543f, -0.349835f, 0.403546f, + -0.845438f, -0.332584f, 0.49337f, -0.803725f, -0.360047f, 0.336858f, -0.869996f, -0.37428f, 0.205791f, + -0.904193f, -0.381752f, -0.06637f, -0.921879f, -0.0f, 0.402725f, -0.915321f, -0.0f, 0.492521f, + -0.870301f, -0.0f, 0.336151f, -0.941808f, -0.0f, 0.205342f, -0.97869f, 0.0f, -0.06624f, -0.997804f, + 0.349835f, 0.403545f, -0.845438f, -0.0f, 0.402725f, -0.915321f, -0.0f, 0.492521f, -0.870301f, + 0.332584f, 0.49337f, -0.803725f, 0.360047f, 0.336859f, -0.869996f, -0.0f, 0.336151f, -0.941808f, + 0.37428f, 0.205791f, -0.904193f, -0.0f, 0.205342f, -0.97869f, 0.381752f, -0.06637f, -0.921879f, 0.0f, + -0.06624f, -0.997804f, 0.646802f, 0.404096f, -0.646802f, 0.614804f, 0.493997f, -0.614804f, 0.665655f, + 0.337351f, -0.665655f, 0.691923f, 0.20612f, -0.691923f, 0.705543f, -0.06648f, -0.705542f, 0.845438f, + 0.403546f, -0.349835f, 0.803725f, 0.49337f, -0.332584f, 0.869996f, 0.336858f, -0.360047f, 0.904193f, + 0.205791f, -0.37428f, 0.921879f, -0.06637f, -0.381752f, 0.915321f, 0.402725f, -0.0f, 0.8703f, + 0.492521f, 0.0f, 0.941808f, 0.336151f, -0.0f, 0.97869f, 0.205342f, -0.0f, 0.997804f, -0.06624f, 0.0f, + 0.831437f, -0.43618f, 0.344179f, 0.900182f, -0.435513f, 0.0f, 0.997804f, -0.06624f, 0.0f, 0.921879f, + -0.06637f, 0.381752f, 0.673512f, -0.684666f, 0.278594f, 0.729611f, -0.683863f, -0.0f, 0.640399f, + -0.720924f, 0.264874f, 0.693951f, -0.720022f, 0.0f, 0.732949f, -0.608995f, 0.303167f, 0.793949f, + -0.607984f, -0.0f, 0.636092f, -0.436777f, 0.636092f, 0.705542f, -0.06648f, 0.705543f, 0.514965f, + -0.68529f, 0.514965f, 0.489651f, -0.721446f, 0.489651f, 0.560555f, -0.609553f, 0.560555f, 0.344179f, + -0.43618f, 0.831437f, 0.381752f, -0.06637f, 0.921879f, 0.278594f, -0.684665f, 0.673512f, 0.264874f, + -0.720924f, 0.640399f, 0.303167f, -0.608995f, 0.732949f, -0.0f, -0.435513f, 0.900182f, -0.0f, + -0.06624f, 0.997804f, 0.0f, -0.683863f, 0.729611f, -0.0f, -0.720022f, 0.693951f, 0.0f, -0.607984f, + 0.793949f, -0.344179f, -0.43618f, 0.831437f, -0.0f, -0.435513f, 0.900182f, -0.0f, -0.06624f, 0.997804f, + -0.381752f, -0.06637f, 0.921879f, -0.278594f, -0.684666f, 0.673512f, 0.0f, -0.683863f, 0.729611f, + -0.264874f, -0.720924f, 0.640399f, -0.0f, -0.720022f, 0.693951f, -0.303167f, -0.608995f, 0.732949f, + 0.0f, -0.607984f, 0.793949f, -0.636092f, -0.436777f, 0.636092f, -0.705543f, -0.06648f, 0.705542f, + -0.514965f, -0.68529f, 0.514965f, -0.489651f, -0.721446f, 0.489651f, -0.560555f, -0.609553f, 0.560555f, + -0.831437f, -0.43618f, 0.344179f, -0.921879f, -0.06637f, 0.381752f, -0.673512f, -0.684665f, 0.278594f, + -0.640399f, -0.720924f, 0.264874f, -0.732949f, -0.608995f, 0.303167f, -0.900182f, -0.435513f, -0.0f, + -0.997804f, -0.06624f, -0.0f, -0.729611f, -0.683863f, 0.0f, -0.693951f, -0.720022f, -0.0f, -0.793949f, + -0.607984f, 0.0f, -0.831437f, -0.43618f, -0.344179f, -0.900182f, -0.435513f, -0.0f, -0.997804f, + -0.06624f, -0.0f, -0.921879f, -0.06637f, -0.381752f, -0.673512f, -0.684666f, -0.278594f, -0.729611f, + -0.683863f, 0.0f, -0.640399f, -0.720924f, -0.264874f, -0.693951f, -0.720022f, -0.0f, -0.732949f, + -0.608995f, -0.303167f, -0.793949f, -0.607984f, 0.0f, -0.636092f, -0.436777f, -0.636092f, -0.705542f, + -0.06648f, -0.705543f, -0.514965f, -0.68529f, -0.514965f, -0.489651f, -0.721446f, -0.489651f, + -0.560555f, -0.609553f, -0.560555f, -0.344179f, -0.43618f, -0.831437f, -0.381752f, -0.06637f, + -0.921879f, -0.278594f, -0.684665f, -0.673512f, -0.264874f, -0.720924f, -0.640399f, -0.303167f, + -0.608995f, -0.732949f, 0.0f, -0.435513f, -0.900182f, 0.0f, -0.06624f, -0.997804f, -0.0f, -0.683863f, + -0.729611f, 0.0f, -0.720022f, -0.693951f, -0.0f, -0.607984f, -0.793949f, 0.344179f, -0.43618f, + -0.831437f, 0.0f, -0.435513f, -0.900182f, 0.0f, -0.06624f, -0.997804f, 0.381752f, -0.06637f, + -0.921879f, 0.278594f, -0.684666f, -0.673512f, -0.0f, -0.683863f, -0.729611f, 0.264874f, -0.720924f, + -0.640399f, 0.0f, -0.720022f, -0.693951f, 0.303167f, -0.608995f, -0.732949f, -0.0f, -0.607984f, + -0.793949f, 0.636092f, -0.436777f, -0.636092f, 0.705543f, -0.06648f, -0.705542f, 0.514965f, -0.68529f, + -0.514965f, 0.489651f, -0.721446f, -0.489651f, 0.560555f, -0.609553f, -0.560555f, 0.831437f, -0.43618f, + -0.344179f, 0.921879f, -0.06637f, -0.381752f, 0.673512f, -0.684665f, -0.278594f, 0.640399f, -0.720924f, + -0.264874f, 0.732949f, -0.608995f, -0.303167f, 0.900182f, -0.435513f, 0.0f, 0.997804f, -0.06624f, 0.0f, + 0.729611f, -0.683863f, -0.0f, 0.693951f, -0.720022f, 0.0f, 0.793949f, -0.607984f, -0.0f, 0.57623f, + -0.7818f, 0.238217f, 0.623859f, -0.781537f, -0.0f, 0.793949f, -0.607984f, -0.0f, 0.732949f, -0.608995f, + 0.303167f, 0.163628f, -0.984208f, 0.067527f, 0.177291f, -0.984159f, 0.0f, 0.045422f, -0.998792f, + 0.018736f, 0.049207f, -0.998789f, 0.0f, 0.0f, -1.0f, -0.0f, 0.045422f, -0.998792f, -0.018736f, + 0.440416f, -0.782348f, 0.440416f, 0.560555f, -0.609553f, 0.560555f, 0.124903f, -0.984276f, 0.124903f, + 0.034662f, -0.998798f, 0.034662f, 0.238217f, -0.7818f, 0.57623f, 0.303167f, -0.608995f, 0.732949f, + 0.067527f, -0.984208f, 0.163628f, 0.018736f, -0.998792f, 0.045422f, 0.0f, -0.781537f, 0.623859f, 0.0f, + -0.607984f, 0.793949f, -0.0f, -0.984159f, 0.177291f, -0.0f, -0.998789f, 0.049207f, -0.238217f, + -0.7818f, 0.57623f, 0.0f, -0.781537f, 0.623859f, 0.0f, -0.607984f, 0.793949f, -0.303167f, -0.608995f, + 0.732949f, -0.067527f, -0.984208f, 0.163628f, -0.0f, -0.984159f, 0.177291f, -0.018736f, -0.998792f, + 0.045422f, -0.0f, -0.998789f, 0.049207f, -0.440416f, -0.782348f, 0.440416f, -0.560555f, -0.609553f, + 0.560555f, -0.124903f, -0.984276f, 0.124903f, -0.034662f, -0.998798f, 0.034662f, -0.57623f, -0.7818f, + 0.238217f, -0.732949f, -0.608995f, 0.303167f, -0.163628f, -0.984208f, 0.067527f, -0.045422f, + -0.998792f, 0.018736f, -0.623859f, -0.781537f, 0.0f, -0.793949f, -0.607984f, 0.0f, -0.177291f, + -0.984159f, -0.0f, -0.049207f, -0.998789f, -0.0f, -0.57623f, -0.7818f, -0.238217f, -0.623859f, + -0.781537f, 0.0f, -0.793949f, -0.607984f, 0.0f, -0.732949f, -0.608995f, -0.303167f, -0.163628f, + -0.984208f, -0.067527f, -0.177291f, -0.984159f, -0.0f, -0.045422f, -0.998792f, -0.018736f, -0.049207f, + -0.998789f, -0.0f, -0.440416f, -0.782348f, -0.440416f, -0.560555f, -0.609553f, -0.560555f, -0.124903f, + -0.984276f, -0.124903f, -0.034662f, -0.998798f, -0.034662f, -0.238217f, -0.7818f, -0.57623f, + -0.303167f, -0.608995f, -0.732949f, -0.067527f, -0.984208f, -0.163628f, -0.018736f, -0.998792f, + -0.045422f, -0.0f, -0.781537f, -0.623859f, -0.0f, -0.607984f, -0.793949f, 0.0f, -0.984159f, -0.177291f, + 0.0f, -0.998789f, -0.049207f, 0.238217f, -0.7818f, -0.57623f, -0.0f, -0.781537f, -0.623859f, -0.0f, + -0.607984f, -0.793949f, 0.303167f, -0.608995f, -0.732949f, 0.067527f, -0.984208f, -0.163628f, 0.0f, + -0.984159f, -0.177291f, 0.018736f, -0.998792f, -0.045422f, 0.0f, -0.998789f, -0.049207f, 0.440416f, + -0.782348f, -0.440416f, 0.560555f, -0.609553f, -0.560555f, 0.124903f, -0.984276f, -0.124903f, + 0.034662f, -0.998798f, -0.034662f, 0.57623f, -0.7818f, -0.238217f, 0.732949f, -0.608995f, -0.303167f, + 0.163628f, -0.984208f, -0.067527f, 0.623859f, -0.781537f, -0.0f, 0.793949f, -0.607984f, -0.0f, + 0.177291f, -0.984159f, 0.0f, 0.049207f, -0.998789f, 0.0f, 0.036127f, -0.837257f, 0.545614f, 0.039138f, + -0.999233f, -9.89E-4f, 0.007786f, -0.99997f, -2.16E-4f, 0.007039f, -0.812494f, 0.582926f, 0.161845f, + -0.810421f, 0.563048f, 0.179512f, -0.983746f, -0.004369f, 0.482365f, -0.595148f, 0.642746f, 0.6123f, + -0.790557f, -0.01046f, 0.73872f, -0.114594f, 0.664199f, 0.986152f, -0.165708f, -0.00667f, 0.002762f, + 0.017107f, 0.99985f, -0.001909f, 0.162121f, 0.986769f, 0.010533f, 0.073398f, 0.997247f, -0.066041f, + 0.13007f, 0.989303f, -0.094427f, 0.016594f, 0.995394f, -0.048606f, 0.840609f, 0.539458f, -0.009203f, + 0.871509f, 0.490293f, -0.223298f, 0.802881f, 0.552739f, -0.596365f, 0.559971f, 0.575136f, -0.803337f, + 0.068236f, 0.591603f, -0.058799f, 0.99827f, 7.1E-4f, -0.010561f, 0.999944f, 1.03E-4f, -0.28071f, + 0.959787f, 0.003269f, -0.749723f, 0.661738f, 0.004268f, -0.997351f, 0.072714f, 0.002059f, -0.046494f, + 0.841178f, -0.538756f, -0.058799f, 0.99827f, 7.1E-4f, -0.010561f, 0.999944f, 1.03E-4f, -0.008792f, + 0.871493f, -0.49033f, -0.217909f, 0.806807f, -0.549161f, -0.28071f, 0.959787f, 0.003269f, -0.59729f, + 0.560026f, -0.574121f, -0.749723f, 0.661738f, 0.004268f, -0.804f, 0.062913f, -0.591292f, -0.997351f, + 0.072714f, 0.002059f, 0.002031f, 0.014555f, -0.999892f, -0.001806f, 0.161691f, -0.98684f, 0.009215f, + 0.060069f, -0.998152f, -0.059334f, 0.113865f, -0.991723f, -0.086899f, 0.01229f, -0.996141f, 0.033783f, + -0.837512f, -0.545373f, 0.006418f, -0.812379f, -0.583095f, 0.157113f, -0.811947f, -0.562189f, + 0.484406f, -0.589366f, -0.646528f, 0.73887f, -0.10132f, -0.666187f, 0.039138f, -0.999233f, -9.89E-4f, + 0.007786f, -0.99997f, -2.16E-4f, 0.179512f, -0.983746f, -0.004369f, 0.6123f, -0.790557f, -0.01046f, + 0.986152f, -0.165708f, -0.00667f, 0.725608f, 0.259351f, 0.637361f, 0.946512f, 0.32265f, -0.003357f, + 0.986152f, -0.165708f, -0.00667f, 0.73872f, -0.114594f, 0.664199f, 0.645945f, 0.461988f, 0.607719f, + 0.82583f, 0.56387f, -0.007452f, 0.531615f, 0.63666f, 0.558614f, 0.650011f, 0.759893f, -0.006937f, + 0.424964f, 0.681717f, 0.595539f, 0.532429f, 0.846458f, -0.005245f, -0.049562f, -0.019755f, 0.998576f, + -0.094427f, 0.016594f, 0.995394f, -0.037817f, -0.035625f, 0.998649f, -0.037914f, -0.036512f, 0.998614f, + -0.168854f, -0.297946f, 0.93953f, -0.742342f, -0.299166f, 0.599523f, -0.803337f, 0.068236f, 0.591603f, + -0.619602f, -0.529406f, 0.579502f, -0.483708f, -0.68576f, 0.543837f, -0.445293f, -0.794355f, 0.413177f, + -0.926513f, -0.376258f, 0.001996f, -0.997351f, 0.072714f, 0.002059f, -0.75392f, -0.656952f, 0.004317f, + -0.566224f, -0.824244f, 0.003461f, -0.481804f, -0.876277f, 0.001851f, -0.744675f, -0.294425f, + -0.598976f, -0.926513f, -0.376258f, 0.001996f, -0.997351f, 0.072714f, 0.002059f, -0.804f, 0.062913f, + -0.591292f, -0.62195f, -0.528114f, -0.578165f, -0.75392f, -0.656952f, 0.004317f, -0.481171f, -0.68834f, + -0.542829f, -0.566224f, -0.824244f, 0.003461f, -0.438055f, -0.797035f, -0.415744f, -0.481804f, + -0.876277f, 0.001851f, -0.044337f, -0.017056f, -0.998871f, -0.086899f, 0.01229f, -0.996141f, + -0.026176f, -0.028167f, -0.99926f, -0.025294f, -0.028332f, -0.999278f, -0.157482f, -0.289392f, + -0.944167f, 0.728244f, 0.25241f, -0.637142f, 0.73887f, -0.10132f, -0.666187f, 0.647055f, 0.459725f, + -0.608254f, 0.522994f, 0.640657f, -0.56217f, 0.409978f, 0.682857f, -0.604669f, 0.946512f, 0.32265f, + -0.003357f, 0.986152f, -0.165708f, -0.00667f, 0.82583f, 0.56387f, -0.007452f, 0.650011f, 0.759893f, + -0.006937f, 0.532429f, 0.846458f, -0.005245f, -0.316721f, 0.63775f, 0.702113f, -0.548936f, 0.835863f, + -0.001511f, -0.230787f, 0.972982f, -0.006523f, -0.152878f, 0.687211f, 0.71019f, -0.601067f, 0.471452f, + 0.64533f, -0.875671f, 0.482807f, 0.009893f, -0.635889f, 0.446089f, 0.629801f, -0.877554f, 0.479097f, + 0.019092f, -0.435746f, 0.601008f, 0.670011f, -0.69619f, 0.717439f, 0.024497f, 0.22331f, 0.00654f, + 0.974726f, 0.111113f, -0.085069f, 0.99016f, 0.190097f, 0.154964f, 0.969458f, 0.005271f, 0.189482f, + 0.98187f, -0.011752f, 0.246688f, 0.969024f, 0.572489f, -0.567656f, 0.591627f, 0.343906f, -0.722796f, + 0.599412f, 0.787436f, -0.256459f, 0.560511f, 0.647097f, -0.306374f, 0.698141f, 0.427528f, -0.499344f, + 0.753575f, 0.67152f, -0.740986f, -8.99E-4f, 0.410926f, -0.911668f, 0.001284f, 0.922026f, -0.38706f, + -0.007253f, 0.84691f, -0.531556f, -0.013854f, 0.535924f, -0.844201f, -0.010505f, 0.578664f, -0.561139f, + -0.591838f, 0.67152f, -0.740986f, -8.99E-4f, 0.410926f, -0.911668f, 0.001284f, 0.341188f, -0.722823f, + -0.600931f, 0.784869f, -0.25102f, -0.566542f, 0.922026f, -0.38706f, -0.007253f, 0.642681f, -0.302257f, + -0.70399f, 0.84691f, -0.531556f, -0.013854f, 0.418589f, -0.500042f, -0.758117f, 0.535924f, -0.844201f, + -0.010505f, 0.232811f, 0.012565f, -0.972441f, 0.115806f, -0.079139f, -0.990114f, 0.206662f, 0.153601f, + -0.96628f, 0.0245f, 0.161443f, -0.986578f, 0.003382f, 0.211115f, -0.977455f, -0.31954f, 0.633073f, + -0.705062f, -0.134912f, 0.687491f, -0.713551f, -0.603902f, 0.461442f, -0.649903f, -0.631815f, + 0.437169f, -0.640072f, -0.424306f, 0.612706f, -0.666751f, -0.548936f, 0.835863f, -0.001511f, + -0.230787f, 0.972982f, -0.006523f, -0.875671f, 0.482807f, 0.009893f, -0.877554f, 0.479097f, 0.019092f, + -0.69619f, 0.717439f, 0.024497f, -0.259858f, 0.791937f, 0.552548f, -0.4258f, 0.904753f, 0.010805f, + -0.69619f, 0.717439f, 0.024497f, -0.435746f, 0.601008f, 0.670011f, 0.009539f, 0.99972f, -0.021673f, + 0.022046f, 0.999756f, 0.001623f, 0.410156f, 0.332913f, -0.849082f, 0.999598f, 0.025879f, 0.011556f, + 0.541522f, -0.54862f, -0.637001f, 0.709587f, -0.704552f, 0.009672f, 0.046311f, 0.455225f, 0.889171f, + -0.011752f, 0.246688f, 0.969024f, -0.010688f, 0.988795f, 0.1489f, -0.044375f, 0.682947f, -0.729118f, + 0.122825f, 0.009233f, -0.992385f, 0.481839f, -0.18044f, 0.857481f, 0.427528f, -0.499344f, 0.753575f, + 0.455273f, 0.73675f, 0.499926f, -0.220541f, 0.907194f, -0.358275f, -0.235919f, 0.65725f, -0.715797f, + 0.728092f, -0.685302f, -0.015585f, 0.535924f, -0.844201f, -0.010505f, 0.888738f, 0.458112f, -0.016678f, + -0.260099f, 0.965582f, 8.01E-4f, -0.371611f, 0.928378f, -0.004418f, 0.480165f, -0.178361f, -0.858853f, + 0.728092f, -0.685302f, -0.015585f, 0.535924f, -0.844201f, -0.010505f, 0.418589f, -0.500042f, + -0.758117f, 0.488104f, 0.716799f, -0.497949f, 0.888738f, 0.458112f, -0.016678f, -0.222004f, 0.9054f, + 0.361891f, -0.260099f, 0.965582f, 8.01E-4f, -0.235404f, 0.66318f, 0.710477f, -0.371611f, 0.928378f, + -0.004418f, 0.058721f, 0.437704f, -0.8972f, 0.003382f, 0.211115f, -0.977455f, 0.001326f, 0.986459f, + -0.164002f, -0.04419f, 0.681677f, 0.730318f, 0.138801f, -0.034188f, 0.98973f, -0.258889f, 0.797206f, + -0.545379f, -0.424306f, 0.612706f, -0.666751f, 0.012271f, 0.999739f, 0.019287f, 0.398631f, 0.354891f, + 0.845663f, 0.537564f, -0.5814f, 0.610737f, -0.4258f, 0.904753f, 0.010805f, -0.69619f, 0.717439f, + 0.024497f, 0.022046f, 0.999756f, 0.001623f, 0.999598f, 0.025879f, 0.011556f, 0.709587f, -0.704552f, + 0.009672f, 0.0f, 1.0f, 0.0f, 0.583357f, 0.565165f, 0.583338f, 0.76264f, 0.565035f, 0.314825f, 0.82454f, + 0.565803f, 1.7E-5f, 0.847982f, -0.397998f, 0.350034f, 0.917701f, -0.397272f, 3.4E-5f, 0.864141f, + -0.355261f, 0.356441f, 0.935268f, -0.353939f, 1.13E-4f, 0.720992f, 0.625625f, 0.297933f, 0.780712f, + 0.62489f, 7.5E-5f, 0.648485f, -0.398726f, 0.648448f, 0.660872f, -0.355894f, 0.660748f, 0.551862f, + 0.62529f, 0.55178f, 0.0f, 1.0f, 0.0f, -1.7E-5f, 0.565803f, 0.82454f, 0.314824f, 0.565051f, 0.762629f, + 0.350045f, -0.397977f, 0.847988f, 0.356474f, -0.3552f, 0.864152f, 0.297983f, 0.625515f, 0.721067f, + -3.3E-5f, -0.397272f, 0.917701f, -1.13E-4f, -0.353939f, 0.935268f, -7.5E-5f, 0.62489f, 0.780712f, 0.0f, + 1.0f, 0.0f, -0.583338f, 0.565165f, 0.583357f, -0.314825f, 0.565035f, 0.76264f, -1.7E-5f, 0.565803f, + 0.82454f, -0.350034f, -0.397998f, 0.847982f, -3.3E-5f, -0.397272f, 0.917701f, -0.356441f, -0.355261f, + 0.864141f, -1.13E-4f, -0.353939f, 0.935268f, -0.297933f, 0.625625f, 0.720992f, -7.5E-5f, 0.62489f, + 0.780712f, -0.648448f, -0.398726f, 0.648485f, -0.660748f, -0.355894f, 0.660872f, -0.55178f, 0.62529f, + 0.551862f, 0.0f, 1.0f, 0.0f, -0.82454f, 0.565803f, -1.7E-5f, -0.762629f, 0.565051f, 0.314824f, + -0.847988f, -0.397977f, 0.350045f, -0.864152f, -0.3552f, 0.356474f, -0.721067f, 0.625515f, 0.297983f, + -0.917701f, -0.397272f, -3.3E-5f, -0.935268f, -0.353939f, -1.13E-4f, -0.780712f, 0.62489f, -7.5E-5f, + 0.0f, 1.0f, 0.0f, -0.583357f, 0.565165f, -0.583338f, -0.76264f, 0.565035f, -0.314825f, -0.82454f, + 0.565803f, -1.7E-5f, -0.847982f, -0.397998f, -0.350034f, -0.917701f, -0.397272f, -3.3E-5f, -0.864141f, + -0.355261f, -0.356441f, -0.935268f, -0.353939f, -1.13E-4f, -0.720992f, 0.625625f, -0.297933f, + -0.780712f, 0.62489f, -7.5E-5f, -0.648485f, -0.398726f, -0.648448f, -0.660872f, -0.355894f, -0.660748f, + -0.551862f, 0.62529f, -0.55178f, 0.0f, 1.0f, 0.0f, 1.7E-5f, 0.565803f, -0.82454f, -0.314824f, + 0.565051f, -0.762629f, -0.350045f, -0.397977f, -0.847988f, -0.356474f, -0.3552f, -0.864152f, + -0.297983f, 0.625515f, -0.721067f, 3.3E-5f, -0.397272f, -0.917701f, 1.13E-4f, -0.353939f, -0.935268f, + 7.5E-5f, 0.62489f, -0.780712f, 0.0f, 1.0f, 0.0f, 0.583338f, 0.565165f, -0.583357f, 0.314825f, + 0.565035f, -0.76264f, 1.7E-5f, 0.565803f, -0.82454f, 0.350034f, -0.397998f, -0.847982f, 3.3E-5f, + -0.397272f, -0.917701f, 0.356441f, -0.355261f, -0.864141f, 1.13E-4f, -0.353939f, -0.935268f, 0.297933f, + 0.625625f, -0.720992f, 7.5E-5f, 0.62489f, -0.780712f, 0.648448f, -0.398726f, -0.648485f, 0.660748f, + -0.355894f, -0.660872f, 0.55178f, 0.62529f, -0.551862f, 0.0f, 1.0f, 0.0f, 0.82454f, 0.565803f, 1.7E-5f, + 0.762629f, 0.565051f, -0.314824f, 0.847988f, -0.397977f, -0.350045f, 0.864152f, -0.3552f, -0.356474f, + 0.721067f, 0.625515f, -0.297983f, 0.917701f, -0.397272f, 3.4E-5f, 0.935268f, -0.353939f, 1.13E-4f, + 0.780712f, 0.62489f, 7.5E-5f, 0.217978f, 0.971775f, 0.090216f, 0.236584f, 0.971611f, 0.0f, 0.780712f, + 0.62489f, 7.5E-5f, 0.720992f, 0.625625f, 0.297933f, 0.159589f, 0.984977f, 0.065961f, 0.173084f, + 0.984907f, 0.0f, 0.350498f, 0.925312f, 0.144739f, 0.379703f, 0.925108f, -0.0f, 0.485589f, 0.850654f, + 0.201474f, 0.526672f, 0.850069f, 0.0f, 0.166631f, 0.971838f, 0.166631f, 0.551862f, 0.62529f, 0.55178f, + 0.121908f, 0.985026f, 0.121908f, 0.267668f, 0.925585f, 0.267668f, 0.371315f, 0.851029f, 0.371315f, + 0.090216f, 0.971775f, 0.217978f, 0.297983f, 0.625515f, 0.721067f, 0.065961f, 0.984977f, 0.159589f, + 0.144739f, 0.925312f, 0.350498f, 0.201474f, 0.850654f, 0.485589f, -0.0f, 0.971611f, 0.236584f, + -7.5E-5f, 0.62489f, 0.780712f, -0.0f, 0.984907f, 0.173084f, 0.0f, 0.925108f, 0.379703f, 0.0f, + 0.850069f, 0.526672f, -0.090216f, 0.971775f, 0.217978f, -0.0f, 0.971611f, 0.236584f, -7.5E-5f, + 0.62489f, 0.780712f, -0.297933f, 0.625625f, 0.720992f, -0.065961f, 0.984977f, 0.159589f, -0.0f, + 0.984907f, 0.173084f, -0.144739f, 0.925312f, 0.350498f, 0.0f, 0.925108f, 0.379703f, -0.201474f, + 0.850654f, 0.485589f, 0.0f, 0.850069f, 0.526672f, -0.166631f, 0.971838f, 0.166631f, -0.55178f, + 0.62529f, 0.551862f, -0.121908f, 0.985026f, 0.121908f, -0.267668f, 0.925585f, 0.267668f, -0.371315f, + 0.851029f, 0.371315f, -0.217978f, 0.971775f, 0.090216f, -0.721067f, 0.625515f, 0.297983f, -0.159589f, + 0.984977f, 0.065961f, -0.350498f, 0.925312f, 0.144739f, -0.485589f, 0.850654f, 0.201474f, -0.236584f, + 0.971611f, -0.0f, -0.780712f, 0.62489f, -7.5E-5f, -0.173084f, 0.984907f, -0.0f, -0.379703f, 0.925108f, + 0.0f, -0.526672f, 0.850069f, 0.0f, -0.217978f, 0.971775f, -0.090216f, -0.236584f, 0.971611f, -0.0f, + -0.780712f, 0.62489f, -7.5E-5f, -0.720992f, 0.625625f, -0.297933f, -0.159589f, 0.984977f, -0.065961f, + -0.173084f, 0.984907f, -0.0f, -0.350498f, 0.925312f, -0.144739f, -0.379703f, 0.925108f, 0.0f, + -0.485589f, 0.850654f, -0.201474f, -0.526672f, 0.850069f, 0.0f, -0.166631f, 0.971838f, -0.166631f, + -0.551862f, 0.62529f, -0.55178f, -0.121908f, 0.985026f, -0.121908f, -0.267668f, 0.925585f, -0.267668f, + -0.371315f, 0.851029f, -0.371315f, -0.090216f, 0.971775f, -0.217978f, -0.297983f, 0.625515f, + -0.721067f, -0.065961f, 0.984977f, -0.159589f, -0.144739f, 0.925312f, -0.350498f, -0.201474f, + 0.850654f, -0.485589f, 0.0f, 0.971611f, -0.236584f, 7.5E-5f, 0.62489f, -0.780712f, 0.0f, 0.984907f, + -0.173084f, -0.0f, 0.925108f, -0.379703f, -0.0f, 0.850069f, -0.526672f, 0.090216f, 0.971775f, + -0.217978f, 0.0f, 0.971611f, -0.236584f, 7.5E-5f, 0.62489f, -0.780712f, 0.297933f, 0.625625f, + -0.720992f, 0.065961f, 0.984977f, -0.159589f, 0.0f, 0.984907f, -0.173084f, 0.144739f, 0.925312f, + -0.350498f, -0.0f, 0.925108f, -0.379703f, 0.201474f, 0.850654f, -0.485589f, -0.0f, 0.850069f, + -0.526672f, 0.166631f, 0.971838f, -0.166631f, 0.55178f, 0.62529f, -0.551862f, 0.121908f, 0.985026f, + -0.121908f, 0.267668f, 0.925585f, -0.267668f, 0.371315f, 0.851029f, -0.371315f, 0.217978f, 0.971775f, + -0.090216f, 0.721067f, 0.625515f, -0.297983f, 0.159589f, 0.984977f, -0.065961f, 0.350498f, 0.925312f, + -0.144739f, 0.485589f, 0.850654f, -0.201474f, 0.236584f, 0.971611f, 0.0f, 0.780712f, 0.62489f, 7.5E-5f, + 0.173084f, 0.984907f, 0.0f, 0.379703f, 0.925108f, -0.0f, 0.526672f, 0.850069f, 0.0f, 0.0f, -1.0f, + -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, + -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, + -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f }; + _meshData.setNormalBuffer(BufferUtils.createFloatBuffer(norms)); + } + + /** + * <code>setTextureData</code> sets the points that define the texture of the Teapot. It uses a range of [0, 2] so + * it is important to use wrapping on your texture. + */ + private void setTextureData() { + final float[] texs = new float[] { 1.75f, 1.975f, 2.0f, 1.975f, 2.0f, 2.0f, 1.75f, 2.0f, 1.75f, 1.95f, 2.0f, + 1.95f, 1.75f, 1.925f, 2.0f, 1.925f, 1.75f, 1.9f, 2.0f, 1.9f, 1.5f, 1.975f, 1.5f, 2.0f, 1.5f, 1.95f, + 1.5f, 1.925f, 1.5f, 1.9f, 1.25f, 1.975f, 1.25f, 2.0f, 1.25f, 1.95f, 1.25f, 1.925f, 1.25f, 1.9f, 1.0f, + 1.975f, 1.0f, 2.0f, 1.0f, 1.95f, 1.0f, 1.925f, 1.0f, 1.9f, 0.75f, 1.975f, 1.0f, 1.975f, 1.0f, 2.0f, + 0.75f, 2.0f, 0.75f, 1.95f, 1.0f, 1.95f, 0.75f, 1.925f, 1.0f, 1.925f, 0.75f, 1.9f, 1.0f, 1.9f, 0.5f, + 1.975f, 0.5f, 2.0f, 0.5f, 1.95f, 0.5f, 1.925f, 0.5f, 1.9f, 0.25f, 1.975f, 0.25f, 2.0f, 0.25f, 1.95f, + 0.25f, 1.925f, 0.25f, 1.9f, 0.0f, 1.975f, 0.0f, 2.0f, 0.0f, 1.95f, 0.0f, 1.925f, 0.0f, 1.9f, 1.75f, + 1.975f, 2.0f, 1.975f, 2.0f, 2.0f, 1.75f, 2.0f, 1.75f, 1.95f, 2.0f, 1.95f, 1.75f, 1.925f, 2.0f, 1.925f, + 1.75f, 1.9f, 2.0f, 1.9f, 1.5f, 1.975f, 1.5f, 2.0f, 1.5f, 1.95f, 1.5f, 1.925f, 1.5f, 1.9f, 1.25f, + 1.975f, 1.25f, 2.0f, 1.25f, 1.95f, 1.25f, 1.925f, 1.25f, 1.9f, 1.0f, 1.975f, 1.0f, 2.0f, 1.0f, 1.95f, + 1.0f, 1.925f, 1.0f, 1.9f, 0.75f, 1.975f, 1.0f, 1.975f, 1.0f, 2.0f, 0.75f, 2.0f, 0.75f, 1.95f, 1.0f, + 1.95f, 0.75f, 1.925f, 1.0f, 1.925f, 0.75f, 1.9f, 1.0f, 1.9f, 0.5f, 1.975f, 0.5f, 2.0f, 0.5f, 1.95f, + 0.5f, 1.925f, 0.5f, 1.9f, 0.25f, 1.975f, 0.25f, 2.0f, 0.25f, 1.95f, 0.25f, 1.925f, 0.25f, 1.9f, 0.0f, + 1.975f, 0.0f, 2.0f, 0.0f, 1.95f, 0.0f, 1.925f, 0.0f, 1.9f, 1.75f, 1.675f, 2.0f, 1.675f, 2.0f, 1.9f, + 1.75f, 1.9f, 1.75f, 1.45f, 2.0f, 1.45f, 1.75f, 1.225f, 2.0f, 1.225f, 1.75f, 1.0f, 2.0f, 1.0f, 1.5f, + 1.675f, 1.5f, 1.9f, 1.5f, 1.45f, 1.5f, 1.225f, 1.5f, 1.0f, 1.25f, 1.675f, 1.25f, 1.9f, 1.25f, 1.45f, + 1.25f, 1.225f, 1.25f, 1.0f, 1.0f, 1.675f, 1.0f, 1.9f, 1.0f, 1.45f, 1.0f, 1.225f, 1.0f, 1.0f, 0.75f, + 1.675f, 1.0f, 1.675f, 1.0f, 1.9f, 0.75f, 1.9f, 0.75f, 1.45f, 1.0f, 1.45f, 0.75f, 1.225f, 1.0f, 1.225f, + 0.75f, 1.0f, 1.0f, 1.0f, 0.5f, 1.675f, 0.5f, 1.9f, 0.5f, 1.45f, 0.5f, 1.225f, 0.5f, 1.0f, 0.25f, + 1.675f, 0.25f, 1.9f, 0.25f, 1.45f, 0.25f, 1.225f, 0.25f, 1.0f, 0.0f, 1.675f, 0.0f, 1.9f, 0.0f, 1.45f, + 0.0f, 1.225f, 0.0f, 1.0f, 1.75f, 1.675f, 2.0f, 1.675f, 2.0f, 1.9f, 1.75f, 1.9f, 1.75f, 1.45f, 2.0f, + 1.45f, 1.75f, 1.225f, 2.0f, 1.225f, 1.75f, 1.0f, 2.0f, 1.0f, 1.5f, 1.675f, 1.5f, 1.9f, 1.5f, 1.45f, + 1.5f, 1.225f, 1.5f, 1.0f, 1.25f, 1.675f, 1.25f, 1.9f, 1.25f, 1.45f, 1.25f, 1.225f, 1.25f, 1.0f, 1.0f, + 1.675f, 1.0f, 1.9f, 1.0f, 1.45f, 1.0f, 1.225f, 1.0f, 1.0f, 0.75f, 1.675f, 1.0f, 1.675f, 1.0f, 1.9f, + 0.75f, 1.9f, 0.75f, 1.45f, 1.0f, 1.45f, 0.75f, 1.225f, 1.0f, 1.225f, 0.75f, 1.0f, 1.0f, 1.0f, 0.5f, + 1.675f, 0.5f, 1.9f, 0.5f, 1.45f, 0.5f, 1.225f, 0.5f, 1.0f, 0.25f, 1.675f, 0.25f, 1.9f, 0.25f, 1.45f, + 0.25f, 1.225f, 0.25f, 1.0f, 0.0f, 1.675f, 0.0f, 1.9f, 0.0f, 1.45f, 0.0f, 1.225f, 0.0f, 1.0f, 1.75f, + 0.85f, 2.0f, 0.85f, 2.0f, 1.0f, 1.75f, 1.0f, 1.75f, 0.7f, 2.0f, 0.7f, 1.75f, 0.55f, 2.0f, 0.55f, 1.75f, + 0.4f, 2.0f, 0.4f, 1.5f, 0.85f, 1.5f, 1.0f, 1.5f, 0.7f, 1.5f, 0.55f, 1.5f, 0.4f, 1.25f, 0.85f, 1.25f, + 1.0f, 1.25f, 0.7f, 1.25f, 0.55f, 1.25f, 0.4f, 1.0f, 0.85f, 1.0f, 1.0f, 1.0f, 0.7f, 1.0f, 0.55f, 1.0f, + 0.4f, 0.75f, 0.85f, 1.0f, 0.85f, 1.0f, 1.0f, 0.75f, 1.0f, 0.75f, 0.7f, 1.0f, 0.7f, 0.75f, 0.55f, 1.0f, + 0.55f, 0.75f, 0.4f, 1.0f, 0.4f, 0.5f, 0.85f, 0.5f, 1.0f, 0.5f, 0.7f, 0.5f, 0.55f, 0.5f, 0.4f, 0.25f, + 0.85f, 0.25f, 1.0f, 0.25f, 0.7f, 0.25f, 0.55f, 0.25f, 0.4f, 0.0f, 0.85f, 0.0f, 1.0f, 0.0f, 0.7f, 0.0f, + 0.55f, 0.0f, 0.4f, 1.75f, 0.85f, 2.0f, 0.85f, 2.0f, 1.0f, 1.75f, 1.0f, 1.75f, 0.7f, 2.0f, 0.7f, 1.75f, + 0.55f, 2.0f, 0.55f, 1.75f, 0.4f, 2.0f, 0.4f, 1.5f, 0.85f, 1.5f, 1.0f, 1.5f, 0.7f, 1.5f, 0.55f, 1.5f, + 0.4f, 1.25f, 0.85f, 1.25f, 1.0f, 1.25f, 0.7f, 1.25f, 0.55f, 1.25f, 0.4f, 1.0f, 0.85f, 1.0f, 1.0f, 1.0f, + 0.7f, 1.0f, 0.55f, 1.0f, 0.4f, 0.75f, 0.85f, 1.0f, 0.85f, 1.0f, 1.0f, 0.75f, 1.0f, 0.75f, 0.7f, 1.0f, + 0.7f, 0.75f, 0.55f, 1.0f, 0.55f, 0.75f, 0.4f, 1.0f, 0.4f, 0.5f, 0.85f, 0.5f, 1.0f, 0.5f, 0.7f, 0.5f, + 0.55f, 0.5f, 0.4f, 0.25f, 0.85f, 0.25f, 1.0f, 0.25f, 0.7f, 0.25f, 0.55f, 0.25f, 0.4f, 0.0f, 0.85f, + 0.0f, 1.0f, 0.0f, 0.7f, 0.0f, 0.55f, 0.0f, 0.4f, 1.75f, 0.3f, 2.0f, 0.3f, 2.0f, 0.4f, 1.75f, 0.4f, + 1.75f, 0.2f, 2.0f, 0.2f, 1.75f, 0.1f, 2.0f, 0.1f, 1.75f, 0.0f, 0.25f, 0.1f, 1.5f, 0.3f, 1.5f, 0.4f, + 1.5f, 0.2f, 1.5f, 0.1f, 1.25f, 0.3f, 1.25f, 0.4f, 1.25f, 0.2f, 1.25f, 0.1f, 1.0f, 0.3f, 1.0f, 0.4f, + 1.0f, 0.2f, 1.0f, 0.1f, 0.75f, 0.3f, 1.0f, 0.3f, 1.0f, 0.4f, 0.75f, 0.4f, 0.75f, 0.2f, 1.0f, 0.2f, + 0.75f, 0.1f, 1.0f, 0.1f, 0.5f, 0.3f, 0.5f, 0.4f, 0.5f, 0.2f, 0.5f, 0.1f, 0.25f, 0.3f, 0.25f, 0.4f, + 0.25f, 0.2f, 0.25f, 0.1f, 0.0f, 0.3f, 0.0f, 0.4f, 0.0f, 0.2f, 0.0f, 0.1f, 1.75f, 0.3f, 2.0f, 0.3f, + 2.0f, 0.4f, 1.75f, 0.4f, 1.75f, 0.2f, 2.0f, 0.2f, 1.75f, 0.1f, 2.0f, 0.1f, 1.5f, 0.3f, 1.5f, 0.4f, + 1.5f, 0.2f, 1.5f, 0.1f, 1.25f, 0.3f, 1.25f, 0.4f, 1.25f, 0.2f, 1.25f, 0.1f, 1.0f, 0.3f, 1.0f, 0.4f, + 1.0f, 0.2f, 1.0f, 0.1f, 0.75f, 0.3f, 1.0f, 0.3f, 1.0f, 0.4f, 0.75f, 0.4f, 0.75f, 0.2f, 1.0f, 0.2f, + 0.75f, 0.1f, 1.0f, 0.1f, 0.5f, 0.3f, 0.5f, 0.4f, 0.5f, 0.2f, 0.5f, 0.1f, 0.25f, 0.3f, 0.25f, 0.4f, + 0.25f, 0.2f, 0.0f, 0.3f, 0.0f, 0.4f, 0.0f, 0.2f, 0.0f, 0.1f, 0.875f, 0.875f, 1.0f, 0.875f, 1.0f, 1.0f, + 0.875f, 1.0f, 0.875f, 0.75f, 1.0f, 0.75f, 0.875f, 0.625f, 1.0f, 0.625f, 0.875f, 0.5f, 1.0f, 0.5f, + 0.75f, 0.875f, 0.75f, 1.0f, 0.75f, 0.75f, 0.75f, 0.625f, 0.75f, 0.5f, 0.625f, 0.875f, 0.625f, 1.0f, + 0.625f, 0.75f, 0.625f, 0.625f, 0.625f, 0.5f, 0.5f, 0.875f, 0.5f, 1.0f, 0.5f, 0.75f, 0.5f, 0.625f, 0.5f, + 0.5f, 0.375f, 0.875f, 0.5f, 0.875f, 0.5f, 1.0f, 0.375f, 1.0f, 0.375f, 0.75f, 0.5f, 0.75f, 0.375f, + 0.625f, 0.5f, 0.625f, 0.375f, 0.5f, 0.5f, 0.5f, 0.25f, 0.875f, 0.25f, 1.0f, 0.25f, 0.75f, 0.25f, + 0.625f, 0.25f, 0.5f, 0.125f, 0.875f, 0.125f, 1.0f, 0.125f, 0.75f, 0.125f, 0.625f, 0.125f, 0.5f, 0.0f, + 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.0f, 0.625f, 0.0f, 0.5f, 0.875f, 0.375f, 1.0f, 0.375f, 1.0f, 0.5f, + 0.875f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.125f, 1.0f, 0.125f, 0.875f, 0.0f, 1.0f, 0.0f, + 0.75f, 0.375f, 0.75f, 0.5f, 0.75f, 0.25f, 0.75f, 0.125f, 0.75f, 0.0f, 0.625f, 0.375f, 0.625f, 0.5f, + 0.625f, 0.25f, 0.625f, 0.125f, 0.625f, 0.0f, 0.5f, 0.375f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.125f, 0.5f, + 0.0f, 0.375f, 0.375f, 0.5f, 0.375f, 0.5f, 0.5f, 0.375f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, + 0.125f, 0.5f, 0.125f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.375f, 0.25f, 0.5f, 0.25f, 0.25f, 0.25f, + 0.125f, 0.25f, 0.0f, 0.125f, 0.375f, 0.125f, 0.5f, 0.125f, 0.25f, 0.125f, 0.125f, 0.125f, 0.0f, 0.0f, + 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.125f, 0.0f, 0.0f, 0.625f, 0.225f, 0.5f, 0.225f, 0.5f, 0.0f, + 0.625f, 0.0f, 0.625f, 0.45f, 0.5f, 0.45f, 0.625f, 0.675f, 0.5f, 0.675f, 0.625f, 0.9f, 0.5f, 0.9f, + 0.75f, 0.225f, 0.75f, 0.0f, 0.75f, 0.45f, 0.75f, 0.675f, 0.75f, 0.9f, 0.875f, 0.225f, 0.875f, 0.0f, + 0.875f, 0.45f, 0.875f, 0.675f, 0.875f, 0.9f, 1.0f, 0.225f, 1.0f, 0.0f, 1.0f, 0.45f, 1.0f, 0.675f, 1.0f, + 0.9f, 0.125f, 0.225f, 0.0f, 0.225f, 0.0f, 0.0f, 0.125f, 0.0f, 0.125f, 0.45f, 0.0f, 0.45f, 0.125f, + 0.675f, 0.0f, 0.675f, 0.125f, 0.9f, 0.0f, 0.9f, 0.25f, 0.225f, 0.25f, 0.0f, 0.25f, 0.45f, 0.25f, + 0.675f, 0.25f, 0.9f, 0.375f, 0.225f, 0.375f, 0.0f, 0.375f, 0.45f, 0.375f, 0.675f, 0.375f, 0.9f, 0.5f, + 0.225f, 0.5f, 0.0f, 0.5f, 0.45f, 0.5f, 0.675f, 0.5f, 0.9f, 0.625f, 0.925f, 0.5f, 0.925f, 0.5f, 0.9f, + 0.625f, 0.9f, 0.625f, 0.95f, 0.5f, 0.95f, 0.625f, 0.975f, 0.5f, 0.975f, 0.625f, 1.0f, 0.5f, 1.0f, + 0.75f, 0.925f, 0.75f, 0.9f, 0.75f, 0.95f, 0.75f, 0.975f, 0.75f, 1.0f, 0.875f, 0.925f, 0.875f, 0.9f, + 0.875f, 0.95f, 0.875f, 0.975f, 0.875f, 1.0f, 1.0f, 0.925f, 1.0f, 0.9f, 1.0f, 0.95f, 1.0f, 0.975f, 1.0f, + 1.0f, 0.125f, 0.925f, 0.0f, 0.925f, 0.0f, 0.9f, 0.125f, 0.9f, 0.125f, 0.95f, 0.0f, 0.95f, 0.125f, + 0.975f, 0.0f, 0.975f, 0.125f, 1.0f, 0.0f, 1.0f, 0.25f, 0.925f, 0.25f, 0.9f, 0.25f, 0.95f, 0.25f, + 0.975f, 0.25f, 1.0f, 0.375f, 0.925f, 0.375f, 0.9f, 0.375f, 0.95f, 0.375f, 0.975f, 0.375f, 1.0f, 0.5f, + 0.925f, 0.5f, 0.9f, 0.5f, 0.95f, 0.5f, 0.975f, 0.5f, 1.0f, 1.0f, 1.0f, 0.75f, 0.75f, 0.875f, 0.75f, + 1.0f, 0.75f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, + 0.5f, 0.75f, 0.25f, 0.75f, 0.0f, 0.75f, 1.0f, 0.5f, 0.75f, 0.625f, 0.75f, 0.625f, 0.5f, 0.625f, 0.25f, + 0.625f, 0.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.375f, 0.75f, 0.5f, + 0.75f, 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.5f, + 0.25f, 0.25f, 0.25f, 0.0f, 0.25f, 1.0f, 0.0f, 0.75f, 0.125f, 0.75f, 0.125f, 0.5f, 0.125f, 0.25f, + 0.125f, 0.0f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f, 1.0f, 0.75f, 0.75f, 0.875f, 0.75f, 1.0f, + 0.75f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.5f, + 0.75f, 0.25f, 0.75f, 0.0f, 0.75f, 1.0f, 0.5f, 0.75f, 0.625f, 0.75f, 0.625f, 0.5f, 0.625f, 0.25f, + 0.625f, 0.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.375f, 0.75f, 0.5f, + 0.75f, 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.5f, + 0.25f, 0.25f, 0.25f, 0.0f, 0.25f, 1.0f, 0.0f, 0.75f, 0.125f, 0.75f, 0.125f, 0.5f, 0.125f, 0.25f, + 0.125f, 0.0f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 0.875f, 0.75f, 1.0f, 0.75f, 1.0f, 1.0f, 0.875f, + 1.0f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.75f, + 0.75f, 1.0f, 0.75f, 0.5f, 0.75f, 0.25f, 0.75f, 0.0f, 0.625f, 0.75f, 0.625f, 1.0f, 0.625f, 0.5f, 0.625f, + 0.25f, 0.625f, 0.0f, 0.5f, 0.75f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.375f, 0.75f, 0.5f, + 0.75f, 0.5f, 1.0f, 0.375f, 1.0f, 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, + 0.5f, 0.0f, 0.25f, 0.75f, 0.25f, 1.0f, 0.25f, 0.5f, 0.25f, 0.25f, 0.25f, 0.0f, 0.125f, 0.75f, 0.125f, + 1.0f, 0.125f, 0.5f, 0.125f, 0.25f, 0.125f, 0.0f, 0.0f, 0.75f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.25f, + 0.0f, 0.0f, 0.875f, 0.75f, 1.0f, 0.75f, 1.0f, 1.0f, 0.875f, 1.0f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, + 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.75f, 0.75f, 1.0f, 0.75f, 0.5f, 0.75f, 0.25f, + 0.75f, 0.0f, 0.625f, 0.75f, 0.625f, 1.0f, 0.625f, 0.5f, 0.625f, 0.25f, 0.625f, 0.0f, 0.5f, 0.75f, 0.5f, + 1.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.375f, 0.75f, 0.5f, 0.75f, 0.5f, 1.0f, 0.375f, 1.0f, + 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.75f, 0.25f, + 1.0f, 0.25f, 0.5f, 0.25f, 0.25f, 0.25f, 0.0f, 0.125f, 0.75f, 0.125f, 1.0f, 0.125f, 0.5f, 0.125f, 0.25f, + 0.125f, 0.0f, 0.0f, 0.75f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 1.5f, 0.0f, 1.25f, 0.0f, + 1.0f, 0.0f, 0.75f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 0.0f, 1.75f, 0.0f, 1.5f, 0.0f, 1.25f, 0.0f, + 1.0f, 0.0f, 0.75f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f }; + _meshData.setTextureBuffer(BufferUtils.createFloatBuffer(texs), 0); + } + + private void setIndexData() { + final short[] indices = { 0, 1, 2, 0, 2, 3, 4, 5, 1, 4, 1, 0, 6, 7, 5, 6, 5, 4, 8, 9, 7, 8, 7, 6, 10, 0, 3, 10, + 3, 11, 12, 4, 0, 12, 0, 10, 13, 6, 4, 13, 4, 12, 14, 8, 6, 14, 6, 13, 15, 10, 11, 15, 11, 16, 17, 12, + 10, 17, 10, 15, 18, 13, 12, 18, 12, 17, 19, 14, 13, 19, 13, 18, 20, 15, 16, 20, 16, 21, 22, 17, 15, 22, + 15, 20, 23, 18, 17, 23, 17, 22, 24, 19, 18, 24, 18, 23, 25, 26, 27, 25, 27, 28, 29, 30, 26, 29, 26, 25, + 31, 32, 30, 31, 30, 29, 33, 34, 32, 33, 32, 31, 35, 25, 28, 35, 28, 36, 37, 29, 25, 37, 25, 35, 38, 31, + 29, 38, 29, 37, 39, 33, 31, 39, 31, 38, 40, 35, 36, 40, 36, 41, 42, 37, 35, 42, 35, 40, 43, 38, 37, 43, + 37, 42, 44, 39, 38, 44, 38, 43, 45, 40, 41, 45, 41, 46, 47, 42, 40, 47, 40, 45, 48, 43, 42, 48, 42, 47, + 49, 44, 43, 49, 43, 48, 50, 51, 52, 50, 52, 53, 54, 55, 51, 54, 51, 50, 56, 57, 55, 56, 55, 54, 58, 59, + 57, 58, 57, 56, 60, 50, 53, 60, 53, 61, 62, 54, 50, 62, 50, 60, 63, 56, 54, 63, 54, 62, 64, 58, 56, 64, + 56, 63, 65, 60, 61, 65, 61, 66, 67, 62, 60, 67, 60, 65, 68, 63, 62, 68, 62, 67, 69, 64, 63, 69, 63, 68, + 70, 65, 66, 70, 66, 71, 72, 67, 65, 72, 65, 70, 73, 68, 67, 73, 67, 72, 74, 69, 68, 74, 68, 73, 75, 76, + 77, 75, 77, 78, 79, 80, 76, 79, 76, 75, 81, 82, 80, 81, 80, 79, 83, 84, 82, 83, 82, 81, 85, 75, 78, 85, + 78, 86, 87, 79, 75, 87, 75, 85, 88, 81, 79, 88, 79, 87, 89, 83, 81, 89, 81, 88, 90, 85, 86, 90, 86, 91, + 92, 87, 85, 92, 85, 90, 93, 88, 87, 93, 87, 92, 94, 89, 88, 94, 88, 93, 95, 90, 91, 95, 91, 96, 97, 92, + 90, 97, 90, 95, 98, 93, 92, 98, 92, 97, 99, 94, 93, 99, 93, 98, 100, 101, 102, 100, 102, 103, 104, 105, + 101, 104, 101, 100, 106, 107, 105, 106, 105, 104, 108, 109, 107, 108, 107, 106, 110, 100, 103, 110, + 103, 111, 112, 104, 100, 112, 100, 110, 113, 106, 104, 113, 104, 112, 114, 108, 106, 114, 106, 113, + 115, 110, 111, 115, 111, 116, 117, 112, 110, 117, 110, 115, 118, 113, 112, 118, 112, 117, 119, 114, + 113, 119, 113, 118, 120, 115, 116, 120, 116, 121, 122, 117, 115, 122, 115, 120, 123, 118, 117, 123, + 117, 122, 124, 119, 118, 124, 118, 123, 125, 126, 127, 125, 127, 128, 129, 130, 126, 129, 126, 125, + 131, 132, 130, 131, 130, 129, 133, 134, 132, 133, 132, 131, 135, 125, 128, 135, 128, 136, 137, 129, + 125, 137, 125, 135, 138, 131, 129, 138, 129, 137, 139, 133, 131, 139, 131, 138, 140, 135, 136, 140, + 136, 141, 142, 137, 135, 142, 135, 140, 143, 138, 137, 143, 137, 142, 144, 139, 138, 144, 138, 143, + 145, 140, 141, 145, 141, 146, 147, 142, 140, 147, 140, 145, 148, 143, 142, 148, 142, 147, 149, 144, + 143, 149, 143, 148, 150, 151, 152, 150, 152, 153, 154, 155, 151, 154, 151, 150, 156, 157, 155, 156, + 155, 154, 158, 159, 157, 158, 157, 156, 160, 150, 153, 160, 153, 161, 162, 154, 150, 162, 150, 160, + 163, 156, 154, 163, 154, 162, 164, 158, 156, 164, 156, 163, 165, 160, 161, 165, 161, 166, 167, 162, + 160, 167, 160, 165, 168, 163, 162, 168, 162, 167, 169, 164, 163, 169, 163, 168, 170, 165, 166, 170, + 166, 171, 172, 167, 165, 172, 165, 170, 173, 168, 167, 173, 167, 172, 174, 169, 168, 174, 168, 173, + 175, 176, 177, 175, 177, 178, 179, 180, 176, 179, 176, 175, 181, 182, 180, 181, 180, 179, 183, 184, + 182, 183, 182, 181, 185, 175, 178, 185, 178, 186, 187, 179, 175, 187, 175, 185, 188, 181, 179, 188, + 179, 187, 189, 183, 181, 189, 181, 188, 190, 185, 186, 190, 186, 191, 192, 187, 185, 192, 185, 190, + 193, 188, 187, 193, 187, 192, 194, 189, 188, 194, 188, 193, 195, 190, 191, 195, 191, 196, 197, 192, + 190, 197, 190, 195, 198, 193, 192, 198, 192, 197, 199, 194, 193, 199, 193, 198, 200, 201, 202, 200, + 202, 203, 204, 205, 201, 204, 201, 200, 206, 207, 205, 206, 205, 204, 208, 209, 207, 208, 207, 206, + 210, 200, 203, 210, 203, 211, 212, 204, 200, 212, 200, 210, 213, 206, 204, 213, 204, 212, 214, 208, + 206, 214, 206, 213, 215, 210, 211, 215, 211, 216, 217, 212, 210, 217, 210, 215, 218, 213, 212, 218, + 212, 217, 219, 214, 213, 219, 213, 218, 220, 215, 216, 220, 216, 221, 222, 217, 215, 222, 215, 220, + 223, 218, 217, 223, 217, 222, 224, 219, 218, 224, 218, 223, 225, 226, 227, 225, 227, 228, 229, 230, + 226, 229, 226, 225, 231, 232, 230, 231, 230, 229, 233, 234, 232, 233, 232, 231, 235, 225, 228, 235, + 228, 236, 237, 229, 225, 237, 225, 235, 238, 231, 229, 238, 229, 237, 239, 233, 231, 239, 231, 238, + 240, 235, 236, 240, 236, 241, 242, 237, 235, 242, 235, 240, 243, 238, 237, 243, 237, 242, 244, 239, + 238, 244, 238, 243, 245, 240, 241, 245, 241, 246, 247, 242, 240, 247, 240, 245, 248, 243, 242, 248, + 242, 247, 249, 244, 243, 249, 243, 248, 250, 251, 252, 250, 252, 253, 254, 255, 251, 254, 251, 250, + 256, 257, 255, 256, 255, 254, 258, 259, 257, 258, 257, 256, 260, 250, 253, 260, 253, 261, 262, 254, + 250, 262, 250, 260, 263, 256, 254, 263, 254, 262, 264, 258, 256, 264, 256, 263, 265, 260, 261, 265, + 261, 266, 267, 262, 260, 267, 260, 265, 268, 263, 262, 268, 262, 267, 269, 264, 263, 269, 263, 268, + 270, 265, 266, 270, 266, 271, 272, 267, 265, 272, 265, 270, 273, 268, 267, 273, 267, 272, 274, 269, + 268, 274, 268, 273, 275, 276, 277, 275, 277, 278, 279, 280, 276, 279, 276, 275, 281, 282, 280, 281, + 280, 279, 283, 284, 282, 283, 282, 281, 285, 275, 278, 285, 278, 286, 287, 279, 275, 287, 275, 285, + 288, 281, 279, 288, 279, 287, 289, 283, 281, 289, 281, 288, 290, 285, 286, 290, 286, 291, 292, 287, + 285, 292, 285, 290, 293, 288, 287, 293, 287, 292, 294, 289, 288, 294, 288, 293, 295, 290, 291, 295, + 291, 296, 297, 292, 290, 297, 290, 295, 298, 293, 292, 298, 292, 297, 299, 294, 293, 299, 293, 298, + 300, 301, 302, 300, 302, 303, 304, 305, 301, 304, 301, 300, 306, 307, 305, 306, 305, 304, 308, 309, + 307, 308, 307, 306, 310, 300, 303, 310, 303, 311, 312, 304, 300, 312, 300, 310, 313, 306, 304, 313, + 304, 312, 314, 310, 311, 314, 311, 315, 316, 312, 310, 316, 310, 314, 317, 313, 312, 317, 312, 316, + 318, 314, 315, 318, 315, 319, 320, 316, 314, 320, 314, 318, 321, 317, 316, 321, 316, 320, 322, 323, + 324, 322, 324, 325, 326, 327, 323, 326, 323, 322, 328, 329, 327, 328, 327, 326, 330, 322, 325, 330, + 325, 331, 332, 326, 322, 332, 322, 330, 333, 328, 326, 333, 326, 332, 334, 330, 331, 334, 331, 335, + 336, 332, 330, 336, 330, 334, 337, 333, 332, 337, 332, 336, 338, 334, 335, 338, 335, 339, 340, 336, + 334, 340, 334, 338, 341, 337, 336, 341, 336, 340, 342, 343, 344, 342, 344, 345, 346, 347, 343, 346, + 343, 342, 348, 349, 347, 348, 347, 346, 350, 342, 345, 350, 345, 351, 352, 346, 342, 352, 342, 350, + 353, 348, 346, 353, 346, 352, 354, 350, 351, 354, 351, 355, 356, 352, 350, 356, 350, 354, 357, 353, + 352, 357, 352, 356, 358, 354, 355, 358, 355, 359, 360, 356, 354, 360, 354, 358, 361, 357, 356, 361, + 356, 360, 362, 363, 364, 362, 364, 365, 366, 367, 363, 366, 363, 362, 368, 369, 367, 368, 367, 366, + 370, 362, 365, 370, 365, 371, 372, 366, 362, 372, 362, 370, 373, 368, 366, 373, 366, 372, 374, 370, + 371, 374, 371, 375, 376, 372, 370, 376, 370, 374, 309, 373, 372, 309, 372, 376, 377, 374, 375, 377, + 375, 378, 379, 376, 374, 379, 374, 377, 380, 309, 376, 380, 376, 379, 381, 382, 383, 381, 383, 384, + 385, 386, 382, 385, 382, 381, 387, 388, 386, 387, 386, 385, 389, 390, 388, 389, 388, 387, 391, 381, + 384, 391, 384, 392, 393, 385, 381, 393, 381, 391, 394, 387, 385, 394, 385, 393, 395, 389, 387, 395, + 387, 394, 396, 391, 392, 396, 392, 397, 398, 393, 391, 398, 391, 396, 399, 394, 393, 399, 393, 398, + 400, 395, 394, 400, 394, 399, 401, 396, 397, 401, 397, 402, 403, 398, 396, 403, 396, 401, 404, 399, + 398, 404, 398, 403, 405, 400, 399, 405, 399, 404, 406, 407, 408, 406, 408, 409, 410, 411, 407, 410, + 407, 406, 412, 413, 411, 412, 411, 410, 414, 415, 413, 414, 413, 412, 416, 406, 409, 416, 409, 417, + 418, 410, 406, 418, 406, 416, 419, 412, 410, 419, 410, 418, 420, 414, 412, 420, 412, 419, 421, 416, + 417, 421, 417, 422, 423, 418, 416, 423, 416, 421, 424, 419, 418, 424, 418, 423, 425, 420, 419, 425, + 419, 424, 426, 421, 422, 426, 422, 427, 428, 423, 421, 428, 421, 426, 429, 424, 423, 429, 423, 428, + 430, 425, 424, 430, 424, 429, 431, 432, 433, 431, 433, 434, 435, 436, 432, 435, 432, 431, 437, 438, + 436, 437, 436, 435, 439, 440, 438, 439, 438, 437, 441, 431, 434, 441, 434, 442, 443, 435, 431, 443, + 431, 441, 444, 437, 435, 444, 435, 443, 445, 439, 437, 445, 437, 444, 446, 441, 442, 446, 442, 447, + 448, 443, 441, 448, 441, 446, 449, 444, 443, 449, 443, 448, 450, 445, 444, 450, 444, 449, 451, 446, + 447, 451, 447, 452, 453, 448, 446, 453, 446, 451, 454, 449, 448, 454, 448, 453, 455, 450, 449, 455, + 449, 454, 456, 457, 458, 456, 458, 459, 460, 461, 457, 460, 457, 456, 462, 463, 461, 462, 461, 460, + 464, 465, 463, 464, 463, 462, 466, 456, 459, 466, 459, 467, 468, 460, 456, 468, 456, 466, 469, 462, + 460, 469, 460, 468, 470, 464, 462, 470, 462, 469, 471, 466, 467, 471, 467, 472, 473, 468, 466, 473, + 466, 471, 474, 469, 468, 474, 468, 473, 475, 470, 469, 475, 469, 474, 476, 471, 472, 476, 472, 477, + 478, 473, 471, 478, 471, 476, 479, 474, 473, 479, 473, 478, 480, 475, 474, 480, 474, 479, 481, 482, + 483, 481, 483, 484, 485, 486, 482, 485, 482, 481, 487, 488, 486, 487, 486, 485, 489, 490, 488, 489, + 488, 487, 491, 481, 484, 491, 484, 492, 493, 485, 481, 493, 481, 491, 494, 487, 485, 494, 485, 493, + 495, 489, 487, 495, 487, 494, 496, 491, 492, 496, 492, 497, 498, 493, 491, 498, 491, 496, 499, 494, + 493, 499, 493, 498, 500, 495, 494, 500, 494, 499, 501, 496, 497, 501, 497, 502, 503, 498, 496, 503, + 496, 501, 504, 499, 498, 504, 498, 503, 505, 500, 499, 505, 499, 504, 506, 507, 508, 506, 508, 509, + 510, 511, 507, 510, 507, 506, 512, 513, 511, 512, 511, 510, 514, 515, 513, 514, 513, 512, 516, 506, + 509, 516, 509, 517, 518, 510, 506, 518, 506, 516, 519, 512, 510, 519, 510, 518, 520, 514, 512, 520, + 512, 519, 521, 516, 517, 521, 517, 522, 523, 518, 516, 523, 516, 521, 524, 519, 518, 524, 518, 523, + 525, 520, 519, 525, 519, 524, 526, 521, 522, 526, 522, 527, 528, 523, 521, 528, 521, 526, 529, 524, + 523, 529, 523, 528, 530, 525, 524, 530, 524, 529, 531, 532, 533, 531, 533, 534, 535, 536, 532, 535, + 532, 531, 537, 538, 536, 537, 536, 535, 539, 540, 538, 539, 538, 537, 541, 531, 534, 541, 534, 542, + 543, 535, 531, 543, 531, 541, 544, 537, 535, 544, 535, 543, 545, 539, 537, 545, 537, 544, 546, 541, + 542, 546, 542, 547, 548, 543, 541, 548, 541, 546, 549, 544, 543, 549, 543, 548, 550, 545, 544, 550, + 544, 549, 551, 546, 547, 551, 547, 552, 553, 548, 546, 553, 546, 551, 554, 549, 548, 554, 548, 553, + 555, 550, 549, 555, 549, 554, 556, 557, 558, 556, 558, 559, 560, 561, 557, 560, 557, 556, 562, 563, + 561, 562, 561, 560, 564, 565, 563, 564, 563, 562, 566, 556, 559, 566, 559, 567, 568, 560, 556, 568, + 556, 566, 569, 562, 560, 569, 560, 568, 570, 564, 562, 570, 562, 569, 571, 566, 567, 571, 567, 572, + 573, 568, 566, 573, 566, 571, 574, 569, 568, 574, 568, 573, 575, 570, 569, 575, 569, 574, 576, 571, + 572, 576, 572, 577, 578, 573, 571, 578, 571, 576, 579, 574, 573, 579, 573, 578, 580, 575, 574, 580, + 574, 579, 581, 582, 583, 581, 583, 584, 585, 586, 584, 585, 584, 583, 587, 588, 586, 587, 586, 585, + 589, 590, 588, 589, 588, 587, 591, 585, 583, 591, 583, 582, 592, 587, 585, 592, 585, 591, 593, 589, + 587, 593, 587, 592, 594, 595, 596, 594, 596, 582, 597, 591, 582, 597, 582, 596, 598, 592, 591, 598, + 591, 597, 599, 593, 592, 599, 592, 598, 600, 597, 596, 600, 596, 595, 601, 598, 597, 601, 597, 600, + 602, 599, 598, 602, 598, 601, 603, 604, 605, 603, 605, 606, 607, 608, 606, 607, 606, 605, 609, 610, + 608, 609, 608, 607, 611, 612, 610, 611, 610, 609, 613, 607, 605, 613, 605, 604, 614, 609, 607, 614, + 607, 613, 615, 611, 609, 615, 609, 614, 616, 617, 618, 616, 618, 604, 619, 613, 604, 619, 604, 618, + 620, 614, 613, 620, 613, 619, 621, 615, 614, 621, 614, 620, 622, 619, 618, 622, 618, 617, 623, 620, + 619, 623, 619, 622, 624, 621, 620, 624, 620, 623, 625, 626, 627, 625, 627, 628, 629, 630, 628, 629, + 628, 627, 631, 632, 630, 631, 630, 629, 633, 634, 632, 633, 632, 631, 635, 629, 627, 635, 627, 626, + 636, 631, 629, 636, 629, 635, 637, 633, 631, 637, 631, 636, 638, 639, 640, 638, 640, 626, 641, 635, + 626, 641, 626, 640, 642, 636, 635, 642, 635, 641, 643, 637, 636, 643, 636, 642, 644, 641, 640, 644, + 640, 639, 645, 642, 641, 645, 641, 644, 646, 643, 642, 646, 642, 645, 647, 648, 649, 647, 649, 650, + 651, 652, 650, 651, 650, 649, 653, 654, 652, 653, 652, 651, 655, 656, 654, 655, 654, 653, 657, 651, + 649, 657, 649, 648, 658, 653, 651, 658, 651, 657, 659, 655, 653, 659, 653, 658, 660, 661, 662, 660, + 662, 648, 663, 657, 648, 663, 648, 662, 664, 658, 657, 664, 657, 663, 665, 659, 658, 665, 658, 664, + 666, 663, 662, 666, 662, 661, 667, 664, 663, 667, 663, 666, 668, 665, 664, 668, 664, 667, 669, 670, + 671, 669, 671, 672, 673, 674, 670, 673, 670, 669, 675, 676, 674, 675, 674, 673, 677, 678, 676, 677, + 676, 675, 679, 669, 672, 679, 672, 680, 681, 673, 669, 681, 669, 679, 682, 675, 673, 682, 673, 681, + 683, 677, 675, 683, 675, 682, 684, 679, 680, 684, 680, 685, 686, 681, 679, 686, 679, 684, 687, 682, + 681, 687, 681, 686, 688, 683, 682, 688, 682, 687, 689, 684, 685, 689, 685, 690, 691, 686, 684, 691, + 684, 689, 692, 687, 686, 692, 686, 691, 693, 688, 687, 693, 687, 692, 694, 695, 696, 694, 696, 697, + 698, 699, 695, 698, 695, 694, 700, 701, 699, 700, 699, 698, 702, 703, 701, 702, 701, 700, 704, 694, + 697, 704, 697, 705, 706, 698, 694, 706, 694, 704, 707, 700, 698, 707, 698, 706, 708, 702, 700, 708, + 700, 707, 709, 704, 705, 709, 705, 710, 711, 706, 704, 711, 704, 709, 712, 707, 706, 712, 706, 711, + 713, 708, 707, 713, 707, 712, 714, 709, 710, 714, 710, 715, 716, 711, 709, 716, 709, 714, 717, 712, + 711, 717, 711, 716, 718, 713, 712, 718, 712, 717, 719, 720, 721, 719, 721, 722, 723, 724, 720, 723, + 720, 719, 725, 726, 724, 725, 724, 723, 727, 728, 726, 727, 726, 725, 729, 719, 722, 729, 722, 730, + 731, 723, 719, 731, 719, 729, 732, 725, 723, 732, 723, 731, 733, 727, 725, 733, 725, 732, 734, 729, + 730, 734, 730, 735, 736, 731, 729, 736, 729, 734, 737, 732, 731, 737, 731, 736, 738, 733, 732, 738, + 732, 737, 739, 734, 735, 739, 735, 740, 741, 736, 734, 741, 734, 739, 742, 737, 736, 742, 736, 741, + 743, 738, 737, 743, 737, 742, 744, 745, 746, 744, 746, 747, 748, 749, 745, 748, 745, 744, 750, 751, + 749, 750, 749, 748, 752, 753, 751, 752, 751, 750, 754, 744, 747, 754, 747, 755, 756, 748, 744, 756, + 744, 754, 757, 750, 748, 757, 748, 756, 758, 752, 750, 758, 750, 757, 759, 754, 755, 759, 755, 760, + 761, 756, 754, 761, 754, 759, 762, 757, 756, 762, 756, 761, 763, 758, 757, 763, 757, 762, 764, 759, + 760, 764, 760, 765, 766, 761, 759, 766, 759, 764, 767, 762, 761, 767, 761, 766, 768, 763, 762, 768, + 762, 767, 306, 313, 769, 313, 317, 770, 317, 321, 771, 329, 328, 772, 328, 333, 773, 333, 337, 774, + 337, 341, 775, 349, 348, 776, 348, 353, 777, 353, 357, 778, 357, 361, 779, 369, 368, 780, 368, 373, + 781, 373, 309, 782 }; + _meshData.setIndexBuffer(BufferUtils.createShortBuffer(indices)); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java new file mode 100644 index 0000000..6f36c28 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class Torus extends Mesh { + + protected int _circleSamples; + + protected int _radialSamples; + + protected double _tubeRadius; + + protected double _centerRadius; + + protected boolean _viewInside; + + /** + * private constructor for Savable use only. + */ + public Torus() { + + } + + /** + * Constructs a new Torus. Center is the origin, but the Torus may be transformed. + * + * @param name + * The name of the Torus. + * @param circleSamples + * The number of samples along the circles. + * @param radialSamples + * The number of samples along the radial. + * @param tubeRadius + * the radius of the torus tube. + * @param centerRadius + * The distance from the center of the torus hole to the center of the torus tube. + */ + public Torus(final String name, final int circleSamples, final int radialSamples, final double tubeRadius, + final double centerRadius) { + + super(name); + _circleSamples = circleSamples; + _radialSamples = radialSamples; + _tubeRadius = tubeRadius; + _centerRadius = centerRadius; + + setGeometryData(); + setIndexData(); + + } + + private void setGeometryData() { + // allocate vertices + final int verts = ((_circleSamples + 1) * (_radialSamples + 1)); + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate normals if requested + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate texture coordinates + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0); + + // generate geometry + final double inverseCircleSamples = 1.0 / _circleSamples; + final double inverseRadialSamples = 1.0 / _radialSamples; + int i = 0; + // generate the cylinder itself + final Vector3 radialAxis = new Vector3(), torusMiddle = new Vector3(), tempNormal = new Vector3(); + for (int circleCount = 0; circleCount < _circleSamples; circleCount++) { + // compute center point on torus circle at specified angle + final double circleFraction = circleCount * inverseCircleSamples; + final double theta = MathUtils.TWO_PI * circleFraction; + final double cosTheta = MathUtils.cos(theta); + final double sinTheta = MathUtils.sin(theta); + radialAxis.set(cosTheta, sinTheta, 0); + radialAxis.multiply(_centerRadius, torusMiddle); + + // compute slice vertices with duplication at end point + final int iSave = i; + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final double radialFraction = radialCount * inverseRadialSamples; + // in [0,1) + final double phi = MathUtils.TWO_PI * radialFraction; + final double cosPhi = MathUtils.cos(phi); + final double sinPhi = MathUtils.sin(phi); + tempNormal.set(radialAxis).multiplyLocal(cosPhi); + tempNormal.setZ(tempNormal.getZ() + sinPhi); + tempNormal.normalizeLocal(); + if (!_viewInside) { + _meshData.getNormalBuffer().put((float) tempNormal.getX()).put((float) tempNormal.getY()) + .put((float) tempNormal.getZ()); + } else { + _meshData.getNormalBuffer().put((float) -tempNormal.getX()).put((float) -tempNormal.getY()) + .put((float) -tempNormal.getZ()); + } + + tempNormal.multiplyLocal(_tubeRadius).addLocal(torusMiddle); + _meshData.getVertexBuffer().put((float) tempNormal.getX()).put((float) tempNormal.getY()) + .put((float) tempNormal.getZ()); + + _meshData.getTextureCoords(0).getBuffer().put((float) radialFraction).put((float) circleFraction); + i++; + } + + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i); + BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iSave, i); + + _meshData.getTextureCoords(0).getBuffer().put(1.0f).put((float) circleFraction); + + i++; + } + + // duplicate the cylinder ends to form a torus + for (int iR = 0; iR <= _radialSamples; iR++, i++) { + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iR, i); + BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iR, i); + BufferUtils.copyInternalVector2(_meshData.getTextureCoords(0).getBuffer(), iR, i); + _meshData.getTextureCoords(0).getBuffer().put(i * 2 + 1, 1.0f); + } + } + + private void setIndexData() { + // allocate connectivity + final int verts = ((_circleSamples + 1) * (_radialSamples + 1)); + final int tris = (2 * _circleSamples * _radialSamples); + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + int i; + // generate connectivity + int connectionStart = 0; + for (int circleCount = 0; circleCount < _circleSamples; circleCount++) { + int i0 = connectionStart; + int i1 = i0 + 1; + connectionStart += _radialSamples + 1; + int i2 = connectionStart; + int i3 = i2 + 1; + for (i = 0; i < _radialSamples; i++) { + if (!_viewInside) { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i2); + _meshData.getIndices().put(i1); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(i2++); + _meshData.getIndices().put(i3++); + } else { + _meshData.getIndices().put(i0++); + _meshData.getIndices().put(i1); + _meshData.getIndices().put(i2); + _meshData.getIndices().put(i1++); + _meshData.getIndices().put(i3++); + _meshData.getIndices().put(i2++); + } + } + } + } + + /** + * + * @return true if the normals are inverted to point into the torus so that the face is oriented for a viewer inside + * the torus. false (the default) for exterior viewing. + */ + public boolean isViewFromInside() { + return _viewInside; + } + + /** + * + * @param viewInside + * if true, the normals are inverted to point into the torus so that the face is oriented for a viewer + * inside the torus. Default is false (for outside viewing) + */ + public void setViewFromInside(final boolean viewInside) { + if (viewInside != _viewInside) { + _viewInside = viewInside; + setGeometryData(); + setIndexData(); + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_circleSamples, "circleSamples", 0); + capsule.write(_radialSamples, "radialSamples", 0); + capsule.write(_tubeRadius, "tubeRadius", 0); + capsule.write(_centerRadius, "centerRadius", 0); + capsule.write(_viewInside, "viewInside", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _circleSamples = capsule.readInt("circleSamples", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + _tubeRadius = capsule.readDouble("tubeRadius", 0); + _centerRadius = capsule.readDouble("centerRadius", 0); + _viewInside = capsule.readBoolean("viewInside", false); + } + +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java new file mode 100644 index 0000000..5b93988 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.shape; + +import java.io.IOException; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +public class Tube extends Mesh { + + private static final long serialVersionUID = 1L; + + public static long getSerialVersionUID() { + return serialVersionUID; + } + + private int _axisSamples; + private int _radialSamples; + + private double _outerRadius; + private double _innerRadius; + private double _height; + + protected boolean _viewInside; + + /** + * Constructor meant for Savable use only. + */ + public Tube() {} + + public Tube(final String name, final double outerRadius, final double innerRadius, final double height, + final int axisSamples, final int radialSamples) { + super(name); + _outerRadius = outerRadius; + _innerRadius = innerRadius; + _height = height; + _axisSamples = axisSamples; + _radialSamples = radialSamples; + allocateVertices(); + } + + public Tube(final String name, final double outerRadius, final double innerRadius, final double height) { + this(name, outerRadius, innerRadius, height, 2, 20); + } + + private void allocateVertices() { + final int verts = (2 * (_axisSamples + 1) * (_radialSamples + 1) + _radialSamples * 4); + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), verts)); + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts)); + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(_meshData.getTextureBuffer(0), verts), 0); + + final int tris = (4 * _radialSamples * (1 + _axisSamples)); + if (_meshData.getIndices() == null || _meshData.getIndices().getBufferLimit() != 3 * tris) { + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + } + + setGeometryData(); + setIndexData(); + } + + public int getAxisSamples() { + return _axisSamples; + } + + public void setAxisSamples(final int axisSamples) { + _axisSamples = axisSamples; + allocateVertices(); + } + + public int getRadialSamples() { + return _radialSamples; + } + + public void setRadialSamples(final int radialSamples) { + _radialSamples = radialSamples; + allocateVertices(); + } + + public double getOuterRadius() { + return _outerRadius; + } + + public void setOuterRadius(final double outerRadius) { + _outerRadius = outerRadius; + allocateVertices(); + } + + public double getInnerRadius() { + return _innerRadius; + } + + public void setInnerRadius(final double innerRadius) { + _innerRadius = innerRadius; + allocateVertices(); + } + + public double getHeight() { + return _height; + } + + public void setHeight(final double height) { + _height = height; + allocateVertices(); + } + + private void setGeometryData() { + _meshData.getVertexBuffer().rewind(); + _meshData.getNormalBuffer().rewind(); + _meshData.getTextureCoords(0).getBuffer().rewind(); + + final double inverseRadial = 1.0 / _radialSamples; + final double axisStep = _height / _axisSamples; + final double axisTextureStep = 1.0 / _axisSamples; + final double halfHeight = 0.5 * _height; + final double innerOuterRatio = _innerRadius / _outerRadius; + final double[] sin = new double[_radialSamples]; + final double[] cos = new double[_radialSamples]; + + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final double angle = MathUtils.TWO_PI * inverseRadial * radialCount; + cos[radialCount] = MathUtils.cos(angle); + sin[radialCount] = MathUtils.sin(angle); + } + + // outer cylinder + for (int radialCount = 0; radialCount < _radialSamples + 1; radialCount++) { + for (int axisCount = 0; axisCount < _axisSamples + 1; axisCount++) { + _meshData.getVertexBuffer().put((float) (cos[radialCount % _radialSamples] * _outerRadius)).put( + (float) (axisStep * axisCount - halfHeight)).put( + (float) (sin[radialCount % _radialSamples] * _outerRadius)); + if (_viewInside) { + _meshData.getNormalBuffer().put((float) cos[radialCount % _radialSamples]).put(0).put( + (float) sin[radialCount % _radialSamples]); + } else { + _meshData.getNormalBuffer().put((float) -cos[radialCount % _radialSamples]).put(0).put( + (float) -sin[radialCount % _radialSamples]); + } + _meshData.getTextureCoords(0).getBuffer().put((float) (radialCount * inverseRadial)).put( + (float) (axisTextureStep * axisCount)); + } + } + // inner cylinder + for (int radialCount = 0; radialCount < _radialSamples + 1; radialCount++) { + for (int axisCount = 0; axisCount < _axisSamples + 1; axisCount++) { + _meshData.getVertexBuffer().put((float) (cos[radialCount % _radialSamples] * _innerRadius)).put( + (float) (axisStep * axisCount - halfHeight)).put( + (float) (sin[radialCount % _radialSamples] * _innerRadius)); + if (_viewInside) { + _meshData.getNormalBuffer().put((float) -cos[radialCount % _radialSamples]).put(0).put( + (float) -sin[radialCount % _radialSamples]); + } else { + _meshData.getNormalBuffer().put((float) cos[radialCount % _radialSamples]).put(0).put( + (float) sin[radialCount % _radialSamples]); + } + _meshData.getTextureCoords(0).getBuffer().put((float) (radialCount * inverseRadial)).put( + (float) (axisTextureStep * axisCount)); + } + } + // bottom edge + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + _meshData.getVertexBuffer().put((float) (cos[radialCount] * _outerRadius)).put((float) -halfHeight).put( + (float) (sin[radialCount] * _outerRadius)); + _meshData.getVertexBuffer().put((float) (cos[radialCount] * _innerRadius)).put((float) -halfHeight).put( + (float) (sin[radialCount] * _innerRadius)); + if (_viewInside) { + _meshData.getNormalBuffer().put(0).put(1).put(0); + _meshData.getNormalBuffer().put(0).put(1).put(0); + } else { + _meshData.getNormalBuffer().put(0).put(-1).put(0); + _meshData.getNormalBuffer().put(0).put(-1).put(0); + } + _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + 0.5 * cos[radialCount])).put( + (float) (0.5 + 0.5 * sin[radialCount])); + _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + innerOuterRatio * 0.5 * cos[radialCount])) + .put((float) (0.5 + innerOuterRatio * 0.5 * sin[radialCount])); + } + // top edge + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + _meshData.getVertexBuffer().put((float) (cos[radialCount] * _outerRadius)).put((float) halfHeight).put( + (float) (sin[radialCount] * _outerRadius)); + _meshData.getVertexBuffer().put((float) (cos[radialCount] * _innerRadius)).put((float) halfHeight).put( + (float) (sin[radialCount] * _innerRadius)); + if (_viewInside) { + _meshData.getNormalBuffer().put(0).put(-1).put(0); + _meshData.getNormalBuffer().put(0).put(-1).put(0); + } else { + _meshData.getNormalBuffer().put(0).put(1).put(0); + _meshData.getNormalBuffer().put(0).put(1).put(0); + } + _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + 0.5 * cos[radialCount])).put( + (float) (0.5 + 0.5 * sin[radialCount])); + _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + innerOuterRatio * 0.5 * cos[radialCount])) + .put((float) (0.5 + innerOuterRatio * 0.5 * sin[radialCount])); + } + + } + + private void setIndexData() { + _meshData.getIndexBuffer().rewind(); + + final int outerCylinder = (_axisSamples + 1) * (_radialSamples + 1); + final int bottomEdge = 2 * outerCylinder; + final int topEdge = bottomEdge + 2 * _radialSamples; + // inner cylinder + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + for (int axisCount = 0; axisCount < _axisSamples; axisCount++) { + final int index0 = axisCount + (_axisSamples + 1) * radialCount; + final int index1 = index0 + 1; + final int index2 = index0 + (_axisSamples + 1); + final int index3 = index2 + 1; + if (_viewInside) { + _meshData.getIndices().put(index0).put(index1).put(index2); + _meshData.getIndices().put(index1).put(index3).put(index2); + } else { + _meshData.getIndices().put(index0).put(index2).put(index1); + _meshData.getIndices().put(index1).put(index2).put(index3); + } + } + } + + // outer cylinder + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + for (int axisCount = 0; axisCount < _axisSamples; axisCount++) { + final int index0 = outerCylinder + axisCount + (_axisSamples + 1) * radialCount; + final int index1 = index0 + 1; + final int index2 = index0 + (_axisSamples + 1); + final int index3 = index2 + 1; + if (_viewInside) { + _meshData.getIndices().put(index0).put(index2).put(index1); + _meshData.getIndices().put(index1).put(index2).put(index3); + } else { + _meshData.getIndices().put(index0).put(index1).put(index2); + _meshData.getIndices().put(index1).put(index3).put(index2); + } + } + } + + // bottom edge + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final int index0 = bottomEdge + 2 * radialCount; + final int index1 = index0 + 1; + final int index2 = bottomEdge + 2 * ((radialCount + 1) % _radialSamples); + final int index3 = index2 + 1; + if (_viewInside) { + _meshData.getIndices().put(index0).put(index2).put(index1); + _meshData.getIndices().put(index1).put(index2).put(index3); + } else { + _meshData.getIndices().put(index0).put(index1).put(index2); + _meshData.getIndices().put(index1).put(index3).put(index2); + } + } + + // top edge + for (int radialCount = 0; radialCount < _radialSamples; radialCount++) { + final int index0 = topEdge + 2 * radialCount; + final int index1 = index0 + 1; + final int index2 = topEdge + 2 * ((radialCount + 1) % _radialSamples); + final int index3 = index2 + 1; + if (_viewInside) { + _meshData.getIndices().put(index0).put(index1).put(index2); + _meshData.getIndices().put(index1).put(index3).put(index2); + } else { + _meshData.getIndices().put(index0).put(index2).put(index1); + _meshData.getIndices().put(index1).put(index2).put(index3); + } + } + } + + /** + * + * @return true if the normals are inverted to point into the torus so that the face is oriented for a viewer inside + * the torus. false (the default) for exterior viewing. + */ + public boolean isViewFromInside() { + return _viewInside; + } + + /** + * + * @param viewInside + * if true, the normals are inverted to point into the torus so that the face is oriented for a viewer + * inside the torus. Default is false (for outside viewing) + */ + public void setViewFromInside(final boolean viewInside) { + if (viewInside != _viewInside) { + _viewInside = viewInside; + setGeometryData(); + setIndexData(); + } + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(getAxisSamples(), "axisSamples", 0); + capsule.write(getRadialSamples(), "radialSamples", 0); + capsule.write(getOuterRadius(), "outerRadius", 0); + capsule.write(getInnerRadius(), "innerRadius", 0); + capsule.write(getHeight(), "height", 0); + capsule.write(_viewInside, "viewInside", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + setAxisSamples(capsule.readInt("axisSamples", 0)); + setRadialSamples(capsule.readInt("radialSamples", 0)); + setOuterRadius(capsule.readDouble("outerRadius", 0)); + setInnerRadius(capsule.readDouble("innerRadius", 0)); + setHeight(capsule.readDouble("height", 0)); + _viewInside = capsule.readBoolean("viewInside", false); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java new file mode 100644 index 0000000..6d8ce8d --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java @@ -0,0 +1,40 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+
+public class DeleteVBOsVisitor implements Visitor {
+ final Renderer _deleter;
+
+ public DeleteVBOsVisitor(final Renderer deleter) {
+ _deleter = deleter;
+ }
+
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ final Mesh mesh = (Mesh) spatial;
+ _deleter.deleteVBOs(mesh.getMeshData().getVertexCoords());
+ _deleter.deleteVBOs(mesh.getMeshData().getIndices());
+ _deleter.deleteVBOs(mesh.getMeshData().getInterleavedData());
+ _deleter.deleteVBOs(mesh.getMeshData().getNormalCoords());
+ _deleter.deleteVBOs(mesh.getMeshData().getTangentCoords());
+ for (final FloatBufferData coords : mesh.getMeshData().getTextureCoords()) {
+ _deleter.deleteVBOs(coords);
+ }
+ _deleter.deleteVBOs(mesh.getMeshData().getColorCoords());
+ _deleter.deleteVBOs(mesh.getMeshData().getFogCoords());
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java new file mode 100644 index 0000000..9e809df --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.scenegraph.visitor; + +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; + +public class SetModelBoundVisitor implements Visitor { + private final BoundingVolume _bound; + + public SetModelBoundVisitor(final BoundingVolume bound) { + _bound = bound; + } + + public void visit(final Spatial spatial) { + if (spatial instanceof Mesh) { + ((Mesh) spatial).setModelBound(_bound.clone(null)); + } + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java new file mode 100644 index 0000000..db2e891 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java @@ -0,0 +1,22 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+
+public class UpdateModelBoundVisitor implements Visitor {
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ ((Mesh) spatial).updateModelBound();
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java new file mode 100644 index 0000000..f90f8c7 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java @@ -0,0 +1,23 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.scenegraph.Spatial;
+
+public interface Visitor {
+
+ /**
+ * Execute our logic on the given Spatial
+ *
+ * @param spatial
+ */
+ void visit(Spatial spatial);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java new file mode 100644 index 0000000..df2fc6c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java @@ -0,0 +1,270 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +/** + * + */ + +package com.ardor3d.spline; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.controller.ComplexSpatialController; +import com.ardor3d.scenegraph.controller.interpolation.InterpolationController; + +/** + * ArcLengthTable class contains methods for generating and storing arc lengths of a curve. Arc Lengths are used to get + * constant speed interpolation over a curve. + * <p> + * This class does not automatically generate the look up tables, you must manually call {@link #generate(int, boolean)} + * to generate the table. + * </p> + */ +public class ArcLengthTable { + + /** Classes logger */ + private static final Logger LOGGER = Logger.getLogger(ArcLengthTable.class.getName()); + + /** Table containing arc lengths for look up */ + private Map<Integer, List<ArcLengthEntry>> _lookupTable; + + /** The curve who's values to cache */ + private final Curve _curve; + + /** + * Creates a new instance of <code>ArcLengthTable</code>. + * + * @param curve + * The curve to create the table for, can not be <code>null</code>. + */ + public ArcLengthTable(final Curve curve) { + super(); + + if (null == curve) { + throw new IllegalArgumentException("curve was null!"); + } + + _curve = curve; + } + + /** + * @param index + * The index of the control point you want the length for. + * @return The total approximate length of the segment starting at the given index. + */ + public double getLength(final int index) { + if (null == _lookupTable) { + throw new IllegalStateException( + "You must generate the look up table before calling this method! see generate()"); + } + + final List<ArcLengthEntry> entries = _lookupTable.get(index); + + if (null == entries) { + throw new IllegalArgumentException("entries was null, the index parameter was invalid. index=" + index); + } + + final ArcLengthEntry arcLength = entries.get(entries.size() - 1); + + return arcLength.getLength(); + } + + /** + * @param index + * The index of the first control point you are interpolating from. + * @param distance + * The distance you want the spatial to travel. + * @return The delta you should use to travel the specified distance from the control point given, will not be + * negative but may be greater than 1.0 if the distance is greater than the length of the segment. + */ + public double getDelta(final int index, final double distance) { + if (null == _lookupTable) { + throw new IllegalStateException( + "You must generate the look up table before calling this method! see generate()"); + } + + ArcLengthEntry previous = null; + ArcLengthEntry next = null; + + final List<ArcLengthEntry> entries = _lookupTable.get(index); + + if (null == entries) { + throw new IllegalArgumentException("entries was null, the index parameter was invalid. index=" + index); + } + + for (final ArcLengthEntry entry : entries) { + if (entry.getLength() <= distance) { + previous = entry; + } + if (entry.getLength() >= distance) { + next = entry; + break; + } + } + + if (null == previous) { + throw new IllegalArgumentException( + "previous was null, either the index or distance parameters were invalid. index=" + index + + ", distance=" + distance); + } + + final double delta; + + /* + * If next is null then the length we need to travel is longer than the length of the segment, in this case we + * work out the delta required to travel from the start of the segment minus what we've already travelled. We + * then add that delta to the delta required to traverse the next segment and return that value, it's up to the + * controller to handle this value correctly (update indices etc) + * + * We need to be careful about wrapping around the end of the curve. + */ + if (null == next) { + final int newIndex = (index + 1 >= _lookupTable.size()) ? 1 : index + 1; + + delta = getDelta(newIndex, distance - previous.getLength()) + previous.getDelta(); + + } else { + if (previous.equals(next)) { + delta = previous.getDelta(); + + } else { + final double d0 = previous.getDelta(); + final double d1 = next.getDelta(); + final double l0 = previous.getLength(); + final double l1 = next.getLength(); + + delta = (d0 + ((distance - l0) / (l1 - l0)) * (d1 - d0)); + } + } + + return delta; + } + + /** + * Actually generates the arc length table, this needs to be called before this class can actually perform any + * useful functions. + * + * @param step + * The larger the step value used the more accurate the resulting table will be and thus the smoother the + * motion will be, must be greater than zero. + * @param reverse + * <code>true</code> to generate the table while stepping from the end of the curve to the beginning, + * <code>false</code> to generate the table from the beginning of the curve. You only need to generate a + * reverse table if you are using the {@link ComplexSpatialController.RepeatType#CYCLE cycle} repeat + * type. + */ + public void generate(final int step, final boolean reverse) { + if (step <= 0) { + throw new IllegalArgumentException("step must be > 0! step=" + step); + } + + _lookupTable = new HashMap<Integer, List<ArcLengthEntry>>(); + + final Vector3 target = Vector3.fetchTempInstance(); + final Vector3 previous = Vector3.fetchTempInstance(); + + final int loopStart = reverse ? (_curve.getControlPointCount() - 2) : 1; + final double tStep = InterpolationController.DELTA_MAX / step; + + for (int i = loopStart; continueLoop(i, reverse); i = updateCounter(i, reverse)) { + final int startIndex = i; + double t = 0f; + double length = 0; + + previous.set(_curve.getControlPoints().get(i)); + + final ArrayList<ArcLengthEntry> entries = new ArrayList<ArcLengthEntry>(); + entries.add(new ArcLengthEntry(0f, 0)); + + final int endIndex = reverse ? startIndex - 1 : startIndex + 1; + + while (true) { + t += tStep; + + /* + * If we are over delta max force to 1. We need to do this to avoid precision issues causing errors + * later on (e.g. if last entry for an index had a delta of 0.996 and later during an update we passed + * 0.998 for that index we'd fail to find a valid entry and error out) + */ + if (t > InterpolationController.DELTA_MAX) { + t = InterpolationController.DELTA_MAX; + } + + _curve.interpolate(startIndex, endIndex, t, target); + + length += previous.distance(target); + + previous.set(target); + + entries.add(new ArcLengthEntry(t, length)); + + if (t == InterpolationController.DELTA_MAX) { + break; + } + } + + _lookupTable.put(i, entries); + } + + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("look up table = " + _lookupTable); + } + + Vector3.releaseTempInstance(target); + Vector3.releaseTempInstance(previous); + } + + private boolean continueLoop(final int i, final boolean reverse) { + return (reverse ? i > 0 : i < _curve.getControlPointCount() - 2); + } + + private int updateCounter(final int i, final boolean reverse) { + return (reverse ? i - 1 : i + 1); + } + + /** + * A private inner class used to store the required arc length variables for the table + */ + private static class ArcLengthEntry implements Serializable { + /** Serial UID */ + private static final long serialVersionUID = 1L; + + private final double _delta; + private final double _length; + + public ArcLengthEntry(final double delta, final double length) { + super(); + + _delta = delta; + _length = length; + } + + public double getDelta() { + return _delta; + } + + public double getLength() { + return _length; + } + + @Override + public String toString() { + return "ArcLengthEntry[length=" + _length + ", delta=" + _delta + ']'; + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java new file mode 100644 index 0000000..14ceb0c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +/** + * + */ + +package com.ardor3d.spline; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +/** + * CatmullRomSpline class is an implementation of spline that uses the Catmull-Rom equation: + * + * <pre> + * q(t) = 0.5 * ((2 * P1) + (-P0 + P2) * t + (2 * P0 - 5 * P1 + 4 * P2 - P3) * t2 + (-P0 + 3 * P1 - 3 * P2 + P3) * t3) + * </pre> + * + * @see <a href="http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Cubic Hermite spline - + * Wikipedia, the free encyclopedia</a> + */ +public class CatmullRomSpline implements Spline { + + /** + * @see #interpolate(ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3, double, Vector3) + */ + public Vector3 interpolate(final ReadOnlyVector3 p0, final ReadOnlyVector3 p1, final ReadOnlyVector3 p2, + final ReadOnlyVector3 p3, final double t) { + + return interpolate(p0, p1, p2, p3, t, new Vector3()); + } + + /** + * If any vector is <code>null</code> then the result is just returned unchanged. + * + * @param p0 + * The start control point. + * @param p1 + * Result will be between this and p2. + * @param p2 + * result will be between this and p1. + * @param p3 + * The end control point. + * @param t + * <code>0.0 <= t <= 1.0</code>, if t <= 0.0 then result will be contain exact same values as p1 and if + * its >= 1.0 it will contain the exact same values as p2. + * @param result + * The results from the interpolation will be stored in this vector. + * @return The result vector as a convenience. + */ + public Vector3 interpolate(final ReadOnlyVector3 p0, final ReadOnlyVector3 p1, final ReadOnlyVector3 p2, + final ReadOnlyVector3 p3, final double t, final Vector3 result) { + + if (null != result && null != p0 && null != p1 && null != p2 && null != p3) { + if (t <= 0.0) { + result.set(p1); + + } else if (t >= 1.0) { + result.set(p2); + + } else { + final double t2 = t * t; + final double t3 = t2 * t; + + result.setX(0.5 * ((2.0 * p1.getX()) + (-p0.getX() + p2.getX()) * t + + (2.0 * p0.getX() - 5.0 * p1.getX() + 4.0 * p2.getX() - p3.getX()) * t2 + (-p0.getX() + 3.0 + * p1.getX() - 3.0 * p2.getX() + p3.getX()) + * t3)); + + result.setY(0.5 * ((2.0 * p1.getY()) + (-p0.getY() + p2.getY()) * t + + (2.0 * p0.getY() - 5.0 * p1.getY() + 4.0 * p2.getY() - p3.getY()) * t2 + (-p0.getY() + 3.0 + * p1.getY() - 3.0 * p2.getY() + p3.getY()) + * t3)); + + result.setZ(0.5 * ((2.0 * p1.getZ()) + (-p0.getZ() + p2.getZ()) * t + + (2.0 * p0.getZ() - 5.0 * p1.getZ() + 4.0 * p2.getZ() - p3.getZ()) * t2 + (-p0.getZ() + 3.0 + * p1.getZ() - 3.0 * p2.getZ() + p3.getZ()) + * t3)); + } + } + + return result; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java new file mode 100644 index 0000000..07121a6 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.spline; + +import java.util.List; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.Point; + +/** + * Curve class contains a list of control points and a spline. It also contains method for visualising itself as a + * renderable series of points or a line. + */ +public class Curve { + + /** @see #setControlPoints(List) */ + private List<ReadOnlyVector3> _controlPoints; + + /** @see #setSpline(Spline) */ + private Spline _spline; + + /** + * Creates a new instance of <code>Curve</code>. + * + * @param controlPoints + * see {@link #setControlPoints(List)} + * @param spline + * see {@link #setSpline(Spline)} + */ + public Curve(final List<ReadOnlyVector3> controlPoints, final Spline spline) { + super(); + + setControlPoints(controlPoints); + setSpline(spline); + } + + /** + * Creates a new <code>Point</code> from the control points making up this curve. It will have the name + * <code>point</code>, no normals, colour or texture, these can be added to the returned point if needed. + * + * @param steps + * The number of iterations to perform between control points, the higher this number the more points + * will be shown, but it will also contain more vertices, must be greater than one. Use two to just show + * the actual control points set for the curve. + * @return A <code>Point</code> containing all the curve points, will not be <code>null</code>. + */ + public Point toRenderablePoint(final int steps) { + return toRenderablePoint(1, getControlPointCount() - 2, steps); + } + + /** + * Creates a new <code>Point</code> from the given control point indices. It will have the name <code>point</code>, + * no normals, colour or texture, these can be added to the returned point if needed. + * + * @param start + * The index of the control point to start from, must be greater than or equal to one and less than + * <code>end</code>. + * @param end + * The index of the control point to end with, must be less than {@link #getControlPointCount() + * controlPointCount} minus one and greater than <code>start</code>. + * @param steps + * The number of iterations to perform between control points, the higher this number the more points + * will be shown, but it will also contain more vertices, must be greater than one. + * @return A <code>Point</code> containing all the curve points, will not be <code>null</code>. + */ + public Point toRenderablePoint(final int start, final int end, final int steps) { + final Vector3[] points = toVector3(start, end, steps); + + return new Point("point", points, null, null, null); + } + + /** + * Creates a new <code>Line</code> from the control points making up this curve. It will have the name + * <code>curve</code>, no normals, colour or texture, these can be added to the returned line if needed. + * + * @param steps + * The number of iterations to perform between control points, the higher this number the smoother the + * returned line will be, but it will also contain more vertices, must be greater than one. + * @return A <code>Line</code> representing this curve, will not be <code>null</code>. + */ + public Line toRenderableLine(final int steps) { + return toRenderableLine(1, getControlPointCount() - 2, steps); + } + + /** + * Creates a new <code>Line</code> from the given control point indices. It will have the name <code>curve</code>, + * no normals, colour or texture, these can be added to the returned line if needed. + * + * @param start + * The index of the control point to start from, must be greater than or equal to one and less than + * <code>end</code>. + * @param end + * The index of the control point to end with, must be less than {@link #getControlPointCount() + * controlPointCount} minus one and greater than <code>start</code>. + * @param steps + * The number of iterations to perform between control points, the higher this number the smoother the + * returned line will be, but it will also contain more vertices, must be greater than one. + * @return A <code>Line</code> representing this curve, will not be <code>null</code>. + */ + public Line toRenderableLine(final int start, final int end, final int steps) { + final Vector3[] vertex = toVector3(start, end, steps); + final Vector3[] normal = null; + final ColorRGBA[] color = null; + final Vector2[] texture = null; + + final Line line = new Line("curve", vertex, normal, color, texture); + + line.getMeshData().setIndexMode(IndexMode.LineStrip); + + return line; + } + + /** + * Calculates the length of this curve. + * <p> + * <strong>Important note:</strong><br /> + * To calculate the length of a curve it must be interpolated (hence the steps parameter), this method will do this + * EVERY time it's called (creating a lot of garbage vectors in the process). This has been done for the sake of + * keeping this class simple and the code as readable as possible. Therefore the length should be manually cached + * somewhere in your code if it is going to be used repeatedly. + * </p> + * + * @param steps + * The number of iterations to perform between control points, the higher this number the more accurate + * the returned result will be. + * @return The length of this curve. + * @see #getApproximateLength(int, int, int) + */ + public double getApproximateLength(final int steps) { + return getApproximateLength(1, getControlPointCount() - 2, steps); + } + + /** + * Calculates the length between the given control point indices. + * <p> + * <strong>Important note:</strong><br /> + * See the Javadoc for the {@link #getApproximateLength(int)} method for important information. + * </p> + * + * @param start + * The index of the control point to start from, must be greater than or equal to one and less than + * <code>end</code>. + * @param end + * The index of the control point to end with, must be less than {@link #getControlPointCount() + * controlPointCount} minus one and greater than <code>start</code>. + * @param steps + * The number of iterations to perform between control points, the higher this number the more accurate + * the returned result will be. + * @return The length between the given control points. + * @see #getApproximateLength(int) + */ + public double getApproximateLength(final int start, final int end, final int steps) { + double length = 0.0; + + final Vector3[] vectors = toVector3(start, end, steps); + + for (int i = 0; i < (vectors.length - 1); i++) { + length += vectors[i].distance(vectors[i + 1]); + } + + return length; + } + + /** + * Interpolates between the control points at the given indices. + * + * @param start + * The index of the control point to start from. + * @param end + * The index of the control point to end at. + * @param t + * Should be between zero and one. Zero will return point <code>start</code> while one will return + * <code>end</code>, a value in between will return an interpolated vector between the two. + * @return The interpolated vector. + */ + public ReadOnlyVector3 interpolate(final int start, final int end, final double t) { + return interpolate(start, end, t, new Vector3()); + } + + /** + * Interpolates between the control points at the given indices. + * + * @param start + * The index of the control point to start from. + * @param end + * The index of the control point to end at. + * @param t + * Should be between zero and one. Zero will return point <code>start</code> while one will return + * <code>end</code>, a value in between will return an interpolated vector between the two. + * @param result + * The result of the interpolation will be stored in this vector. + * @return The result vector as a convenience. + */ + public ReadOnlyVector3 interpolate(final int start, final int end, final double t, final Vector3 result) { + if (start <= 0) { + throw new IllegalArgumentException("start must be > 0! start=" + start); + } + if (end >= (getControlPointCount() - 1)) { + throw new IllegalArgumentException("end must be < " + (getControlPointCount() - 1) + "! end=" + end); + } + + final List<ReadOnlyVector3> points = getControlPoints(); + + return getSpline().interpolate(points.get(start - 1), points.get(start), points.get(end), points.get(end + 1), + t, result); + } + + /** + * @return The number of control points in this curve. + */ + public int getControlPointCount() { + return getControlPoints().size(); + } + + /** + * @param controlPoints + * The new control points, can not be <code>null</code>. + * @see #getControlPoints() + */ + public void setControlPoints(final List<ReadOnlyVector3> controlPoints) { + if (null == controlPoints) { + throw new IllegalArgumentException("controlPoints can not be null!"); + } + if (controlPoints.size() < 4) { + throw new IllegalArgumentException("controlPoints must contain at least 4 elements for this class to work!"); + } + + _controlPoints = controlPoints; + } + + /** + * @return The control points making up this curve, will not be <code>null</code>. + * @see #setControlPoints(List) + */ + public List<ReadOnlyVector3> getControlPoints() { + assert (null != _controlPoints) : "_controlPoints was null, it must be set before use!"; + assert (_controlPoints.size() >= 4) : "_controlPoints contained less than 4 elements, it must be contain at least 4 for this class to work!"; + + return _controlPoints; + } + + /** + * @param spline + * The new spline, can not be <code>null</code>. + * @see #getSpline() + */ + public void setSpline(final Spline spline) { + if (null == spline) { + throw new IllegalArgumentException("spline can not be null!"); + } + + _spline = spline; + } + + /** + * The default is a {@link CatmullRomSpline}. + * + * @return The spline, will not be <code>null</code>. + * @see #setSpline(Spline) + */ + public Spline getSpline() { + assert (null != _spline) : "_spline was null, it must be set before use!"; + + return _spline; + } + + /** + * Interpolates the curve and returns an array of vectors. + */ + private Vector3[] toVector3(final int start, final int end, final int steps) { + if (start <= 0) { + throw new IllegalArgumentException("start must be > 0! start=" + start); + } + if (end >= (getControlPointCount() - 1)) { + throw new IllegalArgumentException("end must be < " + (getControlPointCount() - 1) + "! end=" + end); + } + if (start >= end) { + throw new IllegalArgumentException("start must be < end! start=" + start + ", end=" + end); + } + if (steps <= 1) { + throw new IllegalArgumentException("steps must be >= 1! steps=" + steps); + } + + final List<ReadOnlyVector3> controlPoints = getControlPoints(); + + final int count = (end - start) * steps; + + final Vector3[] vectors = new Vector3[count]; + + int index = start; + + for (int i = 0; i < count; i++) { + final int is = i % steps; + + if (0 == is && i >= steps) { + index++; + } + + final double t = is / (steps - 1.0); + + final int p0 = index - 1; + final int p1 = index; + final int p2 = index + 1; + final int p3 = index + 2; + + vectors[i] = getSpline().interpolate(controlPoints.get(p0), controlPoints.get(p1), controlPoints.get(p2), + controlPoints.get(p3), t); + } + + return vectors; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java new file mode 100644 index 0000000..311f5ba --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java @@ -0,0 +1,62 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.spline;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+/**
+ * Spline interface allows an interpolated vector to be calculated along a path.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Spline_(mathematics)">Spline (mathematics) - Wikipedia, the free
+ * encyclopedia</a>
+ */
+public interface Spline {
+ /**
+ * Will return an interpolated vector between parameters <code>p1</code> and <code>p2</code> using <code>t</code>.
+ *
+ * @param p0
+ * The starting control point.
+ * @param p1
+ * The second control point.
+ * @param p2
+ * The third control point.
+ * @param p3
+ * The final control point.
+ * @param t
+ * Should be between zero and one. Zero will return point <code>p1</code> while one will return
+ * <code>p2</code>, a value in between will return an interpolated vector between the two.
+ * @return The interpolated vector.
+ */
+ public Vector3 interpolate(ReadOnlyVector3 p0, ReadOnlyVector3 p1, ReadOnlyVector3 p2, ReadOnlyVector3 p3, double t);
+
+ /**
+ * Will return an interpolated vector between parameters <code>p1</code> and <code>p2</code> using <code>t</code>.
+ *
+ * @param p0
+ * The starting control point.
+ * @param p1
+ * The second control point.
+ * @param p2
+ * The third control point.
+ * @param p3
+ * The final control point.
+ * @param t
+ * Should be between zero and one. Zero will return point <code>p1</code> while one will return
+ * <code>p2</code>, a value in between will return an interpolated vector between the two.
+ * @param result
+ * The interpolated values will be added to this vector.
+ * @return The result vector passed in.
+ */
+ public Vector3 interpolate(ReadOnlyVector3 p0, ReadOnlyVector3 p1, ReadOnlyVector3 p2, ReadOnlyVector3 p3,
+ double t, Vector3 result);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java new file mode 100644 index 0000000..9e9e343 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java @@ -0,0 +1,954 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.ui.text;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.TextureKey;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.resource.ResourceSource;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Loads a font generated by BMFont (http://www.angelcode.com/products/bmfont/).
+ * <ul>
+ * <li>Font info file *must* be in XML format.
+ * <li>The texture should be saved in 32 bit PNG format - TGA does not appear to work.
+ * <li>This class only supports a single page (see BMFont documentation for details on pages)
+ * </ul>
+ */
+public class BMFont implements Savable {
+ private static Logger logger = Logger.getLogger(BMFont.class.getName());
+
+ private final Map<Integer, Char> _charMap = Maps.newHashMap();
+ private final Map<Integer, Map<Integer, Integer>> _kernMap = Maps.newHashMap();
+
+ private String _styleName; // e.g. "Courier-12-bold"
+ private final ArrayList<Page> _pages = new ArrayList<Page>();
+ private Texture _pageTexture;
+ private RenderStateSetter _blendStateSetter = null;
+ private RenderStateSetter _alphaStateSetter = null;
+ private boolean _useMipMaps;
+ private int _maxCharAdv;
+ private Common _common = null;
+ private Info _info = null;
+
+ /**
+ * This constructor should be used when loading as Savable
+ */
+ public BMFont() {}
+
+ /**
+ * Reads an XML BMFont description file and loads corresponding texture. Note that the TGA written by BMFont does
+ * not seem to be read properly by the Ardor3D loader. PNG works fine.
+ *
+ * @param fileUrl
+ * - the location of the .fnt font file. Can not be null.
+ * @param useMipMaps
+ * if true, use trilinear filtering with max anisotropy, else min filter is bilinear. MipMaps result in
+ * blurrier text, but less shimmering.
+ * @throws IOException
+ * if there are any problems reading the .fnt file.
+ */
+ public BMFont(final ResourceSource source, final boolean useMipMaps) throws IOException {
+ _useMipMaps = useMipMaps;
+
+ parseFontFile(source);
+ initialize(source);
+ }
+
+ /** apply default render states to spatial */
+ public void applyRenderStatesTo(final Spatial spatial, final boolean useBlend) {
+ if (useBlend) {
+ if (_blendStateSetter == null) {
+ _blendStateSetter = new RenderStateSetter(_pageTexture, true);
+ }
+ _blendStateSetter.applyTo(spatial);
+ } else {
+ if (_alphaStateSetter == null) {
+ _alphaStateSetter = new RenderStateSetter(_pageTexture, false);
+ }
+ _alphaStateSetter.applyTo(spatial);
+ }
+ }
+
+ public String getStyleName() {
+ return _styleName;
+ }
+
+ public int getSize() {
+ return Math.abs(_info.size);
+ }
+
+ public int getLineHeight() {
+ return _common.lineHeight;
+ }
+
+ public int getBaseHeight() {
+ return _common.base;
+ }
+
+ public int getTextureWidth() {
+ return _common.scaleW;
+ }
+
+ public int getTextureHeight() {
+ return _common.scaleH;
+ }
+
+ /**
+ * @param chr
+ * ascii character code
+ * @return character descriptor for chr. If character is not in the char set, return '?' (if '?' is not in the char
+ * set, return will be null)
+ */
+ public BMFont.Char getChar(int chr) {
+ BMFont.Char retVal = _charMap.get(chr);
+ if (retVal == null) {
+ chr = '?';
+ retVal = _charMap.get(chr);
+ if (retVal == null) { // if still null, use the first char
+ final Iterator<Char> it = _charMap.values().iterator();
+ retVal = it.next();
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * @return kerning information for this character pair
+ */
+ public int getKerning(final int chr, final int nextChr) {
+ final Map<Integer, Integer> map = _kernMap.get(chr);
+ if (map != null) {
+ final Integer amt = map.get(nextChr);
+ if (amt != null) {
+ return amt;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * @return the largest xadvance in this char set
+ */
+ public int getMaxCharAdvance() {
+ return _maxCharAdv;
+ }
+
+ public int getOutlineWidth() {
+ return _info.outline;
+ }
+
+ public Info getInfo() {
+ return _info;
+ }
+
+ /**
+ * Writes the XML for this font out to the OutputStream provided.
+ *
+ * @param outputStream
+ * the OutputStream to which the XML for this font will be written to
+ * @throws IOException
+ * thrown if there is any problem writing out to the OutputStream
+ */
+ public void writeXML(final OutputStream outputStream) throws IOException {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append("<?xml version=\"1.0\"?>\n");
+ xml.append("<font>\n");
+ xml.append(generateInfoXML());
+ xml.append(generateCommonXML());
+ xml.append(generatePagesXML());
+ xml.append(generateCharsXML());
+ xml.append(generateKerningsXML());
+ xml.append("</font>");
+
+ // Write out to the output stream now
+ outputStream.write(xml.toString().getBytes());
+ outputStream.flush();
+
+ return;
+ }
+
+ private String generateInfoXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <info face=\"");
+ xml.append(_info.face);
+ xml.append("\" size=\"");
+ xml.append(_info.size);
+ xml.append("\" bold=\"");
+ xml.append(_info.bold ? "1" : "0");
+ xml.append("\" italic=\"");
+ xml.append(_info.italic ? "1" : "0");
+ xml.append("\" charset=\"");
+ xml.append(_info.charset);
+ xml.append("\" unicode=\"");
+ xml.append(_info.unicode ? "1" : "0");
+ xml.append("\" stretchH=\"");
+ xml.append(_info.stretchH);
+ xml.append("\" smooth=\"");
+ xml.append(_info.smooth ? "1" : "0");
+ xml.append("\" aa=\"");
+ xml.append(_info.aa ? "1" : "0");
+ xml.append("\" padding=\"");
+
+ for (int i = 0; i < _info.padding.length; i++) {
+ xml.append(_info.padding[i]);
+
+ if (i < (_info.padding.length - 1)) {
+ xml.append(",");
+ }
+ }
+
+ xml.append("\" spacing=\"");
+
+ for (int i = 0; i < _info.spacing.length; i++) {
+ xml.append(_info.spacing[i]);
+
+ if (i < (_info.spacing.length - 1)) {
+ xml.append(",");
+ }
+ }
+
+ xml.append("\" outline=\"");
+ xml.append(_info.outline);
+ xml.append("\"/>\n");
+
+ return xml.toString();
+ }
+
+ private String generateCommonXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <common lineHeight=\"");
+ xml.append(_common.lineHeight);
+ xml.append("\" base=\"");
+ xml.append(_common.base);
+ xml.append("\" scaleW=\"");
+ xml.append(_common.scaleW);
+ xml.append("\" scaleH=\"");
+ xml.append(_common.scaleH);
+ xml.append("\" pages=\"");
+ xml.append(_common.pages);
+ xml.append("\" packed=\"");
+ xml.append(_common.packed ? "1" : "0");
+ xml.append("\" alphaChnl=\"");
+ xml.append(_common.alphaChnl);
+ xml.append("\" redChnl=\"");
+ xml.append(_common.redChnl);
+ xml.append("\" greenChnl=\"");
+ xml.append(_common.greenChnl);
+ xml.append("\" blueChnl=\"");
+ xml.append(_common.blueChnl);
+ xml.append("\"/>\n");
+
+ return xml.toString();
+ }
+
+ private String generatePagesXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <pages>\n");
+
+ for (final Iterator<Page> iterator = _pages.iterator(); iterator.hasNext();) {
+ final Page page = iterator.next();
+
+ xml.append(" <page id=\"");
+ xml.append(page.id);
+ xml.append("\" file=\"");
+ xml.append(page.file);
+ xml.append("\" />\n");
+ }
+
+ xml.append(" </pages>\n");
+
+ return xml.toString();
+ }
+
+ private String generateCharsXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <chars count=\"");
+ xml.append(_charMap.size());
+ xml.append("\">\n");
+
+ for (final Iterator<Integer> iterator = _charMap.keySet().iterator(); iterator.hasNext();) {
+ final Integer key = iterator.next();
+ final Char character = _charMap.get(key);
+
+ xml.append(" <char id=\"");
+ xml.append(character.id);
+ xml.append("\" x=\"");
+ xml.append(character.x);
+ xml.append("\" y=\"");
+ xml.append(character.y);
+ xml.append("\" width=\"");
+ xml.append(character.width);
+ xml.append("\" height=\"");
+ xml.append(character.height);
+ xml.append("\" xoffset=\"");
+ xml.append(character.xoffset);
+ xml.append("\" yoffset=\"");
+ xml.append(character.yoffset);
+ xml.append("\" xadvance=\"");
+ xml.append(character.xadvance);
+ xml.append("\" page=\"");
+ xml.append(character.page);
+ xml.append("\" chnl=\"");
+ xml.append(character.chnl);
+ xml.append("\" />\n");
+ }
+
+ xml.append(" </chars>\n");
+
+ return xml.toString();
+ }
+
+ private String generateKerningsXML() {
+ final StringBuilder xml = new StringBuilder();
+ int count = 0;
+
+ for (final Iterator<Integer> iterator = _kernMap.keySet().iterator(); iterator.hasNext();) {
+ final Integer first = iterator.next();
+ final Map<Integer, Integer> amtHash = _kernMap.get(first);
+
+ for (final Iterator<Integer> iterator2 = amtHash.keySet().iterator(); iterator2.hasNext();) {
+ final Integer second = iterator2.next();
+ final Integer amount = amtHash.get(second);
+
+ xml.append(" <kerning first=\"");
+ xml.append(first);
+ xml.append("\" second=\"");
+ xml.append(second);
+ xml.append("\" amount=\"");
+ xml.append(amount);
+ xml.append("\" />\n");
+
+ count++;
+ }
+ }
+
+ final String xmlString = " <kernings count=\"" + count + "\">\n" + xml.toString() + " </kernings>\n";
+
+ return xmlString;
+ }
+
+ /**
+ * load the texture and create default render states. Only a single page is supported.
+ *
+ * @param fontUrl
+ */
+ // ----------------------------------------------------------
+ protected void initialize(final ResourceSource source) throws MalformedURLException {
+ _styleName = _info.face + "-" + _info.size;
+
+ if (_info.bold) {
+ _styleName += "-bold";
+ } else {
+ _styleName += "-medium";
+ }
+
+ if (_info.italic) {
+ _styleName += "-italic";
+ } else {
+ _styleName += "-regular";
+ }
+
+ // only a single page is supported
+ if (_pages.size() > 0) {
+ final Page page = _pages.get(0);
+
+ final ResourceSource texSrc = source.getRelativeSource("./" + page.file);
+
+ Texture.MinificationFilter minFilter;
+ Texture.MagnificationFilter magFilter;
+
+ magFilter = Texture.MagnificationFilter.Bilinear;
+ minFilter = Texture.MinificationFilter.BilinearNoMipMaps;
+ if (_useMipMaps) {
+ minFilter = Texture.MinificationFilter.Trilinear;
+ }
+ final TextureKey tkey = TextureKey.getKey(texSrc, false, TextureStoreFormat.GuessNoCompressedFormat,
+ minFilter);
+ _pageTexture = TextureManager.loadFromKey(tkey, null, null);
+ _pageTexture.setMagnificationFilter(magFilter);
+
+ // Add a touch higher mipmap selection.
+ _pageTexture.setLodBias(-1);
+
+ if (_useMipMaps) {
+ _pageTexture.setAnisotropicFilterPercent(1.0f);
+ }
+ }
+ }
+
+ public Texture getPageTexture() {
+ return _pageTexture;
+ }
+
+ public List<Integer> getMappedChars() {
+ return Lists.newArrayList(_charMap.keySet());
+ }
+
+ public Map<Integer, Integer> getKerningsForCharacter(final int val) {
+ return _kernMap.get(val);
+ }
+
+ public Map<Integer, Map<Integer, Integer>> getKerningMap() {
+ return _kernMap;
+ }
+
+ /**
+ *
+ * @param fontUrl
+ * @throws IOException
+ */
+ protected void parseFontFile(final ResourceSource source) throws IOException {
+ _maxCharAdv = 0;
+ _charMap.clear();
+ _pages.clear();
+ try {
+ final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ final DocumentBuilder db = dbf.newDocumentBuilder();
+ final Document doc = db.parse(source.openStream());
+
+ doc.getDocumentElement().normalize();
+ recurse(doc.getElementsByTagName("font").item(0));
+
+ // db.reset();
+ } catch (final Throwable t) {
+ final IOException ex = new IOException("Error loading font file " + source.toString());
+ ex.initCause(t);
+ throw ex;
+ }
+ }
+
+ private void recurse(final Node node) {
+ final NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ final Node child = children.item(i);
+ processNode(child);
+ recurse(child);
+ }
+ }
+
+ private void processNode(final Node node) {
+ final String tagName = node.getNodeName();
+ if (tagName != null) {
+ if (tagName.equals("info")) {
+ processInfoNode(node);
+ } else if (tagName.equals("common")) {
+ processCommonNode(node);
+ } else if (tagName.equals("page")) {
+ processPageNode(node);
+ } else if (tagName.equals("char")) {
+ processCharNode(node);
+ } else if (tagName.equals("kerning")) {
+ procesKerningNode(node);
+ }
+ }
+ }
+
+ private void processInfoNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ _info = new Info();
+ _info.face = getStringAttrib("face", attribs);
+ _info.size = getIntAttrib("size", attribs);
+ _info.bold = getBoolAttrib("bold", attribs);
+ _info.italic = getBoolAttrib("italic", attribs);
+ _info.charset = getStringAttrib("charset", attribs);
+ _info.unicode = getBoolAttrib("unicode", attribs);
+ _info.stretchH = getIntAttrib("stretchH", attribs);
+ _info.smooth = getBoolAttrib("smooth", attribs);
+ _info.aa = getBoolAttrib("aa", attribs);
+ _info.padding = getIntArrayAttrib("padding", attribs);
+ _info.spacing = getIntArrayAttrib("spacing", attribs);
+ _info.outline = getIntAttrib("outline", attribs);
+ }
+
+ private void processCommonNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ _common = new Common();
+ _common.lineHeight = getIntAttrib("lineHeight", attribs);
+ _common.base = getIntAttrib("base", attribs);
+ _common.scaleW = getIntAttrib("scaleW", attribs);
+ _common.scaleH = getIntAttrib("scaleH", attribs);
+ _common.pages = getIntAttrib("pages", attribs);
+ _common.packed = getBoolAttrib("packed", attribs);
+ _common.alphaChnl = getIntAttrib("alphaChnl", attribs);
+ _common.redChnl = getIntAttrib("redChnl", attribs);
+ _common.greenChnl = getIntAttrib("greenChnl", attribs);
+ _common.blueChnl = getIntAttrib("blueChnl", attribs);
+ }
+
+ private void processCharNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ final Char c = new Char();
+ c.id = getIntAttrib("id", attribs);
+ c.x = getIntAttrib("x", attribs);
+ c.y = getIntAttrib("y", attribs);
+ c.width = getIntAttrib("width", attribs);
+ c.height = getIntAttrib("height", attribs);
+ c.xoffset = getIntAttrib("xoffset", attribs);
+ c.yoffset = getIntAttrib("yoffset", attribs);
+ c.xadvance = getIntAttrib("xadvance", attribs);
+ c.page = getIntAttrib("page", attribs);
+ c.chnl = getIntAttrib("chnl", attribs);
+ _charMap.put(c.id, c);
+ if (c.xadvance > _maxCharAdv) {
+ _maxCharAdv = c.xadvance;
+ }
+ }
+
+ private void processPageNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ final Page page = new Page();
+ page.id = getIntAttrib("id", attribs);
+ page.file = getStringAttrib("file", attribs);
+ _pages.add(page);
+ if (_pages.size() > 1) {
+ logger.warning("multiple pages defined in font description file, but only a single page is supported.");
+ }
+ }
+
+ private void procesKerningNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ final int first = getIntAttrib("first", attribs);
+ final int second = getIntAttrib("second", attribs);
+ final int amount = getIntAttrib("amount", attribs);
+ Map<Integer, Integer> amtHash;
+ amtHash = _kernMap.get(first);
+ if (amtHash == null) {
+ amtHash = Maps.newHashMap();
+ _kernMap.put(first, amtHash);
+ }
+ amtHash.put(second, amount);
+ }
+
+ // == xml attribute getters ============================
+ int getIntAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ return Integer.parseInt(node.getNodeValue());
+ }
+
+ String getStringAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ return node.getNodeValue();
+ }
+
+ boolean getBoolAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ return (Integer.parseInt(node.getNodeValue()) == 1);
+ }
+
+ int[] getIntArrayAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ final String str = node.getNodeValue();
+ final StringTokenizer strtok = new StringTokenizer(str, ",");
+ final int sz = strtok.countTokens();
+ final int[] retVal = new int[sz];
+ for (int i = 0; i < sz; i++) {
+ retVal[i] = Integer.parseInt(strtok.nextToken());
+ }
+ return retVal;
+ }
+
+ // == support structs ==================================
+ @SavableFactory(factoryMethod = "create")
+ public static class Info implements Savable {
+ public String face;
+ public int size;
+ public boolean bold;
+ public boolean italic;
+ public String charset;
+ public boolean unicode;
+ public int stretchH;
+ public boolean smooth;
+ public boolean aa;
+ public int[] padding;
+ public int[] spacing;
+ public int outline;
+
+ public static Info create() {
+ return new Info();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(face, "face", null);
+ capsule.write(size, "size", 0);
+ capsule.write(bold, "bold", false);
+ capsule.write(italic, "italic", false);
+ capsule.write(charset, "charset", null);
+ capsule.write(unicode, "unicode", false);
+ capsule.write(stretchH, "stretchH", 0);
+ capsule.write(smooth, "smooth", false);
+ capsule.write(aa, "aa", false);
+ capsule.write(padding, "padding", null);
+ capsule.write(spacing, "spacing", null);
+ capsule.write(outline, "outline", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ face = capsule.readString("face", null);
+ size = capsule.readInt("size", 0);
+ bold = capsule.readBoolean("bold", false);
+ italic = capsule.readBoolean("italic", false);
+ charset = capsule.readString("charset", null);
+ unicode = capsule.readBoolean("unicode", false);
+ stretchH = capsule.readInt("stretchH", 0);
+ smooth = capsule.readBoolean("smooth", false);
+ aa = capsule.readBoolean("aa", false);
+ padding = capsule.readIntArray("padding", null);
+ spacing = capsule.readIntArray("spacing", null);
+ outline = capsule.readInt("outline", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Common implements Savable {
+ public int lineHeight;
+ public int base;
+ public int scaleW;
+ public int scaleH;
+ public int pages;
+ public boolean packed;
+ public int alphaChnl;
+ public int redChnl;
+ public int greenChnl;
+ public int blueChnl;
+
+ public static Common create() {
+ return new Common();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(lineHeight, "lineHeight", 0);
+ capsule.write(base, "base", 0);
+ capsule.write(scaleW, "scaleW", 1);
+ capsule.write(scaleH, "scaleH", 1);
+ capsule.write(pages, "pages", 0);
+ capsule.write(packed, "packed", false);
+ capsule.write(alphaChnl, "alphaChnl", 0);
+ capsule.write(redChnl, "redChnl", 0);
+ capsule.write(greenChnl, "greenChnl", 0);
+ capsule.write(blueChnl, "blueChnl", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ lineHeight = capsule.readInt("lineHeight", 0);
+ base = capsule.readInt("base", 0);
+ scaleW = capsule.readInt("scaleW", 0);
+ scaleH = capsule.readInt("scaleH", 0);
+ pages = capsule.readInt("pages", 0);
+ packed = capsule.readBoolean("packed", false);
+ alphaChnl = capsule.readInt("alphaChnl", 0);
+ redChnl = capsule.readInt("redChnl", 0);
+ greenChnl = capsule.readInt("greenChnl", 0);
+ blueChnl = capsule.readInt("blueChnl", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Page implements Savable {
+ public int id;
+ public String file;
+
+ public static Page create() {
+ return new Page();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(id, "id", 0);
+ capsule.write(file, "file", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ id = capsule.readInt("id", 0);
+ file = capsule.readString("file", null);
+
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return this.getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Char implements Savable {
+ public int id;
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+ public int xoffset;
+ public int yoffset;
+ public int xadvance;
+ public int page;
+ public int chnl;
+
+ public static Char create() {
+ return new Char();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(id, "id", 0);
+ capsule.write(x, "x", 0);
+ capsule.write(y, "y", 0);
+ capsule.write(width, "width", 0);
+ capsule.write(height, "height", 0);
+ capsule.write(xoffset, "xoffset", 0);
+ capsule.write(yoffset, "yoffset", 0);
+ capsule.write(xadvance, "xadvance", 0);
+ capsule.write(page, "page", 0);
+ capsule.write(chnl, "chnl", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ id = capsule.readInt("id", 0);
+ x = capsule.readInt("x", 0);
+ y = capsule.readInt("y", 0);
+ width = capsule.readInt("width", 0);
+ height = capsule.readInt("height", 0);
+ xoffset = capsule.readInt("xoffset", 0);
+ yoffset = capsule.readInt("yoffset", 0);
+ xadvance = capsule.readInt("xadvance", 0);
+ page = capsule.readInt("page", 0);
+ chnl = capsule.readInt("chnl", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Kerning implements Savable {
+ public int first;
+ public int second;
+ public int amount;
+
+ public Kerning() {}
+
+ public Kerning(final int first, final int second, final int amount) {
+ this.first = first;
+ this.second = second;
+ this.amount = amount;
+ }
+
+ public static Kerning create() {
+ return new BMFont.Kerning();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(first, "fist", 0);
+ capsule.write(second, "second", 0);
+ capsule.write(amount, "amount", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ first = capsule.readInt("first", 0);
+ second = capsule.readInt("second", 0);
+ amount = capsule.readInt("amount", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ /**
+ * utility to set default render states for text
+ */
+ public class RenderStateSetter {
+ public TextureState textureState;
+ public BlendState blendState;
+ public ZBufferState zBuffState;
+
+ float _blendDisabledTestRef = 0.3f;
+ float _blendEnabledTestRef = 0.02f;
+
+ boolean _useBlend;
+
+ RenderStateSetter(final Texture texture, final boolean useBlend) {
+ textureState = new TextureState();
+ textureState.setTexture(texture);
+
+ blendState = new BlendState();
+ blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ blendState.setTestEnabled(true);
+ blendState.setTestFunction(BlendState.TestFunction.GreaterThan);
+
+ zBuffState = new ZBufferState();
+ zBuffState.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
+
+ setUseBlend(useBlend);
+ }
+
+ void applyTo(final Spatial spatial) {
+ spatial.setRenderState(textureState);
+ spatial.setRenderState(blendState);
+ spatial.setRenderState(zBuffState);
+ if (_useBlend) {
+ spatial.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ } else {
+ spatial.getSceneHints().setRenderBucketType(RenderBucketType.Opaque);
+ }
+ }
+
+ void setUseBlend(final boolean blend) {
+ _useBlend = blend;
+ if (blend == false) {
+ blendState.setBlendEnabled(false);
+ blendState.setReference(_blendDisabledTestRef);
+ zBuffState.setWritable(true);
+ } else {
+ blendState.setBlendEnabled(true);
+ blendState.setReference(_blendEnabledTestRef);
+ zBuffState.setWritable(false);
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ _pageTexture.setStoreImage(true);
+ capsule.write(_useMipMaps, "useMipMaps", false);
+ capsule.write(_styleName, "styleName", null);
+ capsule.write(_pageTexture, "pageTexture", null);
+
+ // Info
+ capsule.write(_info, "info", null);
+ // Common
+ capsule.write(_common, "common", null);
+ // Pages
+ capsule.writeSavableList(_pages, "pages", _pages);
+ // Chars
+ capsule.writeSavableList(new ArrayList<Char>(_charMap.values()), "charMap", null);
+
+ // Kernings
+ final List<Kerning> kernings = new ArrayList<Kerning>();
+ for (final Iterator<Integer> iterator = _kernMap.keySet().iterator(); iterator.hasNext();) {
+ final Integer first = iterator.next();
+ final Map<Integer, Integer> amtHash = _kernMap.get(first);
+ for (final Iterator<Integer> iterator2 = amtHash.keySet().iterator(); iterator2.hasNext();) {
+ final Integer second = iterator2.next();
+ final Integer amount = amtHash.get(second);
+ kernings.add(new Kerning(first, second, amount));
+ }
+ }
+ capsule.writeSavableList(kernings, "kernings", kernings);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ _useMipMaps = capsule.readBoolean("useMipMaps", false);
+ _styleName = capsule.readString("styleName", null);
+ _pageTexture = (Texture) capsule.readSavable("pageTexture", null);
+ _pageTexture = TextureManager.loadFromImage(_pageTexture.getImage(), _pageTexture.getMinificationFilter());
+
+ // Info
+ _info = (Info) capsule.readSavable("info", _info);
+ // Common
+ _common = (Common) capsule.readSavable("common", _common);
+ // Pages
+ _pages.clear();
+ final List<Savable> pages = capsule.readSavableList("pages", new ArrayList<Savable>());
+ for (final Savable savable : pages) {
+ _pages.add((Page) savable);
+ if (_pages.size() > 1) {
+ logger.warning("multiple pages defined in font description file, but only a single page is supported.");
+ }
+ }
+ // Chars
+ _charMap.clear();
+ final List<Savable> chars = capsule.readSavableList("charMap", new ArrayList<Savable>());
+ for (final Savable savable : chars) {
+ final Char c = (Char) savable;
+ _charMap.put(c.id, c);
+ if (c.xadvance > _maxCharAdv) {
+ _maxCharAdv = c.xadvance;
+ }
+ }
+ // Kernings
+ _kernMap.clear();
+ final List<Savable> kernings = capsule.readSavableList("kernings", new ArrayList<Savable>());
+ for (final Savable savable : kernings) {
+ final Kerning k = (Kerning) savable;
+ Map<Integer, Integer> amtHash;
+ amtHash = _kernMap.get(k.first);
+ if (amtHash == null) {
+ amtHash = Maps.newHashMap();
+ _kernMap.put(k.first, amtHash);
+ }
+ amtHash.put(k.second, k.amount);
+ _kernMap.put(k.first, amtHash);
+ }
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java new file mode 100644 index 0000000..c305c45 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java @@ -0,0 +1,761 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.ui.text;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Text spatial which uses textures generated by BMFont
+ */
+@SavableFactory(factoryMethod = "initSavable")
+public class BMText extends Mesh {
+ protected BMFont _font;
+
+ protected String _textString;
+ private final int _tabSize = 4;
+
+ protected double _fontScale = 1.0;
+ protected boolean _autoRotate = true;
+
+ protected int _lines = 1;
+
+ protected final Vector2 _size = new Vector2(); // width and height of text string
+ protected float[] _lineWidths = new float[64]; // size of each line of text
+
+ protected ColorRGBA _textClr = new ColorRGBA(1, 1, 1, 1);
+ protected ColorRGBA _tempClr = new ColorRGBA(1, 1, 1, 1);
+
+ public enum AutoScale {
+ /**
+ * No auto scaling
+ */
+ Off,
+
+ /**
+ * Maintain native point size of font regardless of distance from camera
+ */
+ FixedScreenSize,
+
+ /**
+ * Do not auto scale if font screen size is smaller than native point size, otherwise maintain native point
+ * size.
+ */
+ CapScreenSize;
+ }
+
+ protected AutoScale _autoScale = AutoScale.CapScreenSize;
+
+ /**
+ * @see BMText#setAutoFadeDistanceRange(double, double)
+ * @see BMText#setAutoFadeFixedPixelSize(int)
+ * @see BMText#setAutoFadeFalloff(float)
+ */
+ public enum AutoFade {
+ /**
+ * No auto fade.
+ */
+ Off,
+
+ /**
+ * Fade based on a fixed distance between text and camera.
+ */
+ DistanceRange,
+
+ /**
+ * Fade when screen size is less than fixed pixel size.
+ */
+ FixedPixelSize,
+
+ /**
+ * Fade when screen size is less than native size. Equivalent to FixedPixelSize +
+ * setAutoFadeFixedPixelSize(font.getSize()).
+ */
+ CapScreenSize;
+ }
+
+ protected AutoFade _autoFade = AutoFade.FixedPixelSize;
+ protected int _fixedPixelAlphaThresh = 14;
+ protected float _screenSizeAlphaFalloff = 0.7f; // 0=instant, 1=half size
+ protected final Vector2 _distanceAlphaRange = new Vector2(50, 75);
+ protected boolean _useBlend;
+
+ /**
+ * Justification within a text block
+ */
+ public enum Justify {
+ Left, Center, Right;
+ }
+
+ protected Justify _justify;
+ protected int _spacing = 0; // additional spacing between characters
+
+ /**
+ * Alignment of the text block from the pivot point
+ */
+ public enum Align {
+ North(-0.5f, 0.0f), NorthWest(0.0f, 0.0f), NorthEast(-1.0f, 0.0f), Center(-0.5f, -0.5f), West(0.0f, -0.5f), East(
+ -1.0f, -0.5f), South(-0.5f, -1.0f), SouthWest(0.0f, -1.0f), SouthEast(-1.0f, -1.0f);
+ public final float horizontal;
+ public final float vertical;
+
+ private Align(final float h, final float v) {
+ horizontal = h;
+ vertical = v;
+ }
+ }
+
+ protected Align _align;
+ protected final Vector2 _alignOffset = new Vector2();
+ protected final Vector2 _fixedOffset = new Vector2();
+
+ protected final Vector3 _look = new Vector3();
+ protected final Vector3 _left = new Vector3();
+ protected final Matrix3 _rot = new Matrix3();
+
+ public static BMText initSavable() {
+ return new BMText();
+ }
+
+ protected BMText() {}
+
+ /**
+ *
+ * @param sName
+ * @param text
+ * @param font
+ */
+ public BMText(final String sName, final String text, final BMFont font) {
+ this(sName, text, font, Align.SouthWest);
+ }
+
+ public BMText(final String sName, final String text, final BMFont font, final Align align) {
+ this(sName, text, font, align, Justify.Left);
+ }
+
+ public BMText(final String sName, final String text, final BMFont font, final Align align, final Justify justify) {
+ this(sName, text, font, align, justify, true);
+ }
+
+ /**
+ *
+ * @param sName
+ * spatial name
+ * @param text
+ * text to render.
+ * @param font
+ * @param align
+ * @param justify
+ * @param useBlend
+ * if true: use alpha blending and use transparent render bucket, else if false: alpha test only and use
+ * opaque render bucket
+ */
+ public BMText(final String sName, final String text, final BMFont font, final Align align, final Justify justify,
+ final boolean useBlend) {
+ super(sName);
+ _font = font;
+ _align = align;
+ _justify = justify;
+ _spacing = 0;
+ _useBlend = useBlend;
+ if (_font.getOutlineWidth() > 1) {
+ _spacing = _font.getOutlineWidth() - 1;
+ }
+
+ // -- never cull
+ setModelBound(null);
+ getSceneHints().setCullHint(CullHint.Never);
+
+ // -- default to non-pickable
+ getSceneHints().setAllPickingHints(false);
+
+ // no light, basic texture
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ // triangles
+ getMeshData().setIndexMode(IndexMode.Triangles);
+
+ setText(text);
+
+ _font.applyRenderStatesTo(this, useBlend);
+ }
+
+ public void setTextColor(final ReadOnlyColorRGBA clr) {
+ _textClr.set(clr);
+ setDefaultColor(_textClr);
+ }
+
+ public void setTextColor(final float r, final float g, final float b, final float a) {
+ _textClr.set(r, g, b, a);
+ setDefaultColor(_textClr);
+ }
+
+ /**
+ * If AutoScale is enabled, this scale parameter acts as a bias. Setting the scale to 0.95 will sharpen the font and
+ * increase readability a bit if you're using a bilinear min filter on the texture. When AutoScale is disabled, this
+ * scales the font to world units, e.g. setScale(1) would make the font characters approximately 1 world unit in
+ * size, regardless of the font point size.
+ */
+ public void setFontScale(final double scale) {
+ _fontScale = scale;
+
+ if (_autoScale == AutoScale.Off) {
+ final double unit = 1.0 / _font.getSize();
+ final double s = unit * _fontScale;
+ this.setScale(s, s, -s);
+ }
+ }
+
+ public double getFontScale() {
+ return _fontScale;
+ }
+
+ /**
+ * Set scaling policy
+ */
+ public void setAutoScale(final AutoScale autoScale) {
+ _autoScale = autoScale;
+ setFontScale(_fontScale);
+ }
+
+ public AutoScale getAutoScale() {
+ return _autoScale;
+ }
+
+ public void setAutoFade(final AutoFade autoFade) {
+ _autoFade = autoFade;
+ }
+
+ public AutoFade getAutoFade() {
+ return _autoFade;
+ }
+
+ public void setAutoFadeFixedPixelSize(final int pixelSize) {
+ _fixedPixelAlphaThresh = pixelSize;
+ }
+
+ public int getAutoFadeFixedPixelSize() {
+ return _fixedPixelAlphaThresh;
+ }
+
+ /**
+ * alpha falloff factor used when FixedPixelSize or CapScreenSize is used. Can be any positive value; useful range
+ * is ~ 0-2
+ * <ul>
+ * <li>0 = transparent instantaneously
+ * <li>1 = transparent when approximately 1/2 size
+ * </ul>
+ */
+ public void setAutoFadeFalloff(final float factor) {
+ _screenSizeAlphaFalloff = factor;
+ }
+
+ /**
+ * @param nearOpaque
+ * text is completely opaque when distance between camera and text is less than this value
+ * @param farTransparent
+ * text is completely transparent when distance between camera and text is greater than this value
+ */
+ public void setAutoFadeDistanceRange(final double nearOpaque, final double farTransparent) {
+ _distanceAlphaRange.set(nearOpaque, farTransparent);
+ }
+
+ /**
+ * automatically rotate test to face the camera
+ */
+ public void setAutoRotate(final boolean doAutoTransform) {
+ _autoRotate = doAutoTransform;
+ }
+
+ public boolean getAutoRotate() {
+ return _autoRotate;
+ }
+
+ @Override
+ public synchronized void draw(final Renderer r) {
+ if (_textString.length() > 0) {
+ final Camera cam = Camera.getCurrentCamera();
+
+ if (!(_autoScale == AutoScale.Off && _autoFade == AutoFade.Off)) {
+ updateScaleAndAlpha(cam, r);
+ }
+ correctTransform(cam);
+
+ super.draw(r);
+ }
+ }
+
+ /**
+ *
+ * @param cam
+ */
+ public void correctTransform(final Camera cam) {
+ updateWorldTransform(false);
+
+ if (_autoRotate) {
+ // Billboard rotation
+ _look.set(cam.getDirection());
+ _left.set(cam.getLeft()).negateLocal();
+ _rot.fromAxes(_left, _look, cam.getUp());
+ _worldTransform.setRotation(_rot);
+ }
+ _worldTransform.setScale(_localTransform.getScale());
+ }
+
+ /**
+ * Update the text's scale
+ *
+ * @param cam
+ */
+ public void updateScaleAndAlpha(final Camera cam, final Renderer r) {
+ // get our depth distance
+ _look.set(cam.getLocation());
+ _look.negateLocal().addLocal(_worldTransform.getTranslation());
+
+ final double zDepth = cam.getDirection().dot(_look);
+ if (zDepth > cam.getFrustumFar() || zDepth < cam.getFrustumNear()) {
+ // it is out of the picture.
+ return;
+ }
+
+ // calculate the height in world units of the screen at that depth
+ final double heightAtZ;
+ if (cam.getProjectionMode() == ProjectionMode.Parallel) {
+ heightAtZ = cam.getFrustumTop();
+ } else {
+ heightAtZ = zDepth * cam.getFrustumTop() / cam.getFrustumNear();
+ }
+
+ // determine a unit/pixel ratio using height
+ final double screenHeight = cam.getHeight();
+ final double pixelRatio = heightAtZ / screenHeight;
+
+ final double capSize = 1.0 / (_fontScale * _font.getSize());
+
+ // scale value used to maintain uniform size in screen coords.
+ // when depthScale > unitFont, text is far away
+ final double depthScale = 2 * pixelRatio;
+
+ if (_autoScale != AutoScale.Off) {
+ double finalScale = depthScale;
+ if (_autoScale == AutoScale.CapScreenSize) {
+ if (finalScale > capSize) {
+ finalScale = capSize;
+ }
+ }
+ finalScale *= _fontScale;
+ setScale(finalScale, finalScale, -finalScale);
+ }
+
+ // -- adjust alpha -------
+ switch (_autoFade) {
+ case Off:
+ break;
+ case DistanceRange:
+ distanceAlphaFade(_distanceAlphaRange, _look.length());
+ break;
+ case FixedPixelSize:
+ screenSizeCapAlphaFade(1.0 / _fixedPixelAlphaThresh, depthScale, _screenSizeAlphaFalloff);
+ break;
+ case CapScreenSize:
+ screenSizeCapAlphaFade(capSize, depthScale, _screenSizeAlphaFalloff);
+ break;
+ }
+ }
+
+ /**
+ * Set transparency based on native screen size.
+ *
+ * @param capSize
+ * 1/(font point size)
+ * @param depthScale
+ * @param alphaFallof
+ */
+ protected void screenSizeCapAlphaFade(final double capSize, final double depthScale, final float alphaFallof) {
+ if (capSize < depthScale) {
+ final float unit = (float) ((depthScale - capSize) / capSize);
+ float f = alphaFallof - unit;
+ f = (f < 0) ? 0 : f / alphaFallof;
+ final float alpha = _textClr.getAlpha() * f;
+ _tempClr.set(_textClr);
+ _tempClr.setAlpha(alpha);
+ setDefaultColor(_tempClr);
+ } else {
+ setDefaultColor(_textClr);
+ }
+ }
+
+ /**
+ * Set transparency based on distance from camera to text. if (distance < range.x) then opaque, if (distance >
+ * range.y) then transparent, else lerp
+ */
+ protected void distanceAlphaFade(final ReadOnlyVector2 range, final double distance) {
+ float alpha = 1;
+ if (distance > range.getY()) {
+ alpha = 0;
+ } else if (distance > range.getX()) {
+ final float a = (float) (distance - range.getX());
+ final float r = (float) (range.getY() - range.getX());
+ alpha = 1.0f - a / r;
+ }
+ _tempClr.set(_textClr);
+ _tempClr.setAlpha(_textClr.getAlpha() * alpha);
+ setDefaultColor(_tempClr);
+ }
+
+ /** get width in world units */
+ public float getWidth() {
+ return (_size.getXf() * _worldTransform.getScale().getXf());
+ }
+
+ /** get height in world units */
+ public float getHeight() {
+ return (_size.getYf() * _worldTransform.getScale().getYf());
+ }
+
+ protected void addToLineSizes(final float sizeX, final int lineIndex) {
+ if (lineIndex >= _lineWidths.length) { // make sure array is big enough
+ final float[] newLineSizes = new float[_lineWidths.length * 2];
+ System.arraycopy(_lineWidths, 0, newLineSizes, 0, _lineWidths.length);
+ _lineWidths = newLineSizes;
+ }
+ _lineWidths[lineIndex] = sizeX;
+ }
+
+ /**
+ */
+ protected void calculateSize(final String text) {
+ _size.set(0, 0);
+
+ BMFont.Char chr;
+ float cursorX = 0;
+ float cursorY = 0;
+ final float lineHeight = _font.getLineHeight();
+ _lines = 0;
+
+ _lineWidths[0] = 0;
+ final int strLen = _textString.length();
+ for (int i = 0; i < strLen; i++) {
+ final int charVal = _textString.charAt(i);
+ if (charVal == '\n') { // newline special case
+
+ addToLineSizes(cursorX, _lines);
+ _lines++;
+ if (cursorX > _size.getX()) {
+ _size.setX(cursorX);
+ }
+ cursorX = 0;
+ cursorY = _lines * lineHeight;
+ } else if (charVal == '\t') { // tab special case
+ final float tabStop = _tabSize * _font.getMaxCharAdvance();
+ final float stops = 1 + (float) Math.floor(cursorX / tabStop);
+ cursorX = stops * tabStop;
+ } else { // normal character
+ chr = _font.getChar(charVal);
+ int nextVal = 0;
+ if (i < strLen - 1) {
+ nextVal = _textString.charAt(i + 1);
+ }
+ final int kern = _font.getKerning(charVal, nextVal);
+ cursorX += chr.xadvance + kern + _spacing;
+ }
+ }
+ addToLineSizes(cursorX, _lines);
+ if (cursorX > _size.getX()) {
+ _size.setX(cursorX);
+ }
+
+ _size.setY(cursorY + lineHeight);
+ _lines++;
+ }
+
+ /**
+ */
+ protected void calculateAlignmentOffset() {
+ _alignOffset.set(0, 0);
+ if (_align != null) {
+ _alignOffset.setX(_size.getX() * _align.horizontal);
+ _alignOffset.setY(_size.getY() * _align.vertical);
+ }
+ }
+
+ /**
+ * Check whether buffers have sufficient capacity to hold current string values; if not, increase capacity and set
+ * the limit.
+ *
+ * @param text
+ */
+ protected void checkBuffers(final String text) {
+ final int chunkSize = 20;
+ final int vertices = 6 * text.length();
+ final int chunks = 1 + (vertices / chunkSize);
+ final int required = chunks * chunkSize;
+ FloatBuffer vertexBuffer = getMeshData().getVertexBuffer();
+ FloatBuffer texCrdBuffer = getMeshData().getTextureBuffer(0);
+ if (vertexBuffer == null || vertexBuffer.capacity() < required * 3) {
+ vertexBuffer = BufferUtils.createVector3Buffer(required);
+ texCrdBuffer = BufferUtils.createVector2Buffer(required);
+ getMeshData().setVertexBuffer(vertexBuffer);
+ getMeshData().setTextureBuffer(texCrdBuffer, 0);
+ }
+ vertexBuffer.limit(vertices * 3).rewind();
+ texCrdBuffer.limit(vertices * 2).rewind();
+ }
+
+ protected float getJustificationXOffset(final int lineIndex) {
+ float cursorX = 0;
+ switch (_justify) {
+ case Left:
+ cursorX = 0;
+ break;
+ case Center:
+ cursorX = 0.5f * (_size.getXf() - _lineWidths[lineIndex]);
+ break;
+ case Right:
+ cursorX = _size.getXf() - _lineWidths[lineIndex];
+ break;
+ }
+ return cursorX;
+ }
+
+ public BMFont getFont() {
+ return _font;
+ }
+
+ public void setFont(final BMFont font) {
+ _font = font;
+ _font.applyRenderStatesTo(this, _useBlend);
+ setFontScale(_fontScale);
+ setText(_textString);
+ }
+
+ /**
+ * @param useBlend
+ * if true: use alpha blending and use transparent render bucket, else if false: alpha test only and use
+ * opaque render bucket
+ */
+ public void setUseBlend(final boolean useBlend) {
+ _useBlend = useBlend;
+ _font.applyRenderStatesTo(this, _useBlend);
+ }
+
+ public boolean getUseBlend() {
+ return _useBlend;
+ }
+
+ /**
+ * Set text string and recreate geometry
+ */
+ public synchronized void setText(final String text) {
+ if (text == null) {
+ _textString = "";
+ } else {
+ _textString = text;
+ }
+
+ checkBuffers(_textString);
+ calculateSize(_textString);
+ calculateAlignmentOffset();
+
+ final FloatBuffer vertices = getMeshData().getVertexBuffer();
+ final FloatBuffer texCrds = getMeshData().getTextureBuffer(0);
+
+ BMFont.Char chr;
+ final float txW = _font.getTextureWidth();
+ final float txH = _font.getTextureHeight();
+
+ int lineIndex = 0;
+ float cursorX = getJustificationXOffset(lineIndex);
+ float cursorY = 0;
+ final float lineHeight = _font.getLineHeight();
+ float t, b, l, r;
+
+ float alignX = _size.getXf() * _align.horizontal;
+ float alignY = _size.getYf() * _align.vertical;
+ alignX = Math.round(alignX);
+ alignY = Math.round(alignY);
+ alignX += _fixedOffset.getX();
+ alignY += _fixedOffset.getY();
+
+ final int strLen = _textString.length();
+ for (int i = 0; i < strLen; i++) {
+ final int charVal = _textString.charAt(i);
+
+ if (charVal == '\n') { // newline special case
+ lineIndex++;
+ cursorX = getJustificationXOffset(lineIndex);
+ cursorY += lineHeight;
+ addEmptyCharacter(vertices, texCrds);
+ } else if (charVal == '\t') { // tab special case
+ final float tabStop = _tabSize * _font.getMaxCharAdvance();
+ final float stops = 1 + (float) Math.floor(cursorX / tabStop);
+ cursorX = stops * tabStop;
+ addEmptyCharacter(vertices, texCrds);
+ } else { // normal character
+ chr = _font.getChar(charVal);
+
+ // -- vertices -----------------
+ l = alignX + cursorX + chr.xoffset;
+ t = alignY + cursorY + chr.yoffset;
+ r = alignX + cursorX + chr.xoffset + chr.width;
+ b = alignY + cursorY + chr.yoffset + chr.height;
+
+ vertices.put(l).put(0).put(t); // left top
+ vertices.put(l).put(0).put(b); // left bottom
+ vertices.put(r).put(0).put(t); // right top
+ vertices.put(r).put(0).put(t); // right top
+ vertices.put(l).put(0).put(b); // left bottom
+ vertices.put(r).put(0).put(b); // right bottom
+
+ // -- tex coords ----------------
+ l = chr.x / txW;
+ t = chr.y / txH;
+ r = (chr.x + chr.width) / txW;
+ b = (chr.y + chr.height) / txH;
+
+ texCrds.put(l).put(t); // left top
+ texCrds.put(l).put(b); // left bottom
+ texCrds.put(r).put(t); // right top
+ texCrds.put(r).put(t); // right top
+ texCrds.put(l).put(b); // left bottom
+ texCrds.put(r).put(b); // right bottom
+
+ int nextVal = 0;
+ if (i < strLen - 1) {
+ nextVal = _textString.charAt(i + 1);
+ }
+ final int kern = _font.getKerning(charVal, nextVal);
+ cursorX += chr.xadvance + kern + _spacing;
+ }
+ }
+ _meshData.setVertexBuffer(vertices);
+ _meshData.setTextureBuffer(texCrds, 0);
+ _meshData.setIndices(null);
+ }
+
+ // this is inefficient yet incredibly convenient
+ // used for tab and newline
+ private void addEmptyCharacter(final FloatBuffer vertices, final FloatBuffer uvs) {
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ }
+
+ public synchronized String getText() {
+ return _textString;
+ }
+
+ /**
+ * @param align
+ */
+ public void setAlign(final Align align) {
+ _align = align;
+ setText(_textString);
+ }
+
+ public Align getAlign() {
+ return _align;
+ }
+
+ public void setJustify(final Justify justify) {
+ _justify = justify;
+ setText(_textString);
+ }
+
+ public Justify getJustify() {
+ return _justify;
+ }
+
+ /**
+ * set a fixed offset from the alignment center of rotation IN FONT UNITS
+ */
+ public void setFixedOffset(double x, double y) {
+ x *= _font.getSize();
+ y *= _font.getSize();
+ _fixedOffset.set(x, y);
+ setText(_textString);
+ }
+
+ /**
+ * set a fixed offset from the alignment center of rotation IN FONT UNITS
+ */
+ public void setFixedOffset(final Vector2 offset) {
+ final double x = offset.getX() * _font.getSize();
+ final double y = offset.getY() * _font.getSize();
+ _fixedOffset.set(x, y);
+ setText(_textString);
+ }
+
+ public int getLineCount() {
+ return _lines;
+ }
+
+ @Override
+ public BMText makeCopy(final boolean shareGeometricData) {
+ final BMText text = (BMText) super.makeCopy(shareGeometricData);
+
+ // copy our text properties
+ text._font = _font;
+ text._textString = _textString;
+ text._fontScale = _fontScale;
+ text._autoRotate = _autoRotate;
+ text._lines = _lines;
+ text._size.set(_size);
+ System.arraycopy(_lineWidths, 0, text._lineWidths, 0, _lineWidths.length);
+
+ text._textClr.set(_textClr);
+ text._tempClr.set(_tempClr);
+
+ text._autoScale = _autoScale;
+ text._autoFade = _autoFade;
+
+ text._fixedPixelAlphaThresh = _fixedPixelAlphaThresh;
+ text._screenSizeAlphaFalloff = _screenSizeAlphaFalloff;
+ text._distanceAlphaRange.set(_distanceAlphaRange);
+ text._useBlend = _useBlend;
+
+ text._justify = _justify;
+ text._spacing = _spacing;
+
+ text._align = _align;
+ text._alignOffset.set(_alignOffset);
+ text._fixedOffset.set(_fixedOffset);
+
+ // return
+ return text;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java new file mode 100644 index 0000000..0b96e53 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.ui.text; + +import java.util.logging.Logger; + +import com.ardor3d.annotation.SavableFactory; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Matrix3; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.CullState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.ardor3d.util.resource.URLResourceSource; + +@SavableFactory(factoryMethod = "initSavable") +public class BasicText extends BMText { + static Logger logger = Logger.getLogger(BasicText.class.getName()); + + public static BMFont DEFAULT_FONT; + + public static double DEFAULT_FONT_SIZE = 24; + + static { + try { + DEFAULT_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(BasicText.class, + "com/ardor3d/ui/text/arial-24-bold-regular.fnt")), true); + } catch (final Exception ex) { + logger.throwing(BasicText.class.getCanonicalName(), "static font init", ex); + } + } + + public static BasicText initSavable() { + return new BasicText(); + } + + protected BasicText() {} + + public BasicText(final String name, final String text, final BMFont font, final double fontSize) { + super(name, text, font); + getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + setFontScale(fontSize); + setAutoFade(AutoFade.Off); + setAutoScale(AutoScale.Off); + setAutoRotate(false); + setRotation(new Matrix3().fromAngles(-MathUtils.HALF_PI, 0, 0)); + + final ZBufferState zState = new ZBufferState(); + zState.setEnabled(false); + zState.setWritable(false); + setRenderState(zState); + + final CullState cState = new CullState(); + cState.setEnabled(false); + setRenderState(cState); + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + blend.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + blend.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + blend.setTestEnabled(true); + blend.setReference(0f); + blend.setTestFunction(BlendState.TestFunction.GreaterThan); + setRenderState(blend); + + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + updateModelBound(); + } + + public static BasicText createDefaultTextLabel(final String name, final String text, final double fontSize) { + return new BasicText(name, text, DEFAULT_FONT, fontSize); + } + + public static BasicText createDefaultTextLabel(final String name, final String text) { + return new BasicText(name, text, DEFAULT_FONT, DEFAULT_FONT_SIZE); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java new file mode 100644 index 0000000..d851b96 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +public class Ardor3dException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public Ardor3dException() { + super(); + } + + public Ardor3dException(final String desc) { + super(desc); + } + + public Ardor3dException(final Throwable cause) { + super(cause); + } + + public Ardor3dException(final String desc, final Throwable cause) { + super(desc, cause); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java new file mode 100644 index 0000000..cc8a793 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +/** + * Just a simple flag holder for runtime stripping of various ardor3d logging and debugging features. + */ +public class Constants { + + public static boolean updateGraphs = false; + + public static final boolean useStatePools; + + public static final boolean stats; + + public static final boolean trackDirectMemory; + + public static final boolean useMultipleContexts; + + public static final boolean storeSavableImages; + + public static final int maxStatePoolSize; + + public static final boolean useValidatingTransform; + + public static final boolean enableInstancedGeometrySupport; + + static { + boolean hasPropertyAccess = true; + try { + if (System.getSecurityManager() != null) { + System.getSecurityManager().checkPropertiesAccess(); + } + } catch (final SecurityException e) { + hasPropertyAccess = false; + } + + if (hasPropertyAccess) { + stats = (System.getProperty("ardor3d.stats") != null); + trackDirectMemory = (System.getProperty("ardor3d.trackDirect") != null); + useMultipleContexts = (System.getProperty("ardor3d.useMultipleContexts") != null); + useStatePools = (System.getProperty("ardor3d.noStatePools") == null); + storeSavableImages = (System.getProperty("ardor3d.storeSavableImages") != null); + maxStatePoolSize = (System.getProperty("ardor3d.maxStatePoolSize") != null ? Integer.parseInt(System + .getProperty("ardor3d.maxStatePoolSize")) : 11); + + useValidatingTransform = (System.getProperty("ardor3d.disableValidatingTransform") == null); + enableInstancedGeometrySupport = (System.getProperty("ardor3d.enableInstancedGeometrySupport") != null); + } else { + stats = false; + trackDirectMemory = false; + useMultipleContexts = false; + useStatePools = true; + storeSavableImages = false; + maxStatePoolSize = 11; + useValidatingTransform = true; + enableInstancedGeometrySupport = false; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java new file mode 100644 index 0000000..8ee7111 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.AbstractBufferData; +import com.ardor3d.util.scenegraph.DisplayListDelegate; + +public class ContextGarbageCollector { + + private ContextGarbageCollector() {} + + /** + * Handle detecting and scheduling cleanup of OpenGL assets. This method will place delete calls on the task queue + * of appropriate RenderContexts when an asset such as a Texture is determined to no longer be reachable by Java. + * + * @param immediateDelete + * an optional Renderer to use for immediate cleanup when the asset is owned by the current context. In + * general this is best used in single context applications, and null is a perfectly acceptable value. + */ + public static void doRuntimeCleanup(final Renderer immediateDelete) { + TextureManager.cleanExpiredTextures(immediateDelete, null); + AbstractBufferData.cleanExpiredVBOs(immediateDelete); + DisplayListDelegate.cleanExpiredDisplayLists(immediateDelete); + } + + /** + * Handle cleanup of all open OpenGL assets. This method is meant to be used on application shutdown. + * + * @param immediateDelete + * an optional Renderer to use for immediate cleanup when the asset is owned by the current context. In + * general this is best used in single context applications, and null is a perfectly acceptable value. + * However, if there is more than one context or null was passed, you must have all of the contexts + * process at least one more (empty) frame to allow for the final gl calls to be processed. + */ + public static void doFinalCleanup(final Renderer immediateDelete) { + TextureManager.cleanAllTextures(immediateDelete, null); + AbstractBufferData.cleanAllVBOs(immediateDelete); + DisplayListDelegate.cleanAllDisplayLists(immediateDelete); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java new file mode 100644 index 0000000..427b6cd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; + +public class ContextIdReference<T> extends PhantomReference<T> { + + /** + * Keep a strong reference to these objects until their reference is cleared. + */ + private static final List<ContextIdReference<?>> REFS = Lists.newLinkedList(); + + private final Map<Object, Integer> _idCache; + private Integer _singleContextId; + + public ContextIdReference(final T reference, final ReferenceQueue<? super T> queue) { + super(reference, queue); + if (Constants.useMultipleContexts) { + _idCache = new MapMaker().initialCapacity(2).weakKeys().makeMap(); + } else { + _idCache = null; + } + REFS.add(this); + } + + public boolean containsKey(final Object glContext) { + if (Constants.useMultipleContexts) { + return _idCache.containsKey(glContext); + } else { + return true; + } + } + + public Integer getValue(final Object glContext) { + if (Constants.useMultipleContexts) { + return _idCache.get(glContext); + } else { + return _singleContextId; + } + } + + public Integer removeValue(final Object glContext) { + if (Constants.useMultipleContexts) { + return _idCache.remove(glContext); + } else { + final Integer r = _singleContextId; + _singleContextId = 0; + return r; + } + } + + public void put(final Object glContext, final Integer id) { + if (Constants.useMultipleContexts) { + _idCache.put(glContext, id); + } else { + _singleContextId = id; + } + } + + public Set<Object> getContextObjects() { + if (Constants.useMultipleContexts) { + return _idCache.keySet(); + } else { + return null; + } + } + + @Override + public void clear() { + super.clear(); + _singleContextId = 0; + REFS.remove(this); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java new file mode 100644 index 0000000..e4ca2a8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.geom.Debugger; + +/** + * Camera with additional pssm related functionality. + */ +public class DrawableCamera extends Mesh { + private final Camera trackedCamera; + + private final ColorRGBA color; + + private final short pattern; + + /** + * Instantiates a new drawable camera. + */ + public DrawableCamera() { + this(null, new ColorRGBA(0, 1, 1, 1), (short) 0xF000); + } + + /** + * Instantiates a new drawable camera. + * + * @param width + * the width + * @param height + * the height + */ + public DrawableCamera(final Camera camera, final ColorRGBA color, final short pattern) { + super("DrawableCamera"); + trackedCamera = camera; + this.color = color; + this.pattern = pattern; + } + + @Override + public void draw(final Renderer r) { + Debugger.drawCameraFrustum(r, trackedCamera, color, pattern, true); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java new file mode 100644 index 0000000..23833f2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import com.ardor3d.bounding.BoundingBox; +import com.ardor3d.bounding.BoundingSphere; +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.Vector4; +import com.ardor3d.math.type.ReadOnlyMatrix4; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; + +/** + * Camera with additional pssm related functionality. + */ +public class ExtendedCamera extends Camera { + /** The corners of the camera frustum. */ + protected final Vector3[] _corners = new Vector3[8]; + + /** Temporary vector used for storing extents during corner calculations. */ + protected final Vector3 _extents = new Vector3(); + + /** + * Instantiates a new PSSM camera. + */ + public ExtendedCamera() { + this(0, 0); // copy later + } + + /** + * Instantiates a new PSSM camera. + * + * @param width + * the width + * @param height + * the height + */ + public ExtendedCamera(final int width, final int height) { + super(width, height); + init(); + } + + /** + * Instantiates a new PSSM camera. + * + * @param source + * the source + */ + public ExtendedCamera(final Camera source) { + super(source); + init(); + } + + /** + * Initialize structures. + */ + private void init() { + for (int i = 0; i < _corners.length; i++) { + _corners[i] = new Vector3(); + } + } + + /** + * Compress this camera's near and far frustum planes to be smaller if possible, using the given bounds as a + * measure. + * + * @param sceneBounds + * the scene bounds + */ + public void pack(final BoundingVolume sceneBounds) { + final ReadOnlyVector3 center = sceneBounds.getCenter(); + for (int i = 0; i < _corners.length; i++) { + _corners[i].set(center); + } + + if (sceneBounds instanceof BoundingBox) { + final BoundingBox bbox = (BoundingBox) sceneBounds; + bbox.getExtent(_extents); + } else if (sceneBounds instanceof BoundingSphere) { + final BoundingSphere bsphere = (BoundingSphere) sceneBounds; + _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius()); + } + + _corners[0].addLocal(_extents.getX(), _extents.getY(), _extents.getZ()); + _corners[1].addLocal(_extents.getX(), -_extents.getY(), _extents.getZ()); + _corners[2].addLocal(_extents.getX(), _extents.getY(), -_extents.getZ()); + _corners[3].addLocal(_extents.getX(), -_extents.getY(), -_extents.getZ()); + _corners[4].addLocal(-_extents.getX(), _extents.getY(), _extents.getZ()); + _corners[5].addLocal(-_extents.getX(), -_extents.getY(), _extents.getZ()); + _corners[6].addLocal(-_extents.getX(), _extents.getY(), -_extents.getZ()); + _corners[7].addLocal(-_extents.getX(), -_extents.getY(), -_extents.getZ()); + + final ReadOnlyMatrix4 mvMatrix = getModelViewMatrix(); + double optimalCameraNear = Double.MAX_VALUE; + double optimalCameraFar = -Double.MAX_VALUE; + final Vector4 position = Vector4.fetchTempInstance(); + for (int i = 0; i < _corners.length; i++) { + position.set(_corners[i].getX(), _corners[i].getY(), _corners[i].getZ(), 1); + mvMatrix.applyPre(position, position); + + optimalCameraNear = Math.min(-position.getZ(), optimalCameraNear); + optimalCameraFar = Math.max(-position.getZ(), optimalCameraFar); + } + Vector4.releaseTempInstance(position); + + // XXX: use of getFrustumNear and getFrustumFar seems suspicious... + // XXX: It depends on the frustum being reset each update + optimalCameraNear = Math.min(Math.max(getFrustumNear(), optimalCameraNear), getFrustumFar()); + optimalCameraFar = Math.max(optimalCameraNear, Math.min(getFrustumFar(), optimalCameraFar)); + + final double change = optimalCameraNear / _frustumNear; + setFrustumLeft(getFrustumLeft() * change); + setFrustumRight(getFrustumRight() * change); + setFrustumTop(getFrustumTop() * change); + setFrustumBottom(getFrustumBottom() * change); + + setFrustumNear(optimalCameraNear); + setFrustumFar(optimalCameraFar); + } + + public void calculateFrustum() { + calculateFrustum(_frustumNear, _frustumFar); + } + + /** + * Calculate frustum corners and center. + * + * @param fNear + * the near distance + * @param fFar + * the far distance + */ + public void calculateFrustum(final double fNear, final double fFar) { + double fNearPlaneHeight = (_frustumTop - _frustumBottom) * fNear * 0.5 / _frustumNear; + double fNearPlaneWidth = (_frustumRight - _frustumLeft) * fNear * 0.5 / _frustumNear; + + double fFarPlaneHeight = (_frustumTop - _frustumBottom) * fFar * 0.5 / _frustumNear; + double fFarPlaneWidth = (_frustumRight - _frustumLeft) * fFar * 0.5 / _frustumNear; + + if (getProjectionMode() == ProjectionMode.Parallel) { + fNearPlaneHeight = (_frustumTop - _frustumBottom) * 0.5; + fNearPlaneWidth = (_frustumRight - _frustumLeft) * 0.5; + + fFarPlaneHeight = (_frustumTop - _frustumBottom) * 0.5; + fFarPlaneWidth = (_frustumRight - _frustumLeft) * 0.5; + } + + final Vector3 vNearPlaneCenter = Vector3.fetchTempInstance(); + final Vector3 vFarPlaneCenter = Vector3.fetchTempInstance(); + final Vector3 direction = Vector3.fetchTempInstance(); + final Vector3 left = Vector3.fetchTempInstance(); + final Vector3 up = Vector3.fetchTempInstance(); + + direction.set(getDirection()).multiplyLocal(fNear); + vNearPlaneCenter.set(getLocation()).addLocal(direction); + direction.set(getDirection()).multiplyLocal(fFar); + vFarPlaneCenter.set(getLocation()).addLocal(direction); + + left.set(getLeft()).multiplyLocal(fNearPlaneWidth); + up.set(getUp()).multiplyLocal(fNearPlaneHeight); + _corners[0].set(vNearPlaneCenter).subtractLocal(left).subtractLocal(up); + _corners[1].set(vNearPlaneCenter).subtractLocal(left).addLocal(up); + _corners[2].set(vNearPlaneCenter).addLocal(left).addLocal(up); + _corners[3].set(vNearPlaneCenter).addLocal(left).subtractLocal(up); + + left.set(getLeft()).multiplyLocal(fFarPlaneWidth); + up.set(getUp()).multiplyLocal(fFarPlaneHeight); + _corners[4].set(vFarPlaneCenter).subtractLocal(left).subtractLocal(up); + _corners[5].set(vFarPlaneCenter).subtractLocal(left).addLocal(up); + _corners[6].set(vFarPlaneCenter).addLocal(left).addLocal(up); + _corners[7].set(vFarPlaneCenter).addLocal(left).subtractLocal(up); + + Vector3.releaseTempInstance(vNearPlaneCenter); + Vector3.releaseTempInstance(vFarPlaneCenter); + Vector3.releaseTempInstance(direction); + Vector3.releaseTempInstance(left); + Vector3.releaseTempInstance(up); + } + + public Vector3[] getCorners() { + return _corners; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java new file mode 100644 index 0000000..99034b4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * <code>GameTask</code> is used in <code>GameTaskQueue</code> to manage tasks that have yet to be accomplished. + */ +public class GameTask<V> implements Future<V> { + private static final Logger logger = Logger.getLogger(GameTask.class.getName()); + + private final Callable<V> callable; + + private V _result; + private ExecutionException _exception; + private boolean _cancelled, _finished; + private final ReentrantLock _stateLock = new ReentrantLock(); + private final Condition _finishedCondition = _stateLock.newCondition(); + + public GameTask(final Callable<V> callable) { + this.callable = callable; + } + + public boolean cancel(final boolean mayInterruptIfRunning) { + // TODO mayInterruptIfRunning was ignored in previous code, should this param be removed? + _stateLock.lock(); + try { + if (_result != null) { + return false; + } + _cancelled = true; + + _finishedCondition.signalAll(); + + return true; + } finally { + _stateLock.unlock(); + } + } + + public V get() throws InterruptedException, ExecutionException { + _stateLock.lock(); + try { + while (!isDone()) { + _finishedCondition.await(); + } + if (_exception != null) { + throw _exception; + } + return _result; + } finally { + _stateLock.unlock(); + } + } + + public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, + TimeoutException { + _stateLock.lock(); + try { + if (!isDone()) { + _finishedCondition.await(timeout, unit); + } + if (_exception != null) { + throw _exception; + } + if (_result == null) { + throw new TimeoutException("Object not returned in time allocated."); + } + return _result; + } finally { + _stateLock.unlock(); + } + } + + public boolean isCancelled() { + _stateLock.lock(); + try { + return _cancelled; + } finally { + _stateLock.unlock(); + } + } + + public boolean isDone() { + _stateLock.lock(); + try { + return _finished || _cancelled || (_exception != null); + } finally { + _stateLock.unlock(); + } + } + + public Callable<V> getCallable() { + return callable; + } + + public void invoke() { + try { + final V tmpResult = callable.call(); + + _stateLock.lock(); + try { + _result = tmpResult; + _finished = true; + + _finishedCondition.signalAll(); + } finally { + _stateLock.unlock(); + } + } catch (final Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "invoke()", "Exception", e); + + _stateLock.lock(); + try { + _exception = new ExecutionException(e); + + _finishedCondition.signalAll(); + } finally { + _stateLock.unlock(); + } + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java new file mode 100644 index 0000000..3ae77d5 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.RendererCallable; + +/** + * <code>GameTaskQueue</code> is a simple queuing system to enqueue tasks that need to be accomplished in a specific + * thread or phase of the application execution (for example, the OpenGL rendering thread.) Upon sending in a task, the + * caller gets back a Future object useful for retrieving a return from the Callable that was passed in. + * + * @see Future + * @see Callable + */ +public class GameTaskQueue { + + public static final String RENDER = "render"; + public static final String UPDATE = "update"; + + private final ConcurrentLinkedQueue<GameTask<?>> _queue = new ConcurrentLinkedQueue<GameTask<?>>(); + private final AtomicBoolean _executeMultiple = new AtomicBoolean(); + + // Default execution time is 0, which means only 1 task will be executed at a time. + private long _executionTime = 0; + + /** + * The state of this <code>GameTaskQueue</code> if it will execute all enqueued Callables on an execute invokation. + * + * @return boolean + */ + public boolean isExecuteAll() { + return _executeMultiple.get(); + } + + /** + * @param executeMultiple + * if false, only one task at most is executed per call to execute. If true, we will execute as many + * tasks as are available, bounded by the max execution time. + * @see #setExecutionTime(int) + */ + public void setExecuteMultiple(final boolean executeMultiple) { + _executeMultiple.set(executeMultiple); + if (executeMultiple == true) { + _executionTime = Integer.MAX_VALUE; + } + } + + /** + * Sets the minimum amount of time the queue will execute tasks per frame. If this is set, executeMultiple is + * automatically set to true and the execute() loop will execute as many tasks as it can before the execution window + * threshold is passed. Any remaining tasks will be executed in the following frame. + * + * @param msecs + * the maximum number of milliseconds to start tasks. Note that this does not guarantee the tasks will + * finish under this time, only start. + */ + public void setExecutionTime(final int msecs) { + _executionTime = msecs; + _executeMultiple.set(true); + } + + /** + * min time queue is permitted to execute tasks per frame + * + * @return -1 if executeAll is false, else min time allocated for task execution per frame + */ + public long getExecutionTime() { + if (_executeMultiple.get() == false) { + return -1; + } + return _executionTime; + } + + /** + * Adds the Callable to the internal queue to invoked and returns a Future that wraps the return. This is useful for + * checking the status of the task as well as being able to retrieve the return object from Callable asynchronously. + * + * @param <V> + * @param callable + * @return + */ + public <V> Future<V> enqueue(final Callable<V> callable) { + final GameTask<V> task = new GameTask<V>(callable); + _queue.add(task); + return task; + } + + /** + * Execute the tasks from this queue. Note that depending on the queue type, tasks may expect to be run in a certain + * context (for example, the Render queue expects to be run from the Thread owning a GL context.) + */ + public void execute() { + execute(null); + } + + /** + * Execute the tasks from this queue. Note that depending on the queue type, tasks may expect to be run in a certain + * context (for example, the Render queue expects to be run from the Thread owning a GL context.) + */ + public void execute(final Renderer renderer) { + final long beginTime = System.currentTimeMillis(); + long elapsedTime; + GameTask<?> task = _queue.poll(); + do { + if (task == null) { + return; + } + + // Inject the Renderer if correct type of Callable. + if (renderer != null && task.getCallable() instanceof RendererCallable<?>) { + ((RendererCallable<?>) task.getCallable()).setRenderer(renderer); + } + + while (task.isCancelled()) { + task = _queue.poll(); + if (task == null) { + return; + } + } + task.invoke(); + elapsedTime = System.currentTimeMillis() - beginTime; + } while ((_executeMultiple.get()) && (elapsedTime < _executionTime) && ((task = _queue.poll()) != null)); + } + + /** + * Remove all tasks from this queue without executing them. + */ + public void clear() { + _queue.clear(); + } + + /** + * Move the tasks from the given queue to this one. + * + * @param gameTaskQueue + */ + public void enqueueAll(final GameTaskQueue queue) { + _queue.addAll(queue._queue); + queue._queue.clear(); + } + + /** + * @return count of tasks in queue. + */ + public int size() { + return _queue.size(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java new file mode 100644 index 0000000..4f57874 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; + +import com.google.common.collect.MapMaker; + +/** + * <code>GameTaskQueueManager</code> is just a simple Singleton class allowing easy access to task queues. + */ +public final class GameTaskQueueManager { + + private static final Object MAP_LOCK = new Object(); + private static final ConcurrentMap<Object, GameTaskQueueManager> _managers = new MapMaker().weakKeys().makeMap(); + + private final ConcurrentMap<String, GameTaskQueue> _managedQueues = new ConcurrentHashMap<String, GameTaskQueue>(2); + + public static GameTaskQueueManager getManager(final Object key) { + synchronized (MAP_LOCK) { + GameTaskQueueManager manager = _managers.get(key); + if (manager == null) { + manager = new GameTaskQueueManager(); + _managers.put(key, manager); + } + return manager; + } + } + + public static GameTaskQueueManager clearManager(final Object key) { + return _managers.remove(key); + } + + private GameTaskQueueManager() { + addQueue(GameTaskQueue.RENDER, new GameTaskQueue()); + addQueue(GameTaskQueue.UPDATE, new GameTaskQueue()); + } + + public void addQueue(final String name, final GameTaskQueue queue) { + _managedQueues.put(name, queue); + } + + public GameTaskQueue getQueue(final String name) { + return _managedQueues.get(name); + } + + public void moveTasksTo(final GameTaskQueueManager manager) { + for (final String key : _managedQueues.keySet()) { + final GameTaskQueue q = manager.getQueue(key); + final GameTaskQueue mq = _managedQueues.get(key); + if (q != null && mq.size() > 0) { + q.enqueueAll(mq); + } + } + } + + /** + * Clears all tasks from the queues managed by this manager. + */ + public void clearTasks() { + for (final GameTaskQueue q : _managedQueues.values()) { + q.clear(); + } + } + + /** + * This method adds <code>callable</code> to the queue to be invoked in the update() method in the OpenGL thread. + * The Future returned may be utilized to cancel the task or wait for the return object. + * + * @param callable + * @return Future<V> + */ + + public <V> Future<V> update(final Callable<V> callable) { + return getQueue(GameTaskQueue.UPDATE).enqueue(callable); + } + + /** + * This method adds <code>callable</code> to the queue to be invoked in the render() method in the OpenGL thread. + * The Future returned may be utilized to cancel the task or wait for the return object. + * + * @param callable + * @return Future<V> + */ + + public <V> Future<V> render(final Callable<V> callable) { + return getQueue(GameTaskQueue.RENDER).enqueue(callable); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java new file mode 100644 index 0000000..c3c8ec4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.io.BufferedInputStream; +import java.io.DataInput; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * LittleEndianDataInput is a class to read little-endian stored data via a InputStream. All functions work as defined + * in DataInput, but assume they come from a LittleEndian input stream. + */ +public class LittleEndianDataInput implements DataInput { + + private final BufferedInputStream _stream; + + /** + * Number of bytes to read when reading a char... For data meant to be read from C/C++ this is often 1, for Java and + * C# this is usually 2. + */ + public int CHAR_SIZE = 2; + + /** + * Creates a new LittleEndian reader from the given input stream. The stream is wrapped in a BufferedInputStream + * automatically. + * + * @param in + * The input stream to read from. + */ + public LittleEndianDataInput(final InputStream in) { + _stream = new BufferedInputStream(in); + } + + public final int readUnsignedShort() throws IOException { + return (_stream.read() & 0xff) | ((_stream.read() & 0xff) << 8); + } + + /** + * read an unsigned int as a long + */ + public final long readUnsignedInt() throws IOException { + return ((_stream.read() & 0xff) | ((_stream.read() & 0xff) << 8) | ((_stream.read() & 0xff) << 16) | (((long) (_stream + .read() & 0xff)) << 24)); + } + + public final boolean readBoolean() throws IOException { + return (_stream.read() != 0); + } + + public final byte readByte() throws IOException { + return (byte) _stream.read(); + } + + public final int readUnsignedByte() throws IOException { + return _stream.read(); + } + + public final short readShort() throws IOException { + return (short) readUnsignedShort(); + } + + public final char readChar() throws IOException { + return (char) readUnsignedShort(); + } + + public final int readInt() throws IOException { + return ((_stream.read() & 0xff) | ((_stream.read() & 0xff) << 8) | ((_stream.read() & 0xff) << 16) | ((_stream + .read() & 0xff) << 24)); + } + + public final long readLong() throws IOException { + return ((_stream.read() & 0xff) | ((long) (_stream.read() & 0xff) << 8) + | ((long) (_stream.read() & 0xff) << 16) | ((long) (_stream.read() & 0xff) << 24) + | ((long) (_stream.read() & 0xff) << 32) | ((long) (_stream.read() & 0xff) << 40) + | ((long) (_stream.read() & 0xff) << 48) | ((long) (_stream.read() & 0xff) << 56)); + } + + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public final void readFully(final byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + public final void readFully(final byte b[], final int off, final int len) throws IOException { + // this may look over-complicated, but the problem is that the InputStream.read() methods are + // not guaranteed to fill up the buffer you pass to it. So we need to loop until we have filled + // up the buffer or until we reach the end of the file. + + final int bytesRead = _stream.read(b, off, len); + + if (bytesRead == -1) { + throw new EOFException("EOF reached"); + } + + if (bytesRead < len) { + // we didn't get all the data we wanted, so read some more + readFully(b, off + bytesRead, len - bytesRead); + } + } + + public final int skipBytes(final int n) throws IOException { + return (int) _stream.skip(n); + } + + public final String readLine() throws IOException { + throw new IOException("Unsupported operation"); + } + + public final String readUTF() throws IOException { + throw new IOException("Unsupported operation"); + } + + public final void close() throws IOException { + _stream.close(); + } + + public final int available() throws IOException { + return _stream.available(); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java new file mode 100644 index 0000000..33ced8f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java @@ -0,0 +1,227 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.io.DataInput; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.InvalidMarkException; +import java.nio.charset.Charset; + +import com.ardor3d.util.export.ByteUtils; + +/** + * Utility class useful for reading little-endian stored data in a random access fashion. All functions work as defined + * in DataInput, but assume they come from a LittleEndian input stream. + * + * <p> + * Note: random access is implemented by reading the entire stream into memory. + * </p> + */ +public class LittleEndianRandomAccessDataInput implements DataInput { + private final ByteBuffer _contents; + + /** + * Number of bytes to read when reading a char... For data meant to be read from C/C++ this is often 1, for Java and + * C# this is usually 2. + */ + public int CHAR_SIZE = 2; + + /** + * Creates a new LittleEndian reader from the given input stream. Note that this stream is loaded completely into + * memory. + * + * @param in + * The stream to read from. + */ + public LittleEndianRandomAccessDataInput(final InputStream in) throws IOException { + _contents = ByteBuffer.wrap(ByteUtils.getByteContent(in)); + } + + /** + * Creates a new LittleEndian reader from the given byte buffer. Note that this byte buffer is not cloned or copied, + * so take care not to alter it during read. This constructor is useful for working with memory-mapped files. + * + * @param contents + * The contents to read from. + */ + public LittleEndianRandomAccessDataInput(final ByteBuffer contents) throws IOException { + _contents = contents; + } + + public final int readUnsignedShort() throws IOException { + return (readByte() & 0xff) | ((readByte() & 0xff) << 8); + } + + public final long readUnsignedInt() throws IOException { + return ((readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | (((long) (readByte() & 0xff)) << 24)); + } + + public final boolean readBoolean() throws IOException { + return (readByte() != 0); + } + + public final byte readByte() throws IOException { + return _contents.get(); + } + + public final int readUnsignedByte() throws IOException { + return readByte() & 0xff; + } + + public final short readShort() throws IOException { + return (short) readUnsignedShort(); + } + + public final char readChar() throws IOException { + return (char) readUnsignedShort(); + } + + public final int readInt() throws IOException { + return ((readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 24)); + } + + public final long readLong() throws IOException { + return ((readByte() & 0xff) | ((long) (readByte() & 0xff) << 8) | ((long) (readByte() & 0xff) << 16) + | ((long) (readByte() & 0xff) << 24) | ((long) (readByte() & 0xff) << 32) + | ((long) (readByte() & 0xff) << 40) | ((long) (readByte() & 0xff) << 48) | ((long) (readByte() & 0xff) << 56)); + } + + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public final void readFully(final byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + public final void readFully(final byte b[], final int off, final int len) throws IOException { + if (len - off + _contents.position() > _contents.capacity()) { + throw new EOFException("EOF reached"); + } else { + _contents.get(b, off, len); + } + } + + public final int skipBytes(final int n) throws IOException { + if (_contents.remaining() >= n) { + _contents.position(_contents.position() + n); + return n; + } + final int skipped = _contents.remaining(); + _contents.position(_contents.limit()); + return skipped; + } + + /** + * Sets a mark at the current position in the underlying buffer. This position can be returned to by calling reset. + * + * @return this object + */ + public final LittleEndianRandomAccessDataInput mark() { + _contents.mark(); + return this; + } + + /** + * Seeks to the position of the last mark. The mark is not changed or discarded. + * + * @return this object + * @throws InvalidMarkException + * if mark was not previously called. + */ + public final LittleEndianRandomAccessDataInput reset() { + _contents.reset(); + return this; + } + + /** + * Unsupported. + * + * @throws IOException + * if this method is called. + */ + public final String readLine() throws IOException { + throw new IOException("operation unsupported."); + } + + /** + * Unsupported. + * + * @throws IOException + * if this method is called. + */ + public final String readUTF() throws IOException { + throw new IOException("operation unsupported."); + } + + /** + * Reads a specified number of bytes to form a string. The length of the string (number of characters) is required + * to notify when reading should stop. The index is increased the number of characters read. + * + * @param size + * the length of the string to read. + * @param charset + * the charset used to convert the bytes to a string. + * @return the string read. + * @throws IOException + * if EOS/EOF is reached before "size" number of bytes are read. + */ + public String readString(final int size, final Charset charset) throws IOException { + final int start = position(); + final byte[] content = new byte[size]; + readFully(content); + seek(start + size); + int indexOfNullByte = size; + // Look for zero terminated string from byte array + for (int i = 0; i < size; i++) { + if (content[i] == 0) { + indexOfNullByte = i; + break; + } + } + final String s = new String(content, 0, indexOfNullByte, charset); + return s; + } + + /** + * Reads a specified number of bytes to form a string. The length of the string (number of characters) is required + * to notify when reading should stop. The index is increased the number of characters read. Will use the platform's + * default Charset to convert the bytes to string. + * + * @param size + * the length of the string to read. + * @return the string read. + * @throws IOException + * if EOS/EOF is reached before "size" number of bytes are read. + */ + public String readString(final int size) throws IOException { + return readString(size, Charset.defaultCharset()); + } + + public final void seek(final int pos) throws IOException { + _contents.position(pos); + } + + public int position() { + return _contents.position(); + } + + public int capacity() { + return _contents.capacity(); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java new file mode 100644 index 0000000..281add4 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java @@ -0,0 +1,59 @@ +/** +/ * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +/** + * <code>ReadOnlyTimer</code> is the base interface for all Ardor3D timer implementations. Used throughout Ardor3D for + * framerate and time dependent calculations. + */ +public interface ReadOnlyTimer { + /** + * Get elapsed time in seconds since this timer was created or reset. + * + * @see #getTime() + * + * @return Time in seconds + */ + double getTimeInSeconds(); + + /** + * Get elapsed time since this timer was created or reset, in the resolution specified by the implementation + * (usually in nanoseconds). + * + * @see #getResolution() + * @see #getTimeInSeconds() + * + * @return Time in resolution specified by implementation + */ + long getTime(); + + /** + * Get the resolution used by this timer. Nanosecond resolution would return 10^9 + * + * @return Timer resolution + */ + long getResolution(); + + /** + * Get the current number of frames per second (fps). + * + * @return Current frames per second (fps) + */ + double getFrameRate(); + + /** + * Get the time elapsed between the latest two frames, in seconds. + * + * @return Time between frames, in seconds + */ + double getTimePerFrame(); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java new file mode 100644 index 0000000..74adc79 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.List; + +import com.google.common.collect.Lists; + +public class SimpleContextIdReference<T> extends PhantomReference<T> { + + /** + * Keep a string reference to these objects until their reference is cleared. + */ + private static final List<SimpleContextIdReference<?>> REFS = Lists.newLinkedList(); + + private final int _id; + private final Object _glContext; + + public SimpleContextIdReference(final T reference, final ReferenceQueue<? super T> queue, final int id, + final Object glContext) { + super(reference, queue); + REFS.add(this); + _id = id; + _glContext = glContext; + } + + @Override + public void clear() { + super.clear(); + REFS.remove(this); + } + + public int getId() { + return _id; + } + + public Object getGlContext() { + return _glContext; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java new file mode 100644 index 0000000..da15d1b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.util.Comparator; + +/** + * Shell and merge sort implementations with the goal of reducing garbage and allowing tuning. + */ +public abstract class SortUtil { + + /** + * The size at or below which we will use shell sort instead of the system sort. + */ + public static int SHELL_SORT_THRESHOLD = 17; + + /** + * <p> + * Merge sorts the supplied data, in the given range, using the given comparator. + * </p> + * <p> + * <b>Note: this internally creates a temporary copy of the array to use as work space during sort.</b> + * </p> + * + * @param source + * the array to sort. Will hold the sorted array on completion. + * @param left + * the left-most index of our sort range. + * @param right + * the right-most index of our sort range. + * @param comp + * our object Comparator + */ + @SuppressWarnings("unchecked") + public static <T> void msort(final T[] source, final int left, final int right, final Comparator<? super T> comp) { + final T[] copy = (T[]) new Object[source.length]; + System.arraycopy(source, 0, copy, 0, source.length); + msort(copy, source, left, right, comp); + } + + /** + * <p> + * Merge sorts the supplied data, in the given range, using the given comparator. + * </p> + * + * @param sourceCopy + * contains the elements to be sorted and acts as a work space for the sort. + * @param destinationCopy + * contains the elements to be sorted and will hold the fully sorted array when complete. + * @param left + * the left-most index of our sort range. + * @param right + * the right-most index of our sort range. + * @param comp + * our object Comparator + */ + public static <T> void msort(final T[] source, final T[] copy, final int left, final int right, + final Comparator<? super T> comp) { + // use an insertion sort on small arrays to avoid recursion down to 1:1 + final int length = right - left + 1; + if (length <= SHELL_SORT_THRESHOLD) { + // insertion sort in place + shellSort(copy, left, right, comp); + // copy into destination + return; + } + + // recursively sort each half of array + final int mid = (left + right) >> 1; + msort(copy, source, left, mid, comp); + msort(copy, source, mid + 1, right, comp); + + // merge the sorted halves + merge(source, copy, left, mid, right, comp); + } + + /** + * Performs a merge on two sets of data stored in source, represented by the ranges formed by [left, mid] and + * [mid+1, right]. Stores the result in destination. + * + * @param source + * our source data + * @param destination + * the array to store our result in + * @param left + * @param mid + * @param right + * @param comp + * our object Comparator + */ + protected static <T> void merge(final T[] source, final T[] destination, final int left, final int mid, + final int right, final Comparator<? super T> comp) { + int i = left, j = mid + 1; + + for (int k = left; k <= right; k++) { + if (i == mid + 1) { + destination[k] = source[j++]; + continue; + } else if (j == right + 1) { + destination[k] = source[i++]; + continue; + } else { + destination[k] = comp.compare(source[i], source[j]) <= 0 ? source[i++] : source[j++]; + } + } + } + + /** + * Performs an in-place shell sort (extension of insertion sort) of the provided data. + * + * @param array + * our source data + * @param left + * the left index of the range to sort + * @param right + * the right index (inclusive) of the range to sort + * @param comp + * our object Comparator + */ + public static <T> void shellSort(final T[] array, final int left, final int right, final Comparator<? super T> comp) { + int h; + for (h = 1; h <= (right - 1) / 9; h = 3 * h + 1) { + ; + } + for (; h > 0; h /= 3) { + for (int i = left + h; i <= right; i++) { + int j = i; + final T val = array[i]; + while (j >= left + h && comp.compare(val, array[j - h]) < 0) { + array[j] = array[j - h]; + j -= h; + } + array[j] = val; + } + } + } + + /** + * Performs an in-place shell sort (extension of insertion sort) of the provided data. + * + * @param array + * our source data + * @param left + * the left index of the range to sort + * @param right + * the right index (inclusive) of the range to sort + */ + public static <T extends Comparable<T>> void shellSort(final T[] array, final int left, final int right) { + int h; + for (h = 1; h <= (right - 1) / 9; h = 3 * h + 1) { + ; + } + for (; h > 0; h /= 3) { + for (int i = left + h; i <= right; i++) { + int j = i; + final T val = array[i]; + while (j >= left + h && val.compareTo(array[j - 1]) < 0) { + array[j] = array[j - h]; + j -= h; + } + array[j] = val; + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java new file mode 100644 index 0000000..a8b7bb9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java @@ -0,0 +1,417 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import com.ardor3d.annotation.SavableFactory; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.resource.ResourceSource; +import com.google.common.collect.Lists; + +/** + * <code>TextureKey</code> provides a way for the TextureManager to cache and retrieve <code>Texture</code> objects. + */ +@SavableFactory(factoryMethod = "initSavable") +final public class TextureKey implements Savable { + + /** The source of the image used in this Texture. */ + protected ResourceSource _source = null; + + /** Whether we had asked the loader of the image to flip vertically. */ + protected boolean _flipped; + + /** The stored format of our image. */ + protected TextureStoreFormat _format = TextureStoreFormat.GuessCompressedFormat; + + /** + * An optional id, used to differentiate keys where the rest of the values are the same. RTT operations are a good + * example. + */ + protected String _id; + + /** + * The minification filter used on our texture (generally determines what mipmaps are created - if any - and how.) + */ + protected Texture.MinificationFilter _minFilter = MinificationFilter.Trilinear; + + /** + * In multi-context rendering, this is used to track if this texture is in need of updating. An entry in this list + * means yes for the given Object (usually a RenderContext's glContextRep). + */ + private final List<WeakReference<Object>> _dirtyContexts; + + /** + * In single-context rendering this is used to track if this texture is in need of updating. + */ + private boolean _dirty; + + /** cache of OpenGL context specific texture ids for the associated texture. */ + protected final transient ContextIdReference<TextureKey> _idCache = new ContextIdReference<TextureKey>(this, + TextureManager.getRefQueue()); + + /** cached hashcode value. */ + protected transient int _code = Integer.MAX_VALUE; + + /** cache of texturekey objects allowing us to find an existing texture key. */ + protected static final List<WeakReference<TextureKey>> _keyCache = Lists.newLinkedList(); + + private static final Integer ZERO = new Integer(0); + + /** RTT code use */ + private static AtomicInteger _uniqueTK = new AtomicInteger(Integer.MIN_VALUE); + + /** DO NOT USE. FOR INTERNAL USE ONLY */ + protected TextureKey() { + if (Constants.useMultipleContexts) { + _dirtyContexts = Lists.newArrayList(); + } else { + _dirtyContexts = null; + } + } + + /** DO NOT USE. FOR INTERNAL USE ONLY */ + public static TextureKey initSavable() { + return new TextureKey(); + } + + public void setDirty() { + if (Constants.useMultipleContexts) { + synchronized (_dirtyContexts) { + _dirtyContexts.clear(); + // grab all contexts we currently have ids for and add them all as dirty + for (final Object context : _idCache.getContextObjects()) { + final WeakReference<Object> ref = new WeakReference<Object>(context); + _dirtyContexts.add(ref); + } + } + } else { + _dirty = true; + } + } + + public boolean isDirty(final Object glContext) { + if (Constants.useMultipleContexts) { + synchronized (_dirtyContexts) { + // check if we are empty... + if (_dirtyContexts.isEmpty()) { + return false; + } else { + WeakReference<Object> ref; + Object check; + // look for a matching reference + for (final Iterator<WeakReference<Object>> it = _dirtyContexts.iterator(); it.hasNext();) { + ref = it.next(); + check = ref.get(); + if (check == null) { + // found empty, clean up + it.remove(); + continue; + } + + if (check.equals(glContext)) { + // found match, return true + return true; + } + } + } + return false; + } + } else { + return _dirty; + } + } + + public void setClean(final Object glContext) { + if (Constants.useMultipleContexts) { + synchronized (_dirtyContexts) { + if (!_dirtyContexts.isEmpty()) { + WeakReference<Object> ref; + Object check; + for (final Iterator<WeakReference<Object>> it = _dirtyContexts.iterator(); it.hasNext();) { + ref = it.next(); + check = ref.get(); + if (check != null && check.equals(glContext)) { + it.remove(); + return; + } + } + } + } + } else { + _dirty = false; + } + } + + /** + * Get a new unique TextureKey. This is meant for use by RTT and other situations where we know we are making a + * unique texture. + * + * @param minFilter + * our minification filter value. + * @return the new TextureKey + */ + public static synchronized TextureKey getRTTKey(final MinificationFilter minFilter) { + int val = _uniqueTK.addAndGet(1); + if (val == Integer.MAX_VALUE) { + _uniqueTK.set(Integer.MIN_VALUE); + val = Integer.MIN_VALUE; + } + return getKey(null, false, TextureStoreFormat.GuessCompressedFormat, "RTT_" + val, minFilter); + } + + public static synchronized TextureKey getKey(final ResourceSource source, final boolean flipped, + final TextureStoreFormat storeFormat, final Texture.MinificationFilter minFilter) { + return getKey(source, flipped, storeFormat, null, minFilter); + } + + public static synchronized TextureKey getKey(final ResourceSource source, final boolean flipped, + final TextureStoreFormat storeFormat, final String id, final Texture.MinificationFilter minFilter) { + final TextureKey key = new TextureKey(); + + key._source = source; + key._flipped = flipped; + key._minFilter = minFilter; + key._format = storeFormat; + key._id = id; + key._code = Integer.MAX_VALUE; + + { + WeakReference<TextureKey> ref; + TextureKey check; + for (final Iterator<WeakReference<TextureKey>> it = _keyCache.iterator(); it.hasNext();) { + ref = it.next(); + check = ref.get(); + if (check == null) { + // found empty, clean up + it.remove(); + continue; + } + + if (check.equals(key)) { + // found match, return + return check; + } + } + } + + // not found + _keyCache.add(new WeakReference<TextureKey>(key)); + return key; + } + + public static synchronized boolean clearKey(final TextureKey key) { + WeakReference<TextureKey> ref; + TextureKey check; + for (final Iterator<WeakReference<TextureKey>> it = _keyCache.iterator(); it.hasNext();) { + ref = it.next(); + check = ref.get(); + if (check != null && check.equals(key)) { + it.remove(); + return true; + } + } + return false; + } + + /** + * @param glContext + * the object representing the OpenGL context a texture belongs to. See + * {@link RenderContext#getGlContextRep()} + * @return the texture id of a texture in the given context. If the texture is not found in the given context, 0 is + * returned. + */ + public Integer getTextureIdForContext(final Object glContext) { + final Integer id = _idCache.getValue(glContext); + if (id != null) { + return id; + } + return ZERO; + } + + /** + * @return a Set of context objects that currently reference this texture. + */ + public Set<Object> getContextObjects() { + return _idCache.getContextObjects(); + } + + /** + * <p> + * Removes any texture id for this texture for the given OpenGL context. + * </p> + * <p> + * Note: This does not remove the texture from the card and is provided for use by code that does remove textures + * from the card. + * </p> + * + * @param glContext + * the object representing the OpenGL context this texture belongs to. See + * {@link RenderContext#getGlContextRep()} + */ + public void removeFromIdCache(final Object glContext) { + _idCache.removeValue(glContext); + } + + /** + * <p> + * Clears all currently associated texture ids for this texture. + * </p> + * <p> + * Note: This does not remove the texture from the card and is provided for use by code that does remove textures + * from the card. + * </p> + */ + public void removeFromIdCache() { + _idCache.clear(); + } + + /** + * Sets the id for a texture in regards to the given OpenGL context. + * + * @param glContext + * the object representing the OpenGL context a texture belongs to. See + * {@link RenderContext#getGlContextRep()} + * @param textureId + * the texture id of a texture. To be valid, this must not be 0. + * @throws IllegalArgumentException + * if textureId is equal to 0. + */ + public void setTextureIdForContext(final Object glContext, final int textureId) { + if (textureId == 0) { + throw new IllegalArgumentException("textureId must != 0"); + } + + _idCache.put(glContext, textureId); + } + + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof TextureKey)) { + return false; + } + + final TextureKey that = (TextureKey) other; + if (_source == null) { + if (that._source != null) { + return false; + } + } else if (!_source.equals(that._source)) { + return false; + } + + if (_id == null && that._id != null) { + return false; + } else if (_id != null && !_id.equals(that._id)) { + return false; + } + + if (_minFilter != that._minFilter) { + return false; + } + + if (_format != that._format) { + return false; + } + + if (_flipped != that._flipped) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + if (_code == Integer.MAX_VALUE) { + _code = 17; + + _code += 31 * _code + (_source != null ? _source.hashCode() : 0); + _code += 31 * _code + (_id != null ? _id.hashCode() : 0); + _code += 31 * _code + _minFilter.hashCode(); + _code += 31 * _code + _format.hashCode(); + _code += 31 * _code + (_flipped ? 1 : 0); + } + return _code; + } + + public Texture.MinificationFilter getMinificationFilter() { + return _minFilter; + } + + public TextureStoreFormat getFormat() { + return _format; + } + + /** + * @return Returns the flipped. + */ + public boolean isFlipped() { + return _flipped; + } + + /** + * @return Returns the source. + */ + public ResourceSource getSource() { + return _source; + } + + public String getId() { + return _id; + } + + @Override + public String toString() { + final String x = "tkey: src:" + _source + " flip: " + _flipped + " code: " + hashCode() + " imageType: " + + _format + " id: " + _id; + return x; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public Class<? extends TextureKey> getClassTag() { + return this.getClass(); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_source, "source", null); + capsule.write(_flipped, "flipped", false); + capsule.write(_format, "format", TextureStoreFormat.GuessCompressedFormat); + capsule.write(_minFilter, "minFilter", MinificationFilter.Trilinear); + capsule.write(_id, "id", null); + } + + public void read(final InputCapsule capsule) throws IOException { + _source = (ResourceSource) capsule.readSavable("source", null); + _flipped = capsule.readBoolean("flipped", false); + _format = capsule.readEnum("format", TextureStoreFormat.class, TextureStoreFormat.GuessCompressedFormat); + _minFilter = capsule.readEnum("minFilter", MinificationFilter.class, MinificationFilter.Trilinear); + _id = capsule.readString("id", null); + _code = Integer.MAX_VALUE; + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java new file mode 100644 index 0000000..8a39852 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java @@ -0,0 +1,483 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import com.ardor3d.annotation.MainThread; +import com.ardor3d.image.Image; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.Texture3D; +import com.ardor3d.image.TextureCubeMap; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.image.util.ImageLoaderUtil; +import com.ardor3d.image.util.ImageUtils; +import com.ardor3d.renderer.ContextCleanListener; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.RendererCallable; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.ardor3d.util.resource.ResourceSource; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Multimap; + +/** + * <code>TextureManager</code> provides static methods for building or retrieving a <code>Texture</code> object from + * cache. + */ +final public class TextureManager { + private static final Logger logger = Logger.getLogger(TextureManager.class.getName()); + + private static Map<TextureKey, Texture> _tCache = new MapMaker().weakKeys().weakValues().makeMap(); + + private static ReferenceQueue<TextureKey> _textureRefQueue = new ReferenceQueue<TextureKey>(); + + static { + ContextManager.addContextCleanListener(new ContextCleanListener() { + public void cleanForContext(final RenderContext renderContext) { + TextureManager.cleanAllTextures(null, renderContext, null); + } + }); + } + + private TextureManager() {} + + /** + * Loads a texture by attempting to locate the given name using ResourceLocatorTool. + * + * @param name + * the name of the texture image. + * @param minFilter + * the filter for the near values. Used to determine if we should generate mipmaps. + * @param flipVertically + * If true, the image is flipped vertically during image loading. + * @return the loaded texture. + */ + public static Texture load(final String name, final Texture.MinificationFilter minFilter, + final boolean flipVertically) { + return load(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, name), minFilter, + TextureStoreFormat.GuessNoCompressedFormat, flipVertically); + } + + /** + * Loads a texture by attempting to locate the given name using ResourceLocatorTool. + * + * @param name + * the name of the texture image. + * @param minFilter + * the filter for the near values. Used to determine if we should generate mipmaps. + * @param format + * the specific format to use when storing this texture on the card. + * @param flipVertically + * If true, the image is flipped vertically during image loading. + * @return the loaded texture. + */ + public static Texture load(final String name, final Texture.MinificationFilter minFilter, + final TextureStoreFormat format, final boolean flipVertically) { + return load(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, name), minFilter, format, + flipVertically); + } + + /** + * Loads a texture from the given source. + * + * @param source + * the source of the texture image. + * @param minFilter + * the filter for the near values. Used to determine if we should generate mipmaps. + * @param flipVertically + * If true, the image is flipped vertically during image loading. + * @return the loaded texture. + */ + public static Texture load(final ResourceSource source, final Texture.MinificationFilter minFilter, + final boolean flipVertically) { + return load(source, minFilter, TextureStoreFormat.GuessNoCompressedFormat, flipVertically); + } + + /** + * Loads a texture from the given source. + * + * @param source + * the source of the texture image. + * @param minFilter + * the filter for the near values. Used to determine if we should generate mipmaps. + * @param format + * the specific format to use when storing this texture on the card. + * @param flipVertically + * If true, the image is flipped vertically during image loading. + * @return the loaded texture. + */ + public static Texture load(final ResourceSource source, final Texture.MinificationFilter minFilter, + final TextureStoreFormat format, final boolean flipVertically) { + + if (null == source) { + logger.warning("Could not load image... source was null. defaultTexture used."); + return TextureState.getDefaultTexture(); + } + + final TextureKey tkey = TextureKey.getKey(source, flipVertically, format, minFilter); + + return loadFromKey(tkey, null, null); + } + + /** + * Creates a texture from a given Ardor3D Image object. + * + * @param image + * the Ardor3D image. + * @param minFilter + * the filter for the near values. Used to determine if we should generate mipmaps. + * @return the loaded texture. + */ + public static Texture loadFromImage(final Image image, final Texture.MinificationFilter minFilter) { + return loadFromImage(image, minFilter, TextureStoreFormat.GuessNoCompressedFormat); + } + + /** + * Creates a texture from a given Ardor3D Image object. + * + * @param image + * the Ardor3D image. + * @param minFilter + * the filter for the near values. Used to determine if we should generate mipmaps. + * @param format + * the specific format to use when storing this texture on the card. + * @return the loaded texture. + */ + public static Texture loadFromImage(final Image image, final Texture.MinificationFilter minFilter, + final TextureStoreFormat format) { + final TextureKey key = TextureKey.getKey(null, false, format, "img_" + image.hashCode(), minFilter); + return loadFromKey(key, image, null); + } + + /** + * Load a texture from the given TextureKey. If imageData is given, use that, otherwise load it using the key's + * source information. If store is given, populate and return that Texture object. + * + * @param tkey + * our texture key. Must not be null. + * @param imageData + * optional Image data. If present, this is used instead of loading from source. + * @param store + * if not null, this Texture object is populated and returned instead of a new Texture object. + * @return the resulting texture. + */ + public static Texture loadFromKey(final TextureKey tkey, final Image imageData, final Texture store) { + if (tkey == null) { + logger.warning("TextureKey is null, cannot load"); + return TextureState.getDefaultTexture(); + } + + Texture result = store; + + // First look for the texture using the supplied key + final Texture cache = findCachedTexture(tkey); + + if (cache != null) { + // look into cache. + if (result == null) { + result = cache.createSimpleClone(); + if (result.getTextureKey() == null) { + result.setTextureKey(tkey); + } + return result; + } + cache.createSimpleClone(result); + return result; + } + + Image img = imageData; + if (img == null) { + img = ImageLoaderUtil.loadImage(tkey.getSource(), tkey.isFlipped()); + } + + if (null == img) { + logger.warning("(image null) Could not load: " + tkey.getSource()); + return TextureState.getDefaultTexture(); + } + + // Default to Texture2D + if (result == null) { + if (img.getDataSize() == 6) { + result = new TextureCubeMap(); + } else if (img.getDataSize() > 1) { + result = new Texture3D(); + } else { + result = new Texture2D(); + } + } + + result.setTextureKey(tkey); + result.setImage(img); + result.setMinificationFilter(tkey.getMinificationFilter()); + result.setTextureStoreFormat(ImageUtils.getTextureStoreFormat(tkey.getFormat(), result.getImage())); + + // Cache the no-context version + addToCache(result); + return result; + } + + /** + * Add a given texture to the cache + * + * @param texture + * our texture + */ + public static void addToCache(final Texture texture) { + if (TextureState.getDefaultTexture() == null + || (texture != TextureState.getDefaultTexture() && texture.getImage() != TextureState + .getDefaultTextureImage())) { + _tCache.put(texture.getTextureKey(), texture); + } + } + + /** + * Locate a texture in the cache by key + * + * @param textureKey + * our key + * @return the texture, or null if not found. + */ + public static Texture findCachedTexture(final TextureKey textureKey) { + return _tCache.get(textureKey); + } + + public static Texture removeFromCache(final TextureKey tk) { + return _tCache.remove(tk); + } + + /** + * Delete all textures from card. This will gather all texture ids believed to be on the card and try to delete + * them. If a deleter is passed in, textures that are part of the currently active context (if one is active) will + * be deleted immediately. If a deleter is not passed in, we do not have an active context, or we encounter textures + * that are not part of the current context, then we will queue those textures to be deleted later using the + * GameTaskQueueManager. + * + * @param deleter + * if not null, this renderer will be used to immediately delete any textures in the currently active + * context. All other textures will be queued to delete in their own contexts. + */ + public static void cleanAllTextures(final Renderer deleter) { + cleanAllTextures(deleter, null); + } + + /** + * Delete all textures from card. This will gather all texture ids believed to be on the card and try to delete + * them. If a deleter is passed in, textures that are part of the currently active context (if one is active) will + * be deleted immediately. If a deleter is not passed in, we do not have an active context, or we encounter textures + * that are not part of the current context, then we will queue those textures to be deleted later using the + * GameTaskQueueManager. + * + * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context. + * These objects may be used to discover when the deletion tasks have all completed. + * + * @param deleter + * if not null, this renderer will be used to immediately delete any textures in the currently active + * context. All other textures will be queued to delete in their own contexts. + * @param futureStore + * if not null, this map will be populated with any Future task handles created during cleanup. + */ + public static void cleanAllTextures(final Renderer deleter, final Map<Object, Future<Void>> futureStore) { + // gather up expired textures... these don't exist in our cache + Multimap<Object, Integer> idMap = gatherGCdIds(); + + // Walk through the cached items and gather those too. + for (final TextureKey key : _tCache.keySet()) { + // possibly lazy init + if (idMap == null) { + idMap = ArrayListMultimap.create(); + } + + if (Constants.useMultipleContexts) { + final Set<Object> contextObjects = key.getContextObjects(); + for (final Object o : contextObjects) { + // Add id to map + idMap.put(o, key.getTextureIdForContext(o)); + } + } else { + idMap.put(ContextManager.getCurrentContext().getGlContextRep(), key.getTextureIdForContext(null)); + } + key.removeFromIdCache(); + } + + // delete the ids + if (idMap != null && !idMap.isEmpty()) { + handleTextureDelete(deleter, idMap, futureStore); + } + } + + /** + * Deletes all textures from card for a specific gl context. This will gather all texture ids believed to be on the + * card for the given context and try to delete them. If a deleter is passed in, textures that are part of the + * currently active context (if one is active) will be deleted immediately. If a deleter is not passed in, we do not + * have an active context, or we encounter textures that are not part of the current context, then we will queue + * those textures to be deleted later using the GameTaskQueueManager. + * + * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context. + * These objects may be used to discover when the deletion tasks have all completed. + * + * @param deleter + * if not null, this renderer will be used to immediately delete any textures in the currently active + * context. All other textures will be queued to delete in their own contexts. + * @param context + * the context to delete for. + * @param futureStore + * if not null, this map will be populated with any Future task handles created during cleanup. + */ + public static void cleanAllTextures(final Renderer deleter, final RenderContext context, + final Map<Object, Future<Void>> futureStore) { + // gather up expired textures... these don't exist in our cache + Multimap<Object, Integer> idMap = gatherGCdIds(); + + final Object glRep = context.getGlContextRep(); + // Walk through the cached items and gather those too. + for (final TextureKey key : _tCache.keySet()) { + // possibly lazy init + if (idMap == null) { + idMap = ArrayListMultimap.create(); + } + + final Integer id = key.getTextureIdForContext(glRep); + if (id != 0) { + idMap.put(context.getGlContextRep(), id); + key.removeFromIdCache(glRep); + } + } + + // delete the ids + if (!idMap.isEmpty()) { + handleTextureDelete(deleter, idMap, futureStore); + } + } + + /** + * Delete any textures from the card that have been recently garbage collected in Java. If a deleter is passed in, + * gc'd textures that are part of the currently active context (if one is active) will be deleted immediately. If a + * deleter is not passed in, we do not have an active context, or we encounter gc'd textures that are not part of + * the current context, then we will queue those textures to be deleted later using the GameTaskQueueManager. + * + * @param deleter + * if not null, this renderer will be used to immediately delete any gc'd textures in the currently + * active context. All other gc'd textures will be queued to delete in their own contexts. + */ + public static void cleanExpiredTextures(final Renderer deleter) { + cleanExpiredTextures(deleter, null); + } + + /** + * Delete any textures from the card that have been recently garbage collected in Java. If a deleter is passed in, + * gc'd textures that are part of the currently active context (if one is active) will be deleted immediately. If a + * deleter is not passed in, we do not have an active context, or we encounter gc'd textures that are not part of + * the current context, then we will queue those textures to be deleted later using the GameTaskQueueManager. + * + * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context. + * These objects may be used to discover when the deletion tasks have all completed. + * + * @param deleter + * if not null, this renderer will be used to immediately delete any gc'd textures in the currently + * active context. All other gc'd textures will be queued to delete in their own contexts. + * @param futureStore + * if not null, this map will be populated with any Future task handles created during cleanup. + */ + public static void cleanExpiredTextures(final Renderer deleter, final Map<Object, Future<Void>> futureStore) { + // gather up expired textures... + final Multimap<Object, Integer> idMap = gatherGCdIds(); + + // send to be deleted on next render. + if (idMap != null) { + handleTextureDelete(deleter, idMap, futureStore); + } + } + + @SuppressWarnings("unchecked") + private static Multimap<Object, Integer> gatherGCdIds() { + Multimap<Object, Integer> idMap = null; + // Pull all expired textures from ref queue and add to an id multimap. + ContextIdReference<TextureKey> ref; + Integer id; + while ((ref = (ContextIdReference<TextureKey>) _textureRefQueue.poll()) != null) { + // lazy init + if (idMap == null) { + idMap = ArrayListMultimap.create(); + } + if (Constants.useMultipleContexts) { + final Set<Object> contextObjects = ref.getContextObjects(); + for (final Object o : contextObjects) { + id = ref.getValue(o); + if (id != null && id.intValue() != 0) { + // Add id to map + idMap.put(o, id); + } + } + } else { + id = ref.getValue(null); + if (id != null && id.intValue() != 0) { + idMap.put(ContextManager.getCurrentContext().getGlContextRep(), id); + } + } + ref.clear(); + } + return idMap; + } + + private static void handleTextureDelete(final Renderer deleter, final Multimap<Object, Integer> idMap, + final Map<Object, Future<Void>> futureStore) { + Object currentGLRef = null; + // Grab the current context, if any. + if (deleter != null && ContextManager.getCurrentContext() != null) { + currentGLRef = ContextManager.getCurrentContext().getGlContextRep(); + } + // For each affected context... + for (final Object glref : idMap.keySet()) { + // If we have a deleter and the context is current, immediately delete + if (currentGLRef != null && (!Constants.useMultipleContexts || glref.equals(currentGLRef))) { + deleter.deleteTextureIds(idMap.get(glref)); + } + // Otherwise, add a delete request to that context's render task queue. + else { + final Future<Void> future = GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref)) + .render(new RendererCallable<Void>() { + public Void call() throws Exception { + getRenderer().deleteTextureIds(idMap.get(glref)); + return null; + } + }); + if (futureStore != null) { + futureStore.put(glref, future); + } + } + } + } + + @MainThread + public static void preloadCache(final Renderer r) { + for (final Texture t : _tCache.values()) { + if (t == null) { + continue; + } + if (t.getTextureKey().getSource() != null) { + r.loadTexture(t, 0); + } + } + } + + static ReferenceQueue<TextureKey> getRefQueue() { + return _textureRefQueue; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java new file mode 100644 index 0000000..4f99a60 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +/** + * <code>Timer</code> is a ReadOnlyTimer implementation with nanosecond resolution. + */ +public class Timer implements ReadOnlyTimer { + + private static final long TIMER_RESOLUTION = 1000000000L; + private static final double INVERSE_TIMER_RESOLUTION = 1.0 / TIMER_RESOLUTION; + + private long _startTime; + private long _previousTime; + private double _tpf; + private double _fps; + + public Timer() { + _startTime = System.nanoTime(); + } + + public double getTimeInSeconds() { + return getTime() * INVERSE_TIMER_RESOLUTION; + } + + public long getTime() { + return System.nanoTime() - _startTime; + } + + public long getResolution() { + return TIMER_RESOLUTION; + } + + public double getFrameRate() { + return _fps; + } + + public double getTimePerFrame() { + return _tpf; + } + + /** + * Update should be called once per frame to correctly update "time per frame" and "frame rate (fps)" + */ + public void update() { + final long time = getTime(); + _tpf = (time - _previousTime) * INVERSE_TIMER_RESOLUTION; + _fps = 1.0 / _tpf; + _previousTime = time; + } + + /** + * Reset this timer, so that {@link #getTime()} and {@link #getTimeInSeconds()} reflects the time spend from this + * call. + */ + public void reset() { + _startTime = System.nanoTime(); + _previousTime = getTime(); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java new file mode 100644 index 0000000..d504892 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.net.MalformedURLException; +import java.net.URL; + +public class UrlUtils { + + /** + * Create a new URL by resolving a given relative string (such as "mydir/myfile.ext") against a given url (such as + * "http://www.company.com/mycontent/content.html"). This method is necessary because new URL(URL, String) does not + * handle primaryUrls that contain %2F instead of a slash. + * + * @param primaryUrl + * the primary or base URL. + * @param relativeLoc + * a String representing a relative file or path to resolve against the primary URL. + * @return the resolved URL. + * @throws MalformedURLException + * if we are unable to create the URL. + */ + public static URL resolveRelativeURL(final URL primaryUrl, final String relativeLoc) throws MalformedURLException { + // Because URL(base, string) does not handle correctly URLs that have %2F, we have to manually replace these. + // So, we grab the URL as a string + String url = primaryUrl.toString(); + + // Replace any %2F (or %2f) with forward slashes + url = url.replaceAll("\\%2[F,f]", "/"); + + // And make our new URL + return new URL(new URL(url), relativeLoc); + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java new file mode 100644 index 0000000..d76d5fa --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +public class BinaryClassField { + + public static final byte BYTE = 0; + public static final byte BYTE_1D = 1; + public static final byte BYTE_2D = 2; + + public static final byte INT = 10; + public static final byte INT_1D = 11; + public static final byte INT_2D = 12; + + public static final byte FLOAT = 20; + public static final byte FLOAT_1D = 21; + public static final byte FLOAT_2D = 22; + + public static final byte DOUBLE = 30; + public static final byte DOUBLE_1D = 31; + public static final byte DOUBLE_2D = 32; + + public static final byte LONG = 40; + public static final byte LONG_1D = 41; + public static final byte LONG_2D = 42; + + public static final byte SHORT = 50; + public static final byte SHORT_1D = 51; + public static final byte SHORT_2D = 52; + + public static final byte BOOLEAN = 60; + public static final byte BOOLEAN_1D = 61; + public static final byte BOOLEAN_2D = 62; + + public static final byte STRING = 70; + public static final byte STRING_1D = 71; + public static final byte STRING_2D = 72; + + public static final byte BITSET = 80; + + public static final byte SAVABLE = 90; + public static final byte SAVABLE_1D = 91; + public static final byte SAVABLE_2D = 92; + + public static final byte SAVABLE_ARRAYLIST = 100; + public static final byte SAVABLE_ARRAYLIST_1D = 101; + public static final byte SAVABLE_ARRAYLIST_2D = 102; + + public static final byte SAVABLE_MAP = 105; + public static final byte STRING_SAVABLE_MAP = 106; + + public static final byte FLOATBUFFER_ARRAYLIST = 110; + public static final byte BYTEBUFFER_ARRAYLIST = 111; + + public static final byte FLOATBUFFER = 120; + public static final byte INTBUFFER = 121; + public static final byte BYTEBUFFER = 122; + public static final byte SHORTBUFFER = 123; + + public byte _type; + public String _name; + public byte _alias; + + public BinaryClassField(final String name, final byte alias, final byte type) { + _name = name; + _alias = alias; + _type = type; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java new file mode 100644 index 0000000..06e2595 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +import java.util.HashMap; + +public class BinaryClassObject { + + // When exporting, use nameFields field, importing use aliasFields. + public HashMap<String, BinaryClassField> _nameFields; + public HashMap<Byte, BinaryClassField> _aliasFields; + + public byte[] _alias; + public String _className; + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java new file mode 100644 index 0000000..c2fbc08 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.ardor3d.util.export.Savable; + +/** + * Simple utility class that uses BinaryImporter/Exporter in memory to clone a spatial. + */ +public class BinaryCloner { + @SuppressWarnings("unchecked") + public <T extends Savable> T copy(final T source) { + try { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final BinaryExporter exporter = new BinaryExporter(); + exporter.save(source, bos); + bos.flush(); + final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + final BinaryImporter importer = new BinaryImporter(); + return (T) importer.load(bis); + } catch (final IOException ex) { + // should not happen, since we are dealing with only byte array streams. + throw new RuntimeException(ex); + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java new file mode 100644 index 0000000..1c3359c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java @@ -0,0 +1,357 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.Deflater; +import java.util.zip.GZIPOutputStream; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.util.export.Ardor3dExporter; +import com.ardor3d.util.export.ByteUtils; +import com.ardor3d.util.export.Savable; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * Exports to the ardor3d Binary Format. Format descriptor: (each numbered item denotes a series of bytes that follows + * sequentially one after the next.) + * <p> + * 1. "number of classes" - four bytes - int value representing the number of entries in the class lookup table. + * </p> + * <p> + * CLASS TABLE: There will be X blocks each consisting of numbers 2 thru 9, where X = the number read in 1. + * </p> + * <p> + * 2. "class alias" - 1...X bytes, where X = ((int) MathUtils.log(aliasCount, 256) + 1) - an alias used when writing + * object data to match an object to its appropriate object class type. + * </p> + * <p> + * 3. "full class name size" - four bytes - int value representing number of bytes to read in for next field. + * </p> + * <p> + * 4. "full class name" - 1...X bytes representing a String value, where X = the number read in 3. The String is the + * fully qualified class name of the Savable class, eg "<code>com.ardor3d.math.Vector3</code>" + * </p> + * <p> + * 5. "number of fields" - four bytes - int value representing number of blocks to read in next (numbers 6 - 9), where + * each block represents a field in this class. + * </p> + * <p> + * 6. "field alias" - 1 byte - the alias used when writing out fields in a class. Because it is a single byte, a single + * class can not save out more than a total of 256 fields. + * </p> + * <p> + * 7. "field type" - 1 byte - a value representing the type of data a field contains. This value is taken from the + * static fields of <code>com.ardor3d.util.export.binary.BinaryClassField</code>. + * </p> + * <p> + * 8. "field name size" - 4 bytes - int value representing the size of the next field. + * </p> + * <p> + * 9. "field name" - 1...X bytes representing a String value, where X = the number read in 8. The String is the full + * String value used when writing the current field. + * </p> + * <p> + * 10. "number of unique objects" - four bytes - int value representing the number of data entries in this file. + * </p> + * <p> + * DATA LOOKUP TABLE: There will be X blocks each consisting of numbers 11 and 12, where X = the number read in 10. + * </p> + * <p> + * 11. "data id" - four bytes - int value identifying a single unique object that was saved in this data file. + * </p> + * <p> + * 12. "data location" - four bytes - int value representing the offset in the object data portion of this file where + * the object identified in 11 is located. + * </p> + * <p> + * 13. "future use" - four bytes - hardcoded int value 1. + * </p> + * <p> + * 14. "root id" - four bytes - int value identifying the top level object. + * </p> + * <p> + * OBJECT DATA SECTION: There will be X blocks each consisting of numbers 15 thru 19, where X = the number of unique + * location values named in 12. + * <p> + * 15. "class alias" - see 2. + * </p> + * <p> + * 16. "data length" - four bytes - int value representing the length in bytes of data stored in fields 17 and 18 for + * this object. + * </p> + * <p> + * FIELD ENTRY: There will be X blocks each consisting of numbers 18 and 19 + * </p> + * <p> + * 17. "field alias" - see 6. + * </p> + * <p> + * 18. "field data" - 1...X bytes representing the field data. The data length is dependent on the field type and + * contents. + * </p> + */ + +public class BinaryExporter implements Ardor3dExporter { + private static final Logger logger = Logger.getLogger(BinaryExporter.class.getName()); + + /** + * The default compression level to use during output. Defaults to Deflater.BEST_COMPRESSION. + */ + public static int DEFAULT_COMPRESSION = Deflater.BEST_COMPRESSION; + + protected final int _compression; + + protected int _aliasCount = 1; + protected int _idCount = 1; + + protected final Map<Savable, BinaryIdContentPair> _contentTable = Maps.newIdentityHashMap(); + + protected final Map<Integer, Integer> _locationTable = Maps.newHashMap(); + + // key - class name, value = bco + protected final Map<String, BinaryClassObject> _classes = Maps.newHashMap(); + + protected final List<Savable> _contentKeys = Lists.newArrayList(); + + public BinaryExporter() { + this(DEFAULT_COMPRESSION); + } + + /** + * Construct a new exporter, specifying some options. + * + * @param compression + * the compression type to use. One of the constants from {@link java.util.zip.Deflater} + */ + public BinaryExporter(final int compression) { + _compression = compression; + } + + public void save(final Savable object, final OutputStream os) throws IOException { + try { + GZIPOutputStream zos = new GZIPOutputStream(os) { + { + def.setLevel(_compression); + } + }; + final int id = processBinarySavable(object); + + // write out tag table + int ttbytes = 0; + final int classNum = _classes.keySet().size(); + final int aliasWidth = ((int) MathUtils.log(classNum, 256) + 1); // make all + // aliases a + // fixed width + zos.write(ByteUtils.convertToBytes(classNum)); + for (final String key : _classes.keySet()) { + final BinaryClassObject bco = _classes.get(key); + + // write alias + final byte[] aliasBytes = fixClassAlias(bco._alias, aliasWidth); + zos.write(aliasBytes); + ttbytes += aliasWidth; + + // write classname size & classname + final byte[] classBytes = key.getBytes(); + zos.write(ByteUtils.convertToBytes(classBytes.length)); + zos.write(classBytes); + ttbytes += 4 + classBytes.length; + + zos.write(ByteUtils.convertToBytes(bco._nameFields.size())); + + for (final String fieldName : bco._nameFields.keySet()) { + final BinaryClassField bcf = bco._nameFields.get(fieldName); + zos.write(bcf._alias); + zos.write(bcf._type); + + // write classname size & classname + final byte[] fNameBytes = fieldName.getBytes(); + zos.write(ByteUtils.convertToBytes(fNameBytes.length)); + zos.write(fNameBytes); + ttbytes += 2 + 4 + fNameBytes.length; + } + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // write out data to a seperate stream + int location = 0; + // keep track of location for each piece + final HashMap<String, List<BinaryIdContentPair>> alreadySaved = new HashMap<String, List<BinaryIdContentPair>>( + _contentTable.size()); + for (final Savable savable : _contentKeys) { + // look back at previous written data for matches + final String savableName = savable.getClassTag().getName(); + final BinaryIdContentPair pair = _contentTable.get(savable); + List<BinaryIdContentPair> bucket = alreadySaved.get(savableName + getChunk(pair)); + final int prevLoc = findPrevMatch(pair, bucket); + if (prevLoc != -1) { + _locationTable.put(pair.getId(), prevLoc); + continue; + } + + _locationTable.put(pair.getId(), location); + if (bucket == null) { + bucket = new ArrayList<BinaryIdContentPair>(); + alreadySaved.put(savableName + getChunk(pair), bucket); + } + bucket.add(pair); + final byte[] aliasBytes = fixClassAlias(_classes.get(savableName)._alias, aliasWidth); + out.write(aliasBytes); + location += aliasWidth; + final BinaryOutputCapsule cap = _contentTable.get(savable).getContent(); + out.write(ByteUtils.convertToBytes(cap._bytes.length)); + location += 4; // length of bytes + out.write(cap._bytes); + location += cap._bytes.length; + } + + // write out location table + // tag/location + final int locNum = _locationTable.keySet().size(); + zos.write(ByteUtils.convertToBytes(locNum)); + int locbytes = 0; + for (final Integer key : _locationTable.keySet()) { + zos.write(ByteUtils.convertToBytes(key)); + zos.write(ByteUtils.convertToBytes(_locationTable.get(key))); + locbytes += 8; + } + + // write out number of root ids - hardcoded 1 for now + zos.write(ByteUtils.convertToBytes(1)); + + // write out root id + zos.write(ByteUtils.convertToBytes(id)); + + // append stream to the output stream + out.writeTo(zos); + + zos.finish(); + + out = null; + zos = null; + + if (logger.isLoggable(Level.FINE)) { + logger.fine("Stats:"); + logger.fine("classes: " + classNum); + logger.fine("class table: " + ttbytes + " bytes"); + logger.fine("objects: " + locNum); + logger.fine("location table: " + locbytes + " bytes"); + logger.fine("data: " + location + " bytes"); + } + } finally { + _aliasCount = 1; + _idCount = 1; + + _contentTable.clear(); + _locationTable.clear(); + _classes.clear(); + _contentKeys.clear(); + } + } + + protected String getChunk(final BinaryIdContentPair pair) { + return new String(pair.getContent()._bytes, 0, Math.min(64, pair.getContent()._bytes.length)); + } + + protected int findPrevMatch(final BinaryIdContentPair oldPair, final List<BinaryIdContentPair> bucket) { + if (bucket == null) { + return -1; + } + for (int x = bucket.size(); --x >= 0;) { + final BinaryIdContentPair pair = bucket.get(x); + if (pair.getContent().equals(oldPair.getContent())) { + return _locationTable.get(pair.getId()); + } + } + return -1; + } + + protected byte[] fixClassAlias(final byte[] bytes, final int width) { + if (bytes.length != width) { + final byte[] newAlias = new byte[width]; + for (int x = width - bytes.length; x < width; x++) { + newAlias[x] = bytes[x - bytes.length]; + } + return newAlias; + } + return bytes; + } + + public void save(final Savable object, final File file) throws IOException { + final File parentDirectory = file.getParentFile(); + if (parentDirectory != null && !parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + final FileOutputStream fos = new FileOutputStream(file); + save(object, fos); + fos.close(); + } + + public int processBinarySavable(final Savable object) throws IOException { + if (object == null) { + return -1; + } + BinaryClassObject bco = _classes.get(object.getClassTag().getName()); + // is this class been looked at before? in tagTable? + if (bco == null) { + bco = new BinaryClassObject(); + bco._alias = generateTag(); + bco._nameFields = new HashMap<String, BinaryClassField>(); + _classes.put(object.getClassTag().getName(), bco); + } + + // is object in contentTable? + if (_contentTable.get(object) != null) { + return (_contentTable.get(object).getId()); + } + final BinaryIdContentPair newPair = generateIdContentPair(bco); + final BinaryIdContentPair old = _contentTable.put(object, newPair); + if (old == null) { + _contentKeys.add(object); + } + object.write(_contentTable.get(object).getContent()); + newPair.getContent().finish(); + return newPair.getId(); + + } + + protected byte[] generateTag() { + final int width = ((int) MathUtils.log(_aliasCount, 256) + 1); + int count = _aliasCount; + _aliasCount++; + final byte[] bytes = new byte[width]; + for (int x = width - 1; x >= 0; x--) { + final int pow = (int) Math.pow(256, x); + final int factor = count / pow; + bytes[width - x - 1] = (byte) factor; + count %= pow; + } + return bytes; + } + + protected BinaryIdContentPair generateIdContentPair(final BinaryClassObject bco) { + final BinaryIdContentPair pair = new BinaryIdContentPair(_idCount++, new BinaryOutputCapsule(this, bco)); + return pair; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java new file mode 100644 index 0000000..228f314 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +public class BinaryIdContentPair { + private int _id; + private BinaryOutputCapsule _content; + + public BinaryIdContentPair(final int id, final BinaryOutputCapsule content) { + _id = id; + _content = content; + } + + public BinaryOutputCapsule getContent() { + return _content; + } + + public void setContent(final BinaryOutputCapsule content) { + _content = content; + } + + public int getId() { + return _id; + } + + public void setId(final int id) { + _id = id; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java new file mode 100644 index 0000000..6e2c847 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java @@ -0,0 +1,275 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; + +import com.ardor3d.annotation.SavableFactory; +import com.ardor3d.math.MathUtils; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.export.Ardor3dImporter; +import com.ardor3d.util.export.ByteUtils; +import com.ardor3d.util.export.ReadListener; +import com.ardor3d.util.export.Savable; +import com.google.common.collect.Maps; + +public class BinaryImporter implements Ardor3dImporter { + private static final Logger logger = Logger.getLogger(BinaryImporter.class.getName()); + + // Key - alias, object - bco + protected final Map<String, BinaryClassObject> _classes = Maps.newHashMap(); + // Key - id, object - the savable + protected final Map<Integer, Savable> _contentTable = Maps.newHashMap(); + // Key - savable, object - capsule + protected final Map<Savable, BinaryInputCapsule> _capsuleTable = Maps.newIdentityHashMap(); + // Key - id, opject - location in the file + protected final Map<Integer, Integer> _locationTable = Maps.newHashMap(); + + protected byte[] _dataArray = null; + protected int _aliasWidth = 0; + + public BinaryImporter() {} + + public Savable load(final InputStream is) throws IOException { + return load(is, null, null); + } + + public Savable load(final InputStream is, final ReadListener listener) throws IOException { + return load(is, listener, null); + } + + public Savable load(final InputStream is, final ReadListener listener, final ByteArrayOutputStream reuseableStream) + throws IOException { + try { + final GZIPInputStream zis = new GZIPInputStream(is); + BufferedInputStream bis = new BufferedInputStream(zis); + final int numClasses = ByteUtils.readInt(bis); + int bytes = 4; + _aliasWidth = ((int) MathUtils.log(numClasses, 256) + 1); + for (int i = 0; i < numClasses; i++) { + final String alias = readString(bis, _aliasWidth); + + final int classLength = ByteUtils.readInt(bis); + final String className = readString(bis, classLength); + final BinaryClassObject bco = new BinaryClassObject(); + bco._alias = alias.getBytes(); + bco._className = className; + + final int fields = ByteUtils.readInt(bis); + bytes += (8 + _aliasWidth + classLength); + + bco._nameFields = new HashMap<String, BinaryClassField>(fields); + bco._aliasFields = new HashMap<Byte, BinaryClassField>(fields); + for (int x = 0; x < fields; x++) { + final byte fieldAlias = (byte) bis.read(); + final byte fieldType = (byte) bis.read(); + + final int fieldNameLength = ByteUtils.readInt(bis); + final String fieldName = readString(bis, fieldNameLength); + final BinaryClassField bcf = new BinaryClassField(fieldName, fieldAlias, fieldType); + bco._nameFields.put(fieldName, bcf); + bco._aliasFields.put(fieldAlias, bcf); + bytes += (6 + fieldNameLength); + } + _classes.put(alias, bco); + } + if (listener != null) { + listener.readBytes(bytes); + } + + final int numLocs = ByteUtils.readInt(bis); + bytes = 4; + + for (int i = 0; i < numLocs; i++) { + final int id = ByteUtils.readInt(bis); + final int loc = ByteUtils.readInt(bis); + _locationTable.put(id, loc); + bytes += 8; + } + + @SuppressWarnings("unused") + final int numbIDs = ByteUtils.readInt(bis); // XXX: NOT CURRENTLY USED + final int id = ByteUtils.readInt(bis); + bytes += 8; + if (listener != null) { + listener.readBytes(bytes); + } + + ByteArrayOutputStream baos = reuseableStream; + if (baos == null) { + baos = new ByteArrayOutputStream(bytes); + } else { + baos.reset(); + } + int size = -1; + final byte[] cache = new byte[4096]; + while ((size = bis.read(cache)) != -1) { + baos.write(cache, 0, size); + if (listener != null) { + listener.readBytes(size); + } + } + bis = null; + + _dataArray = baos.toByteArray(); + baos = null; + + final Savable rVal = readObject(id); + + if (logger.isLoggable(Level.FINE)) { + logger.fine("Importer Stats: "); + logger.fine("Tags: " + numClasses); + logger.fine("Objects: " + numLocs); + logger.fine("Data Size: " + _dataArray.length); + } + return rVal; + + } finally { + // Let go of / reset contents. + _aliasWidth = 0; + _contentTable.clear(); + _classes.clear(); + _capsuleTable.clear(); + _locationTable.clear(); + _dataArray = null; + } + } + + public Savable load(final URL url) throws IOException { + return load(url, null); + } + + public Savable load(final URL url, final ReadListener listener) throws IOException { + final InputStream is = url.openStream(); + final Savable rVal = load(is, listener); + is.close(); + return rVal; + } + + public Savable load(final File file) throws IOException { + return load(file, null); + } + + public Savable load(final File file, final ReadListener listener) throws IOException { + final FileInputStream fis = new FileInputStream(file); + final Savable rVal = load(fis, listener); + fis.close(); + return rVal; + } + + public Savable load(final byte[] data) throws IOException { + final ByteArrayInputStream bais = new ByteArrayInputStream(data); + final Savable rVal = load(bais); + bais.close(); + return rVal; + } + + protected String readString(final InputStream is, final int length) throws IOException { + final byte[] data = new byte[length]; + is.read(data, 0, length); + return new String(data); + } + + protected String readString(final int length, final int offset) throws IOException { + final byte[] data = new byte[length]; + for (int j = 0; j < length; j++) { + data[j] = _dataArray[j + offset]; + } + + return new String(data); + } + + public Savable readObject(final int id) { + + if (_contentTable.get(id) != null) { + return _contentTable.get(id); + } + + try { + int loc = _locationTable.get(id); + + final String alias = readString(_aliasWidth, loc); + loc += _aliasWidth; + + final BinaryClassObject bco = _classes.get(alias); + + if (bco == null) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "NULL class object: " + + alias); + return null; + } + + final int dataLength = ByteUtils.convertIntFromBytes(_dataArray, loc); + loc += 4; + + final BinaryInputCapsule cap = new BinaryInputCapsule(this, bco); + cap.setContent(_dataArray, loc, loc + dataLength); + + final Savable out; + + try { + @SuppressWarnings("unchecked") + final Class<? extends Savable> clazz = (Class<? extends Savable>) Class.forName(bco._className); + final SavableFactory ann = clazz.getAnnotation(SavableFactory.class); + if (ann == null) { + out = clazz.newInstance(); + } else { + out = (Savable) clazz.getMethod(ann.factoryMethod(), (Class<?>[]) null).invoke(null, + (Object[]) null); + } + } catch (final InstantiationException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int)", + "Could not access constructor of class '" + bco._className + "'! \n" + + "Some types may require the annotation SavableFactory. Please double check.", e); + throw new Ardor3dException(e); + } catch (final NoSuchMethodException e) { + logger + .logp( + Level.SEVERE, + this.getClass().toString(), + "readObject(int)", + e.getMessage() + + " \n" + + "Method specified in annotation does not appear to exist or has an invalid method signature.", + e); + throw new Ardor3dException(e); + } + + _capsuleTable.put(out, cap); + _contentTable.put(id, out); + + out.read(_capsuleTable.get(out)); + + _capsuleTable.remove(out); + + return out; + + } catch (final Ardor3dException e) { + // rethrow our own exceptions + throw e; + } catch (final Exception e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int)", "Exception", e); + throw new Ardor3dException(e); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java new file mode 100644 index 0000000..ade60ff --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java @@ -0,0 +1,1417 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.util.export.ByteUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +public class BinaryInputCapsule implements InputCapsule { + private static final Logger logger = Logger.getLogger(BinaryInputCapsule.class.getName()); + + protected BinaryImporter _importer; + protected BinaryClassObject _cObj; + protected HashMap<Byte, Object> _fieldData; + + protected int _index = 0; + + public BinaryInputCapsule(final BinaryImporter importer, final BinaryClassObject bco) { + _importer = importer; + _cObj = bco; + } + + public void setContent(final byte[] content, final int start, final int limit) { + _fieldData = new HashMap<Byte, Object>(); + for (_index = start; _index < limit;) { + final byte alias = content[_index]; + + _index++; + + try { + final byte type = _cObj._aliasFields.get(alias)._type; + Object value = null; + + switch (type) { + case BinaryClassField.BITSET: { + value = readBitSet(content); + break; + } + case BinaryClassField.BOOLEAN: { + value = readBoolean(content); + break; + } + case BinaryClassField.BOOLEAN_1D: { + value = readBooleanArray(content); + break; + } + case BinaryClassField.BOOLEAN_2D: { + value = readBooleanArray2D(content); + break; + } + case BinaryClassField.BYTE: { + value = readByte(content); + break; + } + case BinaryClassField.BYTE_1D: { + value = readByteArray(content); + break; + } + case BinaryClassField.BYTE_2D: { + value = readByteArray2D(content); + break; + } + case BinaryClassField.BYTEBUFFER: { + value = readByteBuffer(content); + break; + } + case BinaryClassField.DOUBLE: { + value = readDouble(content); + break; + } + case BinaryClassField.DOUBLE_1D: { + value = readDoubleArray(content); + break; + } + case BinaryClassField.DOUBLE_2D: { + value = readDoubleArray2D(content); + break; + } + case BinaryClassField.FLOAT: { + value = readFloat(content); + break; + } + case BinaryClassField.FLOAT_1D: { + value = readFloatArray(content); + break; + } + case BinaryClassField.FLOAT_2D: { + value = readFloatArray2D(content); + break; + } + case BinaryClassField.FLOATBUFFER: { + value = readFloatBuffer(content); + break; + } + case BinaryClassField.FLOATBUFFER_ARRAYLIST: { + value = readFloatBufferArrayList(content); + break; + } + case BinaryClassField.BYTEBUFFER_ARRAYLIST: { + value = readByteBufferArrayList(content); + break; + } + case BinaryClassField.INT: { + value = readInt(content); + break; + } + case BinaryClassField.INT_1D: { + value = readIntArray(content); + break; + } + case BinaryClassField.INT_2D: { + value = readIntArray2D(content); + break; + } + case BinaryClassField.INTBUFFER: { + value = readIntBuffer(content); + break; + } + case BinaryClassField.LONG: { + value = readLong(content); + break; + } + case BinaryClassField.LONG_1D: { + value = readLongArray(content); + break; + } + case BinaryClassField.LONG_2D: { + value = readLongArray2D(content); + break; + } + case BinaryClassField.SAVABLE: { + value = readSavable(content); + break; + } + case BinaryClassField.SAVABLE_1D: { + value = readSavableArray(content); + break; + } + case BinaryClassField.SAVABLE_2D: { + value = readSavableArray2D(content); + break; + } + case BinaryClassField.SAVABLE_ARRAYLIST: { + value = readSavableArray(content); + break; + } + case BinaryClassField.SAVABLE_ARRAYLIST_1D: { + value = readSavableArray2D(content); + break; + } + case BinaryClassField.SAVABLE_ARRAYLIST_2D: { + value = readSavableArray3D(content); + break; + } + case BinaryClassField.SAVABLE_MAP: { + value = readSavableMap(content); + break; + } + case BinaryClassField.STRING_SAVABLE_MAP: { + value = readStringSavableMap(content); + break; + } + case BinaryClassField.SHORT: { + value = readShort(content); + break; + } + case BinaryClassField.SHORT_1D: { + value = readShortArray(content); + break; + } + case BinaryClassField.SHORT_2D: { + value = readShortArray2D(content); + break; + } + case BinaryClassField.SHORTBUFFER: { + value = readShortBuffer(content); + break; + } + case BinaryClassField.STRING: { + value = readString(content); + break; + } + case BinaryClassField.STRING_1D: { + value = readStringArray(content); + break; + } + case BinaryClassField.STRING_2D: { + value = readStringArray2D(content); + break; + } + + default: + // skip put statement + continue; + } + + _fieldData.put(alias, value); + + } catch (final IOException e) { + logger.logp(Level.SEVERE, this.getClass().toString(), "setContent(byte[] content)", "Exception", e); + } + } + } + + public BitSet readBitSet(final String name, final BitSet defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (BitSet) _fieldData.get(field._alias); + } + + public boolean readBoolean(final String name, final boolean defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Boolean) _fieldData.get(field._alias)).booleanValue(); + } + + public boolean[] readBooleanArray(final String name, final boolean[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (boolean[]) _fieldData.get(field._alias); + } + + public boolean[][] readBooleanArray2D(final String name, final boolean[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (boolean[][]) _fieldData.get(field._alias); + } + + public byte readByte(final String name, final byte defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Byte) _fieldData.get(field._alias)).byteValue(); + } + + public byte[] readByteArray(final String name, final byte[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (byte[]) _fieldData.get(field._alias); + } + + public byte[][] readByteArray2D(final String name, final byte[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (byte[][]) _fieldData.get(field._alias); + } + + public ByteBuffer readByteBuffer(final String name, final ByteBuffer defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (ByteBuffer) _fieldData.get(field._alias); + } + + @SuppressWarnings("unchecked") + public List<ByteBuffer> readByteBufferList(final String name, final List<ByteBuffer> defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (List<ByteBuffer>) _fieldData.get(field._alias); + } + + public double readDouble(final String name, final double defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Double) _fieldData.get(field._alias)).doubleValue(); + } + + public double[] readDoubleArray(final String name, final double[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (double[]) _fieldData.get(field._alias); + } + + public double[][] readDoubleArray2D(final String name, final double[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (double[][]) _fieldData.get(field._alias); + } + + public float readFloat(final String name, final float defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Float) _fieldData.get(field._alias)).floatValue(); + } + + public float[] readFloatArray(final String name, final float[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (float[]) _fieldData.get(field._alias); + } + + public float[][] readFloatArray2D(final String name, final float[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (float[][]) _fieldData.get(field._alias); + } + + public FloatBuffer readFloatBuffer(final String name, final FloatBuffer defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (FloatBuffer) _fieldData.get(field._alias); + } + + @SuppressWarnings("unchecked") + public List<FloatBuffer> readFloatBufferList(final String name, final List<FloatBuffer> defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (List<FloatBuffer>) _fieldData.get(field._alias); + } + + public int readInt(final String name, final int defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Integer) _fieldData.get(field._alias)).intValue(); + } + + public int[] readIntArray(final String name, final int[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (int[]) _fieldData.get(field._alias); + } + + public int[][] readIntArray2D(final String name, final int[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (int[][]) _fieldData.get(field._alias); + } + + public IntBuffer readIntBuffer(final String name, final IntBuffer defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (IntBuffer) _fieldData.get(field._alias); + } + + public long readLong(final String name, final long defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Long) _fieldData.get(field._alias)).longValue(); + } + + public long[] readLongArray(final String name, final long[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (long[]) _fieldData.get(field._alias); + } + + public long[][] readLongArray2D(final String name, final long[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (long[][]) _fieldData.get(field._alias); + } + + public Savable readSavable(final String name, final Savable defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object value = _fieldData.get(field._alias); + if (value == null) { + return null; + } else if (value instanceof ID) { + value = _importer.readObject(((ID) value).id); + _fieldData.put(field._alias, value); + return (Savable) value; + } else { + return defVal; + } + } + + public Savable[] readSavableArray(final String name, final Savable[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object[] values = (Object[]) _fieldData.get(field._alias); + if (values instanceof ID[]) { + values = resolveIDs(values); + _fieldData.put(field._alias, values); + return (Savable[]) values; + } else { + return defVal; + } + } + + private Savable[] resolveIDs(final Object[] values) { + if (values != null) { + final Savable[] savables = new Savable[values.length]; + for (int i = 0; i < values.length; i++) { + final ID id = (ID) values[i]; + savables[i] = id != null ? _importer.readObject(id.id) : null; + } + return savables; + } else { + return null; + } + } + + public Savable[][] readSavableArray2D(final String name, final Savable[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object[][] values = (Object[][]) _fieldData.get(field._alias); + if (values instanceof ID[][]) { + final Savable[][] savables = new Savable[values.length][]; + for (int i = 0; i < values.length; i++) { + if (values[i] != null) { + savables[i] = resolveIDs(values[i]); + } else { + savables[i] = null; + } + } + values = savables; + _fieldData.put(field._alias, values); + } + return (Savable[][]) values; + } + + public Savable[][][] readSavableArray3D(final String name, final Savable[][][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + final Object[][][] values = (Object[][][]) _fieldData.get(field._alias); + if (values instanceof ID[][][]) { + final Savable[][][] savables = new Savable[values.length][][]; + for (int i = 0; i < values.length; i++) { + if (values[i] != null) { + savables[i] = new Savable[values[i].length][]; + for (int j = 0; j < values[i].length; j++) { + savables[i][j] = resolveIDs(values[i][j]); + } + } else { + savables[i] = null; + } + } + _fieldData.put(field._alias, savables); + return savables; + } else { + return defVal; + } + } + + private List<Savable> savableArrayListFromArray(final Savable[] savables) { + if (savables == null) { + return null; + } + final List<Savable> list = new ArrayList<Savable>(savables.length); + for (int x = 0; x < savables.length; x++) { + list.add(savables[x]); + } + return list; + } + + // Assumes array of size 2 arrays where pos 0 is key and pos 1 is value. + private Map<Savable, Savable> savableMapFrom2DArray(final Savable[][] savables) { + if (savables == null) { + return null; + } + final Map<Savable, Savable> map = new HashMap<Savable, Savable>(savables.length); + for (int x = 0; x < savables.length; x++) { + map.put(savables[x][0], savables[x][1]); + } + return map; + } + + private Map<String, Savable> stringSavableMapFromKV(final String[] keys, final Savable[] values) { + if (keys == null || values == null) { + return null; + } + + final Map<String, Savable> map = new HashMap<String, Savable>(keys.length); + for (int x = 0; x < keys.length; x++) { + map.put(keys[x], values[x]); + } + + return map; + } + + @SuppressWarnings("unchecked") + public <E extends Savable> List<E> readSavableList(final String name, final List<E> defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object value = _fieldData.get(field._alias); + if (value instanceof ID[]) { + // read Savable array and convert to ArrayList + final Savable[] savables = readSavableArray(name, null); + value = savableArrayListFromArray(savables); + _fieldData.put(field._alias, value); + } + return (List<E>) value; + } + + @SuppressWarnings("unchecked") + public <E extends Savable> List<E>[] readSavableListArray(final String name, final List<E>[] defVal) + throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object value = _fieldData.get(field._alias); + if (value instanceof ID[][]) { + // read 2D Savable array and convert to ArrayList array + final Savable[][] savables = readSavableArray2D(name, null); + if (savables != null) { + final List<Savable>[] arrayLists = new ArrayList[savables.length]; + for (int i = 0; i < savables.length; i++) { + arrayLists[i] = savableArrayListFromArray(savables[i]); + } + value = arrayLists; + } else { + value = defVal; + } + _fieldData.put(field._alias, value); + } + return (List<E>[]) value; + } + + @SuppressWarnings("unchecked") + public <E extends Savable> List<E>[][] readSavableListArray2D(final String name, final List<E>[][] defVal) + throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object value = _fieldData.get(field._alias); + if (value instanceof ID[][][]) { + // read 3D Savable array and convert to 2D ArrayList array + final Savable[][][] savables = readSavableArray3D(name, null); + if (savables != null && savables.length > 0) { + final List<Savable>[][] arrayLists = new ArrayList[savables.length][]; + for (int i = 0; i < savables.length; i++) { + arrayLists[i] = new ArrayList[savables[i].length]; + for (int j = 0; j < savables[i].length; j++) { + arrayLists[i][j] = savableArrayListFromArray(savables[i][j]); + } + } + value = arrayLists; + } else { + value = defVal; + } + _fieldData.put(field._alias, value); + } + return (List<E>[][]) value; + } + + @SuppressWarnings("unchecked") + public <K extends Savable, V extends Savable> Map<K, V> readSavableMap(final String name, final Map<K, V> defVal) + throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object value = _fieldData.get(field._alias); + if (value instanceof ID[][]) { + // read Savable array and convert to Map + final Savable[][] savables = readSavableArray2D(name, null); + value = savableMapFrom2DArray(savables); + _fieldData.put(field._alias, value); + } + return (Map<K, V>) value; + } + + @SuppressWarnings("unchecked") + public <V extends Savable> Map<String, V> readStringSavableMap(final String name, final Map<String, V> defVal) + throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + Object value = _fieldData.get(field._alias); + if (value instanceof StringIDMap) { + // read Savable array and convert to Map values + final StringIDMap in = (StringIDMap) value; + final Savable[] values = resolveIDs(in.values); + value = stringSavableMapFromKV(in.keys, values); + _fieldData.put(field._alias, value); + } + return (Map<String, V>) value; + } + + public short readShort(final String name, final short defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return ((Short) _fieldData.get(field._alias)).shortValue(); + } + + public short[] readShortArray(final String name, final short[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (short[]) _fieldData.get(field._alias); + } + + public short[][] readShortArray2D(final String name, final short[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (short[][]) _fieldData.get(field._alias); + } + + public ShortBuffer readShortBuffer(final String name, final ShortBuffer defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (ShortBuffer) _fieldData.get(field._alias); + } + + public String readString(final String name, final String defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (String) _fieldData.get(field._alias); + } + + public String[] readStringArray(final String name, final String[] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (String[]) _fieldData.get(field._alias); + } + + public String[][] readStringArray2D(final String name, final String[][] defVal) throws IOException { + final BinaryClassField field = _cObj._nameFields.get(name); + if (field == null || !_fieldData.containsKey(field._alias)) { + return defVal; + } + return (String[][]) _fieldData.get(field._alias); + } + + // byte primitive + + protected byte readByte(final byte[] content) throws IOException { + final byte value = content[_index]; + _index++; + return value; + } + + protected byte[] readByteArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final byte[] value = new byte[length]; + for (int x = 0; x < length; x++) { + value[x] = readByte(content); + } + return value; + } + + protected byte[][] readByteArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final byte[][] value = new byte[length][]; + for (int x = 0; x < length; x++) { + value[x] = readByteArray(content); + } + return value; + } + + // int primitive + + protected int readInt(final byte[] content) throws IOException { + byte[] bytes = inflateFrom(content, _index); + _index += 1 + bytes.length; + bytes = ByteUtils.rightAlignBytes(bytes, 4); + final int value = ByteUtils.convertIntFromBytes(bytes); + if (value == BinaryOutputCapsule.NULL_OBJECT || value == BinaryOutputCapsule.DEFAULT_OBJECT) { + _index -= 4; + } + return value; + } + + protected int[] readIntArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final int[] value = new int[length]; + for (int x = 0; x < length; x++) { + value[x] = readInt(content); + } + return value; + } + + protected int[][] readIntArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final int[][] value = new int[length][]; + for (int x = 0; x < length; x++) { + value[x] = readIntArray(content); + } + return value; + } + + // float primitive + + protected float readFloat(final byte[] content) throws IOException { + final float value = ByteUtils.convertFloatFromBytes(content, _index); + _index += 4; + return value; + } + + protected float[] readFloatArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final float[] value = new float[length]; + for (int x = 0; x < length; x++) { + value[x] = readFloat(content); + } + return value; + } + + protected float[][] readFloatArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final float[][] value = new float[length][]; + for (int x = 0; x < length; x++) { + value[x] = readFloatArray(content); + } + return value; + } + + // double primitive + + protected double readDouble(final byte[] content) throws IOException { + final double value = ByteUtils.convertDoubleFromBytes(content, _index); + _index += 8; + return value; + } + + protected double[] readDoubleArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final double[] value = new double[length]; + for (int x = 0; x < length; x++) { + value[x] = readDouble(content); + } + return value; + } + + protected double[][] readDoubleArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final double[][] value = new double[length][]; + for (int x = 0; x < length; x++) { + value[x] = readDoubleArray(content); + } + return value; + } + + // long primitive + + protected long readLong(final byte[] content) throws IOException { + byte[] bytes = inflateFrom(content, _index); + _index += 1 + bytes.length; + bytes = ByteUtils.rightAlignBytes(bytes, 8); + final long value = ByteUtils.convertLongFromBytes(bytes); + return value; + } + + protected long[] readLongArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final long[] value = new long[length]; + for (int x = 0; x < length; x++) { + value[x] = readLong(content); + } + return value; + } + + protected long[][] readLongArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final long[][] value = new long[length][]; + for (int x = 0; x < length; x++) { + value[x] = readLongArray(content); + } + return value; + } + + // short primitive + + protected short readShort(final byte[] content) throws IOException { + final short value = ByteUtils.convertShortFromBytes(content, _index); + _index += 2; + return value; + } + + protected short[] readShortArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final short[] value = new short[length]; + for (int x = 0; x < length; x++) { + value[x] = readShort(content); + } + return value; + } + + protected short[][] readShortArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final short[][] value = new short[length][]; + for (int x = 0; x < length; x++) { + value[x] = readShortArray(content); + } + return value; + } + + // boolean primitive + + protected boolean readBoolean(final byte[] content) throws IOException { + final boolean value = ByteUtils.convertBooleanFromBytes(content, _index); + _index += 1; + return value; + } + + protected boolean[] readBooleanArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final boolean[] value = new boolean[length]; + for (int x = 0; x < length; x++) { + value[x] = readBoolean(content); + } + return value; + } + + protected boolean[][] readBooleanArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final boolean[][] value = new boolean[length][]; + for (int x = 0; x < length; x++) { + value[x] = readBooleanArray(content); + } + return value; + } + + /* + * UTF-8 crash course: + * + * UTF-8 codepoints map to UTF-16 codepoints and vv, which is what Java uses for it's Strings. (so a UTF-8 codepoint + * can contain all possible values for a Java char) + * + * A UTF-8 codepoint can be 1, 2 or 3 bytes long. How long a codepoint is can be told by reading the first byte: b < + * 0x80, 1 byte (b & 0xC0) == 0xC0, 2 bytes (b & 0xE0) == 0xE0, 3 bytes + * + * However there is an additional restriction to UTF-8, to enable you to find the start of a UTF-8 codepoint, if you + * start reading at a random point in a UTF-8 byte stream. That's why UTF-8 requires for the second and third byte + * of a multibyte codepoint: (b & 0x80) == 0x80 (in other words, first bit must be 1) + */ + private final static int UTF8_START = 0; // next byte should be the start of a new + private final static int UTF8_2BYTE = 2; // next byte should be the second byte of a 2 byte codepoint + private final static int UTF8_3BYTE_1 = 3; // next byte should be the second byte of a 3 byte codepoint + private final static int UTF8_3BYTE_2 = 4; // next byte should be the third byte of a 3 byte codepoint + private final static int UTF8_ILLEGAL = 10; // not an UTF8 string + + // String + protected String readString(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + + /* + * We'll transfer the bytes into a separate byte array. While we do that we'll take the opportunity to check if + * the byte data is valid UTF-8. + * + * If it is not UTF-8 it is most likely saved with the BinaryOutputCapsule bug, that saves Strings using their + * native encoding. Unfortunatly there is no way to know what encoding was used, so we'll parse using the most + * common one in that case; latin-1 aka ISO8859_1 + * + * Encoding of "low" ASCII codepoint (in plain speak: when no special characters are used) will usually look the + * same for UTF-8 and the other 1 byte codepoint encodings (espc true for numbers and regular letters of the + * alphabet). So these are valid UTF-8 and will give the same result (at most a few charakters will appear + * different, such as the euro sign). + * + * However, when "high" codepoints are used (any codepoint that over 0x7F, in other words where the first bit is + * a 1) it's a different matter and UTF-8 and the 1 byte encoding greatly will differ, as well as most 1 byte + * encodings relative to each other. + * + * It is impossible to detect which one-byte encoding is used. Since UTF8 and practically all 1-byte encodings + * share the most used characters (the "none-high" ones) parsing them will give the same result. However, not + * all byte sequences are legal in UTF-8 (see explantion above). If not UTF-8 encoded content is detected we + * therefor fallback on latin1. We also log a warning. + * + * By this method we detect all use of 1 byte encoding if they: - use a "high" codepoint after a "low" codepoint + * or a sequence of codepoints that is valid as UTF-8 bytes, that starts with 1000 - use a "low" codepoint after + * a "high" codepoint - use a "low" codepoint after "high" codepoint, after a "high" codepoint that starts with + * 1110 + * + * In practice this means that unless 2 or 3 "high" codepoints are used after each other in proper order, we'll + * detect the string was not originally UTF-8 encoded. + */ + final byte[] bytes = new byte[length]; + int utf8State = UTF8_START; + int b; + for (int x = 0; x < length; x++) { + bytes[x] = content[_index++]; + b = bytes[x] & 0xFF; // unsign our byte + + switch (utf8State) { + case UTF8_START: + if (b < 0x80) { + // good + } else if ((b & 0xC0) == 0xC0) { + utf8State = UTF8_2BYTE; + } else if ((b & 0xE0) == 0xE0) { + utf8State = UTF8_3BYTE_1; + } else { + utf8State = UTF8_ILLEGAL; + } + break; + case UTF8_3BYTE_1: + case UTF8_3BYTE_2: + case UTF8_2BYTE: + if ((b & 0x80) == 0x80) { + utf8State = utf8State == UTF8_3BYTE_1 ? UTF8_3BYTE_2 : UTF8_START; + } else { + utf8State = UTF8_ILLEGAL; + } + break; + } + } + + try { + // even though so far the parsing might have been a legal UTF-8 sequence, only if a codepoint is fully given + // is it correct UTF-8 + if (utf8State == UTF8_START) { + // Java misspells UTF-8 as UTF8 for official use in java.lang + return new String(bytes, "UTF8"); + } else { + logger.log(Level.WARNING, + "Your export has been saved with an incorrect encoding for it's String fields which means it might not load correctly " + + "due to encoding issues."); + // We use ISO8859_1 to be consistent across platforms. We could default to native encoding, but this + // would lead to inconsistent + // behaviour across platforms! + // Developers that have previously saved their exports using the old exporter (wich uses native + // encoding), can temporarly + // remove the ""ISO8859_1" parameter, and change the above if statement to "if (false)". + // They should then import and re-export their models using the same enviroment they were orginally + // created in. + return new String(bytes, "ISO8859_1"); + } + } catch (final UnsupportedEncodingException uee) { + // as a last resort fall back to platform native. + // JavaDoc is vague about what happens when a decoding a String that contains un undecodable sequence + // it also doesn't specify which encodings have to be supported (though UTF-8 and ISO8859 have been in the + // SUN JRE since at least 1.1) + logger.log( + Level.SEVERE, + "Your export has been saved with an incorrect encoding or your version of Java is unable to decode the stored string. " + + "While your export may load correctly by falling back, using it on different platforms or java versions might lead to " + + "very strange inconsitenties. You should probably re-export your work."); + return new String(bytes); + } + } + + protected String[] readStringArray(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final String[] value = new String[length]; + for (int x = 0; x < length; x++) { + value[x] = readString(content); + } + return value; + } + + protected String[][] readStringArray2D(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final String[][] value = new String[length][]; + for (int x = 0; x < length; x++) { + value[x] = readStringArray(content); + } + return value; + } + + // BitSet + + protected BitSet readBitSet(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final BitSet value = new BitSet(length); + for (int x = 0; x < length; x++) { + value.set(x, readBoolean(content)); + } + return value; + } + + // INFLATOR for int and long + + protected static byte[] inflateFrom(final byte[] contents, final int index) { + final byte firstByte = contents[index]; + if (firstByte == BinaryOutputCapsule.NULL_OBJECT) { + return ByteUtils.convertToBytes(BinaryOutputCapsule.NULL_OBJECT); + } else if (firstByte == BinaryOutputCapsule.DEFAULT_OBJECT) { + return ByteUtils.convertToBytes(BinaryOutputCapsule.DEFAULT_OBJECT); + } else if (firstByte == 0) { + return new byte[0]; + } else { + final byte[] rVal = new byte[firstByte]; + for (int x = 0; x < rVal.length; x++) { + rVal[x] = contents[x + 1 + index]; + } + return rVal; + } + } + + // BinarySavable + + protected ID readSavable(final byte[] content) throws IOException { + final int id = readInt(content); + if (id == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + + return new ID(id); + } + + // BinarySavable array + + protected ID[] readSavableArray(final byte[] content) throws IOException { + final int elements = readInt(content); + if (elements == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final ID[] rVal = new ID[elements]; + for (int x = 0; x < elements; x++) { + rVal[x] = readSavable(content); + } + return rVal; + } + + protected ID[][] readSavableArray2D(final byte[] content) throws IOException { + final int elements = readInt(content); + if (elements == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final ID[][] rVal = new ID[elements][]; + for (int x = 0; x < elements; x++) { + rVal[x] = readSavableArray(content); + } + return rVal; + } + + protected ID[][][] readSavableArray3D(final byte[] content) throws IOException { + final int elements = readInt(content); + if (elements == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final ID[][][] rVal = new ID[elements][][]; + for (int x = 0; x < elements; x++) { + rVal[x] = readSavableArray2D(content); + } + return rVal; + } + + // BinarySavable map + + protected ID[][] readSavableMap(final byte[] content) throws IOException { + final int elements = readInt(content); + if (elements == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final ID[][] rVal = new ID[elements][]; + for (int x = 0; x < elements; x++) { + rVal[x] = readSavableArray(content); + } + return rVal; + } + + protected StringIDMap readStringSavableMap(final byte[] content) throws IOException { + final int elements = readInt(content); + if (elements == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final String[] keys = readStringArray(content); + final ID[] values = readSavableArray(content); + final StringIDMap rVal = new StringIDMap(); + rVal.keys = keys; + rVal.values = values; + return rVal; + } + + // ArrayList<FloatBuffer> + + protected List<FloatBuffer> readFloatBufferArrayList(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final List<FloatBuffer> rVal = new ArrayList<FloatBuffer>(length); + for (int x = 0; x < length; x++) { + rVal.add(readFloatBuffer(content)); + } + return rVal; + } + + // ArrayList<ByteBuffer> + + protected List<ByteBuffer> readByteBufferArrayList(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + final List<ByteBuffer> rVal = new ArrayList<ByteBuffer>(length); + for (int x = 0; x < length; x++) { + rVal.add(readByteBuffer(content)); + } + return rVal; + } + + // NIO BUFFERS + + // float buffer + protected FloatBuffer readFloatBuffer(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + + final boolean direct = readBoolean(content); + + // Pull data in as a little endian byte buffer. + final ByteBuffer buf = ByteBuffer.allocateDirect(length * 4).order(ByteOrder.LITTLE_ENDIAN); + buf.put(content, _index, length * 4).rewind(); + + // increment index + _index += length * 4; + + // Convert to float buffer. + final FloatBuffer value; + final boolean contentCopyRequired; + if (direct) { + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + value = buf.asFloatBuffer(); + contentCopyRequired = false; + } else { + value = BufferUtils.createFloatBuffer(length); + contentCopyRequired = true; + } + } else { + value = BufferUtils.createFloatBufferOnHeap(length); + contentCopyRequired = true; + } + if (contentCopyRequired) { + value.put(buf.asFloatBuffer()); + value.rewind(); + } + + return value; + } + + // int buffer + protected IntBuffer readIntBuffer(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + + final boolean direct = readBoolean(content); + + // Pull data in as a little endian byte buffer. + final ByteBuffer buf = ByteBuffer.allocateDirect(length * 4).order(ByteOrder.LITTLE_ENDIAN); + buf.put(content, _index, length * 4).rewind(); + + // increment index + _index += length * 4; + + // Convert to int buffer. + final IntBuffer value; + final boolean contentCopyRequired; + if (direct) { + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + value = buf.asIntBuffer(); + contentCopyRequired = false; + } else { + value = BufferUtils.createIntBuffer(length); + contentCopyRequired = true; + } + } else { + value = BufferUtils.createIntBufferOnHeap(length); + contentCopyRequired = true; + } + if (contentCopyRequired) { + value.put(buf.asIntBuffer()); + value.rewind(); + } + return value; + } + + // short buffer + protected ShortBuffer readShortBuffer(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + + final boolean direct = readBoolean(content); + + // Pull data in as a little endian byte buffer. + final ByteBuffer buf = ByteBuffer.allocateDirect(length * 2).order(ByteOrder.LITTLE_ENDIAN); + buf.put(content, _index, length * 2).rewind(); + + // increment index + _index += length * 2; + + // Convert to short buffer. + final ShortBuffer value; + final boolean contentCopyRequired; + if (direct) { + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + value = buf.asShortBuffer(); + contentCopyRequired = false; + } else { + value = BufferUtils.createShortBuffer(length); + contentCopyRequired = true; + } + } else { + value = BufferUtils.createShortBufferOnHeap(length); + contentCopyRequired = true; + } + if (contentCopyRequired) { + value.put(buf.asShortBuffer()); + value.rewind(); + } + return value; + } + + // byte buffer + protected ByteBuffer readByteBuffer(final byte[] content) throws IOException { + final int length = readInt(content); + if (length == BinaryOutputCapsule.NULL_OBJECT) { + return null; + } + + final boolean direct = readBoolean(content); + + // Pull data in as a little endian byte buffer. + final ByteBuffer buf = ByteBuffer.allocateDirect(length).order(ByteOrder.LITTLE_ENDIAN); + buf.put(content, _index, length).rewind(); + + // increment index + _index += length; + + // Convert to platform endian buffer. + final ByteBuffer value; + final boolean contentCopyRequired; + if (direct) { + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + value = buf; + contentCopyRequired = false; + } else { + value = BufferUtils.createByteBuffer(length); + contentCopyRequired = true; + } + } else { + value = BufferUtils.createByteBufferOnHeap(length); + contentCopyRequired = true; + } + if (contentCopyRequired) { + value.put(buf); + value.rewind(); + } + return value; + } + + static private class ID { + public int id; + + public ID(final int id) { + this.id = id; + } + } + + static private class StringIDMap { + public String[] keys; + public ID[] values; + } + + public <T extends Enum<T>> T readEnum(final String name, final Class<T> enumType, final T defVal) + throws IOException { + final String eVal = readString(name, defVal != null ? defVal.name() : null); + if (eVal != null) { + return Enum.valueOf(enumType, eVal); + } else { + return null; + } + } + + @SuppressWarnings("unchecked") + public <T extends Enum<T>> T[] readEnumArray(final String name, final Class<T> enumType, final T[] defVal) + throws IOException { + final String[] eVals = readStringArray(name, null); + if (eVals != null) { + final T[] rVal = (T[]) Array.newInstance(enumType, eVals.length); + int i = 0; + for (final String eVal : eVals) { + rVal[i++] = Enum.valueOf(enumType, eVal); + } + return rVal; + } else { + return defVal; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java new file mode 100644 index 0000000..82a14dd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java @@ -0,0 +1,1013 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.binary; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.ardor3d.util.export.ByteUtils; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +public class BinaryOutputCapsule implements OutputCapsule { + + public static final int NULL_OBJECT = -1; + public static final int DEFAULT_OBJECT = -2; + + public static byte[] NULL_BYTES = new byte[] { (byte) -1 }; + public static byte[] DEFAULT_BYTES = new byte[] { (byte) -2 }; + + protected ByteArrayOutputStream _baos; + protected byte[] _bytes; + protected BinaryExporter _exporter; + protected BinaryClassObject _cObj; + protected boolean _forceDirectNioBuffers; + + public BinaryOutputCapsule(final BinaryExporter exporter, final BinaryClassObject bco) { + this(exporter, bco, false); + } + + public BinaryOutputCapsule(final BinaryExporter exporter, final BinaryClassObject bco, + final boolean forceDirectNioBuffers) { + _baos = new ByteArrayOutputStream(); + _exporter = exporter; + _cObj = bco; + _forceDirectNioBuffers = forceDirectNioBuffers; + } + + public void write(final byte value, final String name, final byte defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BYTE); + write(value); + } + + public void write(final byte[] value, final String name, final byte[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BYTE_1D); + write(value); + } + + public void write(final byte[][] value, final String name, final byte[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BYTE_2D); + write(value); + } + + public void write(final int value, final String name, final int defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.INT); + write(value); + } + + public void write(final int[] value, final String name, final int[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.INT_1D); + write(value); + } + + public void write(final int[][] value, final String name, final int[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.INT_2D); + write(value); + } + + public void write(final float value, final String name, final float defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.FLOAT); + write(value); + } + + public void write(final float[] value, final String name, final float[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.FLOAT_1D); + write(value); + } + + public void write(final float[][] value, final String name, final float[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.FLOAT_2D); + write(value); + } + + public void write(final double value, final String name, final double defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.DOUBLE); + write(value); + } + + public void write(final double[] value, final String name, final double[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.DOUBLE_1D); + write(value); + } + + public void write(final double[][] value, final String name, final double[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.DOUBLE_2D); + write(value); + } + + public void write(final long value, final String name, final long defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.LONG); + write(value); + } + + public void write(final long[] value, final String name, final long[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.LONG_1D); + write(value); + } + + public void write(final long[][] value, final String name, final long[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.LONG_2D); + write(value); + } + + public void write(final short value, final String name, final short defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.SHORT); + write(value); + } + + public void write(final short[] value, final String name, final short[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.SHORT_1D); + write(value); + } + + public void write(final short[][] value, final String name, final short[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.SHORT_2D); + write(value); + } + + public void write(final boolean value, final String name, final boolean defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BOOLEAN); + write(value); + } + + public void write(final boolean[] value, final String name, final boolean[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BOOLEAN_1D); + write(value); + } + + public void write(final boolean[][] value, final String name, final boolean[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BOOLEAN_2D); + write(value); + } + + public void write(final String value, final String name, final String defVal) throws IOException { + if (value == null ? defVal == null : value.equals(defVal)) { + return; + } + writeAlias(name, BinaryClassField.STRING); + write(value); + } + + public void write(final String[] value, final String name, final String[] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.STRING_1D); + write(value); + } + + public void write(final String[][] value, final String name, final String[][] defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.STRING_2D); + write(value); + } + + public void write(final BitSet value, final String name, final BitSet defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BITSET); + write(value); + } + + public void write(final Savable object, final String name, final Savable defVal) throws IOException { + if (object == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE); + write(object); + } + + public void write(final Savable[] objects, final String name, final Savable[] defVal) throws IOException { + if (objects == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE_1D); + write(objects); + } + + public void write(final Savable[][] objects, final String name, final Savable[][] defVal) throws IOException { + if (objects == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE_2D); + write(objects); + } + + public void write(final FloatBuffer value, final String name, final FloatBuffer defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.FLOATBUFFER); + write(value); + } + + public void write(final IntBuffer value, final String name, final IntBuffer defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.INTBUFFER); + write(value); + } + + public void write(final ByteBuffer value, final String name, final ByteBuffer defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.BYTEBUFFER); + write(value); + } + + public void write(final ShortBuffer value, final String name, final ShortBuffer defVal) throws IOException { + if (value == defVal) { + return; + } + writeAlias(name, BinaryClassField.SHORTBUFFER); + write(value); + } + + public void writeFloatBufferList(final List<FloatBuffer> array, final String name, final List<FloatBuffer> defVal) + throws IOException { + if (array == defVal) { + return; + } + writeAlias(name, BinaryClassField.FLOATBUFFER_ARRAYLIST); + writeFloatBufferArrayList(array); + } + + public void writeByteBufferList(final List<ByteBuffer> array, final String name, final List<ByteBuffer> defVal) + throws IOException { + if (array == defVal) { + return; + } + writeAlias(name, BinaryClassField.BYTEBUFFER_ARRAYLIST); + writeByteBufferArrayList(array); + } + + public void writeSavableList(final List<? extends Savable> array, final String name, + final List<? extends Savable> defVal) throws IOException { + if (array == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST); + writeSavableArrayList(array); + } + + public void writeSavableListArray(final List<? extends Savable>[] array, final String name, + final List<? extends Savable>[] defVal) throws IOException { + if (array == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST_1D); + writeSavableArrayListArray(array); + } + + public void writeSavableListArray2D(final List<? extends Savable>[][] array, final String name, + final List<? extends Savable>[][] defVal) throws IOException { + if (array == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST_2D); + writeSavableArrayListArray2D(array); + } + + public void writeSavableMap(final Map<? extends Savable, ? extends Savable> map, final String name, + final Map<? extends Savable, ? extends Savable> defVal) throws IOException { + if (map == defVal) { + return; + } + writeAlias(name, BinaryClassField.SAVABLE_MAP); + writeSavableMap(map); + } + + public void writeStringSavableMap(final Map<String, ? extends Savable> map, final String name, + final Map<String, ? extends Savable> defVal) throws IOException { + if (map == defVal) { + return; + } + writeAlias(name, BinaryClassField.STRING_SAVABLE_MAP); + writeStringSavableMap(map); + } + + protected void writeAlias(final String name, final byte fieldType) throws IOException { + if (_cObj._nameFields.get(name) == null) { + generateAlias(name, fieldType); + } + + final byte alias = _cObj._nameFields.get(name)._alias; + write(alias); + } + + // XXX: The generation of aliases is limited to 256 possible values. + // If we run into classes with more than 256 fields, we need to expand this. + // But I mean, come on... + protected void generateAlias(final String name, final byte type) { + final byte alias = (byte) _cObj._nameFields.size(); + _cObj._nameFields.put(name, new BinaryClassField(name, alias, type)); + } + + @Override + public boolean equals(final Object arg0) { + if (!(arg0 instanceof BinaryOutputCapsule)) { + return false; + } + + final byte[] other = ((BinaryOutputCapsule) arg0)._bytes; + if (_bytes.length != other.length) { + return false; + } + return Arrays.equals(_bytes, other); + } + + public void finish() { + // renamed to finish as 'finalize' in java.lang.Object should not be + // overridden like this + // - finalize should not be called directly but is called by garbage + // collection!!! + _bytes = _baos.toByteArray(); + _baos = null; + } + + // byte primitive + + protected void write(final byte value) throws IOException { + _baos.write(value); + } + + protected void write(final byte[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + _baos.write(value); + } + + protected void write(final byte[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // int primitive + + protected void write(final int value) throws IOException { + _baos.write(deflate(ByteUtils.convertToBytes(value))); + } + + protected void write(final int[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final int[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // float primitive + + protected void write(final float value) throws IOException { + _baos.write(ByteUtils.convertToBytes(value)); + } + + protected void write(final float[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final float[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // double primitive + + protected void write(final double value) throws IOException { + _baos.write(ByteUtils.convertToBytes(value)); + } + + protected void write(final double[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final double[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // long primitive + + protected void write(final long value) throws IOException { + _baos.write(deflate(ByteUtils.convertToBytes(value))); + } + + protected void write(final long[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final long[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // short primitive + + protected void write(final short value) throws IOException { + _baos.write(ByteUtils.convertToBytes(value)); + } + + protected void write(final short[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final short[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // boolean primitive + + protected void write(final boolean value) throws IOException { + _baos.write(ByteUtils.convertToBytes(value)); + } + + protected void write(final boolean[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final boolean[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // String + + protected void write(final String value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + // write our output as UTF-8. Java misspells UTF-8 as UTF8 for official use in java.lang + final byte[] bytes = value.getBytes("UTF8"); + write(bytes.length); + _baos.write(bytes); + } + + protected void write(final String[] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + protected void write(final String[][] value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.length); + for (int x = 0; x < value.length; x++) { + write(value[x]); + } + } + + // BitSet + + protected void write(final BitSet value) throws IOException { + if (value == null) { + write(NULL_OBJECT); + return; + } + write(value.size()); + // TODO: MAKE THIS SMALLER + for (int x = 0, max = value.size(); x < max; x++) { + write(value.get(x)); + } + } + + // DEFLATOR for int and long + + protected static byte[] deflate(final byte[] bytes) { + int size = bytes.length; + if (size == 4) { + final int possibleMagic = ByteUtils.convertIntFromBytes(bytes); + if (possibleMagic == NULL_OBJECT) { + return NULL_BYTES; + } else if (possibleMagic == DEFAULT_OBJECT) { + return DEFAULT_BYTES; + } + } + for (int x = 0; x < bytes.length; x++) { + if (bytes[x] != 0) { + break; + } + size--; + } + if (size == 0) { + return new byte[1]; + } + + final byte[] rVal = new byte[1 + size]; + rVal[0] = (byte) size; + for (int x = 1; x < rVal.length; x++) { + rVal[x] = bytes[bytes.length - size - 1 + x]; + } + + return rVal; + } + + // BinarySavable + + protected void write(final Savable object) throws IOException { + if (object == null) { + write(NULL_OBJECT); + return; + } + final int id = _exporter.processBinarySavable(object); + write(id); + } + + // BinarySavable array + + protected void write(final Savable[] objects) throws IOException { + if (objects == null) { + write(NULL_OBJECT); + return; + } + write(objects.length); + for (int x = 0; x < objects.length; x++) { + write(objects[x]); + } + } + + protected void write(final Savable[][] objects) throws IOException { + if (objects == null) { + write(NULL_OBJECT); + return; + } + write(objects.length); + for (int x = 0; x < objects.length; x++) { + write(objects[x]); + } + } + + // List<BinarySavable> + + protected void writeSavableArrayList(final List<? extends Savable> array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.size()); + for (final Savable bs : array) { + write(bs); + } + } + + protected void writeSavableArrayListArray(final List<? extends Savable>[] array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.length); + for (final List<? extends Savable> bs : array) { + writeSavableArrayList(bs); + } + } + + protected void writeSavableArrayListArray2D(final List<? extends Savable>[][] array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.length); + for (final List<? extends Savable>[] bs : array) { + writeSavableArrayListArray(bs); + } + } + + // Map<BinarySavable, BinarySavable> + + protected void writeSavableMap(final Map<? extends Savable, ? extends Savable> array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.size()); + for (final Entry<? extends Savable, ? extends Savable> entry : array.entrySet()) { + write(new Savable[] { entry.getKey(), entry.getValue() }); + } + } + + protected void writeStringSavableMap(final Map<String, ? extends Savable> array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.size()); + + // write String array for keys + final String[] keys = array.keySet().toArray(new String[array.keySet().size()]); + write(keys); + + // write Savable array for values + final Savable[] values = array.values().toArray(new Savable[array.values().size()]); + write(values); + } + + // List<FloatBuffer> + + protected void writeFloatBufferArrayList(final List<FloatBuffer> array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.size()); + for (final FloatBuffer buf : array) { + write(buf); + } + } + + // List<FloatBuffer> + + protected void writeByteBufferArrayList(final List<ByteBuffer> array) throws IOException { + if (array == null) { + write(NULL_OBJECT); + return; + } + write(array.size()); + for (final ByteBuffer buf : array) { + write(buf); + } + } + + // NIO BUFFERS + + // float buffer + protected void write(final FloatBuffer source) throws IOException { + if (source == null) { + write(NULL_OBJECT); + return; + } + + final int sizeof = 4; + + // write length + final int length = source.limit(); + write(length); + + // write boolean for directness + write(_forceDirectNioBuffers || source.isDirect()); + + final byte[] array = new byte[length * sizeof]; + if (source.hasArray()) { + // get the backing array of the source buffer + final float[] backingArray = source.array(); + + // create a tiny store only to perform the conversion into little endian + final ByteBuffer buf = ByteBuffer.allocate(sizeof).order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < backingArray.length; i++) { + buf.putFloat(backingArray[i]).rewind(); + buf.get(array, i * sizeof, sizeof).rewind(); + } + } else { + // duplicate buffer to allow modification of limit/position without changing original. + final FloatBuffer value = source.duplicate(); + + // create little endian store + final ByteBuffer buf = ByteBuffer.allocate(array.length).order(ByteOrder.LITTLE_ENDIAN); + + // place buffer into store. + value.rewind(); + buf.asFloatBuffer().put(value); + buf.rewind(); + + // Pull out store as array + buf.get(array); + } + + // write to stream + _baos.write(array); + } + + // int buffer + protected void write(final IntBuffer source) throws IOException { + if (source == null) { + write(NULL_OBJECT); + return; + } + + final int sizeof = 4; + + // write length + final int length = source.limit(); + write(length); + + // write boolean for directness + write(_forceDirectNioBuffers || source.isDirect()); + + final byte[] array = new byte[length * sizeof]; + if (source.hasArray()) { + // get the backing array of the source buffer + final int[] backingArray = source.array(); + + // create a tiny store only to perform the conversion into little endian + final ByteBuffer buf = ByteBuffer.allocate(sizeof).order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < backingArray.length; i++) { + buf.putInt(backingArray[i]).rewind(); + buf.get(array, i * sizeof, sizeof).rewind(); + } + } else { + // duplicate buffer to allow modification of limit/position without changing original. + final IntBuffer value = source.duplicate(); + + // create little endian store + final ByteBuffer buf = ByteBuffer.allocate(array.length).order(ByteOrder.LITTLE_ENDIAN); + + // place buffer into store. Rewind buffers + value.rewind(); + buf.asIntBuffer().put(value); + buf.rewind(); + + // Pull out store as array + buf.get(array); + } + + // write to stream + _baos.write(array); + } + + // short buffer + protected void write(final ShortBuffer source) throws IOException { + if (source == null) { + write(NULL_OBJECT); + return; + } + + final int sizeof = 2; + + // write length + final int length = source.limit(); + write(length); + + // write boolean for directness + write(_forceDirectNioBuffers || source.isDirect()); + + final byte[] array = new byte[length * sizeof]; + if (source.hasArray()) { + // get the backing array of the source buffer + final short[] backingArray = source.array(); + + // create a tiny store only to perform the conversion into little endian + final ByteBuffer buf = ByteBuffer.allocate(sizeof).order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < backingArray.length; i++) { + buf.putShort(backingArray[i]).rewind(); + buf.get(array, i * sizeof, sizeof).rewind(); + } + } else { + // duplicate buffer to allow modification of limit/position without changing original. + final ShortBuffer value = source.duplicate(); + + // create little endian store + final ByteBuffer buf = ByteBuffer.allocate(array.length).order(ByteOrder.LITTLE_ENDIAN); + + // place buffer into store. Rewind buffers + value.rewind(); + buf.asShortBuffer().put(value); + buf.rewind(); + + // Pull out store as array + buf.get(array); + } + + // write to stream + _baos.write(array); + } + + // byte buffer + protected void write(final ByteBuffer source) throws IOException { + if (source == null) { + write(NULL_OBJECT); + return; + } + + // write length + final int length = source.limit(); + write(length); + + // write boolean for directness + write(_forceDirectNioBuffers || source.isDirect()); + + final byte[] array; + if (source.hasArray()) { + array = source.array(); + } else { + // duplicate buffer to allow modification of limit/position without changing original. + final ByteBuffer value = source.duplicate(); + + // Pull out value as array + array = new byte[length]; + value.rewind(); + value.get(array); + } + + // write to stream + _baos.write(array); + } + + public void write(final Enum<?> value, final String name, final Enum<?> defVal) throws IOException { + if (value == defVal) { + return; + } + if (value == null) { + write(NULL_OBJECT); + } else { + write(value.name(), name, null); + } + } + + public void write(final Enum<?>[] value, final String name) throws IOException { + if (value == null) { + write(NULL_OBJECT); + } else { + final String[] toWrite = new String[value.length]; + int i = 0; + for (final Enum<?> val : value) { + toWrite[i++] = val.name(); + } + write(toWrite, name, null); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java new file mode 100644 index 0000000..00babb9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java @@ -0,0 +1,1295 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.xml; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import com.ardor3d.annotation.SavableFactory; +import com.ardor3d.image.Texture; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.TextureKey; +import com.ardor3d.util.TextureManager; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; +import com.google.common.collect.Lists; + +/** + * Part of the ardor3d XML IO system + */ +public class DOMInputCapsule implements InputCapsule { + + private final Document _doc; + private Element _currentElem; + private boolean _isAtRoot = true; + private final Map<String, Savable> _referencedSavables = new HashMap<String, Savable>(); + + public DOMInputCapsule(final Document doc) { + _doc = doc; + _currentElem = doc.getDocumentElement(); + } + + private static String decodeString(String s) { + if (s == null) { + return null; + } + s = s.replaceAll("\\"", "\"").replaceAll("\\<", "<").replaceAll("\\&", "&"); + return s; + } + + private Element findFirstChildElement(final Element parent) { + Node ret = parent.getFirstChild(); + while (ret != null && (!(ret instanceof Element))) { + ret = ret.getNextSibling(); + } + return (Element) ret; + } + + private Element findChildElement(final Element parent, final String name) { + if (parent == null) { + return null; + } + Node ret = parent.getFirstChild(); + while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { + ret = ret.getNextSibling(); + } + return (Element) ret; + } + + private Element findNextSiblingElement(final Element current) { + Node ret = current.getNextSibling(); + while (ret != null) { + if (ret instanceof Element) { + return (Element) ret; + } + ret = ret.getNextSibling(); + } + return null; + } + + public byte readByte(final String name, final byte defVal) throws IOException { + byte ret = defVal; + try { + ret = Byte.parseByte(_currentElem.getAttribute(name)); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public byte[] readByteArray(final String name, final byte[] defVal) throws IOException { + byte[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final byte[] tmp = new byte[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Byte.parseByte(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public byte[][] readByteArray2D(final String name, final byte[][] defVal) throws IOException { + byte[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final byte[][] tmp = new byte[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readByteArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public int readInt(final String name, final int defVal) throws IOException { + int ret = defVal; + try { + final String s = _currentElem.getAttribute(name); + if (s.length() > 0) { + ret = Integer.parseInt(s); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public int[] readIntArray(final String name, final int[] defVal) throws IOException { + int[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final int[] tmp = new int[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Integer.parseInt(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public int[][] readIntArray2D(final String name, final int[][] defVal) throws IOException { + int[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final int[][] tmp = new int[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readIntArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public float readFloat(final String name, final float defVal) throws IOException { + float ret = defVal; + try { + final String s = _currentElem.getAttribute(name); + if (s.length() > 0) { + ret = Float.parseFloat(s); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public float[] readFloatArray(final String name, final float[] defVal) throws IOException { + float[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final float[] tmp = new float[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Float.parseFloat(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public float[][] readFloatArray2D(final String name, final float[][] defVal) throws IOException { + float[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer")); + final int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer")); + + final float[][] tmp = new float[size_outer][size_inner]; + + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size_outer; i++) { + tmp[i] = new float[size_inner]; + for (int k = 0; k < size_inner; k++) { + tmp[i][k] = Float.parseFloat(strings[i]); + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public double readDouble(final String name, final double defVal) throws IOException { + double ret = defVal; + try { + final String s = _currentElem.getAttribute(name); + if (s.length() > 0) { + ret = Double.parseDouble(s); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public double[] readDoubleArray(final String name, final double[] defVal) throws IOException { + double[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final double[] tmp = new double[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Double.parseDouble(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public double[][] readDoubleArray2D(final String name, final double[][] defVal) throws IOException { + double[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final double[][] tmp = new double[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readDoubleArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public long readLong(final String name, final long defVal) throws IOException { + long ret = defVal; + try { + final String s = _currentElem.getAttribute(name); + if (s.length() > 0) { + ret = Long.parseLong(s); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public long[] readLongArray(final String name, final long[] defVal) throws IOException { + long[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final long[] tmp = new long[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Long.parseLong(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public long[][] readLongArray2D(final String name, final long[][] defVal) throws IOException { + long[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final long[][] tmp = new long[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readLongArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public short readShort(final String name, final short defVal) throws IOException { + short ret = defVal; + try { + final String s = _currentElem.getAttribute(name); + if (s.length() > 0) { + ret = Short.parseShort(s); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public short[] readShortArray(final String name, final short[] defVal) throws IOException { + short[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final short[] tmp = new short[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Short.parseShort(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public short[][] readShortArray2D(final String name, final short[][] defVal) throws IOException { + short[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final short[][] tmp = new short[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readShortArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public boolean readBoolean(final String name, final boolean defVal) throws IOException { + boolean ret = defVal; + try { + final String s = _currentElem.getAttribute(name); + if (s.length() > 0) { + ret = Boolean.parseBoolean(s); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public boolean[] readBooleanArray(final String name, final boolean[] defVal) throws IOException { + boolean[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final boolean[] tmp = new boolean[size]; + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (int i = 0; i < size; i++) { + tmp[i] = Boolean.parseBoolean(strings[i]); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public boolean[][] readBooleanArray2D(final String name, final boolean[][] defVal) throws IOException { + boolean[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final boolean[][] tmp = new boolean[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readBooleanArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public String readString(final String name, final String defVal) throws IOException { + String ret = defVal; + try { + ret = decodeString(_currentElem.getAttribute(name)); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public String[] readStringArray(final String name, final String[] defVal) throws IOException { + String[] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final String[] tmp = new String[size]; + final NodeList nodes = tmpEl.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("String")) { + if (strIndex < size) { + tmp[strIndex++] = ((Element) n).getAttributeNode("value").getValue(); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + // _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public String[][] readStringArray2D(final String name, final String[][] defVal) throws IOException { + String[][] ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final String[][] tmp = new String[size][]; + final NodeList nodes = _currentElem.getChildNodes(); + int strIndex = 0; + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().contains("array")) { + if (strIndex < size) { + tmp[strIndex++] = readStringArray(n.getNodeName(), null); + } else { + throw new IOException("String array contains more elements than specified!"); + } + } + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + _currentElem = (Element) _currentElem.getParentNode(); + return ret; + } + + public BitSet readBitSet(final String name, final BitSet defVal) throws IOException { + BitSet ret = defVal; + try { + final BitSet set = new BitSet(); + final String bitString = _currentElem.getAttribute(name); + final String[] strings = bitString.split("\\s+"); + for (int i = 0; i < strings.length; i++) { + final int isSet = Integer.parseInt(strings[i]); + if (isSet == 1) { + set.set(i); + } + } + ret = set; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public Savable readSavable(final String name, final Savable defVal) throws IOException { + Savable ret = defVal; + + try { + Element tmpEl = null; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + } else if (_isAtRoot) { + tmpEl = _doc.getDocumentElement(); + _isAtRoot = false; + } else { + tmpEl = findFirstChildElement(_currentElem); + } + _currentElem = tmpEl; + ret = readSavableFromCurrentElem(defVal); + if (_currentElem.getParentNode() instanceof Element) { + _currentElem = (Element) _currentElem.getParentNode(); + } else { + _currentElem = null; + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + + return ret; + } + + private Savable readSavableFromCurrentElem(final Savable defVal) throws InstantiationException, + ClassNotFoundException, IOException, IllegalAccessException { + Savable ret = defVal; + Savable tmp = null; + + if (_currentElem == null || _currentElem.getNodeName().equals("null")) { + return null; + } + final String reference = _currentElem.getAttribute("ref"); + if (reference.length() > 0) { + ret = _referencedSavables.get(reference); + } else { + String className = _currentElem.getNodeName(); + if (defVal != null) { + className = defVal.getClass().getName(); + } else if (_currentElem.hasAttribute("class")) { + className = _currentElem.getAttribute("class"); + } + + try { + @SuppressWarnings("unchecked") + final Class<? extends Savable> clazz = (Class<? extends Savable>) Class.forName(className); + final SavableFactory ann = clazz.getAnnotation(SavableFactory.class); + if (ann == null) { + tmp = clazz.newInstance(); + } else { + tmp = (Savable) clazz.getMethod(ann.factoryMethod(), (Class<?>[]) null).invoke(null, + (Object[]) null); + } + } catch (final InstantiationException e) { + Logger.getLogger(getClass().getName()).logp( + Level.SEVERE, + this.getClass().toString(), + "readSavableFromCurrentElem(Savable)", + "Could not access constructor of class '" + className + "'! \n" + + "Some types may require the annotation SavableFactory. Please double check."); + throw new Ardor3dException(e); + } catch (final NoSuchMethodException e) { + Logger.getLogger(getClass().getName()) + .logp(Level.SEVERE, + this.getClass().toString(), + "readSavableFromCurrentElem(Savable)", + e.getMessage() + + " \n" + + "Method specified in annotation does not appear to exist or has an invalid method signature."); + throw new Ardor3dException(e); + } catch (final Exception e) { + Logger.getLogger(getClass().getName()).logp(Level.SEVERE, this.getClass().toString(), + "readSavableFromCurrentElem(Savable)", "Exception", e); + return null; + } + + final String refID = _currentElem.getAttribute("reference_ID"); + if (refID.length() > 0) { + _referencedSavables.put(refID, tmp); + } + if (tmp != null) { + tmp.read(this); + ret = tmp; + } + } + return ret; + } + + private TextureState readTextureStateFromCurrent() { + final Element el = _currentElem; + TextureState ret = null; + try { + ret = (TextureState) readSavableFromCurrentElem(null); + final Savable[] savs = readSavableArray("texture", new Texture[0]); + for (int i = 0; i < savs.length; i++) { + Texture t = (Texture) savs[i]; + final TextureKey tKey = t.getTextureKey(); + t = TextureManager.loadFromKey(tKey, null, t); + ret.setTexture(t, i); + } + } catch (final Exception e) { + Logger.getLogger(DOMInputCapsule.class.getName()).log(Level.SEVERE, null, e); + } + _currentElem = el; + return ret; + } + + private Savable[] readRenderStateList(final Element fromElement, final Savable[] defVal) { + Savable[] ret = defVal; + try { + final List<RenderState> tmp = new ArrayList<RenderState>(); + _currentElem = findFirstChildElement(fromElement); + while (_currentElem != null) { + final Element el = _currentElem; + RenderState rs = null; + if (el.getNodeName().equals("com.ardor3d.scene.state.TextureState")) { + rs = readTextureStateFromCurrent(); + } else { + rs = (RenderState) (readSavableFromCurrentElem(null)); + } + if (rs != null) { + tmp.add(rs); + } + _currentElem = findNextSiblingElement(el); + } + ret = tmp.toArray(new RenderState[0]); + } catch (final Exception e) { + Logger.getLogger(DOMInputCapsule.class.getName()).log(Level.SEVERE, null, e); + } + + return ret; + } + + public Savable[] readSavableArray(final String name, final Savable[] defVal) throws IOException { + Savable[] ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + if (name.equals("renderStateList")) { + ret = readRenderStateList(tmpEl, defVal); + } else { + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final Savable[] tmp = new Savable[size]; + _currentElem = findFirstChildElement(tmpEl); + for (int i = 0; i < size; i++) { + tmp[i] = (readSavableFromCurrentElem(null)); + if (i == size - 1) { + break; + } + _currentElem = findNextSiblingElement(_currentElem); + } + ret = tmp; + } + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public Savable[][] readSavableArray2D(final String name, final Savable[][] defVal) throws IOException { + Savable[][] ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer")); + final int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer")); + + final Savable[][] tmp = new Savable[size_outer][size_inner]; + _currentElem = findFirstChildElement(tmpEl); + for (int i = 0; i < size_outer; i++) { + for (int j = 0; j < size_inner; j++) { + tmp[i][j] = (readSavableFromCurrentElem(null)); + if (i == size_outer - 1 && j == size_inner - 1) { + break; + } + _currentElem = findNextSiblingElement(_currentElem); + } + } + ret = tmp; + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + @SuppressWarnings("unchecked") + public <E extends Savable> List<E> readSavableList(final String name, final List<E> defVal) throws IOException { + List<E> ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final String s = tmpEl.getAttribute("size"); + final int size = Integer.parseInt(s); + @SuppressWarnings("rawtypes") + final List tmp = Lists.newArrayList(); + _currentElem = findFirstChildElement(tmpEl); + for (int i = 0; i < size; i++) { + tmp.add(readSavableFromCurrentElem(null)); + if (i == size - 1) { + break; + } + _currentElem = findNextSiblingElement(_currentElem); + } + ret = tmp; + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + @SuppressWarnings("unchecked") + public <E extends Savable> List<E>[] readSavableListArray(final String name, final List<E>[] defVal) + throws IOException { + List<E>[] ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + _currentElem = tmpEl; + + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final List<E>[] tmp = new ArrayList[size]; + for (int i = 0; i < size; i++) { + final StringBuilder buf = new StringBuilder("SavableArrayList_"); + buf.append(i); + final List<E> al = readSavableList(buf.toString(), null); + tmp[i] = al; + if (i == size - 1) { + break; + } + } + ret = tmp; + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public <E extends Savable> List<E>[][] readSavableListArray2D(final String name, final List<E>[][] defVal) + throws IOException { + List<E>[][] ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + _currentElem = tmpEl; + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + + final List[][] tmp = new ArrayList[size][]; + for (int i = 0; i < size; i++) { + final List[] arr = readSavableListArray("SavableArrayListArray_" + i, null); + tmp[i] = arr; + } + ret = tmp; + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public List<FloatBuffer> readFloatBufferList(final String name, final List<FloatBuffer> defVal) throws IOException { + List<FloatBuffer> ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final List<FloatBuffer> tmp = new ArrayList<FloatBuffer>(size); + _currentElem = findFirstChildElement(tmpEl); + for (int i = 0; i < size; i++) { + tmp.add(readFloatBuffer(null, null)); + if (i == size - 1) { + break; + } + _currentElem = findNextSiblingElement(_currentElem); + } + ret = tmp; + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + @SuppressWarnings("unchecked") + public <K extends Savable, V extends Savable> Map<K, V> readSavableMap(final String name, final Map<K, V> defVal) + throws IOException { + Map<K, V> ret; + Element tempEl; + + if (name != null) { + tempEl = findChildElement(_currentElem, name); + } else { + tempEl = _currentElem; + } + ret = new HashMap<K, V>(); + + final NodeList nodes = tempEl.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + final Element elem = (Element) n; + _currentElem = elem; + final K key = (K) readSavable(XMLExporter.ELEMENT_KEY, null); + final V val = (V) readSavable(XMLExporter.ELEMENT_VALUE, null); + ret.put(key, val); + } + } + _currentElem = (Element) tempEl.getParentNode(); + return ret; + } + + @SuppressWarnings("unchecked") + public <V extends Savable> Map<String, V> readStringSavableMap(final String name, final Map<String, V> defVal) + throws IOException { + Map<String, V> ret = null; + Element tempEl; + + if (name != null) { + tempEl = findChildElement(_currentElem, name); + } else { + tempEl = _currentElem; + } + if (tempEl != null) { + ret = new HashMap<String, V>(); + + final NodeList nodes = tempEl.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + final Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + final Element elem = (Element) n; + _currentElem = elem; + final String key = _currentElem.getAttribute("key"); + final V val = (V) readSavable("Savable", null); + ret.put(key, val); + } + } + } else { + return defVal; + } + _currentElem = (Element) tempEl.getParentNode(); + return ret; + } + + /** + * reads from currentElem if name is null + */ + public FloatBuffer readFloatBuffer(final String name, final FloatBuffer defVal) throws IOException { + FloatBuffer ret = defVal; + try { + Element tmpEl; + if (name != null) { + tmpEl = findChildElement(_currentElem, name); + } else { + tmpEl = _currentElem; + } + if (tmpEl == null) { + return defVal; + } + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final FloatBuffer tmp = BufferUtils.createFloatBuffer(size); + if (size > 0) { + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (final String s : strings) { + tmp.put(Float.parseFloat(s)); + } + tmp.flip(); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public IntBuffer readIntBuffer(final String name, final IntBuffer defVal) throws IOException { + IntBuffer ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final IntBuffer tmp = BufferUtils.createIntBuffer(size); + if (size > 0) { + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (final String s : strings) { + tmp.put(Integer.parseInt(s)); + } + tmp.flip(); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public ByteBuffer readByteBuffer(final String name, final ByteBuffer defVal) throws IOException { + ByteBuffer ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final ByteBuffer tmp = BufferUtils.createByteBuffer(size); + if (size > 0) { + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (final String s : strings) { + tmp.put(Byte.valueOf(s)); + } + tmp.flip(); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public ShortBuffer readShortBuffer(final String name, final ShortBuffer defVal) throws IOException { + ShortBuffer ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final ShortBuffer tmp = BufferUtils.createShortBuffer(size); + if (size > 0) { + final String[] strings = tmpEl.getAttribute("data").split("\\s+"); + for (final String s : strings) { + tmp.put(Short.valueOf(s)); + } + tmp.flip(); + } + ret = tmp; + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public List<ByteBuffer> readByteBufferList(final String name, final List<ByteBuffer> defVal) throws IOException { + List<ByteBuffer> ret = defVal; + try { + final Element tmpEl = findChildElement(_currentElem, name); + if (tmpEl == null) { + return defVal; + } + + final int size = Integer.parseInt(tmpEl.getAttribute("size")); + final List<ByteBuffer> tmp = new ArrayList<ByteBuffer>(size); + _currentElem = findFirstChildElement(tmpEl); + for (int i = 0; i < size; i++) { + tmp.add(readByteBuffer(null, null)); + if (i == size - 1) { + break; + } + _currentElem = findNextSiblingElement(_currentElem); + } + ret = tmp; + _currentElem = (Element) tmpEl.getParentNode(); + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + public <T extends Enum<T>> T readEnum(final String name, final Class<T> enumType, final T defVal) + throws IOException { + T ret = defVal; + try { + final String eVal = _currentElem.getAttribute(name); + if (eVal != null && eVal.length() > 0) { + ret = Enum.valueOf(enumType, eVal); + } + } catch (final Exception e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + return ret; + } + + @SuppressWarnings("unchecked") + public <T extends Enum<T>> T[] readEnumArray(final String name, final Class<T> enumType, final T[] defVal) + throws IOException { + final String[] eVals = readStringArray(name, null); + if (eVals != null) { + final T[] rVal = (T[]) Array.newInstance(enumType, eVals.length); + int i = 0; + for (final String eVal : eVals) { + rVal[i++] = Enum.valueOf(enumType, eVal); + } + return rVal; + } else { + return defVal; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java new file mode 100644 index 0000000..b07df78 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java @@ -0,0 +1,796 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.xml; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.Arrays; +import java.util.BitSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * Part of the ardor3d XML IO system + */ +public class DOMOutputCapsule implements OutputCapsule { + + private static final String _dataAttributeName = "data"; + private final Document _doc; + private Element _currentElement; + private final Map<Savable, Element> _writtenSavables = new IdentityHashMap<Savable, Element>(); + + public DOMOutputCapsule(final Document doc) { + _doc = doc; + _currentElement = null; + } + + public Document getDoc() { + return _doc; + } + + /** + * appends a new Element with the given name to currentElement, sets currentElement to be new Element, and returns + * the new Element as well + */ + private Element appendElement(final String name) { + Element ret = null; + ret = _doc.createElement(name); + if (_currentElement == null) { + _doc.appendChild(ret); + } else { + _currentElement.appendChild(ret); + } + _currentElement = ret; + return ret; + } + + private static String encodeString(String s) { + if (s == null) { + return null; + } + s = s.replaceAll("\\&", "&").replaceAll("\\\"", """).replaceAll("\\<", "<"); + return s; + } + + public void write(final byte value, final String name, final byte defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(byte[] value, final String name, final byte[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final byte b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(buf.length() - 1); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(byte[][] value, final String name, final byte[][] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final byte[] bs : value) { + for (final byte b : bs) { + buf.append(b); + buf.append(" "); + } + buf.append(" "); + } + // remove last spaces + buf.setLength(buf.length() - 2); + + final Element el = appendElement(name); + el.setAttribute("size_outer", String.valueOf(value.length)); + el.setAttribute("size_inner", String.valueOf(value[0].length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final int value, final String name, final int defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(final int[] value, final String name, final int[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + return; + } + if (Arrays.equals(value, defVal)) { + return; + } + + for (final int b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(Math.max(0, buf.length() - 1)); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final int[][] value, final String name, final int[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final int[] array = value[i]; + write(array, "array_" + i, defVal == null ? null : defVal[i]); + } + _currentElement = (Element) el.getParentNode(); + } + + public void write(final float value, final String name, final float defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(float[] value, final String name, final float[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final float b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(buf.length() - 1); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final float[][] value, final String name, final float[][] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + for (final float[] bs : value) { + for (final float b : bs) { + buf.append(b); + buf.append(" "); + } + } + // remove last space + buf.setLength(buf.length() - 1); + + final Element el = appendElement(name); + el.setAttribute("size_outer", String.valueOf(value.length)); + el.setAttribute("size_inner", String.valueOf(value[0].length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final double value, final String name, final double defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(double[] value, final String name, final double[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final double b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(buf.length() - 1); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final double[][] value, final String name, final double[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final double[] array = value[i]; + write(array, "array_" + i, defVal == null ? null : defVal[i]); + } + _currentElement = (Element) el.getParentNode(); + } + + public void write(final long value, final String name, final long defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(long[] value, final String name, final long[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final long b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(buf.length() - 1); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final long[][] value, final String name, final long[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final long[] array = value[i]; + write(array, "array_" + i, defVal == null ? null : defVal[i]); + } + _currentElement = (Element) el.getParentNode(); + } + + public void write(final short value, final String name, final short defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(short[] value, final String name, final short[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final short b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(buf.length() - 1); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final short[][] value, final String name, final short[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final short[] array = value[i]; + write(array, "array_" + i, defVal == null ? null : defVal[i]); + } + _currentElement = (Element) el.getParentNode(); + } + + public void write(final boolean value, final String name, final boolean defVal) throws IOException { + if (value == defVal) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(boolean[] value, final String name, final boolean[] defVal) throws IOException { + final StringBuilder buf = new StringBuilder(); + if (value == null) { + value = defVal; + } + for (final boolean b : value) { + buf.append(b); + buf.append(" "); + } + // remove last space + buf.setLength(Math.max(0, buf.length() - 1)); + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final boolean[][] value, final String name, final boolean[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final boolean[] array = value[i]; + write(array, "array_" + i, defVal == null ? null : defVal[i]); + } + _currentElement = (Element) el.getParentNode(); + } + + public void write(final String value, final String name, final String defVal) throws IOException { + if (value == null || value.equals(defVal)) { + return; + } + _currentElement.setAttribute(name, encodeString(value)); + } + + public void write(String[] value, final String name, final String[] defVal) throws IOException { + final Element el = appendElement(name); + + if (value == null) { + value = defVal; + } + + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final String b = value[i]; + appendElement("String_" + i); + final String val = encodeString(b); + _currentElement.setAttribute("value", val); + _currentElement = el; + } + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void write(final String[][] value, final String name, final String[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.length)); + + for (int i = 0; i < value.length; i++) { + final String[] array = value[i]; + write(array, "array_" + i, defVal == null ? null : defVal[i]); + } + _currentElement = (Element) el.getParentNode(); + } + + public void write(final BitSet value, final String name, final BitSet defVal) throws IOException { + if (value == null || value.equals(defVal)) { + return; + } + final StringBuilder buf = new StringBuilder(); + for (int i = value.nextSetBit(0); i >= 0; i = value.nextSetBit(i + 1)) { + buf.append(i); + buf.append(" "); + } + buf.setLength(Math.max(0, buf.length() - 1)); + _currentElement.setAttribute(name, buf.toString()); + + } + + public void write(final Savable object, String name, final Savable defVal) throws IOException { + if (object == null) { + return; + } + if (object.equals(defVal)) { + return; + } + + final Element old = _currentElement; + Element el = _writtenSavables.get(object); + + String className = null; + if (!object.getClass().getName().equals(name)) { + className = object.getClass().getName(); + } + try { + _doc.createElement(name); + } catch (final DOMException e) { + name = "Object"; + className = object.getClass().getName(); + } + + if (el != null) { + String refID = el.getAttribute("reference_ID"); + if (refID.length() == 0) { + refID = object.getClassTag().getName() + "@" + object.hashCode(); + el.setAttribute("reference_ID", refID); + } + el = appendElement(name); + el.setAttribute("ref", refID); + } else { + el = appendElement(name); + _writtenSavables.put(object, el); + object.write(this); + } + if (className != null) { + el.setAttribute("class", className); + } + + _currentElement = old; + } + + public void write(final Savable[] objects, final String name, final Savable[] defVal) throws IOException { + if (objects == null) { + return; + } + if (Arrays.equals(objects, defVal)) { + return; + } + + final Element old = _currentElement; + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(objects.length)); + for (int i = 0; i < objects.length; i++) { + final Savable o = objects[i]; + if (o == null) { + // renderStateList has special loading code, so we can leave out the null values + if (!name.equals("renderStateList")) { + final Element before = _currentElement; + appendElement("null"); + _currentElement = before; + } + } else { + write(o, o.getClassTag().getName(), null); + } + } + _currentElement = old; + } + + public void write(final Savable[][] value, final String name, final Savable[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size_outer", String.valueOf(value.length)); + el.setAttribute("size_inner", String.valueOf(value[0].length)); + for (final Savable[] bs : value) { + for (final Savable b : bs) { + write(b, b.getClassTag().getSimpleName(), null); + } + } + _currentElement = (Element) _currentElement.getParentNode(); + } + + public void writeSavableList(final List<? extends Savable> array, final String name, + final List<? extends Savable> defVal) throws IOException { + if (array == null) { + return; + } + if (array.equals(defVal)) { + return; + } + final Element old = _currentElement; + final Element el = appendElement(name); + _currentElement = el; + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); + for (final Object o : array) { + if (o == null) { + continue; + } else if (o instanceof Savable) { + final Savable s = (Savable) o; + write(s, s.getClassTag().getName(), null); + } else { + throw new ClassCastException("Not a Savable instance: " + o); + } + } + _currentElement = old; + } + + public void writeSavableListArray(final List<? extends Savable>[] objects, final String name, + final List<? extends Savable>[] defVal) throws IOException { + if (objects == null) { + return; + } + if (Arrays.equals(objects, defVal)) { + return; + } + + final Element old = _currentElement; + final Element el = appendElement(name); + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length)); + for (int i = 0; i < objects.length; i++) { + final List<? extends Savable> o = objects[i]; + if (o == null) { + final Element before = _currentElement; + appendElement("null"); + _currentElement = before; + } else { + final StringBuilder buf = new StringBuilder("SavableArrayList_"); + buf.append(i); + writeSavableList(o, buf.toString(), null); + } + } + _currentElement = old; + } + + public void writeSavableListArray2D(final List<? extends Savable>[][] value, final String name, + final List<? extends Savable>[][] defVal) throws IOException { + if (value == null) { + return; + } + if (Arrays.deepEquals(value, defVal)) { + return; + } + + final Element el = appendElement(name); + final int size = value.length; + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size)); + + for (int i = 0; i < size; i++) { + final List<? extends Savable>[] vi = value[i]; + writeSavableListArray(vi, "SavableArrayListArray_" + i, null); + } + _currentElement = (Element) el.getParentNode(); + } + + public void writeFloatBufferList(final List<FloatBuffer> array, final String name, final List<FloatBuffer> defVal) + throws IOException { + if (array == null) { + return; + } + if (array.equals(defVal)) { + return; + } + final Element el = appendElement(name); + el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); + for (final FloatBuffer o : array) { + write(o, XMLExporter.ELEMENT_FLOATBUFFER, null); + } + _currentElement = (Element) el.getParentNode(); + } + + public void writeSavableMap(final Map<? extends Savable, ? extends Savable> map, final String name, + final Map<? extends Savable, ? extends Savable> defVal) throws IOException { + if (map == null) { + return; + } + if (map.equals(defVal)) { + return; + } + final Element stringMap = appendElement(name); + + final Iterator<? extends Savable> keyIterator = map.keySet().iterator(); + while (keyIterator.hasNext()) { + final Savable key = keyIterator.next(); + appendElement(XMLExporter.ELEMENT_MAPENTRY); + write(key, XMLExporter.ELEMENT_KEY, null); + final Savable value = map.get(key); + write(value, XMLExporter.ELEMENT_VALUE, null); + _currentElement = stringMap; + } + + _currentElement = (Element) stringMap.getParentNode(); + } + + public void writeStringSavableMap(final Map<String, ? extends Savable> map, final String name, + final Map<String, ? extends Savable> defVal) throws IOException { + if (map == null) { + return; + } + if (map.equals(defVal)) { + return; + } + final Element stringMap = appendElement(name); + + final Iterator<String> keyIterator = map.keySet().iterator(); + while (keyIterator.hasNext()) { + final String key = keyIterator.next(); + final Element mapEntry = appendElement("MapEntry"); + mapEntry.setAttribute("key", key); + final Savable s = map.get(key); + write(s, "Savable", null); + _currentElement = stringMap; + } + + _currentElement = (Element) stringMap.getParentNode(); + } + + public void write(final FloatBuffer value, final String name, final FloatBuffer defVal) throws IOException { + if (value == null) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.limit())); + final StringBuilder buf = new StringBuilder(); + final int pos = value.position(); + value.rewind(); + while (value.hasRemaining()) { + buf.append(value.get()); + buf.append(" "); + } + buf.setLength(Math.max(0, buf.length() - 1)); + value.position(pos); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) el.getParentNode(); + } + + public void write(final IntBuffer value, final String name, final IntBuffer defVal) throws IOException { + if (value == null) { + return; + } + if (value.equals(defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.limit())); + final StringBuilder buf = new StringBuilder(); + final int pos = value.position(); + value.rewind(); + while (value.hasRemaining()) { + buf.append(value.get()); + buf.append(" "); + } + buf.setLength(buf.length() - 1); + value.position(pos); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) el.getParentNode(); + } + + public void write(final ByteBuffer value, final String name, final ByteBuffer defVal) throws IOException { + if (value == null) { + return; + } + if (value.equals(defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.limit())); + final StringBuilder buf = new StringBuilder(); + final int pos = value.position(); + value.rewind(); + while (value.hasRemaining()) { + buf.append(value.get()); + buf.append(" "); + } + buf.setLength(buf.length() - 1); + value.position(pos); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) el.getParentNode(); + } + + public void write(final ShortBuffer value, final String name, final ShortBuffer defVal) throws IOException { + if (value == null) { + return; + } + if (value.equals(defVal)) { + return; + } + + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(value.limit())); + final StringBuilder buf = new StringBuilder(); + final int pos = value.position(); + value.rewind(); + while (value.hasRemaining()) { + buf.append(value.get()); + buf.append(" "); + } + buf.setLength(buf.length() - 1); + value.position(pos); + el.setAttribute(_dataAttributeName, buf.toString()); + _currentElement = (Element) el.getParentNode(); + } + + public void writeByteBufferList(final List<ByteBuffer> array, final String name, final List<ByteBuffer> defVal) + throws IOException { + if (array == null) { + return; + } + if (array.equals(defVal)) { + return; + } + final Element el = appendElement(name); + el.setAttribute("size", String.valueOf(array.size())); + for (final ByteBuffer o : array) { + write(o, "ByteBuffer", null); + } + _currentElement = (Element) el.getParentNode(); + + } + + public void write(final Enum<?> value, final String name, final Enum<?> defVal) throws IOException { + if (value == defVal || value == null) { + return; + } + _currentElement.setAttribute(name, String.valueOf(value)); + } + + public void write(final Enum<?>[] value, final String name) throws IOException { + if (value == null) { + return; + } else { + final String[] toWrite = new String[value.length]; + int i = 0; + for (final Enum<?> val : value) { + toWrite[i++] = val.name(); + } + write(toWrite, name, null); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java new file mode 100644 index 0000000..eef7b79 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.xml; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * The DOMSerializer was based primarily off the DOMSerializer.java class from the "Java and XML" 3rd Edition book by + * Brett McLaughlin, and Justin Edelson. Some modifications were made to support formatting of elements and attributes. + * + */ +public class DOMSerializer { + + /** Indentation to use (default is no indentation) */ + private String _indent = ""; + + /** Line separator to use (default is for Windows) */ + private String _lineSeparator = "\n"; + + /** Encoding for output (default is UTF-8) */ + private String _encoding = "UTF8"; + + /** Attributes will be displayed on seperate lines */ + private final boolean _displayAttributesOnSeperateLine = true; + + public void setLineSeparator(final String lineSeparator) { + _lineSeparator = lineSeparator; + } + + public void setEncoding(final String encoding) { + _encoding = encoding; + } + + public void setIndent(final int numSpaces) { + final StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < numSpaces; i++) { + buffer.append("\t"); + } + _indent = buffer.toString(); + } + + public void serialize(final Document doc, final OutputStream out) throws IOException { + final Writer writer = new OutputStreamWriter(out, _encoding); + serialize(doc, writer); + } + + public void serialize(final Document doc, final File file) throws IOException { + final Writer writer = new FileWriter(file); + serialize(doc, writer); + } + + public void serialize(final Document doc, final Writer writer) throws IOException { + // Start serialization recursion with no indenting + serializeNode(doc, writer, ""); + writer.flush(); + } + + private void serializeNode(final Node node, final Writer writer, final String indentLevel) throws IOException { + // Determine action based on node type + switch (node.getNodeType()) { + case Node.DOCUMENT_NODE: + final Document doc = (Document) node; + /** + * DOM Level 2 code writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + */ + writer.write("<?xml version=\""); + writer.write(doc.getXmlVersion()); + writer.write("\" encoding=\"UTF-8\" standalone=\""); + if (doc.getXmlStandalone()) { + writer.write("yes"); + } else { + writer.write("no"); + } + writer.write("\""); + writer.write("?>"); + writer.write(_lineSeparator); + + // recurse on each top-level node + final NodeList nodes = node.getChildNodes(); + if (nodes != null) { + for (int i = 0; i < nodes.getLength(); i++) { + serializeNode(nodes.item(i), writer, ""); + } + } + break; + case Node.ELEMENT_NODE: + final String name = node.getNodeName(); + // writer.write(indentLevel + "<" + name); + writer.write("<" + name); + final NamedNodeMap attributes = node.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + final Node current = attributes.item(i); + String attributeSeperator = " "; + if (_displayAttributesOnSeperateLine && i != 0) { + attributeSeperator = _lineSeparator + indentLevel + _indent; + } + // Double indentLevel to match parent element and then one indention to format below parent + final String attributeStr = attributeSeperator + current.getNodeName() + "=\""; + writer.write(attributeStr); + print(writer, current.getNodeValue()); + writer.write("\""); + } + writer.write(">"); + + // recurse on each child + final NodeList children = node.getChildNodes(); + if (children != null) { + if ((children.item(0) != null) && (children.item(0).getNodeType() == Node.ELEMENT_NODE)) { + // writer.write(lineSeparator); + } + + for (int i = 0; i < children.getLength(); i++) { + serializeNode(children.item(i), writer, indentLevel + _indent); + } + + if ((children.item(0) != null) + && (children.item(children.getLength() - 1).getNodeType() == Node.ELEMENT_NODE)) { + ;// writer.write(indentLevel); + } + } + + writer.write("</" + name + ">"); + // writer.write(lineSeparator); + break; + case Node.TEXT_NODE: + print(writer, node.getNodeValue()); + break; + case Node.CDATA_SECTION_NODE: + writer.write("<![CDATA["); + print(writer, node.getNodeValue()); + writer.write("]]>"); + break; + case Node.COMMENT_NODE: + writer.write(indentLevel + "<!-- " + node.getNodeValue() + " -->"); + writer.write(_lineSeparator); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + writer.write("<?" + node.getNodeName() + " " + node.getNodeValue() + "?>"); + writer.write(_lineSeparator); + break; + case Node.ENTITY_REFERENCE_NODE: + writer.write("&" + node.getNodeName() + ";"); + break; + case Node.DOCUMENT_TYPE_NODE: + final DocumentType docType = (DocumentType) node; + final String publicId = docType.getPublicId(); + final String systemId = docType.getSystemId(); + final String internalSubset = docType.getInternalSubset(); + writer.write("<!DOCTYPE " + docType.getName()); + if (publicId != null) { + writer.write(" PUBLIC \"" + publicId + "\" "); + } else { + writer.write(" SYSTEM "); + } + writer.write("\"" + systemId + "\""); + if (internalSubset != null) { + writer.write(" [" + internalSubset + "]"); + } + writer.write(">"); + writer.write(_lineSeparator); + break; + } + } + + private void print(final Writer writer, final String s) throws IOException { + + if (s == null) { + return; + } + for (int i = 0, len = s.length(); i < len; i++) { + final char c = s.charAt(i); + switch (c) { + case '<': + writer.write("<"); + break; + case '>': + writer.write(">"); + break; + case '&': + writer.write("&"); + break; + case '\r': + writer.write("
"); + break; + default: + writer.write(c); + } + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java new file mode 100644 index 0000000..7aff34a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java @@ -0,0 +1,26 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.OutputStream;
+
+import org.w3c.dom.Document;
+
+/**
+ * Part of the ardor3d XML IO system
+ */
+public class DOM_PrettyPrint {
+ public static void serialize(final Document doc, final OutputStream out) throws Exception {
+ final DOMSerializer serializer = new DOMSerializer();
+ serializer.setIndent(2);
+ serializer.serialize(doc, out);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java new file mode 100644 index 0000000..e23bf32 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.xml; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.xml.parsers.DocumentBuilderFactory; + +import com.ardor3d.util.export.Ardor3dExporter; +import com.ardor3d.util.export.Savable; + +/** + * Part of the ardor3d XML IO system + */ +public class XMLExporter implements Ardor3dExporter { + public static final String ELEMENT_MAPENTRY = "MapEntry"; + public static final String ELEMENT_KEY = "Key"; + public static final String ELEMENT_VALUE = "Value"; + public static final String ELEMENT_FLOATBUFFER = "FloatBuffer"; + public static final String ATTRIBUTE_SIZE = "size"; + + public XMLExporter() { + + } + + public void save(final Savable object, final OutputStream os) throws IOException { + try { + // Initialize Document when saving so we don't retain state of previous exports + final DOMOutputCapsule _domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance() + .newDocumentBuilder().newDocument()); + _domOut.write(object, object.getClass().getName(), null); + DOM_PrettyPrint.serialize(_domOut.getDoc(), os); + os.flush(); + } catch (final Exception ex) { + final IOException e = new IOException(); + e.initCause(ex); + throw e; + } + } + + public void save(final Savable object, final File f) throws IOException { + save(object, new FileOutputStream(f)); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java new file mode 100644 index 0000000..a5caa2a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.export.xml; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + +import com.ardor3d.util.export.Ardor3dImporter; +import com.ardor3d.util.export.Savable; + +/** + * Part of the ardor3d XML IO system + */ +public class XMLImporter implements Ardor3dImporter { + + public XMLImporter() {} + + public Savable load(final InputStream is) throws IOException { + try { + final DOMInputCapsule _domIn = new DOMInputCapsule(DocumentBuilderFactory.newInstance() + .newDocumentBuilder().parse(is)); + return _domIn.readSavable(null, null); + } catch (final SAXException e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } catch (final ParserConfigurationException e) { + final IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } + } + + public Savable load(final URL url) throws IOException { + return load(url.openStream()); + } + + public Savable load(final File f) throws IOException { + return load(new FileInputStream(f)); + } + + public Savable load(final byte[] data) throws IOException { + return load(new ByteArrayInputStream(data)); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java new file mode 100644 index 0000000..ebe13ca --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java @@ -0,0 +1,1697 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.Vector4; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.math.type.ReadOnlyVector4; +import com.ardor3d.scenegraph.ByteBufferData; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.IntBufferData; +import com.ardor3d.scenegraph.ShortBufferData; +import com.ardor3d.util.Ardor3dException; +import com.ardor3d.util.Constants; +import com.google.common.collect.MapMaker; + +/** + * <code>BufferUtils</code> is a helper class for generating nio buffers from ardor3d data classes such as Vectors and + * ColorRGBA. + */ +public final class BufferUtils { + + // // -- TRACKER HASH -- //// + private static final Map<Buffer, Object> trackingHash = new MapMaker().weakKeys().makeMap(); + private static final Object ref = new Object(); + + // // -- COLORRGBA METHODS -- //// + + /** + * Generate a new FloatBuffer using the given array of ColorRGBA objects. The FloatBuffer will be 4 * data.length + * long and contain the color data as data[0].r, data[0].g, data[0].b, data[0].a, data[1].r... etc. + * + * @param data + * array of ColorRGBA objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final ReadOnlyColorRGBA... data) { + if (data == null) { + return null; + } + return createFloatBuffer(0, data.length, data); + } + + /** + * Generate a new FloatBuffer using the given array of ColorRGBA objects. The FloatBuffer will be 4 * data.length + * long and contain the color data as data[0].r, data[0].g, data[0].b, data[0].a, data[1].r... etc. + * + * @param offset + * the starting index to read from in our data array + * @param length + * the number of colors to read + * @param data + * array of ColorRGBA objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyColorRGBA... data) { + if (data == null) { + return null; + } + final FloatBuffer buff = createFloatBuffer(4 * length); + for (int x = offset; x < length; x++) { + if (data[x] != null) { + buff.put(data[x].getRed()).put(data[x].getGreen()).put(data[x].getBlue()).put(data[x].getAlpha()); + } else { + buff.put(0).put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of ColorRGBA object data. + * + * @param colors + * number of colors that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createColorBuffer(final int colors) { + final FloatBuffer colorBuff = createFloatBuffer(4 * colors); + return colorBuff; + } + + /** + * Sets the data contained in the given color into the FloatBuffer at the specified index. + * + * @param color + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the position to place the data; in terms of colors not floats + */ + public static void setInBuffer(final ReadOnlyColorRGBA color, final FloatBuffer buf, final int index) { + buf.position(index * 4); + buf.put(color.getRed()); + buf.put(color.getGreen()); + buf.put(color.getBlue()); + buf.put(color.getAlpha()); + } + + /** + * Updates the values of the given color from the specified buffer at the index provided. + * + * @param store + * the color to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of colors, not floats) to read from the buf + */ + public static void populateFromBuffer(final ColorRGBA store, final FloatBuffer buf, final int index) { + store.setRed(buf.get(index * 4)); + store.setGreen(buf.get(index * 4 + 1)); + store.setBlue(buf.get(index * 4 + 2)); + store.setAlpha(buf.get(index * 4 + 3)); + } + + /** + * Generates a ColorRGBA array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of ColorRGBA objects + */ + public static ColorRGBA[] getColorArray(final FloatBuffer buff) { + buff.rewind(); + final ColorRGBA[] colors = new ColorRGBA[buff.limit() >> 2]; + for (int x = 0; x < colors.length; x++) { + final ColorRGBA c = new ColorRGBA(buff.get(), buff.get(), buff.get(), buff.get()); + colors[x] = c; + } + return colors; + } + + /** + * Generates a ColorRGBA array from the given FloatBufferData. + * + * @param buff + * the FloatBufferData to read from + * @param defaults + * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is + * smaller than 4. + * @return a newly generated array of ColorRGBA objects + */ + public static ColorRGBA[] getColorArray(final FloatBufferData data, final ReadOnlyColorRGBA defaults) { + final FloatBuffer buff = data.getBuffer(); + buff.clear(); + final ColorRGBA[] colors = new ColorRGBA[data.getTupleCount()]; + final int tupleSize = data.getValuesPerTuple(); + for (int x = 0; x < colors.length; x++) { + final ColorRGBA c = new ColorRGBA(defaults); + c.setRed(buff.get()); + if (tupleSize > 1) { + c.setGreen(buff.get()); + } + if (tupleSize > 2) { + c.setBlue(buff.get()); + } + if (tupleSize > 3) { + c.setAlpha(buff.get()); + } + if (tupleSize > 4) { + buff.position(buff.position() + tupleSize - 4); + } + colors[x] = c; + } + return colors; + } + + /** + * Copies a ColorRGBA from one position in the buffer to another. The index values are in terms of color number (eg, + * color number 0 is positions 0-3 in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the color to copy + * @param toPos + * the index to copy the color to + */ + public static void copyInternalColor(final FloatBuffer buf, final int fromPos, final int toPos) { + copyInternal(buf, fromPos * 4, toPos * 4, 4); + } + + /** + * Checks to see if the given ColorRGBA is equals to the data stored in the buffer at the given data index. + * + * @param check + * the color to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of colors, not floats) of the color in the buffer to check against + * @return + */ + public static boolean equals(final ReadOnlyColorRGBA check, final FloatBuffer buf, final int index) { + final ColorRGBA temp = new ColorRGBA(); + populateFromBuffer(temp, buf, index); + return temp.equals(check); + } + + // // -- Vector4 METHODS -- //// + + /** + * Generate a new FloatBuffer using the given array of Vector4 objects. The FloatBuffer will be 4 * data.length long + * and contain the vector data as data[0].x, data[0].y, data[0].z, data[0].w, data[1].x... etc. + * + * @param offset + * the starting index to read from in our data array + * @param length + * the number of vectors to read + * @param data + * array of Vector4 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final ReadOnlyVector4... data) { + if (data == null) { + return null; + } + return createFloatBuffer(0, data.length, data); + } + + /** + * Generate a new FloatBuffer using the given array of Vector4 objects. The FloatBuffer will be 4 * data.length long + * and contain the vector data as data[0].x, data[0].y, data[0].z, data[0].w, data[1].x... etc. + * + * @param data + * array of Vector4 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyVector4... data) { + if (data == null) { + return null; + } + final FloatBuffer buff = createFloatBuffer(4 * length); + for (int x = offset; x < length; x++) { + if (data[x] != null) { + buff.put(data[x].getXf()).put(data[x].getYf()).put(data[x].getZf()).put(data[x].getWf()); + } else { + buff.put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector4 object data. + * + * @param vertices + * number of vertices that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector4Buffer(final int vertices) { + final FloatBuffer vBuff = createFloatBuffer(4 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector4 object data only if the + * given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector4Buffer(final FloatBuffer buf, final int vertices) { + if (buf != null && buf.limit() == 4 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(4 * vertices); + } + + /** + * Sets the data contained in the given Vector4 into the FloatBuffer at the specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the position to place the data; in terms of vectors not floats + */ + public static void setInBuffer(final ReadOnlyVector4 vector, final FloatBuffer buf, final int index) { + if (buf == null) { + return; + } + if (vector == null) { + buf.put(index * 4, 0); + buf.put((index * 4) + 1, 0); + buf.put((index * 4) + 2, 0); + buf.put((index * 4) + 3, 0); + } else { + buf.put(index * 4, vector.getXf()); + buf.put((index * 4) + 1, vector.getYf()); + buf.put((index * 4) + 2, vector.getZf()); + buf.put((index * 4) + 3, vector.getWf()); + } + } + + /** + * Updates the values of the given vector from the specified buffer at the index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from the buffer + */ + public static void populateFromBuffer(final Vector4 vector, final FloatBuffer buf, final int index) { + vector.setX(buf.get(index * 4)); + vector.setY(buf.get(index * 4 + 1)); + vector.setZ(buf.get(index * 4 + 2)); + vector.setW(buf.get(index * 4 + 3)); + } + + /** + * Generates a Vector4 array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector3 objects + */ + public static Vector4[] getVector4Array(final FloatBuffer buff) { + buff.clear(); + final Vector4[] verts = new Vector4[buff.limit() / 4]; + for (int x = 0; x < verts.length; x++) { + final Vector4 v = new Vector4(buff.get(), buff.get(), buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Generates a Vector4 array from the given FloatBufferData. + * + * @param buff + * the FloatBufferData to read from + * @param defaults + * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is + * smaller than 4. + * @return a newly generated array of Vector4 objects + */ + public static Vector4[] getVector4Array(final FloatBufferData data, final ReadOnlyVector4 defaults) { + final FloatBuffer buff = data.getBuffer(); + buff.clear(); + final Vector4[] verts = new Vector4[data.getTupleCount()]; + final int tupleSize = data.getValuesPerTuple(); + for (int x = 0; x < verts.length; x++) { + final Vector4 v = new Vector4(defaults); + v.setX(buff.get()); + if (tupleSize > 1) { + v.setY(buff.get()); + } + if (tupleSize > 2) { + v.setZ(buff.get()); + } + if (tupleSize > 3) { + v.setW(buff.get()); + } + if (tupleSize > 4) { + buff.position(buff.position() + tupleSize - 4); + } + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector3 from one position in the buffer to another. The index values are in terms of vector number (eg, + * vector number 0 is positions 0-2 in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector4(final FloatBuffer buf, final int fromPos, final int toPos) { + copyInternal(buf, fromPos * 4, toPos * 4, 4); + } + + /** + * Normalize a Vector4 in-buffer. + * + * @param buf + * the buffer to find the Vector4 within + * @param index + * the position (in terms of vectors, not floats) of the vector to normalize + */ + public static void normalizeVector4(final FloatBuffer buf, final int index) { + final Vector4 temp = Vector4.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.normalizeLocal(); + setInBuffer(temp, buf, index); + Vector4.releaseTempInstance(temp); + } + + /** + * Add to a Vector4 in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector4 within + * @param index + * the position (in terms of vectors, not floats) of the vector to add to + */ + public static void addInBuffer(final ReadOnlyVector4 toAdd, final FloatBuffer buf, final int index) { + final Vector4 temp = Vector4.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.addLocal(toAdd); + setInBuffer(temp, buf, index); + Vector4.releaseTempInstance(temp); + } + + /** + * Multiply and store a Vector3 in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector3 within + * @param index + * the position (in terms of vectors, not floats) of the vector to multiply + */ + public static void multInBuffer(final ReadOnlyVector4 toMult, final FloatBuffer buf, final int index) { + final Vector4 temp = Vector4.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.multiplyLocal(toMult); + setInBuffer(temp, buf, index); + Vector4.releaseTempInstance(temp); + } + + /** + * Checks to see if the given Vector3 is equals to the data stored in the buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector in the buffer to check against + * @return + */ + public static boolean equals(final ReadOnlyVector4 check, final FloatBuffer buf, final int index) { + final Vector4 temp = Vector4.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + final boolean equals = temp.equals(check); + Vector4.releaseTempInstance(temp); + return equals; + } + + // // -- Vector3 METHODS -- //// + + /** + * Generate a new FloatBuffer using the given array of Vector3 objects. The FloatBuffer will be 3 * data.length long + * and contain the vector data as data[0].x, data[0].y, data[0].z, data[1].x... etc. + * + * @param data + * array of Vector3 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final ReadOnlyVector3... data) { + if (data == null) { + return null; + } + return createFloatBuffer(0, data.length, data); + } + + /** + * Generate a new FloatBuffer using the given array of Vector3 objects. The FloatBuffer will be 3 * data.length long + * and contain the vector data as data[0].x, data[0].y, data[0].z, data[1].x... etc. + * + * @param offset + * the starting index to read from in our data array + * @param length + * the number of vectors to read + * @param data + * array of Vector3 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyVector3... data) { + if (data == null) { + return null; + } + final FloatBuffer buff = createFloatBuffer(3 * length); + for (int x = offset; x < length; x++) { + if (data[x] != null) { + buff.put(data[x].getXf()).put(data[x].getYf()).put(data[x].getZf()); + } else { + buff.put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector3 object data. + * + * @param vertices + * number of vertices that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector3Buffer(final int vertices) { + final FloatBuffer vBuff = createFloatBuffer(3 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector3 object data only if the + * given buffer is not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector3Buffer(final FloatBuffer buf, final int vertices) { + if (buf != null && buf.limit() == 3 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(3 * vertices); + } + + /** + * Sets the data contained in the given Vector3 into the FloatBuffer at the specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the position to place the data; in terms of vectors not floats + */ + public static void setInBuffer(final ReadOnlyVector3 vector, final FloatBuffer buf, final int index) { + if (buf == null) { + return; + } + if (vector == null) { + buf.put(index * 3, 0); + buf.put((index * 3) + 1, 0); + buf.put((index * 3) + 2, 0); + } else { + buf.put(index * 3, vector.getXf()); + buf.put((index * 3) + 1, vector.getYf()); + buf.put((index * 3) + 2, vector.getZf()); + } + } + + /** + * Updates the values of the given vector from the specified buffer at the index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from the buf + */ + public static void populateFromBuffer(final Vector3 vector, final FloatBuffer buf, final int index) { + vector.setX(buf.get(index * 3)); + vector.setY(buf.get(index * 3 + 1)); + vector.setZ(buf.get(index * 3 + 2)); + } + + /** + * Generates a Vector3 array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector3 objects + */ + public static Vector3[] getVector3Array(final FloatBuffer buff) { + buff.clear(); + final Vector3[] verts = new Vector3[buff.limit() / 3]; + for (int x = 0; x < verts.length; x++) { + final Vector3 v = new Vector3(buff.get(), buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Generates a Vector3 array from the given FloatBufferData. + * + * @param buff + * the FloatBufferData to read from + * @param defaults + * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is + * smaller than 3. + * @return a newly generated array of Vector3 objects + */ + public static Vector3[] getVector3Array(final FloatBufferData data, final ReadOnlyVector3 defaults) { + final FloatBuffer buff = data.getBuffer(); + buff.clear(); + final Vector3[] verts = new Vector3[data.getTupleCount()]; + final int tupleSize = data.getValuesPerTuple(); + for (int x = 0; x < verts.length; x++) { + final Vector3 v = new Vector3(defaults); + v.setX(buff.get()); + if (tupleSize > 1) { + v.setY(buff.get()); + } + if (tupleSize > 2) { + v.setZ(buff.get()); + } + if (tupleSize > 3) { + buff.position(buff.position() + tupleSize - 3); + } + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector3 from one position in the buffer to another. The index values are in terms of vector number (eg, + * vector number 0 is positions 0-2 in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector3(final FloatBuffer buf, final int fromPos, final int toPos) { + copyInternal(buf, fromPos * 3, toPos * 3, 3); + } + + /** + * Normalize a Vector3 in-buffer. + * + * @param buf + * the buffer to find the Vector3 within + * @param index + * the position (in terms of vectors, not floats) of the vector to normalize + */ + public static void normalizeVector3(final FloatBuffer buf, final int index) { + final Vector3 temp = Vector3.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.normalizeLocal(); + setInBuffer(temp, buf, index); + Vector3.releaseTempInstance(temp); + } + + /** + * Add to a Vector3 in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector3 within + * @param index + * the position (in terms of vectors, not floats) of the vector to add to + */ + public static void addInBuffer(final ReadOnlyVector3 toAdd, final FloatBuffer buf, final int index) { + final Vector3 temp = Vector3.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.addLocal(toAdd); + setInBuffer(temp, buf, index); + Vector3.releaseTempInstance(temp); + } + + /** + * Multiply and store a Vector3 in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector3 within + * @param index + * the position (in terms of vectors, not floats) of the vector to multiply + */ + public static void multInBuffer(final ReadOnlyVector3 toMult, final FloatBuffer buf, final int index) { + final Vector3 temp = Vector3.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.multiplyLocal(toMult); + setInBuffer(temp, buf, index); + Vector3.releaseTempInstance(temp); + } + + /** + * Checks to see if the given Vector3 is equals to the data stored in the buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector in the buffer to check against + * @return + */ + public static boolean equals(final ReadOnlyVector3 check, final FloatBuffer buf, final int index) { + final Vector3 temp = Vector3.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + final boolean equals = temp.equals(check); + Vector3.releaseTempInstance(temp); + return equals; + } + + // // -- Vector2 METHODS -- //// + + /** + * Generate a new FloatBuffer using the given array of Vector2 objects. The FloatBuffer will be 2 * data.length long + * and contain the vector data as data[0].x, data[0].y, data[1].x... etc. + * + * @param data + * array of Vector2 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final ReadOnlyVector2... data) { + if (data == null) { + return null; + } + return createFloatBuffer(0, data.length, data); + } + + /** + * Generate a new FloatBuffer using the given array of Vector2 objects. The FloatBuffer will be 2 * data.length long + * and contain the vector data as data[0].x, data[0].y, data[1].x... etc. + * + * @param offset + * the starting index to read from in our data array + * @param length + * the number of vectors to read + * @param data + * array of Vector2 objects to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyVector2... data) { + if (data == null) { + return null; + } + final FloatBuffer buff = createFloatBuffer(2 * length); + for (int x = offset; x < length; x++) { + if (data[x] != null) { + buff.put(data[x].getXf()).put(data[x].getYf()); + } else { + buff.put(0).put(0); + } + } + buff.flip(); + return buff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector2 object data. + * + * @param vertices + * number of vertices that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector2Buffer(final int vertices) { + final FloatBuffer vBuff = createFloatBuffer(2 * vertices); + return vBuff; + } + + /** + * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector2 object data only if the + * given buffer if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param vertices + * number of vertices that need to be held by the newly created buffer + * @return the requested new FloatBuffer + */ + public static FloatBuffer createVector2Buffer(final FloatBuffer buf, final int vertices) { + if (buf != null && buf.limit() == 2 * vertices) { + buf.rewind(); + return buf; + } + + return createFloatBuffer(2 * vertices); + } + + /** + * Sets the data contained in the given Vector2 into the FloatBuffer at the specified index. + * + * @param vector + * the data to insert + * @param buf + * the buffer to insert into + * @param index + * the position to place the data; in terms of vectors not floats + */ + public static void setInBuffer(final ReadOnlyVector2 vector, final FloatBuffer buf, final int index) { + buf.put(index * 2, vector.getXf()); + buf.put((index * 2) + 1, vector.getYf()); + } + + /** + * Updates the values of the given vector from the specified buffer at the index provided. + * + * @param vector + * the vector to set data on + * @param buf + * the buffer to read from + * @param index + * the position (in terms of vectors, not floats) to read from the buf + */ + public static void populateFromBuffer(final Vector2 vector, final FloatBuffer buf, final int index) { + vector.setX(buf.get(index * 2)); + vector.setY(buf.get(index * 2 + 1)); + } + + /** + * Generates a Vector2 array from the given FloatBuffer. + * + * @param buff + * the FloatBuffer to read from + * @return a newly generated array of Vector2 objects + */ + public static Vector2[] getVector2Array(final FloatBuffer buff) { + buff.clear(); + final Vector2[] verts = new Vector2[buff.limit() / 2]; + for (int x = 0; x < verts.length; x++) { + final Vector2 v = new Vector2(buff.get(), buff.get()); + verts[x] = v; + } + return verts; + } + + /** + * Generates a Vector2 array from the given FloatBufferData. + * + * @param buff + * the FloatBufferData to read from + * @param defaults + * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is + * smaller than 2. + * @return a newly generated array of Vector2 objects + */ + public static Vector2[] getVector2Array(final FloatBufferData data, final ReadOnlyVector2 defaults) { + final FloatBuffer buff = data.getBuffer(); + buff.clear(); + final Vector2[] verts = new Vector2[data.getTupleCount()]; + final int tupleSize = data.getValuesPerTuple(); + for (int x = 0; x < verts.length; x++) { + final Vector2 v = new Vector2(defaults); + v.setX(buff.get()); + if (tupleSize > 1) { + v.setY(buff.get()); + } + if (tupleSize > 2) { + buff.position(buff.position() + tupleSize - 2); + } + verts[x] = v; + } + return verts; + } + + /** + * Copies a Vector2 from one position in the buffer to another. The index values are in terms of vector number (eg, + * vector number 0 is positions 0-1 in the FloatBuffer.) + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the index of the vector to copy + * @param toPos + * the index to copy the vector to + */ + public static void copyInternalVector2(final FloatBuffer buf, final int fromPos, final int toPos) { + copyInternal(buf, fromPos * 2, toPos * 2, 2); + } + + /** + * Normalize a Vector2 in-buffer. + * + * @param buf + * the buffer to find the Vector2 within + * @param index + * the position (in terms of vectors, not floats) of the vector to normalize + */ + public static void normalizeVector2(final FloatBuffer buf, final int index) { + final Vector2 temp = Vector2.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.normalizeLocal(); + setInBuffer(temp, buf, index); + Vector2.releaseTempInstance(temp); + } + + /** + * Add to a Vector2 in-buffer. + * + * @param toAdd + * the vector to add from + * @param buf + * the buffer to find the Vector2 within + * @param index + * the position (in terms of vectors, not floats) of the vector to add to + */ + public static void addInBuffer(final ReadOnlyVector2 toAdd, final FloatBuffer buf, final int index) { + final Vector2 temp = Vector2.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.addLocal(toAdd); + setInBuffer(temp, buf, index); + Vector2.releaseTempInstance(temp); + } + + /** + * Multiply and store a Vector2 in-buffer. + * + * @param toMult + * the vector to multiply against + * @param buf + * the buffer to find the Vector2 within + * @param index + * the position (in terms of vectors, not floats) of the vector to multiply + */ + public static void multInBuffer(final ReadOnlyVector2 toMult, final FloatBuffer buf, final int index) { + final Vector2 temp = Vector2.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + temp.multiplyLocal(toMult); + setInBuffer(temp, buf, index); + Vector2.releaseTempInstance(temp); + } + + /** + * Checks to see if the given Vector2 is equals to the data stored in the buffer at the given data index. + * + * @param check + * the vector to check against - null will return false. + * @param buf + * the buffer to compare data with + * @param index + * the position (in terms of vectors, not floats) of the vector in the buffer to check against + * @return + */ + public static boolean equals(final ReadOnlyVector2 check, final FloatBuffer buf, final int index) { + final Vector2 temp = Vector2.fetchTempInstance(); + populateFromBuffer(temp, buf, index); + final boolean equals = temp.equals(check); + Vector2.releaseTempInstance(temp); + return equals; + } + + // // -- INT METHODS -- //// + + /** + * Generate a new IntBuffer using the given array of ints. The IntBuffer will be data.length long and contain the + * int data as data[0], data[1]... etc. + * + * @param data + * array of ints to place into a new IntBuffer + */ + public static IntBuffer createIntBuffer(final int... data) { + if (data == null) { + return null; + } + final IntBuffer buff = createIntBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Create a new int[] array and populate it with the given IntBuffer's contents. + * + * @param buff + * the IntBuffer to read from + * @return a new int array populated from the IntBuffer + */ + public static int[] getIntArray(final IntBuffer buff) { + if (buff == null) { + return null; + } + buff.rewind(); + final int[] inds = new int[buff.limit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + /** + * Create a new int[] array and populate it with the given IndexBufferData's contents. + * + * @param buff + * the IndexBufferData to read from + * @return a new int array populated from the IndexBufferData + */ + public static int[] getIntArray(final IndexBufferData<?> buff) { + if (buff == null || buff.getBufferLimit() == 0) { + return null; + } + buff.getBuffer().rewind(); + final int[] inds = new int[buff.getBufferLimit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + /** + * Create a new float[] array and populate it with the given FloatBuffer's contents. + * + * @param buff + * the FloatBuffer to read from + * @return a new float array populated from the FloatBuffer + */ + public static float[] getFloatArray(final FloatBuffer buff) { + if (buff == null) { + return null; + } + buff.clear(); + final float[] inds = new float[buff.limit()]; + for (int x = 0; x < inds.length; x++) { + inds[x] = buff.get(); + } + return inds; + } + + // // -- GENERAL DOUBLE ROUTINES -- //// + + /** + * Create a new DoubleBuffer of the specified size. + * + * @param size + * required number of double to store. + * @return the new DoubleBuffer + */ + public static DoubleBuffer createDoubleBufferOnHeap(final int size) { + final DoubleBuffer buf = ByteBuffer.allocate(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer(); + buf.clear(); + return buf; + } + + /** + * Create a new DoubleBuffer of the specified size. + * + * @param size + * required number of double to store. + * @return the new DoubleBuffer + */ + public static DoubleBuffer createDoubleBuffer(final int size) { + final DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer(); + buf.clear(); + if (Constants.trackDirectMemory) { + trackingHash.put(buf, ref); + } + return buf; + } + + /** + * Create a new DoubleBuffer of an appropriate size to hold the specified number of doubles only if the given buffer + * if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of doubles that need to be held by the newly created buffer + * @return the requested new DoubleBuffer + */ + public static DoubleBuffer createDoubleBuffer(DoubleBuffer buf, final int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createDoubleBuffer(size); + return buf; + } + + /** + * Creates a new DoubleBuffer with the same contents as the given DoubleBuffer. The new DoubleBuffer is seperate + * from the old one and changes are not reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the DoubleBuffer to copy + * @return the copy + */ + public static DoubleBuffer clone(final DoubleBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + final DoubleBuffer copy; + if (buf.isDirect()) { + copy = createDoubleBuffer(buf.limit()); + } else { + copy = createDoubleBufferOnHeap(buf.limit()); + } + copy.put(buf); + + return copy; + } + + // // -- GENERAL FLOAT ROUTINES -- //// + + /** + * Create a new FloatBuffer of the specified size. + * + * @param size + * required number of floats to store. + * @return the new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final int size) { + final FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer(); + buf.clear(); + if (Constants.trackDirectMemory) { + trackingHash.put(buf, ref); + } + return buf; + } + + /** + * Create a new FloatBuffer of the specified size. + * + * @param size + * required number of floats to store. + * @return the new FloatBuffer + */ + public static FloatBuffer createFloatBufferOnHeap(final int size) { + final FloatBuffer buf = ByteBuffer.allocate(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer(); + buf.clear(); + return buf; + } + + /** + * Generate a new FloatBuffer using the given array of float primitives. + * + * @param data + * array of float primitives to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final float... data) { + return createFloatBuffer(null, data); + } + + /** + * Generate a new FloatBuffer using the given array of float primitives. + * + * @param data + * array of float primitives to place into a new FloatBuffer + */ + public static FloatBuffer createFloatBuffer(final FloatBuffer reuseStore, final float... data) { + if (data == null) { + return null; + } + final FloatBuffer buff; + if (reuseStore == null || reuseStore.capacity() != data.length) { + buff = createFloatBuffer(data.length); + } else { + buff = reuseStore; + buff.clear(); + } + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + public static IntBuffer createIntBuffer(final IntBuffer reuseStore, final int... data) { + if (data == null) { + return null; + } + final IntBuffer buff; + if (reuseStore == null || reuseStore.capacity() != data.length) { + buff = createIntBuffer(data.length); + } else { + buff = reuseStore; + buff.clear(); + } + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Copies floats from one buffer to another. + * + * @param source + * the buffer to copy from + * @param fromPos + * the starting point to copy from + * @param destination + * the buffer to copy to + * @param toPos + * the starting point to copy to + * @param length + * the number of floats to copy + */ + public static void copy(final FloatBuffer source, final int fromPos, final FloatBuffer destination, + final int toPos, final int length) { + final int oldLimit = source.limit(); + source.position(fromPos); + source.limit(fromPos + length); + destination.position(toPos); + destination.put(source); + source.limit(oldLimit); + } + + /** + * Copies floats from one position in the buffer to another. + * + * @param buf + * the buffer to copy from/to + * @param fromPos + * the starting point to copy from + * @param toPos + * the starting point to copy to + * @param length + * the number of floats to copy + */ + public static void copyInternal(final FloatBuffer buf, final int fromPos, final int toPos, final int length) { + final float[] data = new float[length]; + buf.position(fromPos); + buf.get(data); + buf.position(toPos); + buf.put(data); + } + + /** + * Creates a new FloatBuffer with the same contents as the given FloatBuffer. The new FloatBuffer is seperate from + * the old one and changes are not reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the FloatBuffer to copy + * @return the copy + */ + public static FloatBuffer clone(final FloatBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + final FloatBuffer copy; + if (buf.isDirect()) { + copy = createFloatBuffer(buf.limit()); + } else { + copy = createFloatBufferOnHeap(buf.limit()); + } + copy.put(buf); + + return copy; + } + + // // -- GENERAL INT ROUTINES -- //// + + /** + * Create a new IntBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static IntBuffer createIntBufferOnHeap(final int size) { + final IntBuffer buf = ByteBuffer.allocate(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer(); + buf.clear(); + return buf; + } + + /** + * Create a new IntBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static IntBuffer createIntBuffer(final int size) { + final IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer(); + buf.clear(); + if (Constants.trackDirectMemory) { + trackingHash.put(buf, ref); + } + return buf; + } + + /** + * Create a new IntBuffer of an appropriate size to hold the specified number of ints only if the given buffer if + * not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of ints that need to be held by the newly created buffer + * @return the requested new IntBuffer + */ + public static IntBuffer createIntBuffer(IntBuffer buf, final int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createIntBuffer(size); + return buf; + } + + /** + * Creates a new IntBuffer with the same contents as the given IntBuffer. The new IntBuffer is seperate from the old + * one and changes are not reflected across. If you want to reflect changes, consider using Buffer.duplicate(). + * + * @param buf + * the IntBuffer to copy + * @return the copy + */ + public static IntBuffer clone(final IntBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + final IntBuffer copy; + if (buf.isDirect()) { + copy = createIntBuffer(buf.limit()); + } else { + copy = createIntBufferOnHeap(buf.limit()); + } + copy.put(buf); + + return copy; + } + + // // -- GENERAL BYTE ROUTINES -- //// + + /** + * Create a new ByteBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static ByteBuffer createByteBuffer(final int size) { + final ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + buf.clear(); + if (Constants.trackDirectMemory) { + trackingHash.put(buf, ref); + } + return buf; + } + + /** + * Create a new ByteBuffer of an appropriate size to hold the specified number of ints only if the given buffer if + * not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of bytes that need to be held by the newly created buffer + * @return the requested new IntBuffer + */ + public static ByteBuffer createByteBuffer(ByteBuffer buf, final int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createByteBuffer(size); + return buf; + } + + /** + * Creates a new ByteBuffer with the same contents as the given ByteBuffer. The new ByteBuffer is seperate from the + * old one and changes are not reflected across. If you want to reflect changes, consider using Buffer.duplicate(). + * + * @param buf + * the ByteBuffer to copy + * @return the copy + */ + public static ByteBuffer clone(final ByteBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + final ByteBuffer copy; + if (buf.isDirect()) { + copy = createByteBuffer(buf.limit()); + } else { + copy = createByteBufferOnHeap(buf.limit()); + } + copy.put(buf); + + return copy; + } + + // // -- GENERAL SHORT ROUTINES -- //// + + /** + * Create a new ShortBuffer of the specified size. + * + * @param size + * required number of shorts to store. + * @return the new ShortBuffer + */ + public static ShortBuffer createShortBufferOnHeap(final int size) { + final ShortBuffer buf = ByteBuffer.allocate(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer(); + buf.clear(); + return buf; + } + + /** + * Create a new ShortBuffer of the specified size. + * + * @param size + * required number of shorts to store. + * @return the new ShortBuffer + */ + public static ShortBuffer createShortBuffer(final int size) { + final ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer(); + buf.clear(); + if (Constants.trackDirectMemory) { + trackingHash.put(buf, ref); + } + return buf; + } + + /** + * Generate a new ShortBuffer using the given array of short primitives. + * + * @param data + * array of short primitives to place into a new ShortBuffer + */ + public static ShortBuffer createShortBuffer(final short... data) { + if (data == null) { + return null; + } + final ShortBuffer buff = createShortBuffer(data.length); + buff.clear(); + buff.put(data); + buff.flip(); + return buff; + } + + /** + * Create a new ShortBuffer of an appropriate size to hold the specified number of shorts only if the given buffer + * if not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of shorts that need to be held by the newly created buffer + * @return the requested new ShortBuffer + */ + public static ShortBuffer createShortBuffer(ShortBuffer buf, final int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createShortBuffer(size); + return buf; + } + + /** + * Creates a new ShortBuffer with the same contents as the given ShortBuffer. The new ShortBuffer is seperate from + * the old one and changes are not reflected across. If you want to reflect changes, consider using + * Buffer.duplicate(). + * + * @param buf + * the ShortBuffer to copy + * @return the copy + */ + public static ShortBuffer clone(final ShortBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + final ShortBuffer copy; + if (buf.isDirect()) { + copy = createShortBuffer(buf.limit()); + } else { + copy = createShortBufferOnHeap(buf.limit()); + } + copy.put(buf); + + return copy; + } + + /** + * Ensures there is at least the <code>required</code> number of entries left after the current position of the + * buffer. If the buffer is too small a larger one is created and the old one copied to the new buffer. + * + * @param buffer + * buffer that should be checked/copied (may be null) + * @param required + * minimum number of elements that should be remaining in the returned buffer + * @return a buffer large enough to receive at least the <code>required</code> number of entries, same position as + * the input buffer, not null + */ + public static FloatBuffer ensureLargeEnough(FloatBuffer buffer, final int required) { + if (buffer == null || (buffer.remaining() < required)) { + final int position = (buffer != null ? buffer.position() : 0); + final FloatBuffer newVerts = createFloatBuffer(position + required); + if (buffer != null) { + buffer.rewind(); + newVerts.put(buffer); + newVerts.position(position); + } + buffer = newVerts; + } + return buffer; + } + + // // -- GENERAL INDEXBUFFERDATA ROUTINES -- //// + + /** + * Create a new IndexBufferData of the specified size. The specific implementation will be chosen based on the max + * value you need to store in your buffer. If that value is less than 2^8, a ByteBufferData is used. If it is less + * than 2^16, a ShortBufferData is used. Otherwise an IntBufferData is used. + * + * @param size + * required number of values to store. + * @param maxValue + * the largest value you will need to store in your buffer. Often this is equal to + * ("size of vertex buffer" - 1). + * @return the new IndexBufferData + */ + public static IndexBufferData<?> createIndexBufferData(final int size, final int maxValue) { + if (maxValue < 256) { // 2^8 + return createIndexBufferData(size, ByteBufferData.class); + } else if (maxValue < 65536) { // 2^16 + return createIndexBufferData(size, ShortBufferData.class); + } else { + return createIndexBufferData(size, IntBufferData.class); + } + } + + /** + * Create a new IndexBufferData of the specified size and class. + * + * @param size + * required number of values to store. + * @param clazz + * The class type to instantiate. + * @return the new IndexBufferData + */ + public static IndexBufferData<?> createIndexBufferData(final int size, + final Class<? extends IndexBufferData<?>> clazz) { + try { + return clazz.getConstructor(int.class).newInstance(size); + } catch (final Exception ex) { + throw new Ardor3dException(ex.getMessage(), ex); + } + } + + /** + * Creates a new IndexBufferData with the same contents as the given IndexBufferData. The new IndexBufferData is + * separate from the old one and changes are not reflected across. + * + * @param buf + * the IndexBufferData to copy + * @return the copy + */ + @SuppressWarnings("unchecked") + public static IndexBufferData<?> clone(final IndexBufferData<?> buf) { + if (buf == null) { + return null; + } + + final IndexBufferData<?> copy = createIndexBufferData(buf.getBufferLimit(), + (Class<? extends IndexBufferData<?>>) buf.getClass()); + if (buf.getBuffer() == null) { + copy.setBuffer(null); + } else { + buf.getBuffer().rewind(); + copy.put(buf); + } + + return copy; + } + + // // -- GENERAL HEAP BYTE ROUTINES -- //// + + /** + * Create a new ByteBuffer of the specified size. + * + * @param size + * required number of ints to store. + * @return the new IntBuffer + */ + public static ByteBuffer createByteBufferOnHeap(final int size) { + final ByteBuffer buf = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder()); + buf.clear(); + return buf; + } + + /** + * Create a new ByteBuffer of an appropriate size to hold the specified number of ints only if the given buffer if + * not already the right size. + * + * @param buf + * the buffer to first check and rewind + * @param size + * number of bytes that need to be held by the newly created buffer + * @return the requested new IntBuffer + */ + public static ByteBuffer createByteBufferOnHeap(ByteBuffer buf, final int size) { + if (buf != null && buf.limit() == size) { + buf.rewind(); + return buf; + } + + buf = createByteBufferOnHeap(size); + return buf; + } + + /** + * Creates a new ByteBuffer with the same contents as the given ByteBuffer. The new ByteBuffer is seperate from the + * old one and changes are not reflected across. If you want to reflect changes, consider using Buffer.duplicate(). + * + * @param buf + * the ByteBuffer to copy + * @return the copy + */ + public static ByteBuffer cloneOnHeap(final ByteBuffer buf) { + if (buf == null) { + return null; + } + buf.rewind(); + + final ByteBuffer copy = createByteBufferOnHeap(buf.limit()); + copy.put(buf); + + return copy; + } + + public static void printCurrentDirectMemory(StringBuilder store) { + long totalHeld = 0; + // make a new set to hold the keys to prevent concurrency issues. + final List<Buffer> bufs = new ArrayList<Buffer>(trackingHash.keySet()); + int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0; + int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0; + for (final Buffer b : bufs) { + if (b instanceof ByteBuffer) { + totalHeld += b.capacity(); + bBufsM += b.capacity(); + bBufs++; + } else if (b instanceof FloatBuffer) { + totalHeld += b.capacity() * 4; + fBufsM += b.capacity() * 4; + fBufs++; + } else if (b instanceof IntBuffer) { + totalHeld += b.capacity() * 4; + iBufsM += b.capacity() * 4; + iBufs++; + } else if (b instanceof ShortBuffer) { + totalHeld += b.capacity() * 2; + sBufsM += b.capacity() * 2; + sBufs++; + } else if (b instanceof DoubleBuffer) { + totalHeld += b.capacity() * 8; + dBufsM += b.capacity() * 8; + dBufs++; + } + } + final boolean printStout = store == null; + if (store == null) { + store = new StringBuilder(); + } + store.append("Existing buffers: ").append(bufs.size()).append("\n"); + store.append("(b: ").append(bBufs).append(" f: ").append(fBufs).append(" i: ").append(iBufs).append(" s: ") + .append(sBufs).append(" d: ").append(dBufs).append(")").append("\n"); + store.append("Total direct memory held: ").append(totalHeld / 1024).append("kb\n"); + store.append("(b: ").append(bBufsM / 1024).append("kb f: ").append(fBufsM / 1024).append("kb i: ") + .append(iBufsM / 1024).append("kb s: ").append(sBufsM / 1024).append("kb d: ").append(dBufsM / 1024) + .append("kb)").append("\n"); + if (printStout) { + System.out.println(store.toString()); + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java new file mode 100644 index 0000000..c68fa8f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java @@ -0,0 +1,22 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.ardor3d.scenegraph.Spatial;
+
+@Deprecated
+public interface CopyLogic {
+
+ Spatial copy(Spatial source, AtomicBoolean recurse);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java new file mode 100644 index 0000000..56b2fbe --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java @@ -0,0 +1,701 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.nio.FloatBuffer; + +import com.ardor3d.bounding.BoundingBox; +import com.ardor3d.bounding.BoundingSphere; +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.bounding.OrientedBoundingBox; +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.renderer.TextureRendererFactory; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.WireframeState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.NormalsMode; +import com.ardor3d.scenegraph.shape.AxisRods; +import com.ardor3d.scenegraph.shape.Box; +import com.ardor3d.scenegraph.shape.OrientedBox; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.scenegraph.shape.Sphere; +import com.ardor3d.util.ExtendedCamera; + +/** + * Debugger provides tools for viewing scene data such as boundings and normals. + * + * Make sure you set the RenderStateFactory before using this class. + * + * @see Debugger#setRenderStateFactory(RenderStateFactory) + */ +public final class Debugger { + + // -- **** METHODS FOR DRAWING BOUNDING VOLUMES **** -- // + + private static final Sphere boundingSphere = new Sphere("bsphere", 10, 10, 1); + static { + boundingSphere.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + boundingSphere.getSceneHints().setNormalsMode(NormalsMode.Off); + boundingSphere.setRenderState(new WireframeState()); + boundingSphere.setRenderState(new ZBufferState()); + boundingSphere.updateWorldRenderStates(false); + } + private static final Box boundingBox = new Box("bbox", new Vector3(), 1, 1, 1); + static { + boundingBox.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + boundingBox.getSceneHints().setNormalsMode(NormalsMode.Off); + boundingBox.setRenderState(new WireframeState()); + boundingBox.setRenderState(new ZBufferState()); + boundingBox.updateWorldRenderStates(false); + } + private static final OrientedBox boundingOB = new OrientedBox("bobox"); + static { + boundingOB.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + boundingOB.getSceneHints().setNormalsMode(NormalsMode.Off); + boundingOB.setRenderState(new WireframeState()); + boundingOB.setRenderState(new ZBufferState()); + boundingOB.updateWorldRenderStates(false); + } + + /** + * <code>drawBounds</code> draws the bounding volume for a given Spatial and its children. + * + * @param se + * the Spatial to draw boundings for. + * @param r + * the Renderer to use to draw the bounding. + */ + public static void drawBounds(final Spatial se, final Renderer r) { + drawBounds(se, r, true); + } + + /** + * <code>drawBounds</code> draws the bounding volume for a given Spatial and optionally its children. + * + * @param se + * the Spatial to draw boundings for. + * @param r + * the Renderer to use to draw the bounding. + * @param doChildren + * if true, boundings for any children will also be drawn + */ + public static void drawBounds(final Spatial se, final Renderer r, boolean doChildren) { + if (se == null) { + return; + } + + if (se.getWorldBound() != null && se.getSceneHints().getCullHint() != CullHint.Always) { + final Camera cam = Camera.getCurrentCamera(); + final int state = cam.getPlaneState(); + if (cam.contains(se.getWorldBound()) != Camera.FrustumIntersect.Outside) { + drawBounds(se.getWorldBound(), r); + } else { + doChildren = false; + } + cam.setPlaneState(state); + } + if (doChildren && se instanceof Node) { + final Node n = (Node) se; + if (n.getNumberOfChildren() != 0) { + for (int i = n.getNumberOfChildren(); --i >= 0;) { + drawBounds(n.getChild(i), r, true); + } + } + } + } + + public static void drawBounds(final BoundingVolume bv, final Renderer r) { + + switch (bv.getType()) { + case AABB: + drawBoundingBox((BoundingBox) bv, r); + break; + case Sphere: + drawBoundingSphere((BoundingSphere) bv, r); + break; + case OBB: + drawOBB((OrientedBoundingBox) bv, r); + break; + default: + break; + } + } + + public static void setBoundsColor(final ReadOnlyColorRGBA color) { + boundingBox.setDefaultColor(color); + boundingOB.setDefaultColor(color); + boundingSphere.setDefaultColor(color); + } + + public static void drawBoundingSphere(final BoundingSphere sphere, final Renderer r) { + boundingSphere.setData(sphere.getCenter(), 10, 10, sphere.getRadius()); + boundingSphere.draw(r); + } + + public static void drawBoundingBox(final BoundingBox box, final Renderer r) { + boundingBox.setData(box.getCenter(), box.getXExtent(), box.getYExtent(), box.getZExtent()); + boundingBox.draw(r); + } + + public static void drawOBB(final OrientedBoundingBox box, final Renderer r) { + boundingOB.getCenter().set(box.getCenter()); + boundingOB.getxAxis().set(box.getXAxis()); + boundingOB.getYAxis().set(box.getYAxis()); + boundingOB.getZAxis().set(box.getZAxis()); + boundingOB.getExtent().set(box.getExtent()); + boundingOB.computeInformation(); + boundingOB.draw(r); + } + + // -- **** METHODS FOR DRAWING NORMALS **** -- // + + private static final Line normalLines = new Line("normLine"); + static { + normalLines.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + normalLines.setRenderState(new ZBufferState()); + normalLines.setLineWidth(3.0f); + normalLines.getMeshData().setIndexMode(IndexMode.Lines); + normalLines.getMeshData().setVertexBuffer(BufferUtils.createVector3Buffer(500)); + normalLines.getMeshData().setColorBuffer(BufferUtils.createColorBuffer(500)); + normalLines.updateWorldRenderStates(false); + } + private static final Vector3 _normalVect = new Vector3(), _normalVect2 = new Vector3(); + public static final ColorRGBA NORMAL_COLOR_BASE = new ColorRGBA(ColorRGBA.RED); + public static final ColorRGBA NORMAL_COLOR_TIP = new ColorRGBA(ColorRGBA.PINK); + public static final ColorRGBA TANGENT_COLOR_BASE = new ColorRGBA(ColorRGBA.ORANGE); + public static final ColorRGBA TANGENT_COLOR_TIP = new ColorRGBA(ColorRGBA.YELLOW); + protected static final BoundingBox measureBox = new BoundingBox(); + public static double AUTO_NORMAL_RATIO = .05; + + /** + * <code>drawNormals</code> draws lines representing normals for a given Spatial and its children. + * + * @param element + * the Spatial to draw normals for. + * @param r + * the Renderer to use to draw the normals. + */ + public static void drawNormals(final Spatial element, final Renderer r) { + drawNormals(element, r, -1f, true); + } + + public static void drawTangents(final Spatial element, final Renderer r) { + drawTangents(element, r, -1f, true); + } + + /** + * <code>drawNormals</code> draws the normals for a given Spatial and optionally its children. + * + * @param element + * the Spatial to draw normals for. + * @param r + * the Renderer to use to draw the normals. + * @param size + * the length of the drawn normal (default is -1.0 which means autocalc based on boundings - if any). + * @param doChildren + * if true, normals for any children will also be drawn + */ + public static void drawNormals(final Spatial element, final Renderer r, final double size, final boolean doChildren) { + if (element == null) { + return; + } + + final Camera cam = Camera.getCurrentCamera(); + final int state = cam.getPlaneState(); + if (element.getWorldBound() != null && cam.contains(element.getWorldBound()) == Camera.FrustumIntersect.Outside) { + cam.setPlaneState(state); + return; + } + cam.setPlaneState(state); + if (element instanceof Mesh && element.getSceneHints().getCullHint() != CullHint.Always) { + final Mesh mesh = (Mesh) element; + + double rSize = size; + if (rSize == -1) { + final BoundingVolume vol = element.getWorldBound(); + if (vol != null) { + measureBox.setCenter(vol.getCenter()); + measureBox.setXExtent(0); + measureBox.setYExtent(0); + measureBox.setZExtent(0); + measureBox.mergeLocal(vol); + rSize = AUTO_NORMAL_RATIO + * ((measureBox.getXExtent() + measureBox.getYExtent() + measureBox.getZExtent()) / 3); + } else { + rSize = 1.0; + } + + if (Double.isInfinite(rSize) || Double.isNaN(rSize)) { + rSize = 1.0; + } + } + + final FloatBuffer norms = mesh.getMeshData().getNormalBuffer(); + final FloatBuffer verts = mesh.getMeshData().getVertexBuffer(); + if (norms != null && verts != null && norms.limit() == verts.limit()) { + FloatBuffer lineVerts = normalLines.getMeshData().getVertexBuffer(); + if (lineVerts.capacity() < (3 * (2 * mesh.getMeshData().getVertexCount()))) { + normalLines.getMeshData().setVertexBuffer(null); + lineVerts = BufferUtils.createVector3Buffer(mesh.getMeshData().getVertexCount() * 2); + normalLines.getMeshData().setVertexBuffer(lineVerts); + } else { + lineVerts.clear(); + lineVerts.limit(3 * 2 * mesh.getMeshData().getVertexCount()); + normalLines.getMeshData().setVertexBuffer(lineVerts); + } + + FloatBuffer lineColors = normalLines.getMeshData().getColorBuffer(); + if (lineColors.capacity() < (4 * (2 * mesh.getMeshData().getVertexCount()))) { + normalLines.getMeshData().setColorBuffer(null); + lineColors = BufferUtils.createColorBuffer(mesh.getMeshData().getVertexCount() * 2); + normalLines.getMeshData().setColorBuffer(lineColors); + } else { + lineColors.clear(); + } + + IndexBufferData<?> lineInds = normalLines.getMeshData().getIndices(); + if (lineInds == null || lineInds.getBufferCapacity() < (normalLines.getMeshData().getVertexCount())) { + normalLines.getMeshData().setIndices(null); + lineInds = BufferUtils.createIndexBufferData(mesh.getMeshData().getVertexCount() * 2, normalLines + .getMeshData().getVertexCount() - 1); + normalLines.getMeshData().setIndices(lineInds); + } else { + lineInds.getBuffer().clear(); + lineInds.getBuffer().limit(normalLines.getMeshData().getVertexCount()); + } + + verts.rewind(); + norms.rewind(); + lineVerts.rewind(); + lineInds.getBuffer().rewind(); + + for (int x = 0; x < mesh.getMeshData().getVertexCount(); x++) { + _normalVect.set(verts.get(), verts.get(), verts.get()); + mesh.getWorldTransform().applyForward(_normalVect); + lineVerts.put(_normalVect.getXf()); + lineVerts.put(_normalVect.getYf()); + lineVerts.put(_normalVect.getZf()); + + lineColors.put(NORMAL_COLOR_BASE.getRed()); + lineColors.put(NORMAL_COLOR_BASE.getGreen()); + lineColors.put(NORMAL_COLOR_BASE.getBlue()); + lineColors.put(NORMAL_COLOR_BASE.getAlpha()); + + lineInds.put(x * 2); + + _normalVect2.set(norms.get(), norms.get(), norms.get()); + mesh.getWorldTransform().applyForwardVector(_normalVect2).normalizeLocal().multiplyLocal(rSize); + _normalVect.addLocal(_normalVect2); + lineVerts.put(_normalVect.getXf()); + lineVerts.put(_normalVect.getYf()); + lineVerts.put(_normalVect.getZf()); + + lineColors.put(NORMAL_COLOR_TIP.getRed()); + lineColors.put(NORMAL_COLOR_TIP.getGreen()); + lineColors.put(NORMAL_COLOR_TIP.getBlue()); + lineColors.put(NORMAL_COLOR_TIP.getAlpha()); + + lineInds.put((x * 2) + 1); + } + + normalLines.onDraw(r); + } + + } + + if (doChildren && element instanceof Node) { + final Node n = (Node) element; + if (n.getNumberOfChildren() != 0) { + for (int i = n.getNumberOfChildren(); --i >= 0;) { + drawNormals(n.getChild(i), r, size, true); + } + } + } + } + + public static void drawTangents(final Spatial element, final Renderer r, final double size, final boolean doChildren) { + if (element == null) { + return; + } + + final Camera cam = Camera.getCurrentCamera(); + final int state = cam.getPlaneState(); + if (element.getWorldBound() != null && cam.contains(element.getWorldBound()) == Camera.FrustumIntersect.Outside) { + cam.setPlaneState(state); + return; + } + cam.setPlaneState(state); + if (element instanceof Mesh && element.getSceneHints().getCullHint() != CullHint.Always) { + final Mesh mesh = (Mesh) element; + + double rSize = size; + if (rSize == -1) { + final BoundingVolume vol = element.getWorldBound(); + if (vol != null) { + measureBox.setCenter(vol.getCenter()); + measureBox.setXExtent(0); + measureBox.setYExtent(0); + measureBox.setZExtent(0); + measureBox.mergeLocal(vol); + rSize = AUTO_NORMAL_RATIO + * ((measureBox.getXExtent() + measureBox.getYExtent() + measureBox.getZExtent()) / 3f); + } else { + rSize = 1.0; + } + } + + final FloatBuffer norms = mesh.getMeshData().getTangentBuffer(); + final FloatBuffer verts = mesh.getMeshData().getVertexBuffer(); + if (norms != null && verts != null && norms.limit() == verts.limit()) { + FloatBuffer lineVerts = normalLines.getMeshData().getVertexBuffer(); + if (lineVerts.capacity() < (3 * (2 * mesh.getMeshData().getVertexCount()))) { + normalLines.getMeshData().setVertexBuffer(null); + lineVerts = BufferUtils.createVector3Buffer(mesh.getMeshData().getVertexCount() * 2); + normalLines.getMeshData().setVertexBuffer(lineVerts); + } else { + lineVerts.clear(); + lineVerts.limit(3 * 2 * mesh.getMeshData().getVertexCount()); + normalLines.getMeshData().setVertexBuffer(lineVerts); + } + + FloatBuffer lineColors = normalLines.getMeshData().getColorBuffer(); + if (lineColors.capacity() < (4 * (2 * mesh.getMeshData().getVertexCount()))) { + normalLines.getMeshData().setColorBuffer(null); + lineColors = BufferUtils.createColorBuffer(mesh.getMeshData().getVertexCount() * 2); + normalLines.getMeshData().setColorBuffer(lineColors); + } else { + lineColors.clear(); + } + + IndexBufferData<?> lineInds = normalLines.getMeshData().getIndices(); + if (lineInds == null || lineInds.getBufferCapacity() < (normalLines.getMeshData().getVertexCount())) { + normalLines.getMeshData().setIndices(null); + lineInds = BufferUtils.createIndexBufferData(mesh.getMeshData().getVertexCount() * 2, normalLines + .getMeshData().getVertexCount() - 1); + normalLines.getMeshData().setIndices(lineInds); + } else { + lineInds.getBuffer().clear(); + lineInds.getBuffer().limit(normalLines.getMeshData().getVertexCount()); + } + + verts.rewind(); + norms.rewind(); + lineVerts.rewind(); + lineInds.getBuffer().rewind(); + + for (int x = 0; x < mesh.getMeshData().getVertexCount(); x++) { + _normalVect.set(verts.get(), verts.get(), verts.get()); + _normalVect.multiplyLocal(mesh.getWorldScale()); + lineVerts.put(_normalVect.getXf()); + lineVerts.put(_normalVect.getYf()); + lineVerts.put(_normalVect.getZf()); + + lineColors.put(TANGENT_COLOR_BASE.getRed()); + lineColors.put(TANGENT_COLOR_BASE.getGreen()); + lineColors.put(TANGENT_COLOR_BASE.getBlue()); + lineColors.put(TANGENT_COLOR_BASE.getAlpha()); + + lineInds.put(x * 2); + + _normalVect.addLocal(norms.get() * rSize, norms.get() * rSize, norms.get() * rSize); + lineVerts.put(_normalVect.getXf()); + lineVerts.put(_normalVect.getYf()); + lineVerts.put(_normalVect.getZf()); + + lineColors.put(TANGENT_COLOR_TIP.getRed()); + lineColors.put(TANGENT_COLOR_TIP.getGreen()); + lineColors.put(TANGENT_COLOR_TIP.getBlue()); + lineColors.put(TANGENT_COLOR_TIP.getAlpha()); + + lineInds.put((x * 2) + 1); + } + + normalLines.setWorldTranslation(mesh.getWorldTranslation()); + normalLines.setWorldRotation(mesh.getWorldRotation()); + normalLines.onDraw(r); + } + + } + + if (doChildren && element instanceof Node) { + final Node n = (Node) element; + if (n.getNumberOfChildren() != 0) { + for (int i = n.getNumberOfChildren(); --i >= 0;) { + drawTangents(n.getChild(i), r, size, true); + } + } + } + } + + // -- **** METHODS FOR DRAWING AXIS **** -- // + + private static final AxisRods rods = new AxisRods("debug_rods", true, 1); + static { + rods.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + } + private static boolean axisInited = false; + + public static void drawAxis(final Spatial spat, final Renderer r) { + drawAxis(spat, r, true, false); + } + + public static void drawAxis(final Spatial spat, final Renderer r, final boolean drawChildren, final boolean drawAll) { + if (!axisInited) { + final BlendState blendState = new BlendState(); + blendState.setBlendEnabled(true); + blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + rods.setRenderState(blendState); + rods.updateGeometricState(0, false); + axisInited = true; + } + + if (drawAll || (spat instanceof Mesh)) { + if (spat.getWorldBound() != null) { + double rSize; + final BoundingVolume vol = spat.getWorldBound(); + if (vol != null) { + measureBox.setCenter(vol.getCenter()); + measureBox.setXExtent(0); + measureBox.setYExtent(0); + measureBox.setZExtent(0); + measureBox.mergeLocal(vol); + rSize = 1 * ((measureBox.getXExtent() + measureBox.getYExtent() + measureBox.getZExtent()) / 3); + } else { + rSize = 1.0; + } + + rods.setTranslation(spat.getWorldBound().getCenter()); + rods.setScale(rSize); + } else { + rods.setTranslation(spat.getWorldTranslation()); + rods.setScale(spat.getWorldScale()); + } + rods.setRotation(spat.getWorldRotation()); + rods.updateGeometricState(0, false); + + rods.draw(r); + } + + if ((spat instanceof Node) && drawChildren) { + final Node n = (Node) spat; + if (n.getNumberOfChildren() == 0) { + return; + } + for (int x = 0, count = n.getNumberOfChildren(); x < count; x++) { + drawAxis(n.getChild(x), r, drawChildren, drawAll); + } + } + } + + // -- **** METHODS FOR DISPLAYING BUFFERS **** -- // + public static final int NORTHWEST = 0; + public static final int NORTHEAST = 1; + public static final int SOUTHEAST = 2; + public static final int SOUTHWEST = 3; + + private static final Quad bQuad = new Quad("", 128, 128); + private static Texture2D bufTexture; + private static TextureRenderer bufTexRend; + + static { + bQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + bQuad.getSceneHints().setCullHint(CullHint.Never); + } + + public static void drawBuffer(final TextureStoreFormat rttFormat, final int location, final Renderer r) { + final Camera cam = Camera.getCurrentCamera(); + drawBuffer(rttFormat, location, r, cam.getWidth() / 6.25); + } + + public static void drawBuffer(final TextureStoreFormat rttFormat, final int location, final Renderer r, + final double size) { + final Camera cam = Camera.getCurrentCamera(); + r.flushGraphics(); + double locationX = cam.getWidth(), locationY = cam.getHeight(); + bQuad.resize(size, (cam.getHeight() / (double) cam.getWidth()) * size); + if (bQuad.getLocalRenderState(RenderState.StateType.Texture) == null) { + final TextureState ts = new TextureState(); + bufTexture = new Texture2D(); + ts.setTexture(bufTexture); + bQuad.setRenderState(ts); + } + + int width = cam.getWidth(); + if (!MathUtils.isPowerOfTwo(width)) { + int newWidth = 2; + do { + newWidth <<= 1; + + } while (newWidth < width); + bQuad.getMeshData().getTextureBuffer(0).put(4, width / (float) newWidth); + bQuad.getMeshData().getTextureBuffer(0).put(6, width / (float) newWidth); + width = newWidth; + } + + int height = cam.getHeight(); + if (!MathUtils.isPowerOfTwo(height)) { + int newHeight = 2; + do { + newHeight <<= 1; + + } while (newHeight < height); + bQuad.getMeshData().getTextureBuffer(0).put(1, height / (float) newHeight); + bQuad.getMeshData().getTextureBuffer(0).put(7, height / (float) newHeight); + height = newHeight; + } + if (bufTexRend == null) { + bufTexRend = TextureRendererFactory.INSTANCE.createTextureRenderer(width, height, r, ContextManager + .getCurrentContext().getCapabilities()); + bufTexRend.setupTexture(bufTexture); + } + bufTexRend.copyToTexture(bufTexture, 0, 0, width, height, 0, 0); + + final double loc = size * .75; + switch (location) { + case NORTHWEST: + locationX = loc; + locationY -= loc; + break; + case NORTHEAST: + locationX -= loc; + locationY -= loc; + break; + case SOUTHEAST: + locationX -= loc; + locationY = loc; + break; + case SOUTHWEST: + default: + locationX = loc; + locationY = loc; + break; + } + + bQuad.setWorldTranslation(locationX, locationY, 0); + + bQuad.updateGeometricState(0); + bQuad.onDraw(r); + r.flushGraphics(); + } + + // -- **** METHODS FOR DISPLAYING CAMERAS **** -- // + private static Line lineFrustum; + private static final ExtendedCamera extendedCamera = new ExtendedCamera(); + + public static void drawCameraFrustum(final Renderer r, final Camera camera, final ReadOnlyColorRGBA color, + final short pattern, final boolean drawOriginConnector) { + drawCameraFrustum(r, camera, camera.getFrustumNear(), camera.getFrustumFar(), color, pattern, + drawOriginConnector); + } + + public static void drawCameraFrustum(final Renderer r, final Camera camera, final double fNear, final double fFar, + final ReadOnlyColorRGBA color, final short pattern, final boolean drawOriginConnector) { + if (lineFrustum == null) { + final FloatBuffer verts = BufferUtils.createVector3Buffer(24); + final FloatBuffer colors = BufferUtils.createColorBuffer(24); + + lineFrustum = new Line("Lines", verts, null, colors, null); + lineFrustum.getMeshData().setIndexModes( + new IndexMode[] { IndexMode.LineLoop, IndexMode.LineLoop, IndexMode.Lines, IndexMode.Lines }); + lineFrustum.getMeshData().setIndexLengths(new int[] { 4, 4, 8, 8 }); + lineFrustum.setLineWidth(2); + lineFrustum.getSceneHints().setLightCombineMode(LightCombineMode.Off); + + final BlendState lineBlendState = new BlendState(); + lineBlendState.setEnabled(true); + lineBlendState.setBlendEnabled(true); + lineBlendState.setTestEnabled(true); + lineBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + lineBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + lineFrustum.setRenderState(lineBlendState); + + final ZBufferState zstate = new ZBufferState(); + lineFrustum.setRenderState(zstate); + lineFrustum.updateGeometricState(0.0); + + lineFrustum.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + } + + lineFrustum.setDefaultColor(color); + lineFrustum.setStipplePattern(pattern); + + extendedCamera.set(camera); + extendedCamera.calculateFrustum(fNear, fFar); + + final FloatBuffer colors = lineFrustum.getMeshData().getColorBuffer(); + for (int i = 0; i < 16; i++) { + BufferUtils.setInBuffer(color, colors, i); + } + final float alpha = drawOriginConnector ? 0.4f : 0.0f; + for (int i = 16; i < 24; i++) { + colors.position(i * 4); + colors.put(color.getRed()); + colors.put(color.getGreen()); + colors.put(color.getBlue()); + colors.put(alpha); + } + + final Vector3[] corners = extendedCamera.getCorners(); + + final FloatBuffer verts = lineFrustum.getMeshData().getVertexBuffer(); + BufferUtils.setInBuffer(corners[0], verts, 0); + BufferUtils.setInBuffer(corners[1], verts, 1); + BufferUtils.setInBuffer(corners[2], verts, 2); + BufferUtils.setInBuffer(corners[3], verts, 3); + + BufferUtils.setInBuffer(corners[4], verts, 4); + BufferUtils.setInBuffer(corners[5], verts, 5); + BufferUtils.setInBuffer(corners[6], verts, 6); + BufferUtils.setInBuffer(corners[7], verts, 7); + + BufferUtils.setInBuffer(corners[0], verts, 8); + BufferUtils.setInBuffer(corners[4], verts, 9); + BufferUtils.setInBuffer(corners[1], verts, 10); + BufferUtils.setInBuffer(corners[5], verts, 11); + BufferUtils.setInBuffer(corners[2], verts, 12); + BufferUtils.setInBuffer(corners[6], verts, 13); + BufferUtils.setInBuffer(corners[3], verts, 14); + BufferUtils.setInBuffer(corners[7], verts, 15); + + BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 16); + BufferUtils.setInBuffer(corners[0], verts, 17); + BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 18); + BufferUtils.setInBuffer(corners[1], verts, 19); + BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 20); + BufferUtils.setInBuffer(corners[2], verts, 21); + BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 22); + BufferUtils.setInBuffer(corners[3], verts, 23); + + lineFrustum.draw(r); + } + +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java new file mode 100644 index 0000000..53d5b49 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.util.EnumSet; +import java.util.Map; +import java.util.logging.Logger; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.google.common.collect.Maps; + +/** + * This tool assists in reducing geometry information.<br> + * + * Note: Does not work with geometry using texcoords other than 2d coords. <br> + * TODO: Consider adding an option for "close enough" vertex matches... ie, smaller than X distance apart.<br> + */ +public abstract class GeometryTool { + private static final Logger logger = Logger.getLogger(GeometryTool.class.getName()); + + /** + * Condition options for determining if one vertex is "equal" to another. + */ + public enum MatchCondition { + /** Vertices must have identical normals. */ + Normal, + /** Vertices must have identical texture coords on all channels. */ + UVs, + /** Vertices must have identical vertex coloring. */ + Color, + /** Vertices must be in same group. */ + Group; + } + + /** + * Attempt to collapse duplicate vertex data in a given mesh. Vertices are considered duplicate if they occupy the + * same place in space and match the supplied conditions. All vertices in the mesh are considered part of the same + * vertex "group". + * + * @param mesh + * the mesh to reduce + * @param conditions + * our match conditions. + * @return a mapping of old vertex positions to their new positions. + */ + public static VertMap minimizeVerts(final Mesh mesh, final EnumSet<MatchCondition> conditions) { + final VertGroupData groupData = new VertGroupData(); + groupData.setGroupConditions(VertGroupData.DEFAULT_GROUP, conditions); + return minimizeVerts(mesh, groupData); + } + + /** + * Attempt to collapse duplicate vertex data in a given mesh. Vertices are consider duplicate if they occupy the + * same place in space and match the supplied conditions. The conditions are supplied per vertex group. + * + * @param mesh + * the mesh to reduce + * @param groupData + * grouping data for the vertices in this mesh. + * @return a mapping of old vertex positions to their new positions. + */ + public static VertMap minimizeVerts(final Mesh mesh, final VertGroupData groupData) { + final long start = System.currentTimeMillis(); + + int vertCount = -1; + final int oldCount = mesh.getMeshData().getVertexCount(); + int newCount = 0; + + final VertMap result = new VertMap(mesh); + + // while we have not run through this optimization and ended up the same... + // XXX: could optimize this to run all in arrays, then write to buffer after while loop. + while (vertCount != newCount) { + vertCount = mesh.getMeshData().getVertexCount(); + // go through each vert... + final Vector3[] verts = BufferUtils.getVector3Array(mesh.getMeshData().getVertexCoords(), Vector3.ZERO); + Vector3[] norms = null; + if (mesh.getMeshData().getNormalBuffer() != null) { + norms = BufferUtils.getVector3Array(mesh.getMeshData().getNormalCoords(), Vector3.UNIT_Y); + } + + // see if we have vertex colors + ColorRGBA[] colors = null; + if (mesh.getMeshData().getColorBuffer() != null) { + colors = BufferUtils.getColorArray(mesh.getMeshData().getColorCoords(), ColorRGBA.WHITE); + } + + // see if we have uv coords + final Vector2[][] tex = new Vector2[mesh.getMeshData().getNumberOfUnits()][]; + for (int x = 0; x < tex.length; x++) { + if (mesh.getMeshData().getTextureCoords(x) != null) { + tex[x] = BufferUtils.getVector2Array(mesh.getMeshData().getTextureCoords(x), Vector2.ZERO); + } + } + + final Map<VertKey, Integer> store = Maps.newHashMap(); + final Map<Integer, Integer> indexRemap = Maps.newHashMap(); + int good = 0; + long group; + for (int x = 0, max = verts.length; x < max; x++) { + group = groupData.getGroupForVertex(x); + final VertKey vkey = new VertKey(verts[x], norms != null ? norms[x] : null, colors != null ? colors[x] + : null, getTexs(tex, x), groupData.getGroupConditions(group), group); + // if we've already seen it, swap it for the max, and decrease max. + if (store.containsKey(vkey)) { + final int newInd = store.get(vkey); + if (indexRemap.containsKey(x)) { + indexRemap.put(max, newInd); + } else { + indexRemap.put(x, newInd); + } + max--; + if (x != max) { + indexRemap.put(max, x); + verts[x] = verts[max]; + verts[max] = null; + if (norms != null) { + norms[newInd].addLocal(norms[x].normalizeLocal()); + norms[x] = norms[max]; + } + if (colors != null) { + colors[x] = colors[max]; + } + for (int y = 0; y < tex.length; y++) { + if (mesh.getMeshData().getTextureCoords(y) != null) { + tex[y][x] = tex[y][max]; + } + } + x--; + } else { + verts[max] = null; + } + } + + // otherwise just store it + else { + store.put(vkey, x); + good++; + } + } + + if (norms != null) { + for (final Vector3 norm : norms) { + norm.normalizeLocal(); + } + } + + mesh.getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(0, good, verts)); + if (norms != null) { + mesh.getMeshData().setNormalBuffer(BufferUtils.createFloatBuffer(0, good, norms)); + } + if (colors != null) { + mesh.getMeshData().setColorBuffer(BufferUtils.createFloatBuffer(0, good, colors)); + } + + for (int x = 0; x < tex.length; x++) { + if (tex[x] != null) { + mesh.getMeshData().setTextureBuffer(BufferUtils.createFloatBuffer(0, good, tex[x]), x); + } + } + + if (mesh.getMeshData().getIndices() == null || mesh.getMeshData().getIndices().getBufferCapacity() == 0) { + final IndexBufferData<?> indexBuffer = BufferUtils.createIndexBufferData(oldCount, oldCount); + mesh.getMeshData().setIndices(indexBuffer); + for (int i = 0; i < oldCount; i++) { + if (indexRemap.containsKey(i)) { + indexBuffer.put(indexRemap.get(i)); + } else { + indexBuffer.put(i); + } + } + } else { + final IndexBufferData<?> indexBuffer = mesh.getMeshData().getIndices(); + final int[] inds = BufferUtils.getIntArray(indexBuffer); + indexBuffer.rewind(); + for (final int i : inds) { + if (indexRemap.containsKey(i)) { + indexBuffer.put(indexRemap.get(i)); + } else { + indexBuffer.put(i); + } + } + } + result.applyRemapping(indexRemap); + newCount = mesh.getMeshData().getVertexCount(); + } + + logger.info("Vertex reduction complete on: " + mesh + " old vertex count: " + oldCount + " new vertex count: " + + newCount + " (in " + (System.currentTimeMillis() - start) + " ms)"); + + return result; + } + + private static Vector2[] getTexs(final Vector2[][] tex, final int i) { + final Vector2[] res = new Vector2[tex.length]; + for (int x = 0; x < tex.length; x++) { + if (tex[x] != null) { + res[x] = tex[x][i]; + } + } + return res; + } + + public static void trimEmptyBranches(final Spatial spatial) { + if (spatial instanceof Node) { + final Node node = (Node) spatial; + for (int i = node.getNumberOfChildren(); --i >= 0;) { + trimEmptyBranches(node.getChild(i)); + } + if (node.getNumberOfChildren() <= 0) { + spatial.removeFromParent(); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java new file mode 100644 index 0000000..87a3573 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java @@ -0,0 +1,448 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; + +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.visitor.Visitor; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +/** + * Utility for combining multiple Meshes into a single Mesh. Note that you generally will want to combine Mesh objects + * that have the same render states. + * + * XXX: should add in a way to combine only meshes with similar renderstates<br> + * XXX: Might be able to reduce memory usage in the singular case where all sources do not have indices defined + * (arrays).<br> + * XXX: combining of triangle strips may not work properly? <br> + * XXX: Could be smarter about texcoords and have a tuple size per channel.<br> + */ +public class MeshCombiner { + public static final float[] DEFAULT_COLOR = { 1f, 1f, 1f, 1f }; + public static final float[] DEFAULT_NORMAL = { 0f, 1f, 0f }; + public static final float[] DEFAULT_TEXCOORD = { 0 }; + + /** + * <p> + * Combine all mesh objects that fall under the scene graph the given source node. All Mesh objects must have + * vertices and texcoords that have the same tuple width. It is possible to merge Mesh objects together that have + * mismatched normals/colors/etc. (eg. one with colors and one without.) + * </p> + * + * @param source + * our source node + * @return the combined Mesh. + */ + public final static Mesh combine(final Node source) { + return combine(source, new MeshCombineLogic()); + } + + public final static Mesh combine(final Spatial source, final MeshCombineLogic logic) { + final List<Mesh> sources = Lists.newArrayList(); + source.acceptVisitor(new Visitor() { + @Override + public void visit(final Spatial spatial) { + if (spatial instanceof Mesh) { + sources.add((Mesh) spatial); + } + } + }, true); + + return combine(sources, logic); + } + + /** + * Combine the given array of Mesh objects into a single Mesh. All Mesh objects must have vertices and texcoords + * that have the same tuple width. It is possible to merge Mesh objects together that have mismatched + * normals/colors/etc. (eg. one with colors and one without.) + * + * @param sources + * our Mesh objects to combine. + * @return the combined Mesh. + */ + public final static Mesh combine(final Mesh... sources) { + return combine(Lists.newArrayList(sources)); + } + + /** + * Combine the given collection of Mesh objects into a single Mesh. All Mesh objects must have vertices and + * texcoords that have the same tuple width. It is possible to merge Mesh objects together that have mismatched + * normals/colors/etc. (eg. one with colors and one without.) + * + * @param sources + * our collection of Mesh objects to combine. + * @return the combined Mesh. + */ + public final static Mesh combine(final Collection<Mesh> sources) { + return combine(sources, new MeshCombineLogic()); + } + + public final static Mesh combine(final Collection<Mesh> sources, final MeshCombineLogic logic) { + if (sources == null || sources.isEmpty()) { + return null; + } + + // go through each MeshData to see what buffers we need and validate sizes. + for (final Mesh mesh : sources) { + logic.addSource(mesh); + } + + // initialize return buffers + logic.initDataBuffers(); + + // combine sources into buffers + logic.combineSources(); + + // get and return our combined mesh + return logic.getCombinedMesh(); + } + + public static class MeshCombineLogic { + protected boolean useIndices = false, useNormals = false, useTextures = false, useColors = false, first = true; + protected int maxTextures = 0, totalVertices = 0, totalIndices = 0, texCoords = 2, vertCoords = 3; + protected IndexMode mode = null; + protected EnumMap<StateType, RenderState> states = null; + protected MeshData data = new MeshData(); + protected BoundingVolume volumeType = null; + protected List<Mesh> sources = Lists.newArrayList(); + private FloatBufferData vertices; + private FloatBufferData colors; + private FloatBufferData normals; + private List<FloatBufferData> texCoordsList; + + public Mesh getMesh() { + final Mesh mesh = new Mesh("combined"); + mesh.setMeshData(data); + return mesh; + } + + public Mesh getCombinedMesh() { + final Mesh mesh = getMesh(); + + // set our bounding volume using the volume type of our first source found above. + mesh.setModelBound(volumeType); + + // set the render states from the first mesh + for (final RenderState state : states.values()) { + mesh.setRenderState(state); + } + + return mesh; + } + + public void combineSources() { + final IndexCombiner iCombiner = new IndexCombiner(); + + // Walk through our source meshes and populate return MeshData buffers. + int vertexOffset = 0; + for (final Mesh mesh : sources) { + + final MeshData md = mesh.getMeshData(); + + // Vertices + md.getVertexBuffer().rewind(); + vertices.getBuffer().put(mesh.getWorldVectors(null)); + + // Normals + if (useNormals) { + final FloatBuffer nb = md.getNormalBuffer(); + if (nb != null) { + nb.rewind(); + normals.getBuffer().put(mesh.getWorldNormals(null)); + } else { + for (int i = 0; i < md.getVertexCount(); i++) { + normals.getBuffer().put(DEFAULT_NORMAL); + } + } + } + + // Colors + if (useColors) { + final FloatBuffer cb = md.getColorBuffer(); + if (cb != null) { + cb.rewind(); + colors.getBuffer().put(cb); + } else { + for (int i = 0; i < md.getVertexCount(); i++) { + colors.getBuffer().put(DEFAULT_COLOR); + } + } + } + + // Tex Coords + if (useTextures) { + for (int i = 0; i < maxTextures; i++) { + final FloatBuffer dest = texCoordsList.get(i).getBuffer(); + final FloatBuffer tb = md.getTextureBuffer(i); + if (tb != null) { + tb.rewind(); + dest.put(tb); + } else { + for (int j = 0; j < md.getVertexCount() * texCoords; j++) { + dest.put(DEFAULT_TEXCOORD); + } + } + } + } + + // Indices + if (useIndices) { + iCombiner.addEntry(md, vertexOffset); + vertexOffset += md.getVertexCount(); + } + } + + // Apply our index combiner to the mesh + if (useIndices) { + iCombiner.saveTo(data); + } else { + data.setIndexLengths(null); + data.setIndexMode(mode); + } + } + + public void initDataBuffers() { + // Generate our buffers based on the information collected above and populate MeshData + vertices = new FloatBufferData(totalVertices * vertCoords, vertCoords); + data.setVertexCoords(vertices); + + colors = useColors ? new FloatBufferData(totalVertices * 4, 4) : null; + data.setColorCoords(colors); + + normals = useNormals ? new FloatBufferData(totalVertices * 3, 3) : null; + data.setNormalCoords(normals); + + texCoordsList = Lists.newArrayListWithCapacity(maxTextures); + for (int i = 0; i < maxTextures; i++) { + texCoordsList.add(new FloatBufferData(totalVertices * texCoords, texCoords)); + } + data.setTextureCoords(useTextures ? texCoordsList : null); + } + + public void addSource(final Mesh mesh) { + sources.add(mesh); + + // update world transforms + mesh.updateWorldTransform(false); + + final MeshData md = mesh.getMeshData(); + if (first) { + // copy info from first mesh + vertCoords = md.getVertexCoords().getValuesPerTuple(); + volumeType = mesh.getModelBound(null); + states = mesh.getLocalRenderStates(); + first = false; + } else if (vertCoords != md.getVertexCoords().getValuesPerTuple()) { + throw new IllegalArgumentException("all MeshData vertex coords must use same tuple size."); + } + + // update total vertices + totalVertices += md.getVertexCount(); + + // check for indices + if (useIndices || md.getIndexBuffer() != null) { + useIndices = true; + if (md.getIndexBuffer() != null) { + totalIndices += md.getIndices().capacity(); + } else { + totalIndices += md.getVertexCount(); + } + } else { + mode = md.getIndexMode(0); + } + + // check for normals + if (!useNormals && md.getNormalBuffer() != null) { + useNormals = true; + } + + // check for colors + if (!useColors && md.getColorBuffer() != null) { + useColors = true; + } + + // check for texcoord usage + if (md.getNumberOfUnits() > 0) { + if (!useTextures) { + useTextures = true; + texCoords = md.getTextureCoords(0).getValuesPerTuple(); + } else if (md.getTextureCoords(0) != null && texCoords != md.getTextureCoords(0).getValuesPerTuple()) { + throw new IllegalArgumentException("all MeshData objects with texcoords must use same tuple size."); + } + maxTextures = Math.max(maxTextures, md.getNumberOfUnits()); + } + } + } +} + +class IndexCombiner { + Multimap<IndexMode, int[]> sectionMap = ArrayListMultimap.create(); + + public void addEntry(final MeshData source, final int vertexOffset) { + // arrays or elements? + if (source.getIndexBuffer() == null) { + // arrays... + int offset = 0; + int indexModeCounter = 0; + final IndexMode[] modes = source.getIndexModes(); + // walk through each section + for (int i = 0, maxI = source.getSectionCount(); i < maxI; i++) { + // make an int array and populate it. + final int size = source.getIndexLengths() != null ? source.getIndexLengths()[i] : source + .getVertexCount(); + final int[] indices = new int[size]; + for (int j = 0; j < size; j++) { + indices[j] = j + vertexOffset + offset; + } + + // add to map + sectionMap.put(modes[indexModeCounter], indices); + + // move our offsets forward to the section + offset += size; + if (indexModeCounter < modes.length - 1) { + indexModeCounter++; + } + } + } else { + // elements... + final IndexBufferData<?> ib = source.getIndices(); + ib.rewind(); + int offset = 0; + int indexModeCounter = 0; + final IndexMode[] modes = source.getIndexModes(); + // walk through each section + for (int i = 0, maxI = source.getSectionCount(); i < maxI; i++) { + // make an int array and populate it. + final int size = source.getIndexLengths() != null ? source.getIndexLengths()[i] : source.getIndices() + .capacity(); + final int[] indices = new int[size]; + for (int j = 0; j < size; j++) { + indices[j] = ib.get(j + offset) + vertexOffset; + } + + // add to map + sectionMap.put(modes[indexModeCounter], indices); + + // move our offsets forward to the section + offset += size; + if (indexModeCounter < modes.length - 1) { + indexModeCounter++; + } + } + } + } + + public void saveTo(final MeshData data) { + final List<IntBuffer> sections = Lists.newArrayList(); + final List<IndexMode> modes = Lists.newArrayList(); + int max = 0; + // walk through index modes and combine those we can. + for (final IndexMode mode : sectionMap.keySet()) { + final Collection<int[]> sources = sectionMap.get(mode); + switch (mode) { + case Triangles: + case Quads: + case Lines: + case Points: { + // we can combine these as-is to our heart's content. + int size = 0; + for (final int[] indices : sources) { + size += indices.length; + } + max += size; + final IntBuffer newSection = BufferUtils.createIntBufferOnHeap(size); + for (final int[] indices : sources) { + newSection.put(indices); + } + // save + sections.add(newSection); + modes.add(mode); + break; + } + case TriangleFan: + case QuadStrip: + case LineLoop: + case LineStrip: { + // these have to be kept, as is. + int size; + for (final int[] indices : sources) { + size = indices.length; + max += size; + final IntBuffer newSection = BufferUtils.createIntBufferOnHeap(size); + newSection.put(indices); + + sections.add(newSection); + modes.add(mode); + } + break; + } + case TriangleStrip: { + // we CAN combine these, but we have to add degenerate triangles. + int size = 0; + for (final int[] indices : sources) { + size += indices.length + 2; + } + size -= 2; + max += size; + final IntBuffer newSection = BufferUtils.createIntBufferOnHeap(size); + int i = 0; + for (final int[] indices : sources) { + if (i != 0) { + newSection.put(indices[0]); + } + newSection.put(indices); + if (i < sources.size() - 1) { + newSection.put(indices[indices.length - 1]); + } + i++; + } + // save + sections.add(newSection); + modes.add(mode); + break; + } + } + } + + // compile into data + final IndexBufferData<?> finalIndices = BufferUtils.createIndexBufferData(max, data.getVertexCount() - 1); + data.setIndices(finalIndices); + final int[] sectionCounts = new int[sections.size()]; + for (int i = 0; i < sectionCounts.length; i++) { + final IntBuffer ib = sections.get(i); + ib.rewind(); + sectionCounts[i] = ib.remaining(); + while (ib.hasRemaining()) { + finalIndices.put(ib.get()); + } + } + + data.setIndexLengths(sectionCounts); + data.setIndexModes(modes.toArray(new IndexMode[modes.size()])); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java new file mode 100644 index 0000000..78ef6d2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.util.Arrays; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; + +/** + * A utility class to generate normals for a set of vertices. The triangles must be defined by just the vertices, so + * that every 3 consecutive vertices define one triangle. However, an index array must be specified to identify + * identical vertices properly (see method {@link #generateNormals(double[], int[], double)}. If the index aray is not + * specified, the vertex normals are currently simply taken from the faces they belong to (this might be changed in the + * future, so that vertices are compared by their values). + */ +public class NonIndexedNormalGenerator { + + private final Vector3 _temp1 = new Vector3(); + + private final Vector3 _temp2 = new Vector3(); + + private final Vector3 _temp3 = new Vector3(); + + private int[] _indices; + + private double _creaseAngle; + + private double[] _faceNormals; + + private int[] _normalsToSet; + + /** + * Calculates the normals for a set of faces determined by the specified vertices. Every 3 consecutive vertices + * define one triangle.<br /> <strong>Please note:</strong> This method uses class fields and is not synchronized! + * Therefore it should only be called from a single thread, unless synchronization is taken care of externally. + * + * @param vertices + * The vertex coordinates. Every three values define one vertex + * @param indices + * An array containing int values. Each value belongs to one vertex in the <code>vertices</code> array, + * the values are stored in the same order as the vertices. For equal vertices in the + * <code>vertices</code> array, the indices are also equal. + * @param creaseAngle + * The maximum angle in radians between faces to which normals between the faces are interpolated to + * create a smooth transition + * @return An array containing the generated normals for the geometry + */ + public double[] generateNormals(final double[] vertices, final int[] indices, final double creaseAngle) { + + _indices = indices; + _creaseAngle = creaseAngle; + _normalsToSet = new int[10]; + Arrays.fill(_normalsToSet, -1); + + initFaceNormals(vertices); + + if (creaseAngle < MathUtils.ZERO_TOLERANCE || indices == null) { + return getFacetedVertexNormals(); + } + return getVertexNormals(); + } + + /** + * Initializes the array <code>faceNormals</code> with the normals of all faces (triangles) of the mesh. + * + * @param vertices + * The array containing all vertex coordinates + */ + private void initFaceNormals(final double[] vertices) { + _faceNormals = new double[vertices.length / 3]; + + for (int i = 0; i * 9 < vertices.length; i++) { + _temp1.set(vertices[i * 9 + 0], vertices[i * 9 + 1], vertices[i * 9 + 2]); + _temp2.set(vertices[i * 9 + 3], vertices[i * 9 + 4], vertices[i * 9 + 5]); + _temp3.set(vertices[i * 9 + 6], vertices[i * 9 + 7], vertices[i * 9 + 8]); + + _temp2.subtractLocal(_temp1); // A -> B + _temp3.subtractLocal(_temp1); // A -> C + + _temp2.cross(_temp3, _temp1); + _temp1.normalizeLocal(); // Normal + + _faceNormals[i * 3 + 0] = _temp1.getX(); + _faceNormals[i * 3 + 1] = _temp1.getY(); + _faceNormals[i * 3 + 2] = _temp1.getZ(); + } + } + + /** + * Creates an array containing the interpolated normals for all vertices + * + * @return The array with the vertex normals + */ + private double[] getVertexNormals() { + + final double[] normals = new double[_faceNormals.length * 3]; + final boolean[] setNormals = new boolean[_faceNormals.length]; + + for (int i = 0; i * 3 < _faceNormals.length; i++) { + for (int j = 0; j < 3; j++) { + if (!setNormals[i * 3 + j]) { + setInterpolatedNormal(normals, setNormals, i, j); + } + } + } + + return normals; + } + + /** + * Computes the interpolated normal for the specified vertex of the specified face and applies it to all identical + * vertices for which the normal is interpolated. + * + * @param normals + * The array to store the vertex normals + * @param setNormals + * An array indicating which vertex normals have already been set + * @param face + * The index of the face containing the current vertex + * @param vertex + * The index of the vertex inside the face (0 - 2) + */ + private void setInterpolatedNormal(final double[] normals, final boolean[] setNormals, final int face, + final int vertex) { + + // temp1: Normal of the face the specified vertex belongs to + _temp1.set(_faceNormals[face * 3 + 0], _faceNormals[face * 3 + 1], _faceNormals[face * 3 + 2]); + + // temp2: Sum of all face normals to be interpolated + _temp2.set(_temp1); + + final int vertIndex = _indices[face * 3 + vertex]; + _normalsToSet[0] = face * 3 + vertex; + int count = 1; + + /* + * Get the normals of all faces containing the specified vertex whose angle to the specified one is less than + * the crease angle + */ + for (int i = face * 3 + vertex + 1; i < _indices.length; i++) { + if (_indices[i] == vertIndex && !setNormals[face * 3 + vertex]) { + // temp3: Normal of the face the current vertex belongs to + _temp3.set(_faceNormals[(i / 3) * 3 + 0], _faceNormals[(i / 3) * 3 + 1], _faceNormals[(i / 3) * 3 + 2]); + if (_temp1.smallestAngleBetween(_temp3) < _creaseAngle) { + _normalsToSet = setValue(_normalsToSet, count, i); + count++; + _temp2.addLocal(_temp3); + } + } + } + + _temp2.normalizeLocal(); + + // Set the normals for all vertices marked for interpolation + for (int i = 0; i < _normalsToSet.length && _normalsToSet[i] != -1; i++) { + normals[_normalsToSet[i] * 3 + 0] = _temp2.getX(); + normals[_normalsToSet[i] * 3 + 1] = _temp2.getY(); + normals[_normalsToSet[i] * 3 + 2] = _temp2.getZ(); + setNormals[_normalsToSet[i]] = true; + _normalsToSet[i] = -1; + } + } + + /** + * Puts the value into the array at the specified index. If the index is out of bounds, an new array with a length + * of 3 fields more than the specified one is created first and the values copied to it. + * + * @param array + * The array + * @param index + * The index to insert the value + * @param value + * The value to insert + * @return The array with the values, either the specified one or the new one + */ + private int[] setValue(int[] array, final int index, final int value) { + if (index >= array.length) { + final int[] temp = new int[array.length + 3]; + Arrays.fill(temp, -1); + System.arraycopy(array, 0, temp, 0, array.length); + array = temp; + } + + array[index] = value; + return array; + } + + /** + * Simply copies the face normals to the vertices contained in each face, creating a faceted appearance. + * + * @return The vertex normals + */ + private double[] getFacetedVertexNormals() { + final double[] normals = new double[_faceNormals.length * 3]; + for (int i = 0; i * 3 < _faceNormals.length; i++) { + for (int j = 0; j < 3; j++) { + normals[i * 9 + j + 0] = _faceNormals[i * 3 + j]; + normals[i * 9 + j + 3] = _faceNormals[i * 3 + j]; + normals[i * 9 + j + 6] = _faceNormals[i * 3 + j]; + } + } + return normals; + } + + public void generateNormals(final Mesh mesh) { + + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java new file mode 100644 index 0000000..aa9d32a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java @@ -0,0 +1,812 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.logging.Logger; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.IntBufferData; +import com.ardor3d.scenegraph.Mesh; + +/** + * A utility class to generate normals for Meshes.<br /> + * <br /> + * The generator generates the normals using a given crease angle up to which the transitions between two triangles are + * to be smoothed. Normals will be generated for the mesh, as long as it uses the default triangle mode ( + * <code>TRIANGLE</code>) - the modes <code>TRIANGLE_FAN</code> and <code>TRIANGLE_STRIP</code> are not supported.<br /> + * <br /> + * The generator currently only supports a single set of texture coordinates in a mesh. If more than one set of texture + * coordinates is specified in a mesh, all sets after the first one will be deleted.<br /> + * <br /> + * <strong>Please note:</strong> The mesh must be <cite>manifold</cite>, i.e. only 2 triangles may be connected by one + * edge, and the mesh has to be connected by means of edges, not vertices. Otherwise, the normal generation might fail, + * with undefined results. + */ +public class NormalGenerator { + + private static final Logger logger = Logger.getLogger(NormalGenerator.class.getName()); + + // The angle up to which edges between triangles are smoothed + private float _creaseAngle; + + // Source data + private Vector3[] _sourceVerts; + private ColorRGBA[] _sourceColors; + private Vector2[] _sourceTexCoords; + private int[] _sourceInds; + private LinkedList<Triangle> _triangles; + + // Computed (destination) data for one mesh split + private List<Vector3> _destVerts; + private List<ColorRGBA> _destColors; + private List<Vector2> _destTexCoords; + private LinkedList<Triangle> _destTris; + private LinkedList<Edge> _edges; + + // The lists to store the split data for the final mesh + private LinkedList<LinkedList<Triangle>> _splitMeshes; + private LinkedList<LinkedList<Edge>> _splitMeshBorders; + private Vector3[] _splitVerts; + private ColorRGBA[] _splitColors; + private Vector2[] _splitTexCoords; + private Vector3[] _splitNormals; + private int[] _splitIndices; + + // The data used to compute the final mesh + private boolean[] _borderIndices; + + // Temporary data used for computation + private final Vector3 _compVect0 = new Vector3(); + private final Vector3 _compVect1 = new Vector3(); + + /** + * Generates the normals for one Mesh, using the specified crease angle. + * + * @param mesh + * The Mesh to generate the normals for + * @param creaseAngle + * The angle between two triangles up to which the normal between the two triangles will be interpolated, + * creating a smooth transition + */ + public void generateNormals(final Mesh mesh, final float creaseAngle) { + if (mesh != null) { + _creaseAngle = creaseAngle; + generateNormals(mesh); + cleanup(); + } + } + + /** + * Generates the normals for one Mesh, using the crease angle stored in the field <code>creaseAngle</code> + * + * @param mesh + * The Mesh to generate the normals for + */ + private void generateNormals(final Mesh mesh) { + // FIXME: only uses 1st index. + if (mesh.getMeshData().getIndexMode(0) != IndexMode.Triangles) { + logger.info("Invalid triangles mode in " + mesh); + return; + } + + // Get the data of the mesh as arrays + _sourceInds = BufferUtils.getIntArray(mesh.getMeshData().getIndices()); + _sourceVerts = BufferUtils.getVector3Array(mesh.getMeshData().getVertexBuffer()); + if (mesh.getMeshData().getColorBuffer() != null) { + _sourceColors = BufferUtils.getColorArray(mesh.getMeshData().getColorBuffer()); + } else { + _sourceColors = null; + } + if (mesh.getMeshData().getTextureCoords(0) != null) { + _sourceTexCoords = BufferUtils.getVector2Array(mesh.getMeshData().getTextureCoords(0).getBuffer()); + } else { + _sourceTexCoords = null; + } + + // Initialize the lists needed to generate the normals for the mesh + initialize(); + + // Process all triangles in the mesh. Create one connected mesh for + // every set of triangles that are connected by their vertex indices + // with an angle not greater than the creaseAngle. + while (!_triangles.isEmpty()) { + createMeshSplit(); + } + + // Duplicate all vertices that are shared by different split meshes + if (!_splitMeshes.isEmpty()) { + _borderIndices = new boolean[_sourceVerts.length]; + fillBorderIndices(); + duplicateCreaseVertices(); + } + + // Set up the arrays for reconstructing the mesh: Vertices, + // texture coordinates, colors, normals and indices + _splitVerts = _destVerts.toArray(new Vector3[_destVerts.size()]); + if (_destColors != null) { + _splitColors = _destColors.toArray(new ColorRGBA[_destColors.size()]); + } else { + _splitColors = null; + } + if (_destTexCoords != null) { + _splitTexCoords = _destTexCoords.toArray(new Vector2[_destTexCoords.size()]); + } else { + _splitTexCoords = null; + } + _splitNormals = new Vector3[_destVerts.size()]; + for (int j = 0; j < _splitNormals.length; j++) { + _splitNormals[j] = new Vector3(); + } + int numTris = 0; + for (final LinkedList<Triangle> tris : _splitMeshes) { + numTris += tris.size(); + } + _splitIndices = new int[numTris * 3]; + + // For each of the split meshes, create the interpolated normals + // between its triangles and set up its index array in the process + computeNormalsAndIndices(); + + // Set up the buffers for the mesh + + // Vertex buffer: + FloatBuffer vertices = mesh.getMeshData().getVertexBuffer(); + if (vertices.capacity() < _splitVerts.length * 3) { + vertices = BufferUtils.createFloatBuffer(_splitVerts); + } else { + vertices.clear(); + for (final Vector3 vertex : _splitVerts) { + vertices.put((float) vertex.getX()).put((float) vertex.getY()).put((float) vertex.getZ()); + } + vertices.flip(); + } + + // Normal buffer: + FloatBuffer normals = mesh.getMeshData().getNormalBuffer(); + if (normals == null || normals.capacity() < _splitNormals.length * 3) { + normals = BufferUtils.createFloatBuffer(_splitNormals); + } else { + normals.clear(); + for (final Vector3 normal : _splitNormals) { + normals.put((float) normal.getX()).put((float) normal.getY()).put((float) normal.getZ()); + } + normals.flip(); + } + + // Color buffer: + FloatBuffer colors = null; + if (_splitColors != null) { + colors = mesh.getMeshData().getColorBuffer(); + if (colors.capacity() < _splitColors.length * 4) { + colors = BufferUtils.createFloatBuffer(_splitColors); + } else { + colors.clear(); + for (final ColorRGBA color : _splitColors) { + colors.put(color.getRed()).put(color.getGreen()).put(color.getBlue()).put(color.getAlpha()); + } + colors.flip(); + } + } + + // Tex coord buffer: + FloatBuffer texCoords = null; + if (_splitTexCoords != null) { + texCoords = mesh.getMeshData().getTextureCoords(0).getBuffer(); + if (texCoords.capacity() < _splitTexCoords.length * 2) { + texCoords = BufferUtils.createFloatBuffer(_splitTexCoords); + } else { + texCoords.clear(); + for (final Vector2 texCoord : _splitTexCoords) { + texCoords.put((float) texCoord.getX()).put((float) texCoord.getY()); + } + texCoords.flip(); + } + } + + // Index buffer: + IndexBufferData<?> indices = mesh.getMeshData().getIndices(); + if (indices.getBuffer().capacity() < _splitIndices.length) { + indices = new IntBufferData(BufferUtils.createIntBuffer(_splitIndices)); + } else { + indices.getBuffer().clear(); + for (final int i : _splitIndices) { + indices.put(i); + } + indices.getBuffer().flip(); + } + + // Apply the buffers to the mesh + mesh.getMeshData().setVertexBuffer(vertices); + mesh.getMeshData().setNormalBuffer(normals); + if (colors != null) { + mesh.getMeshData().setColorBuffer(colors); + } + mesh.getMeshData().getTextureCoords().clear(); + if (texCoords != null) { + mesh.getMeshData().setTextureBuffer(texCoords, 0); + } + mesh.getMeshData().setIndices(indices); + } + + /** + * Sets up the lists to get the data for normal generation from. + */ + private void initialize() { + // Copy the source vertices as a base for the normal generation + _destVerts = new ArrayList<Vector3>(_sourceVerts.length); + for (int i = 0; i < _sourceVerts.length; i++) { + _destVerts.add(_sourceVerts[i]); + } + if (_sourceColors != null) { + _destColors = new ArrayList<ColorRGBA>(_sourceColors.length); + for (int i = 0; i < _sourceColors.length; i++) { + _destColors.add(_sourceColors[i]); + } + } else { + _destColors = null; + } + if (_sourceTexCoords != null) { + _destTexCoords = new ArrayList<Vector2>(_sourceTexCoords.length); + for (int i = 0; i < _sourceTexCoords.length; i++) { + _destTexCoords.add(_sourceTexCoords[i]); + } + } else { + _destTexCoords = null; + } + + // Set up the base triangles of the mesh and their face normals + _triangles = new LinkedList<Triangle>(); + for (int i = 0; i * 3 < _sourceInds.length; i++) { + final Triangle tri = new Triangle(_sourceInds[i * 3 + 0], _sourceInds[i * 3 + 1], _sourceInds[i * 3 + 2]); + tri.computeNormal(_sourceVerts); + _triangles.add(tri); + } + + // Set up the lists to store the created mesh split data + if (_splitMeshes == null) { + _splitMeshes = new LinkedList<LinkedList<Triangle>>(); + } else { + _splitMeshes.clear(); + } + if (_splitMeshBorders == null) { + _splitMeshBorders = new LinkedList<LinkedList<Edge>>(); + } else { + _splitMeshBorders.clear(); + } + } + + /** + * Assembles one set of triangles ("split mesh") that are connected and do not contain a transition with an angle + * greater than the creaseAngle. The Triangles of the split mesh are stored in splitMeshes, and the Edges along the + * border of the split mesh in splitMeshBorders. + */ + private void createMeshSplit() { + _destTris = new LinkedList<Triangle>(); + _edges = new LinkedList<Edge>(); + final Triangle tri = _triangles.removeFirst(); + _destTris.addLast(tri); + _edges.addLast(tri.edges[0]); + _edges.addLast(tri.edges[1]); + _edges.addLast(tri.edges[2]); + + Triangle newTri; + do { + newTri = insertTriangle(); + } while (newTri != null); + + _splitMeshes.addLast(_destTris); + _splitMeshBorders.addLast(_edges); + } + + /** + * Finds one triangle connected to the split mesh currently being assembled over an edge whose angle does not exceed + * the creaseAngle. The Triangle is inserted into destTris and the list edges is updated with the edges of the + * triangle accordingly. + * + * @return The triangle, if one was found, or <code>null</code> otherwise + */ + private Triangle insertTriangle() { + final ListIterator<Triangle> triIt = _triangles.listIterator(); + ListIterator<Edge> edgeIt = null; + + // Find a triangle that is connected to the border of the currently + // assembled mesh split and whose angle to the connected border triangle + // is less than or equal to the creaseAngle + Triangle result = null; + int connected = -1; + Edge borderEdge = null; + while (result == null && triIt.hasNext()) { + final Triangle tri = triIt.next(); + edgeIt = _edges.listIterator(); + while (result == null && edgeIt.hasNext()) { + borderEdge = edgeIt.next(); + for (int i = 0; i < tri.edges.length && result == null; i++) { + if (borderEdge.isConnectedTo(tri.edges[i]) && checkAngle(tri, borderEdge.parent)) { + connected = i; + result = tri; + } + } + } + } + + // If a triangle has been found, remove it from the list of remaining + // triangles and add it to the current split mesh, including its borders + if (result != null) { + // Connect the triangle to the split mesh + triIt.remove(); + _destTris.addLast(result); + borderEdge.connected = result; + final Edge resultEdge = result.edges[connected]; + resultEdge.connected = borderEdge.parent; + edgeIt.remove(); + edgeIt.add(result.edges[(connected + 1) % 3]); + edgeIt.add(result.edges[(connected + 2) % 3]); + + // If the connected edge had cloned vertices, use them for the new + // triangle too + if (borderEdge.newI0 > -1) { + resultEdge.newI1 = borderEdge.newI0; + result.edges[(connected + 1) % 3].newI0 = borderEdge.newI0; + } + if (borderEdge.newI1 > -1) { + resultEdge.newI0 = borderEdge.newI1; + result.edges[(connected + 2) % 3].newI1 = borderEdge.newI1; + } + + // Check if the triangle is connected to other edges along the + // border of the current split mesh + for (int i = connected + 1; i < connected + 3; i++) { + connectEdge(result, i % 3); + } + } + + return result; + } + + /** + * Connects the remaining edges of the given triangle to the split mesh currently being assembled, if possible. The + * respective edges are removed from the border, and if the crease angle at this additional connection is exceeded, + * the vertices at this link are duplicated. + * + * @param triangle + * The triangle being connected to the split mesh + * @param i + * The index of the edge in the triangle that is already connected to the split mesh + */ + private void connectEdge(final Triangle triangle, final int i) { + final Edge edge = triangle.edges[i]; + final ListIterator<Edge> edgeIt = _edges.listIterator(); + boolean found = false; + while (!found && edgeIt.hasNext()) { + final Edge borderEdge = edgeIt.next(); + if (borderEdge.isConnectedTo(edge)) { + // Connected => remove the connected edges from the + // border + found = true; + edgeIt.remove(); + _edges.remove(edge); + if (!checkAngle(triangle, borderEdge.parent)) { + // Crease angle exceeded => duplicate the vertices, colors + // and texCoords + duplicateValues(edge.i0); + edge.newI0 = _destVerts.size() - 1; + triangle.edges[(i + 2) % 3].newI1 = edge.newI0; + duplicateValues(edge.i1); + edge.newI1 = _destVerts.size() - 1; + triangle.edges[(i + 1) % 3].newI0 = edge.newI1; + } else { + // Crease angle okay => share duplicate vertices, if + // any + if (borderEdge.newI0 > -1) { + edge.newI1 = borderEdge.newI0; + triangle.edges[(i + 1) % 3].newI0 = borderEdge.newI0; + } + if (borderEdge.newI1 > -1) { + edge.newI0 = borderEdge.newI1; + triangle.edges[(i + 2) % 3].newI1 = borderEdge.newI1; + } + } + } + } + } + + /** + * Checks if the transition between the tqo given triangles should be smooth, according to the creaseAngle. + * + * @param tri1 + * The first triangle + * @param tri2 + * The second triangle + * @return <code>true</code>, if the angle between the two triangles is less than or equal to the creaseAngle; + * otherwise <code>false</code> + */ + private boolean checkAngle(final Triangle tri1, final Triangle tri2) { + return (tri1.normal.smallestAngleBetween(tri2.normal) <= _creaseAngle + MathUtils.ZERO_TOLERANCE); + } + + /** + * Copies the vertex, color and texCoord at the given index in each of the source lists (if not null) and adds it to + * the end of the list. + * + * @param index + * The index to copy the value in each list from + */ + private void duplicateValues(final int index) { + _destVerts.add(_destVerts.get(index)); + if (_destColors != null) { + _destColors.add(_destColors.get(index)); + } + if (_destTexCoords != null) { + _destTexCoords.add(_destTexCoords.get(index)); + } + } + + /** + * Fills the borderIndices array with the all indices contained in the first set of border edges stored in + * splitMeshBorders. All values not set in the process are set to -1. + */ + private void fillBorderIndices() { + Arrays.fill(_borderIndices, false); + final LinkedList<Edge> edges0 = _splitMeshBorders.getFirst(); + for (final Edge edge : edges0) { + _borderIndices[edge.i0] = true; + _borderIndices[edge.i1] = true; + } + } + + /** + * Finds all vertices that are used by several split meshes and copies them (including updating the indices in the + * corresponding triangles). + */ + private void duplicateCreaseVertices() { + if (_splitMeshBorders.size() < 2) { + return; + } + + final int[] replacementIndices = new int[_sourceVerts.length]; + + // Check the borders of all split meshes, starting with the second one + final ListIterator<LinkedList<Edge>> borderIt = _splitMeshBorders.listIterator(); + borderIt.next(); + final ListIterator<LinkedList<Triangle>> meshIt = _splitMeshes.listIterator(); + meshIt.next(); + while (borderIt.hasNext()) { + Arrays.fill(replacementIndices, -1); + final LinkedList<Edge> edges0 = borderIt.next(); // Border of the split + final LinkedList<Triangle> destTris0 = meshIt.next(); // Triangles of the + // split + + // Check every border edge of the split mesh. If its indices are + // already set in borderIndices, the corresponding vertices are + // already used by another split and have to be duplicated + final ListIterator<Edge> edgeIt = edges0.listIterator(); + while (edgeIt.hasNext()) { + final Edge edge = edgeIt.next(); + + if (edge.newI0 == -1) { + if (_borderIndices[edge.i0]) { + if (replacementIndices[edge.i0] == -1) { + duplicateValues(edge.i0); + replacementIndices[edge.i0] = _destVerts.size() - 1; + } + } else { + replacementIndices[edge.i0] = edge.i0; + } + } + + if (edge.newI1 == -1) { + if (_borderIndices[edge.i1]) { + if (replacementIndices[edge.i1] == -1) { + duplicateValues(edge.i1); + replacementIndices[edge.i1] = _destVerts.size() - 1; + } + } else { + replacementIndices[edge.i1] = edge.i1; + } + } + } + + // Replace all indices in the split mesh whose vertices have been + // duplicated + for (int i = 0; i < _borderIndices.length; i++) { + if (_borderIndices[i]) { + for (final Triangle tri : destTris0) { + replaceIndex(tri, i, replacementIndices[i]); + } + } else if (replacementIndices[i] > -1) { + _borderIndices[i] = true; + } + } + } + } + + /** + * If the triangle contains the given index, it is replaced with the replacement index, unless it is already + * overridden with a newIndex (newI0, newI1). + * + * @param tri + * The triangle + * @param index + * The index to replace + * @param replacement + * The replacement index + */ + private void replaceIndex(final Triangle tri, final int index, final int replacement) { + for (int i = 0; i < 3; i++) { + final Edge edge = tri.edges[i]; + if (edge.newI0 == -1 && edge.i0 == index) { + edge.newI0 = replacement; + } + if (edge.newI1 == -1 && edge.i1 == index) { + edge.newI1 = replacement; + } + } + } + + /** + * Sets up the normals and indices for all split meshes. + */ + private void computeNormalsAndIndices() { + + // First, sum up the normals of the adjacent triangles for each vertex. + // Store the triangle indices in the process. + int count = 0; + for (final LinkedList<Triangle> tris : _splitMeshes) { + for (final Triangle tri : tris) { + for (int i = 0; i < 3; i++) { + if (tri.edges[i].newI0 > -1) { + _splitNormals[tri.edges[i].newI0].addLocal(tri.normal); + _splitIndices[count++] = tri.edges[i].newI0; + } else { + _splitNormals[tri.edges[i].i0].addLocal(tri.normal); + _splitIndices[count++] = tri.edges[i].i0; + } + + } + } + } + + // Normalize all normals + for (int i = 0; i < _splitNormals.length; i++) { + if (_splitNormals[i].distanceSquared(Vector3.ZERO) > MathUtils.ZERO_TOLERANCE) { + _splitNormals[i].normalizeLocal(); + } + } + } + + /** + * Clears and nulls all used arrays and lists, so the garbage collector can clean them up. + */ + private void cleanup() { + _creaseAngle = 0; + Arrays.fill(_sourceVerts, null); + _sourceVerts = null; + if (_sourceColors != null) { + Arrays.fill(_sourceColors, null); + _sourceColors = null; + } + if (_sourceTexCoords != null) { + Arrays.fill(_sourceTexCoords, null); + _sourceTexCoords = null; + } + _sourceInds = null; + + if (_triangles != null) { + _triangles.clear(); + _triangles = null; + } + if (_destVerts != null) { + _destVerts.clear(); + _destVerts = null; + } + if (_destColors != null) { + _destColors.clear(); + _destColors = null; + } + if (_destTexCoords != null) { + _destTexCoords.clear(); + _destTexCoords = null; + } + if (_destTris != null) { + _destTris.clear(); + _destTris = null; + } + if (_edges != null) { + _edges.clear(); + _edges = null; + } + + if (_splitMeshes != null) { + for (final LinkedList<Triangle> tris : _splitMeshes) { + tris.clear(); + } + _splitMeshes.clear(); + _splitMeshes = null; + } + if (_splitMeshBorders != null) { + for (final LinkedList<Edge> edges : _splitMeshBorders) { + edges.clear(); + } + _splitMeshBorders.clear(); + _splitMeshBorders = null; + } + + _splitVerts = null; + _splitNormals = null; + _splitColors = null; + _splitTexCoords = null; + _splitIndices = null; + _borderIndices = null; + } + + /** + * A helper class for the normal generator. Stores one triangle, consisting of 3 edges, and the normal for the + * triangle. + * + * @author M. Sattler + */ + private class Triangle { + + public Edge[] edges = new Edge[3]; + + public Vector3 normal = new Vector3(0, 0, 0); + + public Triangle() {} + + /** + * Creates the triangle. + * + * @param i0 + * The index of vertex 0 in the triangle + * @param i1 + * The index of vertex 1 in the triangle + * @param i2 + * The index of vertex 2 in the triangle + */ + public Triangle(final int i0, final int i1, final int i2) { + edges[0] = new Edge(this, i0, i1); + edges[1] = new Edge(this, i1, i2); + edges[2] = new Edge(this, i2, i0); + } + + /** + * Computes the normal from the three vertices in the given array that are indexed by the edges. + * + * @param verts + * The array containing the vertices + */ + public void computeNormal(final Vector3[] verts) { + final int i0 = edges[0].i0; + final int i1 = edges[1].i0; + final int i2 = edges[2].i0; + verts[i2].subtract(verts[i1], _compVect0); + verts[i0].subtract(verts[i1], _compVect1); + normal.set(_compVect0.crossLocal(_compVect1)).normalizeLocal(); + } + + /** + * @param edge + * An Edge to get the index of + * @return The index of the edge in the triangle, or -1, if it is not contained in the triangle + */ + public int indexOf(final Edge edge) { + for (int i = 0; i < 3; i++) { + if (edges[i] == edge) { + return i; + } + } + return -1; + } + + @Override + public String toString() { + final StringBuilder result = new StringBuilder("Triangle ("); + for (int i = 0; i < 3; i++) { + final Edge edge = edges[i]; + if (edge == null) { + result.append("?"); + } else { + if (edge.newI0 > -1) { + result.append(edge.newI0); + } else { + result.append(edge.i0); + } + } + if (i < 2) { + result.append(", "); + } + } + result.append(")"); + return result.toString(); + } + } + + /** + * Another helper class for the normal generator. Stores one edge in the mesh, consisting of two vertex indices, the + * triangle the edge belongs to, and, if applicable, another triangle the edge is connected to. + * + * @author M. Sattler + */ + private class Edge { + + // The indices of the vertices in the mesh + public int i0; + public int i1; + + // The indices of duplicated vertices, if > -1 + public int newI0 = -1; + public int newI1 = -1; + + // The Triangle containing this Edge + public Triangle parent; + + // A Triangle this Edge is connected to, or null, if it is not connected + public Triangle connected; + + public Edge() {} + + /** + * Creates this edge. + * + * @param parent + * The Triangle containing this Edge + * @param i0 + * The index of the first vertex of this edge + * @param i1 + * The index of the second vertex of this edge + */ + public Edge(final Triangle parent, final int i0, final int i1) { + this.parent = parent; + this.i0 = i0; + this.i1 = i1; + } + + /** + * Checks if this edge is connected to another one. + * + * @param other + * The other edge + * @return <code>true</code>, if the indices in this edge and the other one are identical, but in inverse order + */ + public boolean isConnectedTo(final Edge other) { + return (i0 == other.i1 && i1 == other.i0); + } + + @Override + public String toString() { + String result = "Edge ("; + if (newI0 > -1) { + result += newI0; + } else { + result += i0; + } + result += ", "; + if (newI1 > -1) { + result += newI1; + } else { + result += i1; + } + result += ")"; + return result; + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java new file mode 100644 index 0000000..cb018e6 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Spatial; + +@Deprecated +public abstract class SceneCopier { + + public static Spatial makeCopy(final Spatial source, final CopyLogic logic) { + return makeCopy(source, null, logic); + } + + private static Spatial makeCopy(final Spatial source, final Spatial parent, final CopyLogic logic) { + final AtomicBoolean recurse = new AtomicBoolean(); + final Spatial result = logic.copy(source, recurse); + if (recurse.get() && source instanceof Node && result instanceof Node + && ((Node) source).getNumberOfChildren() > 0) { + for (final Spatial child : ((Node) source).getChildren()) { + final Spatial copy = makeCopy(child, result, logic); + if (copy != null) { + ((Node) result).attachChild(copy); + } + } + } + return result; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java new file mode 100644 index 0000000..200ad05 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java @@ -0,0 +1,96 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * @deprecated use {@link Spatial#makeCopy(boolean)} instead.
+ */
+@Deprecated
+public class SharedCopyLogic implements CopyLogic {
+ private static final Logger logger = Logger.getLogger(SharedCopyLogic.class.getName());
+
+ public Spatial copy(final Spatial source, final AtomicBoolean recurse) {
+ recurse.set(false);
+ if (source instanceof Node) {
+ recurse.set(true);
+ return clone((Node) source);
+ } else if (source instanceof Mesh) {
+ final Mesh result = clone((Mesh) source);
+ result.setMeshData(((Mesh) source).getMeshData());
+ result.updateModelBound();
+ return result;
+ }
+ return null;
+ }
+
+ protected Mesh clone(final Mesh original) {
+ Mesh copy = null;
+ try {
+ copy = original.getClass().newInstance();
+ } catch (final InstantiationException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ }
+ copy.setName(original.getName() + "_copy");
+ copy.getSceneHints().set(original.getSceneHints());
+ copy.setTransform(original.getTransform());
+ copy.setDefaultColor(original.getDefaultColor());
+
+ for (final StateType type : StateType.values()) {
+ final RenderState state = original.getLocalRenderState(type);
+ if (state != null) {
+ copy.setRenderState(state);
+ }
+ }
+ return copy;
+ }
+
+ protected Node clone(final Node original) {
+ Node copy = null;
+ try {
+ copy = original.getClass().newInstance();
+ } catch (final InstantiationException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ }
+ copy.setName(original.getName() + "_copy");
+ copy.getSceneHints().set(original.getSceneHints());
+ copy.setTransform(original.getTransform());
+
+ for (final StateType type : StateType.values()) {
+ final RenderState state = original.getLocalRenderState(type);
+ if (state != null) {
+ copy.setRenderState(state);
+ }
+ }
+ return copy;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java new file mode 100644 index 0000000..afa63c8 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.MeshData; + +public class TangentUtil { + public static FloatBuffer generateTangentBuffer(final MeshData meshData) { + return generateTangentBuffer(meshData, 0); + } + + public static FloatBuffer generateTangentBuffer(final MeshData meshData, final int uvUnit) { + final FloatBuffer vertexBuffer = meshData.getVertexBuffer(); + if (vertexBuffer == null) { + throw new IllegalArgumentException("Vertex buffer is null!"); + } + + final FloatBuffer normalBuffer = meshData.getNormalBuffer(); + if (normalBuffer == null) { + throw new IllegalArgumentException("Normal buffer is null!"); + } + + FloatBuffer textureBuffer = meshData.getTextureBuffer(uvUnit); + if (textureBuffer == null && uvUnit != 0) { + textureBuffer = meshData.getTextureBuffer(0); + } + if (textureBuffer == null) { + throw new IllegalArgumentException("Texture buffer is null!"); + } + + final IndexBufferData<?> indexBuffer = meshData.getIndices(); + if (indexBuffer == null) { + throw new IllegalArgumentException("Index buffer is null!"); + } + + final int vertexCount = meshData.getVertexCount(); + final int triangleCount = meshData.getTotalPrimitiveCount(); + + final Vector3[] tan1 = new Vector3[vertexCount]; + final Vector3[] tan2 = new Vector3[vertexCount]; + for (int i = 0; i < vertexCount; i++) { + tan1[i] = new Vector3(); + tan2[i] = new Vector3(); + } + + final Vector3[] vertex = BufferUtils.getVector3Array(vertexBuffer); + final Vector3[] normal = BufferUtils.getVector3Array(normalBuffer); + final Vector2[] texcoord = BufferUtils.getVector2Array(textureBuffer); + + for (int a = 0; a < triangleCount; a++) { + final int i1 = indexBuffer.get(a * 3); + final int i2 = indexBuffer.get(a * 3 + 1); + final int i3 = indexBuffer.get(a * 3 + 2); + + final Vector3 v1 = vertex[i1]; + final Vector3 v2 = vertex[i2]; + final Vector3 v3 = vertex[i3]; + + final Vector2 w1 = texcoord[i1]; + final Vector2 w2 = texcoord[i2]; + final Vector2 w3 = texcoord[i3]; + + final float x1 = v2.getXf() - v1.getXf(); + final float x2 = v3.getXf() - v1.getXf(); + final float y1 = v2.getYf() - v1.getYf(); + final float y2 = v3.getYf() - v1.getYf(); + final float z1 = v2.getZf() - v1.getZf(); + final float z2 = v3.getZf() - v1.getZf(); + + final float s1 = w2.getXf() - w1.getXf(); + final float s2 = w3.getXf() - w1.getXf(); + final float t1 = w2.getYf() - w1.getYf(); + final float t2 = w3.getYf() - w1.getYf(); + + final float r = 1.0F / (s1 * t2 - s2 * t1); + if (Float.isNaN(r) || Float.isInfinite(r)) { + continue; + } + final Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); + final Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); + + tan1[i1].addLocal(sdir); + tan1[i2].addLocal(sdir); + tan1[i3].addLocal(sdir); + + tan2[i1].addLocal(tdir); + tan2[i2].addLocal(tdir); + tan2[i3].addLocal(tdir); + } + + final FloatBuffer tangentBuffer = BufferUtils.createVector4Buffer(vertexCount); + + final Vector3 calc1 = new Vector3(); + final Vector3 calc2 = new Vector3(); + for (int a = 0; a < vertexCount; a++) { + final Vector3 n = normal[a]; + final Vector3 t = tan1[a]; + + // Gram-Schmidt orthogonalize + double dot = n.dot(t); + calc1.set(t).subtractLocal(n.multiply(dot, calc2)).normalizeLocal(); + tangentBuffer.put(calc1.getXf()).put(calc1.getYf()).put(calc1.getZf()); + + // Calculate handedness + dot = calc1.set(n).crossLocal(t).dot(tan2[a]); + final float w = dot < 0.0f ? -1.0f : 1.0f; + tangentBuffer.put(w); + } + + return tangentBuffer; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java new file mode 100644 index 0000000..91bc14a --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.util.EnumSet; +import java.util.Map; + +import com.ardor3d.util.geom.GeometryTool.MatchCondition; +import com.google.common.collect.Maps; + +public class VertGroupData { + + public static final int DEFAULT_GROUP = 0; + + private final Map<Long, EnumSet<MatchCondition>> _groupConditions = Maps.newHashMap(); + private long[] _vertGroups = null; + + public VertGroupData() {} + + public void setGroupConditions(final long groupNumber, final EnumSet<MatchCondition> conditions) { + _groupConditions.put(groupNumber, conditions); + } + + public EnumSet<MatchCondition> getGroupConditions(final long groupNumber) { + return _groupConditions.get(groupNumber); + } + + public long getGroupForVertex(final int index) { + if (_vertGroups != null) { + return _vertGroups[index]; + } + return DEFAULT_GROUP; + } + + public void setVertGroups(final long[] vertGroupMap) { + _vertGroups = vertGroupMap; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java new file mode 100644 index 0000000..2d0621b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.util.EnumSet; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.util.geom.GeometryTool.MatchCondition; + +public class VertKey { + + private final Vector3 _vert; + private final Vector3 _norm; + private final ColorRGBA _color; + private final Vector2[] _texs; + private final long _smoothGroup; + private final EnumSet<MatchCondition> _options; + private int _hashCode = 0; + + public VertKey(final Vector3 vert, final Vector3 norm, final ColorRGBA color, final Vector2[] texs, + final EnumSet<MatchCondition> options) { + this(vert, norm, color, texs, options, 0); + } + + public VertKey(final Vector3 vert, final Vector3 norm, final ColorRGBA color, final Vector2[] texs, + final EnumSet<MatchCondition> options, final long smoothGroup) { + _vert = vert; + _options = options != null ? options : EnumSet.noneOf(MatchCondition.class); + _norm = (_options.contains(MatchCondition.Normal)) ? norm : null; + _color = (_options.contains(MatchCondition.Color)) ? color : null; + _texs = (_options.contains(MatchCondition.UVs)) ? texs : null; + _smoothGroup = (_options.contains(MatchCondition.Group)) ? smoothGroup : 0; + } + + @Override + public int hashCode() { + if (_hashCode != 0) { + return _hashCode; + } + _hashCode = _vert.hashCode(); + if (_options.contains(MatchCondition.Normal) && _norm != null) { + final long x = Double.doubleToLongBits(_norm.getX()); + _hashCode += 31 * _hashCode + (int) (x ^ (x >>> 32)); + + final long y = Double.doubleToLongBits(_norm.getY()); + _hashCode += 31 * _hashCode + (int) (y ^ (y >>> 32)); + + final long z = Double.doubleToLongBits(_norm.getZ()); + _hashCode += 31 * _hashCode + (int) (z ^ (z >>> 32)); + } + if (_options.contains(MatchCondition.Color) && _color != null) { + final int r = Float.floatToIntBits(_color.getRed()); + _hashCode += 31 * _hashCode + r; + + final int g = Float.floatToIntBits(_color.getGreen()); + _hashCode += 31 * _hashCode + g; + + final int b = Float.floatToIntBits(_color.getBlue()); + _hashCode += 31 * _hashCode + b; + + final int a = Float.floatToIntBits(_color.getAlpha()); + _hashCode += 31 * _hashCode + a; + } + if (_options.contains(MatchCondition.UVs) && _texs != null) { + for (int i = 0; i < _texs.length; i++) { + if (_texs[i] != null) { + final long x = Double.doubleToLongBits(_texs[i].getX()); + _hashCode += 31 * _hashCode + (int) (x ^ (x >>> 32)); + + final long y = Double.doubleToLongBits(_texs[i].getY()); + _hashCode += 31 * _hashCode + (int) (y ^ (y >>> 32)); + } + } + } + if (_options.contains(MatchCondition.Group)) { + _hashCode += 31 * _hashCode + _smoothGroup; + } + return _hashCode; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof VertKey)) { + return false; + } + + final VertKey other = (VertKey) obj; + + if (other._options != _options) { + return false; + } + if (!other._vert.equals(_vert)) { + return false; + } + + if (_options.contains(MatchCondition.Normal)) { + if (_norm != null) { + if (!_norm.equals(other._norm)) { + return false; + } + } else if (other._norm != null) { + return false; + } + } + + if (_options.contains(MatchCondition.Color)) { + if (_color != null) { + if (!_color.equals(other._color)) { + return false; + } + } else if (other._color != null) { + return false; + } + } + + if (_options.contains(MatchCondition.UVs)) { + if (_texs != null) { + if (other._texs == null || other._texs.length != _texs.length) { + return false; + } + for (int x = 0; x < _texs.length; x++) { + if (_texs[x] != null) { + if (!_texs[x].equals(other._texs[x])) { + return false; + } + } else if (other._texs[x] != null) { + return false; + } + } + } else if (other._texs != null) { + return false; + } + } + + if (_options.contains(MatchCondition.Group)) { + if (other._smoothGroup != _smoothGroup) { + return false; + } + } + + return true; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java new file mode 100644 index 0000000..bb33c38 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.geom; + +import java.util.Map; + +import com.ardor3d.scenegraph.Mesh; + +public class VertMap { + + private int[] _lookupTable; + + public VertMap(final Mesh mesh) { + setupTable(mesh); + } + + private void setupTable(final Mesh mesh) { + _lookupTable = new int[mesh.getMeshData().getVertexCount()]; + for (int x = 0; x < _lookupTable.length; x++) { + _lookupTable[x] = x; + } + } + + public int getNewIndex(final int oldIndex) { + return _lookupTable[oldIndex]; + } + + public int getFirstOldIndex(final int newIndex) { + for (int i = 0; i < _lookupTable.length; i++) { + if (_lookupTable[i] == newIndex) { + return i; + } + } + return -1; + } + + public void applyRemapping(final Map<Integer, Integer> indexRemap) { + for (int i = 0; i < _lookupTable.length; i++) { + if (indexRemap.containsKey(_lookupTable[i])) { + _lookupTable[i] = indexRemap.get(_lookupTable[i]); + } + } + } + + public int[] getLookupTable() { + return _lookupTable; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java new file mode 100644 index 0000000..7ed7ce2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; + +/** + * This class extends the behavior of the {@link SimpleResourceLocator} by replacing the resource's file extension with + * different various provided extensions. If none of these work, it will try the original resource name as-is. You can + * choose to have the original file searched for first, or last using {@link #setTrySpecifiedFormatFirst(boolean)}. + */ +public class MultiFormatResourceLocator extends SimpleResourceLocator { + + private final String[] _extensions; + private boolean _trySpecifiedFormatFirst = false; + + /** + * Construct a new MultiFormatResourceLocator using the given URI as our context and the list of possible extensions + * as extensions to try during file search. + * + * @param baseDir + * our base context. This is meant to be a "directory" wherein we will search for resources. Therefore, + * if it does not end in /, a / will be added to ensure we are talking about children of the given + * baseDir. + * @param extensions + * an array of extensions (eg. ".png", ".dds", ".tga", etc.) to try while searching for a resource with + * this locator. This is done by replacing any existing extension in the resource name with each of the + * given extensions. + * @throws URISyntaxException + * if the given URI does not end in / and we can not make a new URI with a trailing / from it. + */ + public MultiFormatResourceLocator(final URI baseDir, final String... extensions) throws URISyntaxException { + super(baseDir); + + if (extensions == null) { + throw new NullPointerException("extensions can not be null."); + } + _extensions = extensions; + } + + /** + * Construct a new MultiFormatResourceLocator using the given URL as our context and the list of possible extensions + * as extensions to try during file search. + * + * @param baseDir + * our base context. This is converted to a URI. This is meant to be a "directory" wherein we will search + * for resources. Therefore, if it does not end in /, a / will be added to ensure we are talking about + * children of the given baseDir. + * @param extensions + * an array of extensions (eg. ".png", ".dds", ".tga", etc.) to try while searching for a resource with + * this locator. This is done by replacing any existing extension in the resource name with each of the + * given extensions. + * @throws URISyntaxException + * if this URL can not be converted to a URI, or if the converted URI does not end in / and we can not + * make a new URI with a trailing / from it. + */ + public MultiFormatResourceLocator(final URL baseDir, final String... extensions) throws URISyntaxException { + this(baseDir.toURI(), extensions); + } + + @Override + public ResourceSource locateResource(String resourceName) { + resourceName = cleanup(resourceName); + + if (_trySpecifiedFormatFirst) { + final ResourceSource src = doRecursiveLocate(resourceName); + if (src != null) { + return src; + } + } + + final String baseFileName = getBaseFileName(resourceName); + for (final String extension : _extensions) { + final ResourceSource src = doRecursiveLocate(baseFileName + extension); + if (src != null) { + return src; + } + } + + if (!_trySpecifiedFormatFirst) { + // If all else fails, just try the original name. + return doRecursiveLocate(resourceName); + } else { + return null; + } + } + + private String getBaseFileName(final String resourceName) { + final File f = new File(resourceName); + final String name = f.getPath(); + final int dot = name.lastIndexOf('.'); + if (dot < 0) { + return name; + } else { + return name.substring(0, dot); + } + } + + public boolean isTrySpecifiedFormatFirst() { + return _trySpecifiedFormatFirst; + } + + public void setTrySpecifiedFormatFirst(final boolean trySpecifiedFormatFirst) { + _trySpecifiedFormatFirst = trySpecifiedFormatFirst; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof MultiFormatResourceLocator) { + return getBaseDir().equals(((MultiFormatResourceLocator) obj).getBaseDir()) + && Arrays.equals(_extensions, ((MultiFormatResourceLocator) obj)._extensions); + } + return super.equals(obj); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java new file mode 100644 index 0000000..5f959f9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +/** + * This locator takes a base resource and tries to find other resources that are "relative" to it. What relative means + * may be up to the type of ResourceSource given. + */ +public class RelativeResourceLocator implements ResourceLocator { + + private final ResourceSource _baseSource; + + /** + * Construct a new RelativeResourceLocator using the given source as our base. + * + * @param resource + * our base source. + */ + public RelativeResourceLocator(final ResourceSource resource) { + assert (resource != null) : "source may not be null."; + + _baseSource = resource; + } + + public ResourceSource getBaseSource() { + return _baseSource; + } + + public ResourceSource locateResource(String resourceName) { + // Trim off any prepended local dir. + while (resourceName.startsWith("./") && resourceName.length() > 2) { + resourceName = resourceName.substring(2); + } + while (resourceName.startsWith(".\\") && resourceName.length() > 2) { + resourceName = resourceName.substring(2); + } + + return _baseSource.getRelativeSource(resourceName); + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof RelativeResourceLocator) { + return _baseSource.equals(((RelativeResourceLocator) obj)._baseSource); + } + return false; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java new file mode 100644 index 0000000..8353527 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +/** + * Interface for locating resources from resource names. + */ +public interface ResourceLocator { + + /** + * Locates a resource according to the strategy of the resource locator implementation (subclass). + * + * @param resourceName + * the name of the resource to locate; it this is a path it must be slash separated (no backslashes) + * @see SimpleResourceLocator + * @see MultiFormatResourceLocator + * @return a source for the resource, null if the resource was not found + */ + public ResourceSource locateResource(String resourceName); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java new file mode 100644 index 0000000..2fdc2cc --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.common.collect.Sets; + +/** + * Manager class for locator utility classes used to find various assets. (XXX: Needs more documentation) + */ +public class ResourceLocatorTool { + private static final Logger logger = Logger.getLogger(ResourceLocatorTool.class.getName()); + + public static final String TYPE_TEXTURE = "texture"; + public static final String TYPE_MODEL = "model"; + public static final String TYPE_PARTICLE = "particle"; + public static final String TYPE_AUDIO = "audio"; + public static final String TYPE_SHADER = "shader"; + + private static final Map<String, List<ResourceLocator>> _locatorMap = new HashMap<String, List<ResourceLocator>>(); + + public static ResourceSource locateResource(final String resourceType, String resourceName) { + if (resourceName == null) { + return null; + } + + try { + resourceName = URLDecoder.decode(resourceName, "UTF-8"); + } catch (final UnsupportedEncodingException ex) { + ex.printStackTrace(); + } + + synchronized (_locatorMap) { + final List<ResourceLocator> bases = _locatorMap.get(resourceType); + if (bases != null) { + for (int i = bases.size(); --i >= 0;) { + final ResourceLocator loc = bases.get(i); + final ResourceSource rVal = loc.locateResource(resourceName); + if (rVal != null) { + return rVal; + } + } + } + // last resort... + try { + final URL u = ResourceLocatorTool.getClassPathResource(ResourceLocatorTool.class, resourceName); + if (u != null) { + return new URLResourceSource(u); + } + } catch (final Exception e) { + logger.logp(Level.WARNING, ResourceLocatorTool.class.getName(), "locateResource(String, String)", e + .getMessage(), e); + } + + logger.warning("Unable to locate: " + resourceName); + return null; + } + } + + public static void addResourceLocator(final String resourceType, final ResourceLocator locator) { + if (locator == null) { + return; + } + synchronized (_locatorMap) { + List<ResourceLocator> bases = _locatorMap.get(resourceType); + if (bases == null) { + bases = new ArrayList<ResourceLocator>(); + _locatorMap.put(resourceType, bases); + } + + if (!bases.contains(locator)) { + bases.add(locator); + } + } + } + + public static boolean removeResourceLocator(final String resourceType, final ResourceLocator locator) { + synchronized (_locatorMap) { + final List<ResourceLocator> bases = _locatorMap.get(resourceType); + if (bases == null) { + return false; + } + return bases.remove(locator); + } + } + + /** + * Locate a resource using various classloaders. + * + * <ul> + * <li>First it tries the Thread.currentThread().getContextClassLoader().</li> + * <li>Then it tries the ClassLoader.getSystemClassLoader() (if not same as context class loader).</li> + * <li>Finally it tries the clazz.getClassLoader()</li> + * </ul> + * + * @param clazz + * a class to use as a local reference. + * @param name + * the name and path of the resource. + * @return the URL of the resource, or null if none found. + */ + public static URL getClassPathResource(final Class<?> clazz, final String name) { + URL result = Thread.currentThread().getContextClassLoader().getResource(name); + if (result == null + && !Thread.currentThread().getContextClassLoader().equals(ClassLoader.getSystemClassLoader())) { + result = ClassLoader.getSystemResource(name); + } + if (result == null) { + result = clazz.getClassLoader().getResource(name); + } + return result; + } + + /** + * Locate a resource using various classloaders and open a stream to it. + * + * @param clazz + * a class to use as a local reference. + * @param name + * the name and path of the resource. + * @return the input stream if resource is found, or null if not. + */ + public static InputStream getClassPathResourceAsStream(final Class<?> clazz, final String name) { + InputStream result = Thread.currentThread().getContextClassLoader().getResourceAsStream(name); + if (result == null + && !Thread.currentThread().getContextClassLoader().equals(ClassLoader.getSystemClassLoader())) { + result = ClassLoader.getSystemResourceAsStream(name); + } + if (result == null) { + result = clazz.getClassLoader().getResourceAsStream(name); + } + return result; + } + + /** + * Locate all instances of a resource using various classloaders. + * + * @param clazz + * a class to use as a local reference. + * @param name + * the name and path of the resource. + * @return a set containing the located URLs of the named resource. + */ + public static Set<URL> getClassPathResources(final Class<?> clazz, final String name) { + final Set<URL> results = Sets.newHashSet(); + Enumeration<URL> urls = null; + try { + urls = Thread.currentThread().getContextClassLoader().getResources(name); + for (; urls.hasMoreElements();) { + results.add(urls.nextElement()); + } + } catch (final IOException ioe) { + } + if (!Thread.currentThread().getContextClassLoader().equals(ClassLoader.getSystemClassLoader())) { + try { + urls = ClassLoader.getSystemResources(name); + for (; urls.hasMoreElements();) { + results.add(urls.nextElement()); + } + } catch (final IOException ioe) { + } + } + try { + urls = clazz.getClassLoader().getResources(name); + for (; urls.hasMoreElements();) { + results.add(urls.nextElement()); + } + } catch (final IOException ioe) { + } + return results; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java new file mode 100644 index 0000000..0e6f024 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +import java.io.IOException; +import java.io.InputStream; + +import com.ardor3d.util.export.Savable; + +/** + * Represents a named resource + */ +public interface ResourceSource extends Savable { + + public static final String UNKNOWN_TYPE = "-unknown-"; + + /** + * @return the name of this resource. + */ + String getName(); + + /** + * @return the "type" of resource we are pointing to. For example ".jpg", ".dae", etc. + */ + String getType(); + + /** + * Generate and return a new ResourceSource pointing to a named resource that is relative to this object's resource. + * + * @param name + * the name of the resource we want. eg. "./mypic.jpg" etc. + * @return the relative resource, or null if none is found. Will also return null if this ResourceSource type does + * not support relative source. + */ + ResourceSource getRelativeSource(String name); + + /** + * @return an InputStream to this resource's contents. + * @throws IOException + */ + InputStream openStream() throws IOException; + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java new file mode 100644 index 0000000..580f408 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; + +/** + * This locator takes a base location for finding resources specified with a relative path. If it cannot find the path + * relative to the location, it successively omits the starting components of the relative path until it can find a + * resources with such a trimmed path. If no resource is found with this method null is returned. + */ +public class SimpleResourceLocator implements ResourceLocator { + + private final URI _baseDir; + + /** + * Construct a new SimpleResourceLocator using the given URI as our context. + * + * @param baseDir + * our base context. This is meant to be a "directory" wherein we will search for resources. Therefore, + * if it does not end in /, a / will be added to ensure we are talking about children of the given + * baseDir. + * @throws NullPointerException + * if the given URI is null. + * @throws URISyntaxException + * if the given URI does not end in / and we can not make a new URI with a trailing / from it. + */ + public SimpleResourceLocator(final URI baseDir) throws URISyntaxException { + if (baseDir == null) { + throw new NullPointerException("baseDir can not be null."); + } + + final String uri = baseDir.toString(); + if (!uri.endsWith("/")) { + _baseDir = new URI(baseDir.toString() + "/"); + } else { + _baseDir = baseDir; + } + } + + /** + * Construct a new SimpleResourceLocator using the given URL as our context. + * + * @param baseDir + * our base context. This is converted to a URI. This is meant to be a "directory" wherein we will search + * for resources. Therefore, if it does not end in /, a / will be added to ensure we are talking about + * children of the given baseDir. + * @throws NullPointerException + * if the given URL is null. + * @throws URISyntaxException + * if this URL can not be converted to a URI, or if the converted URI does not end in / and we can not + * make a new URI with a trailing / from it. + */ + public SimpleResourceLocator(final URL baseDir) throws URISyntaxException { + this(baseDir.toURI()); + } + + public URI getBaseDir() { + return _baseDir; + } + + public ResourceSource locateResource(final String resourceName) { + return doRecursiveLocate(cleanup(resourceName)); + } + + protected ResourceSource doRecursiveLocate(String resourceName) { + // Trim off any prepended local dir. + while (resourceName.startsWith("./") && resourceName.length() > 2) { + resourceName = resourceName.substring(2); + } + while (resourceName.startsWith(".\\") && resourceName.length() > 2) { + resourceName = resourceName.substring(2); + } + + // Try to locate using resourceName as is. + try { + String spec = URLEncoder.encode(resourceName, "UTF-8"); + // this fixes a bug in JRE1.5 (file handler does not decode "+" to spaces) + spec = spec.replaceAll("\\+", "%20"); + + final URL rVal = new URL(_baseDir.toURL(), spec); + // open a stream to see if this is a valid resource + // XXX: Perhaps this is wasteful? Also, what info will determine validity? + rVal.openStream().close(); + return new URLResourceSource(rVal); + } catch (final IOException e) { + // URL wasn't valid in some way, so try up a path. + } catch (final IllegalArgumentException e) { + // URL wasn't valid in some way, so try up a path. + } + + resourceName = trimResourceName(resourceName); + if (resourceName == null) { + return null; + } else { + return doRecursiveLocate(resourceName); + } + } + + protected String trimResourceName(String resourceName) { + // it's possible this URL has back slashes, so replace them. + resourceName = cleanup(resourceName); + final int firstSlashIndex = resourceName.indexOf('/'); + if (firstSlashIndex >= 0 && firstSlashIndex < resourceName.length() - 1) { + return resourceName.substring(firstSlashIndex + 1); + } else { + return null; + } + } + + protected String cleanup(String name) { + // Replace any %2F (or %2f) with forward slashes + name = name.replaceAll("\\%2[F,f]", "/"); + // replace back slashes with forward + name = name.replace('\\', '/'); + return name; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof SimpleResourceLocator) { + return _baseDir.equals(((SimpleResourceLocator) obj)._baseDir); + } + return false; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java new file mode 100644 index 0000000..69a2339 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * ResourceSource that pulls its content from a String. This source type does not support relative sources. + */ +public class StringResourceSource implements ResourceSource { + + /** Our class logger */ + private static final Logger logger = Logger.getLogger(StringResourceSource.class.getName()); + + /** The data this source returns. */ + private String _data; + + /** An optional type value for the source. */ + private String _type; + + /** + * Construct a new StringResourceSource. + * + * @param data + * the data this source should return. + */ + public StringResourceSource(final String data) { + this(data, null); + } + + /** + * Construct a new StringResourceSource. + * + * @param data + * the data this source should return. + * @param type + * the type for this source. Usually a file extension such as .txt or .js. Required for generic loading + * when multiple resource handlers could be used. + */ + public StringResourceSource(final String data, final String type) { + _data = data; + _type = type; + } + + /** + * Returns "string resource" as strings have no name. + */ + public String getName() { + return "string resource"; + } + + /** + * Returns null and logs a warning as this is not supported. + */ + public ResourceSource getRelativeSource(final String name) { + if (logger.isLoggable(Level.WARNING)) { + logger.logp(Level.WARNING, getClass().getName(), "getRelativeSource(String)", + "StringResourceSource does not support this method."); + } + return null; + } + + public String getType() { + return _type; + } + + /** + * Grabs our data as a UTF8 byte array and returns it in a ByteArrayInputStream. + */ + public InputStream openStream() throws IOException { + return new ByteArrayInputStream(_data.getBytes("UTF8")); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public Class<?> getClassTag() { + return StringResourceSource.class; + } + + public void read(final InputCapsule capsule) throws IOException { + _data = capsule.readString("data", null); + _type = capsule.readString("type", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_data, "data", null); + capsule.write(_type, "type", null); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java new file mode 100644 index 0000000..9bcc3c0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.resource; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.util.UrlUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class URLResourceSource implements ResourceSource { + private static final Logger logger = Logger.getLogger(URLResourceSource.class.getName()); + + private URL _url; + private String _type; + + // used to make equals more efficient + private transient String _urlToString = null; + + /** + * Construct a new URLResourceSource. Must set URL separately. + */ + public URLResourceSource() {} + + /** + * Construct a new URLResourceSource from a specific URL. + * + * @param sourceUrl + * The url to load the resource from. Must not be null. If the URL has a valid URL filename (see + * {@link URL#getFile()}) and an extension (eg. http://url/myFile.png) then the extension (.png in this + * case) is used as the type. + */ + public URLResourceSource(final URL sourceUrl) { + assert (sourceUrl != null) : "sourceUrl must not be null"; + setURL(sourceUrl); + + // add type, if present + final String fileName = _url.getFile(); + if (fileName != null) { + final int dot = fileName.lastIndexOf('.'); + if (dot >= 0) { + _type = fileName.substring(dot); + } else { + _type = UNKNOWN_TYPE; + } + } + } + + /** + * Construct a new URLResourceSource from a specific URL and type. + * + * @param sourceUrl + * The url to load the resource from. Must not be null. + * @param type + * our type. Usually a file extension such as .png. Required for generic loading when multiple resource + * handlers could be used. + */ + public URLResourceSource(final URL sourceUrl, final String type) { + assert (sourceUrl != null) : "sourceUrl must not be null"; + setURL(sourceUrl); + + _type = type; + } + + public ResourceSource getRelativeSource(final String name) { + try { + final URL srcURL = UrlUtils.resolveRelativeURL(_url, "./" + name); + if (srcURL != null) { + // check if the URL can be opened + // just force it to try to grab info + srcURL.openStream().close(); + // Ok satisfied... return + return new URLResourceSource(srcURL); + + } + } catch (final MalformedURLException ex) { + } catch (final IOException ex) { + } + if (logger.isLoggable(Level.FINEST)) { + logger.logp(Level.FINEST, getClass().getName(), "getRelativeSource(String)", + "Unable to find relative file {0} from {1}.", new Object[] { name, _url }); + } + return null; + } + + public void setURL(final URL url) { + _url = url; + _urlToString = url != null ? url.toString() : null; + } + + public URL getURL() { + return _url; + } + + public String getName() { + return _urlToString; + } + + public String getType() { + return _type; + } + + public void setType(final String type) { + _type = type; + } + + public InputStream openStream() throws IOException { + return _url.openStream(); + } + + /** + * @return the string representation of this URLResourceSource. + */ + @Override + public String toString() { + return "URLResourceSource [url=" + _urlToString + ", type=" + _type + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((_type == null) ? 0 : _type.hashCode()); + result = prime * result + ((_urlToString == null) ? 0 : _urlToString.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof URLResourceSource)) { + return false; + } + final URLResourceSource other = (URLResourceSource) obj; + if (_type == null) { + if (other._type != null) { + return false; + } + } else if (!_type.equals(other._type)) { + return false; + } + if (_url == null) { + if (other._url != null) { + return false; + } + } else if (!_urlToString.equals(other._urlToString)) { + return false; + } + return true; + } + + public Class<?> getClassTag() { + return URLResourceSource.class; + } + + public void read(final InputCapsule capsule) throws IOException { + final String protocol = capsule.readString("protocol", null); + final String host = capsule.readString("host", null); + final String file = capsule.readString("file", null); + if (file != null) { + // see if we would like to divert this to a new location. + final ResourceSource src = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, + URLDecoder.decode(file, "UTF-8")); + if (src instanceof URLResourceSource) { + setURL(((URLResourceSource) src)._url); + _type = ((URLResourceSource) src)._type; + return; + } + } + + if (protocol != null && host != null && file != null) { + setURL(new URL(protocol, host, file)); + } + + _type = capsule.readString("type", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_url.getProtocol(), "protocol", null); + capsule.write(_url.getHost(), "host", null); + capsule.write(_url.getFile(), "file", null); + + capsule.write(_type, "type", null); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java new file mode 100644 index 0000000..3f9af0e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.scenegraph; + +public class CompileOptions { + + private boolean _displayList; + + public boolean isDisplayList() { + return _displayList; + } + + public void setDisplayList(final boolean displayList) { + _displayList = displayList; + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java new file mode 100644 index 0000000..b8f5680 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.scenegraph; + +import java.lang.ref.ReferenceQueue; +import java.util.Map; + +import com.ardor3d.renderer.ContextCleanListener; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.RendererCallable; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.GameTaskQueueManager; +import com.ardor3d.util.SimpleContextIdReference; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Multimap; + +public class DisplayListDelegate implements RenderDelegate { + + private static Map<DisplayListDelegate, Object> _identityCache = new MapMaker().weakKeys().makeMap(); + private static final Object STATIC_REF = new Object(); + + private static ReferenceQueue<DisplayListDelegate> _refQueue = new ReferenceQueue<DisplayListDelegate>(); + + static { + ContextManager.addContextCleanListener(new ContextCleanListener() { + public void cleanForContext(final RenderContext renderContext) { + // TODO: Need a way to call back to the creator of the display list? + } + }); + } + + private final SimpleContextIdReference<DisplayListDelegate> _id; + + public DisplayListDelegate(final int id, final Object glContext) { + _id = new SimpleContextIdReference<DisplayListDelegate>(this, _refQueue, id, glContext); + _identityCache.put(this, STATIC_REF); + } + + public void render(final Spatial spatial, final Renderer renderer) { + // do transforms + final boolean transformed = renderer.doTransforms(spatial.getWorldTransform()); + + // render display list. + renderer.renderDisplayList(_id.getId()); + + // Our states are in an unknown state at this point, so invalidate tracking. + ContextManager.getCurrentContext().invalidateStates(); + + // undo transforms + if (transformed) { + renderer.undoTransforms(spatial.getWorldTransform()); + } + } + + public static void cleanAllDisplayLists(final Renderer deleter) { + final Multimap<Object, Integer> idMap = ArrayListMultimap.create(); + + // gather up expired Display Lists... these don't exist in our cache + gatherGCdIds(idMap); + + // Walk through the cached items and delete those too. + for (final DisplayListDelegate buf : _identityCache.keySet()) { + // Add id to map + idMap.put(buf._id.getGlContext(), buf._id.getId()); + } + + handleDisplayListDelete(deleter, idMap); + } + + public static void cleanExpiredDisplayLists(final Renderer deleter) { + // gather up expired display lists... + final Multimap<Object, Integer> idMap = gatherGCdIds(null); + + if (idMap != null) { + // send to be deleted on next render. + handleDisplayListDelete(deleter, idMap); + } + } + + @SuppressWarnings("unchecked") + private static Multimap<Object, Integer> gatherGCdIds(Multimap<Object, Integer> store) { + // Pull all expired display lists from ref queue and add to an id multimap. + SimpleContextIdReference<DisplayListDelegate> ref; + while ((ref = (SimpleContextIdReference<DisplayListDelegate>) _refQueue.poll()) != null) { + if (store == null) { // lazy init + store = ArrayListMultimap.create(); + } + store.put(ref.getGlContext(), ref.getId()); + ref.clear(); + } + return store; + } + + private static void handleDisplayListDelete(final Renderer deleter, final Multimap<Object, Integer> idMap) { + Object currentGLRef = null; + // Grab the current context, if any. + if (deleter != null && ContextManager.getCurrentContext() != null) { + currentGLRef = ContextManager.getCurrentContext().getGlContextRep(); + } + // For each affected context... + for (final Object glref : idMap.keySet()) { + // If we have a deleter and the context is current, immediately delete + if (deleter != null && glref.equals(currentGLRef)) { + deleter.deleteDisplayLists(idMap.get(glref)); + } + // Otherwise, add a delete request to that context's render task queue. + else { + GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref)).render( + new RendererCallable<Void>() { + public Void call() throws Exception { + getRenderer().deleteDisplayLists(idMap.get(glref)); + return null; + } + }); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java new file mode 100644 index 0000000..3b43e29 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.scenegraph; + +import com.ardor3d.renderer.Renderer; +import com.ardor3d.scenegraph.Spatial; + +public interface RenderDelegate { + + void render(Spatial spatial, Renderer renderer); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java new file mode 100644 index 0000000..285806e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it under the terms of its license which may be found + * in the accompanying LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.scenegraph; + +import com.ardor3d.bounding.BoundingVolume; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.RenderContext; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.visitor.Visitor; + +public class SceneCompiler { + + public static void compile(final Spatial scene, final Renderer renderer, final CompileOptions options) { + // are we making a display list? + if (options.isDisplayList()) { + // grab our current context + final RenderContext context = ContextManager.getCurrentContext(); + + // handle camera... + // save the current camera... + final Camera originalCam = context.getCurrentCamera(); + // replace with a camera that will always pass frustum checks + final Camera yesCam = new Camera(originalCam) { + @Override + public FrustumIntersect contains(final BoundingVolume bound) { + return FrustumIntersect.Inside; + } + }; + context.setCurrentCamera(yesCam); + + // setup for display list... + // force all textures to load so their setup calls are not part of the displaylist + scene.acceptVisitor(new TextureApplyVisitor(renderer), true); + // invalidate any current opengl state information. + context.invalidateStates(); + // generate a DL id by starting our list + final int id = renderer.startDisplayList(); + // push our current buckets to back + renderer.getQueue().pushBuckets(); + + // render... + // render our spatial + scene.draw(renderer); + // process buckets and then pop them + renderer.renderBuckets(); + renderer.getQueue().popBuckets(); + + // end list + renderer.endDisplayList(); + + // restore old camera + context.setCurrentCamera(originalCam); + + // add a display list delegate to the given Spatial + scene.setRenderDelegate(new DisplayListDelegate(id, context.getGlContextRep()), context.getGlContextRep()); + } + } + + static class TextureApplyVisitor implements Visitor { + private final Renderer _renderer; + + public TextureApplyVisitor(final Renderer renderer) { + _renderer = renderer; + } + + public void visit(final Spatial spatial) { + if (spatial instanceof Mesh) { + final Mesh mesh = (Mesh) spatial; + final TextureState state = (TextureState) mesh.getWorldRenderState(StateType.Texture); + if (state != null) { + _renderer.applyState(state.getType(), state); + } + } + } + + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java new file mode 100644 index 0000000..2031798 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.screen; + +import java.nio.ByteBuffer; + +import com.ardor3d.image.ImageDataFormat; + +public interface ScreenExportable { + + /** + * Export the given image data (byte buffer) in a manner of our choosing. Note that this byte buffer should be + * treated by the implementing class as immutable and temporary. If you need access to it after returning from the + * method, make a copy. + * + * @param data + * the data from the screen. Please respect the data's limit() value. + * @param width + * @param height + */ + public void export(ByteBuffer data, int width, int height); + + /** + * + * @return the image data format we'd like to pull in. + */ + public ImageDataFormat getFormat(); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java new file mode 100644 index 0000000..cd88e96 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.screen; + +import java.nio.ByteBuffer; + +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.image.util.ImageUtils; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.util.geom.BufferUtils; + +public class ScreenExporter { + + static ByteBuffer _scratch = BufferUtils.createByteBuffer(1); + + public synchronized static void exportCurrentScreen(final Renderer renderer, final ScreenExportable exportable) { + final ImageDataFormat format = exportable.getFormat(); + final Camera camera = Camera.getCurrentCamera(); + final int width = camera.getWidth(), height = camera.getHeight(); + + // prepare our data buffer + final int size = width * height * ImageUtils.getPixelByteSize(format, PixelDataType.UnsignedByte); + if (_scratch.capacity() < size) { + _scratch = BufferUtils.createByteBuffer(size); + } else { + _scratch.limit(size); + _scratch.rewind(); + } + + // Ask the renderer for the current scene to be stored in the buffer + renderer.grabScreenContents(_scratch, format, 0, 0, width, height); + + // send the buffer to the exportable object for processing. + exportable.export(_scratch, width, height); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java new file mode 100644 index 0000000..a425753 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * An utily class to store shader's uniform variables content. + */ +public class ShaderVariable implements Savable { + /** Name of the uniform variable. * */ + public String name; + + /** ID of uniform. * */ + public int variableID = -1; + + /** Needs to be refreshed */ + public boolean needsRefresh = true; + + public boolean errorLogged = false; + + public boolean hasData() { + return true; + } + + public int getSize() { + return 1; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ShaderVariable)) { + return false; + } + final ShaderVariable comp = (ShaderVariable) o; + if (variableID != -1) { + return comp.variableID == variableID; + } else if (comp.variableID != -1) { + return comp.variableID == variableID; + } else { + return (name.equals(comp.name)); + } + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(name, "name", ""); + capsule.write(variableID, "variableID", -1); + } + + public void read(final InputCapsule capsule) throws IOException { + name = capsule.readString("name", ""); + variableID = capsule.readInt("variableID", -1); + } + + public Class<? extends ShaderVariable> getClassTag() { + return this.getClass(); + } +}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java new file mode 100644 index 0000000..ea380fa --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableFloat */ +public class ShaderVariableFloat extends ShaderVariable { + public float value1; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0.0f); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readFloat("value1", 0.0f); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java new file mode 100644 index 0000000..ff6f7b1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableFloat2 */ +public class ShaderVariableFloat2 extends ShaderVariable { + public float value1; + public float value2; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0.0f); + capsule.write(value2, "value2", 0.0f); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readFloat("value1", 0.0f); + value2 = capsule.readFloat("value2", 0.0f); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java new file mode 100644 index 0000000..076f5de --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableFloat3 */ +public class ShaderVariableFloat3 extends ShaderVariable { + public float value1; + public float value2; + public float value3; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0.0f); + capsule.write(value2, "value2", 0.0f); + capsule.write(value3, "value3", 0.0f); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readFloat("value1", 0.0f); + value2 = capsule.readFloat("value2", 0.0f); + value3 = capsule.readFloat("value3", 0.0f); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java new file mode 100644 index 0000000..2aef387 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableFloat4 */ +public class ShaderVariableFloat4 extends ShaderVariable { + public float value1; + public float value2; + public float value3; + public float value4; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0.0f); + capsule.write(value2, "value2", 0.0f); + capsule.write(value3, "value3", 0.0f); + capsule.write(value4, "value4", 0.0f); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readFloat("value1", 0.0f); + value2 = capsule.readFloat("value2", 0.0f); + value3 = capsule.readFloat("value3", 0.0f); + value4 = capsule.readFloat("value4", 0.0f); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java new file mode 100644 index 0000000..fbbd891 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java @@ -0,0 +1 @@ +/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package com.ardor3d.util.shader.uniformtypes;
import java.io.IOException;
import java.nio.FloatBuffer;
import com.ardor3d.util.export.InputCapsule;
import com.ardor3d.util.export.OutputCapsule;
import com.ardor3d.util.shader.ShaderVariable;
/** ShaderVariableFloatArray */
public class ShaderVariableFloatArray extends ShaderVariable {
public FloatBuffer value;
/**
* Specifies the number of values for each element of the array. Must be 1, 2, 3, or 4.
*/
public int size = 1;
@Override
public boolean hasData() {
return value != null;
}
@Override
public void write(final OutputCapsule capsule) throws IOException {
super.write(capsule);
capsule.write(value, "value", null);
capsule.write(size, "size", 1);
}
@Override
public void read(final InputCapsule capsule) throws IOException {
super.read(capsule);
value = capsule.readFloatBuffer("value", null);
size = capsule.readInt("size", 1);
}
}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java new file mode 100644 index 0000000..d3f09a9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableInt */ +public class ShaderVariableInt extends ShaderVariable { + public int value1; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readInt("value1", 0); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java new file mode 100644 index 0000000..fc9e4ca --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableInt2 */ +public class ShaderVariableInt2 extends ShaderVariable { + public int value1; + public int value2; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0); + capsule.write(value2, "value2", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readInt("value1", 0); + value2 = capsule.readInt("value2", 0); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java new file mode 100644 index 0000000..ac859cd --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableInt3 */ +public class ShaderVariableInt3 extends ShaderVariable { + public int value1; + public int value2; + public int value3; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0); + capsule.write(value2, "value2", 0); + capsule.write(value3, "value3", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readInt("value1", 0); + value2 = capsule.readInt("value2", 0); + value3 = capsule.readInt("value3", 0); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java new file mode 100644 index 0000000..52184bc --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableInt4 */ +public class ShaderVariableInt4 extends ShaderVariable { + public int value1; + public int value2; + public int value3; + public int value4; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(value1, "value1", 0); + capsule.write(value2, "value2", 0); + capsule.write(value3, "value3", 0); + capsule.write(value4, "value4", 0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + value1 = capsule.readInt("value1", 0); + value2 = capsule.readInt("value2", 0); + value3 = capsule.readInt("value3", 0); + value4 = capsule.readInt("value4", 0); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java new file mode 100644 index 0000000..ca3f21b --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java @@ -0,0 +1 @@ +/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package com.ardor3d.util.shader.uniformtypes;
import java.io.IOException;
import java.nio.IntBuffer;
import com.ardor3d.util.export.InputCapsule;
import com.ardor3d.util.export.OutputCapsule;
import com.ardor3d.util.shader.ShaderVariable;
/** ShaderVariableIntArray */
public class ShaderVariableIntArray extends ShaderVariable {
public IntBuffer value;
/**
* Specifies the number of values for each element of the array. Must be 1, 2, 3, or 4.
*/
public int size = 1;
@Override
public boolean hasData() {
return value != null;
}
@Override
public void write(final OutputCapsule capsule) throws IOException {
super.write(capsule);
capsule.write(value, "value", null);
capsule.write(size, "size", 1);
}
@Override
public void read(final InputCapsule capsule) throws IOException {
super.read(capsule);
value = capsule.readIntBuffer("value", null);
size = capsule.readInt("size", 1);
}
}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java new file mode 100644 index 0000000..2727fad --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableMatrix2 */ +public class ShaderVariableMatrix2 extends ShaderVariable { + public FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(4); + public boolean rowMajor; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(matrixBuffer, "matrixBuffer", null); + capsule.write(rowMajor, "rowMajor", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null); + rowMajor = capsule.readBoolean("rowMajor", false); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java new file mode 100644 index 0000000..ab2320c --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableMatrix3 */ +public class ShaderVariableMatrix3 extends ShaderVariable { + public FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(9); + public boolean rowMajor; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(matrixBuffer, "matrixBuffer", null); + capsule.write(rowMajor, "rowMajor", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null); + rowMajor = capsule.readBoolean("rowMajor", false); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java new file mode 100644 index 0000000..eb2a4d0 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariableMatrix4 */ +public class ShaderVariableMatrix4 extends ShaderVariable { + public FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); + public boolean rowMajor; + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(matrixBuffer, "matrixBuffer", null); + capsule.write(rowMajor, "rowMajor", false); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null); + rowMajor = capsule.readBoolean("rowMajor", false); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java new file mode 100644 index 0000000..a638270 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java @@ -0,0 +1 @@ +/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package com.ardor3d.util.shader.uniformtypes;
import java.io.IOException;
import java.nio.FloatBuffer;
import com.ardor3d.util.export.InputCapsule;
import com.ardor3d.util.export.OutputCapsule;
import com.ardor3d.util.shader.ShaderVariable;
/** ShaderVariableMatrix4Array */
public class ShaderVariableMatrix4Array extends ShaderVariable {
public FloatBuffer matrixBuffer;
public boolean rowMajor;
@Override
public boolean hasData() {
return matrixBuffer != null;
}
@Override
public void write(final OutputCapsule capsule) throws IOException {
super.write(capsule);
capsule.write(matrixBuffer, "matrixBuffer", null);
capsule.write(rowMajor, "rowMajor", false);
}
@Override
public void read(final InputCapsule capsule) throws IOException {
super.read(capsule);
matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null);
rowMajor = capsule.readBoolean("rowMajor", false);
}
}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java new file mode 100644 index 0000000..4c8e7bf --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import com.ardor3d.scenegraph.ByteBufferData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariablePointerByte */ +public class ShaderVariablePointerByte extends ShaderVariable { + /** + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4. + */ + public int size; + /** + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute + * values are understood to be tightly packed in the array. + */ + public int stride; + /** + * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values + * (false) when they are accessed. + */ + public boolean normalized; + /** Specifies if the data is in signed or unsigned format */ + public boolean unsigned; + /** The data for the attribute value */ + public ByteBufferData data; + + @Override + public boolean hasData() { + return data != null && data.getBuffer() != null; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(size, "size", 0); + capsule.write(stride, "stride", 0); + capsule.write(normalized, "normalized", false); + capsule.write(unsigned, "unsigned", false); + capsule.write(data, "data", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + size = capsule.readInt("size", 0); + stride = capsule.readInt("stride", 0); + normalized = capsule.readBoolean("normalized", false); + unsigned = capsule.readBoolean("unsigned", false); + data = (ByteBufferData) capsule.readSavable("data", null); + // XXX: transitional + if (data == null) { + final ByteBuffer buff = capsule.readByteBuffer("data", null); + if (buff != null) { + data = new ByteBufferData(buff); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java new file mode 100644 index 0000000..6da1c17 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariablePointerFloat */ +public class ShaderVariablePointerFloat extends ShaderVariable { + /** + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4. + */ + public int size; + /** + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute + * values are understood to be tightly packed in the array. + */ + public int stride; + /** + * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values + * (false) when they are accessed. + */ + public boolean normalized; + /** The data for the attribute value */ + public FloatBufferData data; + + @Override + public boolean hasData() { + return data != null && data.getBuffer() != null; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(size, "size", 0); + capsule.write(stride, "stride", 0); + capsule.write(normalized, "normalized", false); + capsule.write(data, "data", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + size = capsule.readInt("size", 0); + stride = capsule.readInt("stride", 0); + normalized = capsule.readBoolean("normalized", false); + data = (FloatBufferData) capsule.readSavable("data", null); + // XXX: transitional + if (data == null) { + final FloatBuffer buff = capsule.readFloatBuffer("data", null); + if (buff != null) { + data = new FloatBufferData(buff, size); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java new file mode 100644 index 0000000..7f74fef --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java @@ -0,0 +1,69 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/**
+ * ShaderVariablePointerFloatMatrix - data is stored by row... all matrices row 0, then all matrices row 1, etc.
+ */
+public class ShaderVariablePointerFloatMatrix extends ShaderVariable {
+ /**
+ * Specifies the number of rows and cols in the matrix. Must be 2, 3, or 4.
+ */
+ public int size;
+ /**
+ * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values
+ * (false) when they are accessed.
+ */
+ public boolean normalized;
+ /** The data for the attribute value */
+ public FloatBufferData data;
+
+ @Override
+ public boolean hasData() {
+ return data != null && data.getBuffer() != null;
+ }
+
+ @Override
+ public int getSize() {
+ return size;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(size, "size", 0);
+ capsule.write(normalized, "normalized", false);
+ capsule.write(data, "bdata", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ size = capsule.readInt("size", 0);
+ normalized = capsule.readBoolean("normalized", false);
+ data = (FloatBufferData) capsule.readSavable("bdata", null);
+ // XXX: transitional
+ if (data == null) {
+ final FloatBuffer buff = capsule.readFloatBuffer("data", null);
+ if (buff != null) {
+ data = new FloatBufferData(buff, size);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java new file mode 100644 index 0000000..eaa90cb --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.IntBuffer; + +import com.ardor3d.scenegraph.IntBufferData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariablePointerInt */ +public class ShaderVariablePointerInt extends ShaderVariable { + /** + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4. + */ + public int size; + /** + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute + * values are understood to be tightly packed in the array. + */ + public int stride; + /** + * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values + * (false) when they are accessed. + */ + public boolean normalized; + /** Specifies if the data is in signed or unsigned format */ + public boolean unsigned; + /** The data for the attribute value */ + public IntBufferData data; + + @Override + public boolean hasData() { + return data != null && data.getBuffer() != null; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(size, "size", 0); + capsule.write(stride, "stride", 0); + capsule.write(normalized, "normalized", false); + capsule.write(unsigned, "unsigned", false); + capsule.write(data, "data", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + size = capsule.readInt("size", 0); + stride = capsule.readInt("stride", 0); + normalized = capsule.readBoolean("normalized", false); + unsigned = capsule.readBoolean("unsigned", false); + data = (IntBufferData) capsule.readSavable("data", null); + // XXX: transitional + if (data == null) { + final IntBuffer buff = capsule.readIntBuffer("data", null); + if (buff != null) { + data = new IntBufferData(buff); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java new file mode 100644 index 0000000..48a0d37 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.shader.uniformtypes; + +import java.io.IOException; +import java.nio.ShortBuffer; + +import com.ardor3d.scenegraph.ShortBufferData; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.shader.ShaderVariable; + +/** ShaderVariablePointerShort */ +public class ShaderVariablePointerShort extends ShaderVariable { + /** + * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4. + */ + public int size; + /** + * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute + * values are understood to be tightly packed in the array. + */ + public int stride; + /** + * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values + * (false) when they are accessed. + */ + public boolean normalized; + /** Specifies if the data is in signed or unsigned format */ + public boolean unsigned; + /** The data for the attribute value */ + public ShortBufferData data; + + @Override + public boolean hasData() { + return data != null && data.getBuffer() != null; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(size, "size", 0); + capsule.write(stride, "stride", 0); + capsule.write(normalized, "normalized", false); + capsule.write(unsigned, "unsigned", false); + capsule.write(data, "data", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + size = capsule.readInt("size", 0); + stride = capsule.readInt("stride", 0); + normalized = capsule.readBoolean("normalized", false); + unsigned = capsule.readBoolean("unsigned", false); + data = (ShortBufferData) capsule.readSavable("data", null); + // XXX: transitional + if (data == null) { + final ShortBuffer buff = capsule.readShortBuffer("data", null); + if (buff != null) { + data = new ShortBufferData(buff); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java new file mode 100644 index 0000000..1674c86 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat; + +import java.util.HashMap; +import java.util.Set; + +import com.google.common.collect.Maps; + +public class MultiStatSample { + private final HashMap<StatType, StatValue> _values = Maps.newHashMap(); + private double _elapsedTime = 0.0; + + public static MultiStatSample createNew(final HashMap<StatType, StatValue> current) { + final MultiStatSample rVal = new MultiStatSample(); + for (final StatType type : current.keySet()) { + final StatValue entry = current.get(type); + // only count values we've seen at least 1 time from this sample set. + if (entry.getIterations() > 0) { + final StatValue store = new StatValue(entry); + rVal._values.put(type, store); + } + } + return rVal; + } + + public void setTimeElapsed(final double time) { + _elapsedTime = time; + } + + public boolean containsStat(final StatType type) { + return _values.containsKey(type); + } + + public StatValue getStatValue(final StatType type) { + return _values.get(type); + } + + public Set<StatType> getStatTypes() { + return _values.keySet(); + } + + public double getElapsedTime() { + return _elapsedTime; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java new file mode 100644 index 0000000..51e3432 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java @@ -0,0 +1,356 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; +import java.util.logging.Logger; + +import com.ardor3d.util.Timer; +import com.google.common.collect.Lists; + +/** + * This class acts as a centralized data store for statistics. As data is added to the collector, a sum total is kept as + * well as the total number of data samples given for the particular stat. + */ +public abstract class StatCollector { + private static final Logger logger = Logger.getLogger(StatCollector.class.getName()); + + /** + * How many distinct past aggregate samples are kept before the oldest one is dropped on add. You can multiply this + * by the time sample rate to determine the total history length in time. + */ + protected static int maxSamples = 100; + + /** + * Our map of current stat values. Current means values that have been collected within the current time sample. For + * example, if sampleRate = 1.0, then current will hold values collected since the last 1 second ping. + */ + protected static HashMap<StatType, StatValue> current = new HashMap<StatType, StatValue>(); + + protected static List<MultiStatSample> historical = Collections.synchronizedList(new LinkedList<MultiStatSample>()); + + /** + * How long to gather stats as a single unit before pushing them onto the historical stack. + */ + protected static double sampleRateMS = 1000; + + protected static double lastSampleTime = 0; + + protected static double lastTimeCheckMS = 0; + + protected static List<StatListener> listeners = Lists.newArrayList(); + + protected static double startOffset = 0; + + protected static boolean ignoreStats = false; + + protected static Stack<StatType> timeStatStack = new Stack<StatType>(); + protected static HashSet<StatType> timedStats = new HashSet<StatType>(); + + protected static Timer timer = new Timer(); + + protected static final double TO_MS = 1000.0 / timer.getResolution(); + + protected static long pausedTime; + + protected static long pausedStartTime; + + /** + * Construct a new StatCollector. + * + * @param sampleRateMS + * The amount of time between aggregated samples in milliseconds. + */ + public static void init(final long sampleRateMS, final int maxHistorical) { + StatCollector.sampleRateMS = sampleRateMS; + StatCollector.maxSamples = maxHistorical; + } + + public static void addStat(final StatType type, final double statValue) { + if (ignoreStats) { + return; + } + + synchronized (current) { + StatValue val = current.get(type); + if (val == null) { + val = new StatValue(); + current.put(type, val); + } + val.incrementValue(statValue); + val.incrementIterations(); + } + } + + public static void startStat(final StatType type) { + if (ignoreStats || !timedStats.contains(type)) { + return; + } + + synchronized (current) { + final StatType top = !timeStatStack.isEmpty() ? timeStatStack.peek() : null; + + final double timeMS = timer.getTime() * TO_MS; + if (top != null) { + // tally timer and include in stats. + final StatValue val = current.get(top); + val.incrementValue(timeMS - lastTimeCheckMS); + } else { + StatValue val = current.get(StatType.STAT_UNSPECIFIED_TIMER); + if (val == null) { + val = new StatValue(); + val.setIterations(1); + current.put(StatType.STAT_UNSPECIFIED_TIMER, val); + } + val.incrementValue(timeMS - lastTimeCheckMS); + } + + lastTimeCheckMS = timeMS; + timeStatStack.push(type); + + if (type != null) { + StatValue val = current.get(type); + if (val == null) { + val = new StatValue(); + current.put(type, val); + } + val.incrementIterations(); + } + } + } + + public static void endStat(final StatType type) { + if (ignoreStats || !timedStats.contains(type)) { + return; + } + + synchronized (current) { + // This will throw error if called out of turn. + StatType top = timeStatStack.pop(); + + final double timeMS = timer.getTime() * TO_MS; + + // tally timer and include in stats. + final StatValue val = current.get(top); + val.incrementValue(timeMS - lastTimeCheckMS); + + lastTimeCheckMS = timeMS; + + // Pop until we find our stat type + while (!top.equals(type)) { + logger.warning("Mismatched endStat, found " + top + ". Expected '" + type + "'"); + top = timeStatStack.pop(); + } + } + } + + public static synchronized void update() { + final double timeMS = timer.getTime() * TO_MS; + final double elapsed = timeMS - lastSampleTime; + + // Only continue if we've gone past our sample time threshold + if (elapsed < sampleRateMS) { + return; + } + + synchronized (current) { + // Check if we have a timed stat currently tracking... if so, update it + if (!timeStatStack.isEmpty()) { + // tally timer and include in stats. + final StatValue val = current.get(timeStatStack.peek()); + val.incrementValue(timeMS - lastTimeCheckMS - (pausedTime * TO_MS)); + lastTimeCheckMS = timeMS; + // reset iterations of all stack to 0 to indicate we have not used it yet + for (int x = timeStatStack.size(); --x >= 0;) { + final StatValue val2 = current.get(timeStatStack.get(x)); + if (val2 != null) { + val2.setIterations(0); + } + } + + // set current iterations to 1 to indicate we've used this stat + val.setIterations(1); + } else { + final StatValue val = current.get(StatType.STAT_UNSPECIFIED_TIMER); + if (val != null) { + val.incrementValue(timeMS - lastTimeCheckMS - (pausedTime * TO_MS)); + lastTimeCheckMS = timeMS; + val.setIterations(1); + } + } + + // Add "current" hash into historical stat list + final MultiStatSample sample = MultiStatSample.createNew(current); + sample.setTimeElapsed(elapsed); + historical.add(sample); // adds onto tail + + // reset the "current" hash... basically set things to 0 to decrease + // object recreation + for (final StatValue value : current.values()) { + value.reset(); + } + } + + // reset startOffset + startOffset = 0; + pausedTime = 0; + + // stat list should drop old stats from list when greater than a certain + // threshold. + while (historical.size() > maxSamples) { + final MultiStatSample removed = historical.remove(0); // removes from head + if (removed != null) { + startOffset += removed.getElapsedTime(); + } + } + + lastSampleTime = timeMS; + fireActionEvent(); + } + + /** + * Add a listener to the pool of listeners that are notified when a new stats aggregate is created (at the end of + * each time sample). + * + * @param listener + * the listener to add + */ + public static void addStatListener(final StatListener listener) { + listeners.add(listener); + } + + /** + * Removes a listener from the pool of listeners that are notified when a new stats aggregate is created (at the end + * of each time sample). + * + * @param listener + * the listener to remove + */ + public static boolean removeStatListener(final StatListener listener) { + return listeners.remove(listener); + } + + /** + * Cleans the listener pool of all listeners. + */ + public static void removeAllListeners() { + listeners.clear(); + } + + /** + * Add a type to the set of stat types that are paid attention to when doing timed stat checking. + * + * @param type + * the listener to add + */ + public static void addTimedStat(final StatType type) { + timedStats.add(type); + } + + /** + * Removes a type from the set of stat types that are paid attention to when doing timed stat checking. + * + * @param type + * the listener to remove + */ + public static boolean removeTimedStat(final StatType type) { + return timedStats.remove(type); + } + + /** + * Cleans the set of stat types we paid attention to when doing timed stat checking. + */ + public static void removeAllTimedStats() { + timedStats.clear(); + } + + /** + * Notifies all registered listeners that a new stats aggregate was created. + */ + public static void fireActionEvent() { + for (final StatListener l : listeners) { + l.statsUpdated(); + } + } + + public static double getStartOffset() { + return startOffset; + } + + public static double getSampleRate() { + return sampleRateMS; + } + + public static void setSampleRate(final long sampleRateMS) { + StatCollector.sampleRateMS = sampleRateMS; + } + + public static void setMaxSamples(final int samples) { + StatCollector.maxSamples = samples; + } + + public static int getMaxSamples() { + return maxSamples; + } + + public static List<MultiStatSample> getHistorical() { + return historical; + } + + public static MultiStatSample lastStats() { + if (historical.size() == 0) { + return null; + } + return historical.get(historical.size() - 1); + } + + public static boolean isIgnoreStats() { + return ignoreStats; + } + + public static void setIgnoreStats(final boolean ignoreStats) { + StatCollector.ignoreStats = ignoreStats; + } + + public static boolean hasHistoricalStat(final StatType type) { + for (final MultiStatSample mss : historical) { + if (mss.containsStat(type)) { + return true; + } + } + return false; + } + + /** + * Call this if you've caught an error, etc and you need to reset timed stats collecting. + * + * NOTE: You must ensure you are not inside a START/END timed block, (or you recreate any necessary start calls) + * otherwise when endStat is called a stack exception will occur. + */ + public static void resetTimedStack() { + timeStatStack.clear(); + } + + public static void pause() { + setIgnoreStats(true); + pausedStartTime = timer.getTime(); + } + + public static void resume() { + setIgnoreStats(false); + pausedTime += (timer.getTime() - pausedStartTime); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java new file mode 100644 index 0000000..845b681 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat; + +public interface StatListener { + + public void statsUpdated(); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java new file mode 100644 index 0000000..2d4436f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat; + +public class StatType implements Comparable<StatType> { + + public static final StatType STAT_FRAMES = new StatType("_frames"); + + public static final StatType STAT_TRIANGLE_COUNT = new StatType("_triCount"); + public static final StatType STAT_QUAD_COUNT = new StatType("_quadCount"); + public static final StatType STAT_LINE_COUNT = new StatType("_lineCount"); + public static final StatType STAT_POINT_COUNT = new StatType("_pointCount"); + public static final StatType STAT_VERTEX_COUNT = new StatType("_vertCount"); + public static final StatType STAT_MESH_COUNT = new StatType("_meshCount"); + public static final StatType STAT_TEXTURE_BINDS = new StatType("_texBind"); + public static final StatType STAT_SHADER_BINDS = new StatType("_shaderBind"); + + public static final StatType STAT_UNSPECIFIED_TIMER = new StatType("_timedOther"); + public static final StatType STAT_RENDER_TIMER = new StatType("_timedRenderer"); + public static final StatType STAT_STATES_TIMER = new StatType("_timedStates"); + public static final StatType STAT_TEXTURE_STATE_TIMER = new StatType("_timedTextureState"); + public static final StatType STAT_SHADER_STATE_TIMER = new StatType("_timedShaderState"); + public static final StatType STAT_UPDATE_TIMER = new StatType("_timedUpdates"); + public static final StatType STAT_DISPLAYSWAP_TIMER = new StatType("_timedSwap"); + + private String _statName = "-unknown-"; + + public StatType(final String name) { + _statName = name; + } + + public String getStatName() { + return _statName; + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof StatType)) { + return false; + } + final StatType other = (StatType) obj; + if (!_statName.equals(other._statName)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + final int hash = _statName.hashCode(); + return hash; + } + + public int compareTo(final StatType obj) { + final StatType other = obj; + return _statName.compareTo(other._statName); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java new file mode 100644 index 0000000..6e748cf --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat; + +public class StatValue { + private double _accumulatedValue = 0; + private double _averageValue = 0; + private long _iterations; + private boolean _averageDirty = true; + + public StatValue() {} + + public StatValue(final StatValue entry) { + _accumulatedValue = entry._accumulatedValue; + _averageValue = entry._averageValue; + _averageDirty = entry._averageDirty; + _iterations = entry._iterations; + } + + public double getAccumulatedValue() { + return _accumulatedValue; + } + + public long getIterations() { + return _iterations; + } + + public double getAverageValue() { + if (_averageDirty) { + _averageValue = _iterations > 0 ? _accumulatedValue / _iterations : _accumulatedValue; + _averageDirty = false; + } + return _averageValue; + } + + public void incrementValue(final double statValue) { + _accumulatedValue += statValue; + _averageDirty = true; + } + + public void incrementIterations() { + _iterations++; + _averageDirty = true; + } + + public void setIterations(final long iterations) { + _iterations = iterations; + _averageDirty = true; + } + + public void reset() { + _accumulatedValue = 0; + _iterations = 0; + _averageValue = 0; + _averageDirty = false; + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java new file mode 100644 index 0000000..10dc3b9 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import java.util.HashMap; +import java.util.TreeMap; + +import com.ardor3d.image.Texture2D; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.TextureRenderer; +import com.ardor3d.renderer.TextureRendererFactory; +import com.ardor3d.util.stat.StatListener; +import com.ardor3d.util.stat.StatType; + +/** + * Base class for graphers. + */ +public abstract class AbstractStatGrapher implements StatListener { + + protected TextureRenderer _textureRenderer; + protected Texture2D _texture; + protected int _gWidth, _gHeight; + + protected TreeMap<StatType, HashMap<String, Object>> _config = new TreeMap<StatType, HashMap<String, Object>>(); + + protected boolean _enabled = true; + + /** + * Must be constructed in the GL thread. + * + * @param factory + */ + public AbstractStatGrapher(final int width, final int height, final Renderer renderer, + final ContextCapabilities caps) { + _gWidth = width; + _gHeight = height; + // prepare our TextureRenderer + _textureRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(width, height, renderer, caps); + + if (_textureRenderer != null) { + _textureRenderer.setBackgroundColor(new ColorRGBA(ColorRGBA.BLACK)); + } + } + + // - set a texture for offscreen rendering + public void setTexture(final Texture2D tex) { + _textureRenderer.setupTexture(tex); + _texture = tex; + } + + public TextureRenderer getTextureRenderer() { + return _textureRenderer; + } + + public void clearConfig() { + _config.clear(); + } + + public void clearConfig(final StatType type) { + if (_config.get(type) != null) { + _config.get(type).clear(); + } + } + + public void clearConfig(final StatType type, final String key) { + if (_config.get(type) != null) { + _config.get(type).remove(key); + } + } + + public void addConfig(final StatType type, final HashMap<String, Object> configs) { + _config.put(type, configs); + } + + public void addConfig(final StatType type, final String key, final Object value) { + HashMap<String, Object> vals = _config.get(type); + if (vals == null) { + vals = new HashMap<String, Object>(); + _config.put(type, vals); + } + vals.put(key, value); + } + + protected ColorRGBA getColorConfig(final StatType type, final String configName, final ColorRGBA defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof ColorRGBA) { + return (ColorRGBA) val; + } + } + return defaultVal; + } + + protected String getStringConfig(final StatType type, final String configName, final String defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof String) { + return (String) val; + } + } + return defaultVal; + } + + protected short getShortConfig(final StatType type, final String configName, final short defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof Number) { + return ((Number) val).shortValue(); + } + } + return defaultVal; + } + + protected int getIntConfig(final StatType type, final String configName, final int defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof Number) { + return ((Number) val).intValue(); + } + } + return defaultVal; + } + + protected long getLongConfig(final StatType type, final String configName, final long defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof Number) { + return ((Number) val).longValue(); + } + } + return defaultVal; + } + + protected float getFloatConfig(final StatType type, final String configName, final float defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof Number) { + return ((Number) val).floatValue(); + } + } + return defaultVal; + } + + protected double getDoubleConfig(final StatType type, final String configName, final double defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof Number) { + return ((Number) val).doubleValue(); + } + } + return defaultVal; + } + + protected boolean getBooleanConfig(final StatType type, final String configName, final boolean defaultVal) { + final HashMap<String, Object> vals = _config.get(type); + if (vals != null && vals.containsKey(configName)) { + final Object val = vals.get(configName); + if (val instanceof Boolean) { + return (Boolean) val; + } + } + return defaultVal; + } + + public boolean hasConfig(final StatType type) { + return _config.containsKey(type) && !_config.get(type).isEmpty(); + } + + public boolean isEnabled() { + return _enabled; + } + + public void setEnabled(final boolean enabled) { + _enabled = enabled; + } + + /** + * Called when the graph needs to be reset back to the original display state. (iow, remove all points, lines, etc.) + */ + public abstract void reset(); +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java new file mode 100644 index 0000000..c69b02f --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.scenegraph.controller.SpatialController; +import com.ardor3d.scenegraph.hint.CullHint; + +/** + * <p> + * A controller that changes over time the alpha value of the default color of a given Geometry. When coupled with an + * appropriate BlendState, this can be used to fade in and out unlit objects. + * </p> + * + * <p> + * An example of an appropriate BlendState to use with this class: + * </p> + * + * <pre> + * BlendState blend = new BlendState(); + * blend.setBlendEnabled(true); + * blend.setSourceFunction(SourceFunction.SourceAlpha); + * blend.setDestinationFunction(DestinationFunction.OneMinusSourceAlpha); + * </pre> + */ +public class DefColorFadeController implements SpatialController<Spatial> { + + private Mesh _target; + private final float _targetAlpha; + private final double _rate; + private final boolean _dir; + + /** + * Sets up a new instance of the controller. The + * + * @param target + * the object whose default color we want to change the alpha on. + * @param targetAlpha + * the alpha value we want to end up at. + * @param rate + * the amount, per second, to change the alpha. This value will be have its sign flipped if it is not the + * appropriate direction given the current default color's alpha. + */ + public DefColorFadeController(final Mesh target, final float targetAlpha, double rate) { + _target = target; + _targetAlpha = targetAlpha; + _dir = target.getDefaultColor().getAlpha() > targetAlpha; + if ((_dir && rate > 0) || (!_dir && rate < 0)) { + rate *= -1; + } + _rate = rate; + } + + public void update(final double time, final Spatial caller) { + if (_target == null) { + return; + } + final ColorRGBA color = ColorRGBA.fetchTempInstance().set(_target.getDefaultColor()); + float alpha = color.getAlpha(); + + alpha += _rate * time; + if (_dir && alpha <= _targetAlpha) { + alpha = _targetAlpha; + } else if (!_dir && alpha >= _targetAlpha) { + alpha = _targetAlpha; + } + + if (alpha != 0) { + _target.getSceneHints().setCullHint(CullHint.Inherit); + } else { + _target.getSceneHints().setCullHint(CullHint.Always); + } + + color.setAlpha(alpha); + _target.setDefaultColor(color); + ColorRGBA.releaseTempInstance(color); + + if (alpha == _targetAlpha) { + _target.removeController(this); + + // enable gc + _target = null; + } + } + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java new file mode 100644 index 0000000..a93c02e --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import java.nio.FloatBuffer; + +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.Texture.MagnificationFilter; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.BlendState.DestinationFunction; +import com.ardor3d.renderer.state.BlendState.SourceFunction; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.util.stat.StatCollector; + +/** + * Factory class useful for setting up various types of graphs. + */ +public abstract class GraphFactory { + + /** + * Makes a new line grapher and sets up a quad to display it. + * + * @param width + * the width in pixels of the graph + * @param height + * the height in pixels of the graph + * @param quad + * the quad on whose surface we'll display our graph. + * @return the new LineGrapher + */ + public static LineGrapher makeLineGraph(final int width, final int height, final Quad quad, + final Renderer renderer, final ContextCapabilities caps) { + final LineGrapher grapher = new LineGrapher(width, height, renderer, caps); + grapher.setThreshold(1); + StatCollector.addStatListener(grapher); + final Texture2D graphTex = setupGraphTexture(grapher); + + final float dW = (float) width / grapher._textureRenderer.getWidth(); + final float dH = (float) height / grapher._textureRenderer.getHeight(); + + setupGraphQuad(quad, graphTex, dW, dH); + + return grapher; + } + + /** + * Makes a new area grapher and sets up a quad to display it. + * + * @param width + * the width in pixels of the graph + * @param height + * the height in pixels of the graph + * @param quad + * the quad on whose surface we'll display our graph. + * @return the new TimedAreaGrapher + */ + public static TimedAreaGrapher makeTimedGraph(final int width, final int height, final Quad quad, + final Renderer renderer, final ContextCapabilities caps) { + final TimedAreaGrapher grapher = new TimedAreaGrapher(width, height, renderer, caps); + grapher.setThreshold(1); + StatCollector.addStatListener(grapher); + final Texture2D graphTex = setupGraphTexture(grapher); + final float dW = (float) width / grapher._textureRenderer.getWidth(); + final float dH = (float) height / grapher._textureRenderer.getHeight(); + + setupGraphQuad(quad, graphTex, dW, dH); + + return grapher; + } + + /** + * Makes a new label grapher and sets up a quad to display it. + * + * @param width + * the width in pixels of the graph + * @param height + * the height in pixels of the graph + * @param quad + * the quad on whose surface we'll display our graph. + * @return the new TabledLabelGrapher + */ + public static TabledLabelGrapher makeTabledLabelGraph(final int width, final int height, final Quad quad, + final Renderer renderer, final ContextCapabilities caps) { + final TabledLabelGrapher grapher = new TabledLabelGrapher(width, height, renderer, caps); + grapher.setThreshold(1); + StatCollector.addStatListener(grapher); + final Texture2D graphTex = setupGraphTexture(grapher); + final float dW = (float) width / grapher._textureRenderer.getWidth(); + final float dH = (float) height / grapher._textureRenderer.getHeight(); + + setupGraphQuad(quad, graphTex, dW, dH); + + return grapher; + } + + /** + * Creates and sets up a texture to be used as the texture for a given grapher. Also applies appropriate texture + * filter modes. (NearestNeighborNoMipMaps and Bilinear) + * + * @param grapher + * the grapher to associate the texture with + * @return the texture + */ + private static Texture2D setupGraphTexture(final AbstractStatGrapher grapher) { + final Texture2D graphTex = new Texture2D(); + graphTex.setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps); + graphTex.setMagnificationFilter(MagnificationFilter.Bilinear); + grapher.setTexture(graphTex); + return graphTex; + } + + /** + * Sets up a Quad to be used as the display surface for a grapher. Puts it in the ortho mode, sets up UVs, and sets + * up a TextureState and an alpha transparency BlendState. + * + * @param quad + * the Quad to use + * @param graphTexture + * the texture to use + * @param maxU + * the maximum value along the U axis to use in the texture for UVs + * @param maxV + * the maximum value along the V axis to use in the texture for UVs + */ + private static void setupGraphQuad(final Quad quad, final Texture2D graphTexture, final float maxU, final float maxV) { + quad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + quad.getSceneHints().setLightCombineMode(LightCombineMode.Off); + quad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + quad.getSceneHints().setOrthoOrder(-1); + + final FloatBuffer tbuf = quad.getMeshData().getTextureCoords(0).getBuffer(); + tbuf.clear(); + tbuf.put(0).put(0); + tbuf.put(0).put(maxV); + tbuf.put(maxU).put(maxV); + tbuf.put(maxU).put(0); + tbuf.rewind(); + + final TextureState texState = new TextureState(); + texState.setTexture(graphTexture); + quad.setRenderState(texState); + + final BlendState blend = new BlendState(); + blend.setBlendEnabled(true); + blend.setSourceFunction(SourceFunction.SourceAlpha); + blend.setDestinationFunction(DestinationFunction.OneMinusSourceAlpha); + quad.setRenderState(blend); + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java new file mode 100644 index 0000000..544b530 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java @@ -0,0 +1,338 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.Point; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.util.Constants; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.stat.MultiStatSample; +import com.ardor3d.util.stat.StatCollector; +import com.ardor3d.util.stat.StatType; + +public class LineGrapher extends AbstractStatGrapher implements TableLinkable { + + public static final StatType Vertical = new StatType("_lineGrapher_vert"); + public static final StatType Horizontal = new StatType("_lineGrapher_horiz"); + + public enum ConfigKeys { + ShowPoints, PointSize, PointColor, Antialias, ShowLines, Width, Stipple, Color, FrameAverage, + } + + protected Node _graphRoot = new Node("root"); + protected Line _horizontals, _verticals; + protected int _eventCount = 0; + protected int _threshold = 1; + protected float _startMarker = 0; + private float _off; + private float _vSpan; + private static final int majorHBar = 20; + private static final int majorVBar = 10; + + private final HashMap<StatType, LineEntry> _entries = new HashMap<StatType, LineEntry>(); + + private BlendState _defBlendState = null; + + public LineGrapher(final int width, final int height, final Renderer renderer, final ContextCapabilities caps) { + super(width, height, renderer, caps); + + // Setup our static horizontal graph lines + createHLines(); + + _defBlendState = new BlendState(); + _defBlendState.setEnabled(true); + _defBlendState.setBlendEnabled(true); + _defBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + _defBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + _graphRoot.setRenderState(_defBlendState); + _graphRoot.getSceneHints().setCullHint(CullHint.Never); + } + + public void statsUpdated() { + if (!isEnabled() || !Constants.updateGraphs) { + return; + } + + // Turn off stat collection while we draw this graph. + StatCollector.pause(); + + // some basic stats: + final int texWidth = _gWidth; + final int texHeight = _gHeight; + + // On stat event: + // - check if enough events have been triggered to cause an update. + _eventCount++; + _off += StatCollector.getStartOffset(); + if (_eventCount < _threshold) { + return; + } else { + _eventCount = 0; + } + + // - (Re)attach horizontal bars. + if (!_graphRoot.equals(_horizontals.getParent())) { + _graphRoot.attachChild(_horizontals); + } + + // - Check if we have valid vertical bars: + final float newVSpan = calcVSpan(); + if (_verticals == null || newVSpan != _vSpan) { + _vSpan = newVSpan; + createVLines(); + } + _off %= (StatCollector.getSampleRate() * majorVBar); + + // - (Re)attach vertical bars. + if (!_graphRoot.equals(_verticals.getParent())) { + _graphRoot.attachChild(_verticals); + } + + // - shift verticals based on current time + shiftVerticals(); + + for (final StatType type : _entries.keySet()) { + _entries.get(type).visited = false; + _entries.get(type).verts.clear(); + } + + // - For each sample, add points and extend the lines of the + // corresponding Line objects. + synchronized (StatCollector.getHistorical()) { + for (int i = 0; i < StatCollector.getHistorical().size(); i++) { + final MultiStatSample sample = StatCollector.getHistorical().get(i); + for (final StatType type : _config.keySet()) { + if (sample.containsStat(type)) { + LineEntry entry = _entries.get(type); + // Prepare our entry object as needed. + if (entry == null || entry.maxSamples != StatCollector.getMaxSamples()) { + entry = new LineEntry(StatCollector.getMaxSamples(), type); + _entries.put(type, entry); + } + + final double value = getBooleanConfig(type, ConfigKeys.FrameAverage.name(), false) ? sample + .getStatValue(type).getAverageValue() : sample.getStatValue(type).getAccumulatedValue(); + + final Vector3 point = new Vector3(i, value, 0); + // Now, add + entry.verts.add(point); + + // Update min/max + if (entry.max < value) { + entry.max = value; + } + + entry.visited = true; + } else { + final LineEntry entry = _entries.get(type); + if (entry != null) { + entry.verts.add(new Vector3(i, 0, 0)); + } + } + } + } + } + + for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) { + final LineEntry entry = _entries.get(i.next()); + // - Go through the entries list and remove any that were not visited. + if (!entry.visited) { + entry.line.removeFromParent(); + entry.point.removeFromParent(); + i.remove(); + continue; + } + + // - Update the Point and Line params with the verts and count. + final FloatBuffer fb = BufferUtils.createFloatBuffer(entry.verts.toArray(new Vector3[entry.verts.size()])); + entry.point.getMeshData().setVertexBuffer(fb); + final double scaleWidth = texWidth / (StatCollector.getMaxSamples() - 1.0); + final double scaleHeight = texHeight / (entry.max * 1.02); + entry.point.setScale(new Vector3(scaleWidth, scaleHeight, 1)); + entry.line.getMeshData().setVertexBuffer(fb); + entry.line.setScale(new Vector3(scaleWidth, scaleHeight, 1)); + fb.rewind(); + + // - attach point/line to root as needed + if (!_graphRoot.equals(entry.line.getParent())) { + _graphRoot.attachChild(entry.line); + } + if (!_graphRoot.equals(entry.point.getParent())) { + _graphRoot.attachChild(entry.point); + } + } + + // - Now, draw to texture via a TextureRenderer + _graphRoot.updateGeometricState(0, true); + _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + + // Turn stat collection back on. + StatCollector.resume(); + } + + private float calcVSpan() { + return _textureRenderer.getWidth() * majorVBar / StatCollector.getMaxSamples(); + } + + private void shiftVerticals() { + final int texWidth = _textureRenderer.getWidth(); + final double xOffset = -(_off * texWidth) / (StatCollector.getMaxSamples() * StatCollector.getSampleRate()); + final ReadOnlyVector3 trans = _verticals.getTranslation(); + _verticals.setTranslation(xOffset, trans.getY(), trans.getZ()); + } + + public int getThreshold() { + return _threshold; + } + + public void setThreshold(final int threshold) { + _threshold = threshold; + } + + // - Setup horizontal bars + private void createHLines() { + // some basic stats: + final int texWidth = _textureRenderer.getWidth(); + final int texHeight = _textureRenderer.getHeight(); + + final FloatBuffer verts = BufferUtils.createVector3Buffer((100 / majorHBar) * 2); + + final float div = texHeight * majorHBar / 100f; + + for (int y = 0, i = 0; i < verts.capacity(); i += 6, y += div) { + verts.put(0).put(y).put(0); + verts.put(texWidth).put(y).put(0); + } + + _horizontals = new Line("horiz", verts, null, null, null); + _horizontals.getMeshData().setIndexMode(IndexMode.Lines); + _horizontals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + + _horizontals.setDefaultColor(getColorConfig(LineGrapher.Horizontal, ConfigKeys.Color.name(), new ColorRGBA( + ColorRGBA.BLUE))); + _horizontals.setLineWidth(getIntConfig(LineGrapher.Horizontal, ConfigKeys.Width.name(), 1)); + _horizontals + .setStipplePattern(getShortConfig(LineGrapher.Horizontal, ConfigKeys.Stipple.name(), (short) 0xFF00)); + _horizontals.setAntialiased(getBooleanConfig(LineGrapher.Horizontal, ConfigKeys.Antialias.name(), true)); + } + + // - Setup enough vertical bars to have one at every (10 X samplerate) + // secs... we'll need +1 bar. + private void createVLines() { + // some basic stats: + final int texWidth = _textureRenderer.getWidth(); + final int texHeight = _textureRenderer.getHeight(); + + final FloatBuffer verts = BufferUtils.createVector3Buffer(((int) (texWidth / _vSpan) + 1) * 2); + + for (float x = _vSpan; x <= texWidth + _vSpan; x += _vSpan) { + verts.put(x).put(0).put(0); + verts.put(x).put(texHeight).put(0); + } + + _verticals = new Line("vert", verts, null, null, null); + _verticals.getMeshData().setIndexMode(IndexMode.Lines); + _verticals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + + _verticals.setDefaultColor(getColorConfig(LineGrapher.Vertical, ConfigKeys.Color.name(), new ColorRGBA( + ColorRGBA.RED))); + _verticals.setLineWidth(getIntConfig(LineGrapher.Vertical, ConfigKeys.Width.name(), 1)); + _verticals.setStipplePattern(getShortConfig(LineGrapher.Vertical, ConfigKeys.Stipple.name(), (short) 0xFF00)); + _verticals.setAntialiased(getBooleanConfig(LineGrapher.Vertical, ConfigKeys.Antialias.name(), true)); + } + + class LineEntry { + public List<Vector3> verts = new ArrayList<Vector3>(); + public int maxSamples; + public double min = 0; + public double max = 10; + public boolean visited; + public Point point; + public Line line; + + public LineEntry(final int maxSamples, final StatType type) { + this.maxSamples = maxSamples; + + point = new Point("p", BufferUtils.createVector3Buffer(maxSamples), null, null, null); + point.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + + point.setDefaultColor(getColorConfig(type, ConfigKeys.PointColor.name(), new ColorRGBA(ColorRGBA.WHITE))); + point.setPointSize(getIntConfig(type, ConfigKeys.PointSize.name(), 5)); + point.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true)); + if (!getBooleanConfig(type, ConfigKeys.ShowPoints.name(), false)) { + point.getSceneHints().setCullHint(CullHint.Always); + } + + line = new Line("l", BufferUtils.createVector3Buffer(maxSamples), null, null, null); + line.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + line.getMeshData().setIndexMode(IndexMode.LineStrip); + + line.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY))); + line.setLineWidth(getIntConfig(type, ConfigKeys.Width.name(), 3)); + line.setStipplePattern(getShortConfig(type, ConfigKeys.Stipple.name(), (short) 0xFFFF)); + line.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true)); + if (!getBooleanConfig(type, ConfigKeys.ShowLines.name(), true)) { + line.getSceneHints().setCullHint(CullHint.Always); + } + } + } + + public Line updateLineKey(final StatType type, Line lineKey) { + if (lineKey == null) { + lineKey = new Line("lk", BufferUtils.createVector3Buffer(2), null, null, null); + final FloatBuffer fb = BufferUtils.createFloatBuffer(new Vector3[] { new Vector3(0, 0, 0), + new Vector3(30, 0, 0) }); + fb.rewind(); + lineKey.getMeshData().setVertexBuffer(fb); + } + + lineKey.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + lineKey.getMeshData().setIndexMode(IndexMode.LineStrip); + + lineKey.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY))); + lineKey.setLineWidth(getIntConfig(type, ConfigKeys.Width.name(), 3)); + lineKey.setStipplePattern(getShortConfig(type, ConfigKeys.Stipple.name(), (short) 0xFFFF)); + lineKey.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true)); + if (!getBooleanConfig(type, ConfigKeys.ShowLines.name(), true)) { + lineKey.getSceneHints().setCullHint(CullHint.Always); + } + + return lineKey; + } + + @Override + public void reset() { + synchronized (StatCollector.getHistorical()) { + for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) { + final LineEntry entry = _entries.get(i.next()); + entry.line.removeFromParent(); + entry.point.removeFromParent(); + i.remove(); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java new file mode 100644 index 0000000..2cdd290 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import com.ardor3d.scenegraph.Line; +import com.ardor3d.util.stat.StatType; + +/** + * Interface describing the ability for a class to create or update a line with values, usually to match those in + * another graph. + */ +public interface TableLinkable { + + /** + * Update/Create a line to reflect the color, stipple, antialias and width used in the other graph. + * + * @param type + * the StatType the Line is associated with. + * @param lineKey + * the Line we want to update values on (if null, a new Line should be created.) + * @return the updated (or created) Line + */ + public Line updateLineKey(StatType type, Line lineKey); + +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java new file mode 100644 index 0000000..359c158 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java @@ -0,0 +1,303 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Iterator; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.ui.text.BasicText; +import com.ardor3d.util.Constants; +import com.ardor3d.util.stat.MultiStatSample; +import com.ardor3d.util.stat.StatCollector; +import com.ardor3d.util.stat.StatType; +import com.ardor3d.util.stat.StatValue; + +public class TabledLabelGrapher extends AbstractStatGrapher { + + public enum ConfigKeys { + TextColor, Name, FrameAverage, Decimals, FontScale, ValueScale, Abbreviate, + } + + public static final int DEFAULT_DECIMALS = 2; + + protected Node _graphRoot = new Node("root"); + protected int _eventCount = 0; + protected int _threshold = 1; + protected int _columns = 1; + + protected Quad _bgQuad = new Quad("bgQuad", 1, 1); + + protected BlendState _defBlendState = null; + + private final HashMap<StatType, LabelEntry> _entries = new HashMap<StatType, LabelEntry>(); + + private boolean _minimalBackground; + + private AbstractStatGrapher _linkedGraph; + + public TabledLabelGrapher(final int width, final int height, final Renderer renderer, final ContextCapabilities caps) { + super(width, height, renderer, caps); + + _defBlendState = new BlendState(); + _defBlendState.setEnabled(true); + _defBlendState.setBlendEnabled(true); + _defBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + _defBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + _graphRoot.setRenderState(_defBlendState); + + _bgQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + _bgQuad.setDefaultColor(new ColorRGBA(ColorRGBA.BLACK)); + _graphRoot.getSceneHints().setCullHint(CullHint.Never); + } + + public void statsUpdated() { + if (!isEnabled() || !Constants.updateGraphs) { + return; + } + + // Turn off stat collection while we draw this graph. + StatCollector.pause(); + + // some basic stats: + final int texWidth = _gWidth; + final int texHeight = _gHeight; + + // On stat event: + // - check if enough events have been triggered to cause an update. + _eventCount++; + if (_eventCount < _threshold) { + return; + } else { + _eventCount = 0; + } + + int col = 0; + double lastY = texHeight - 3, maxY = 0; + final float colSize = texWidth / (float) getColumns(); + + // clear visitations + for (final StatType type : _entries.keySet()) { + _entries.get(type).visited = false; + } + + // - We only care about the most recent stats + synchronized (StatCollector.getHistorical()) { + final MultiStatSample sample = StatCollector.getHistorical().get(StatCollector.getHistorical().size() - 1); + // - go through things we are configured for + for (final StatType type : _config.keySet()) { + StatValue val = sample.getStatValue(type); + if (val == null) { + if (!StatCollector.hasHistoricalStat(type)) { + continue; + } else { + val = new StatValue(); + val.incrementIterations(); + } + } + + LabelEntry entry = _entries.get(type); + // Prepare our entry object as needed. + if (entry == null) { + entry = new LabelEntry(type); + _entries.put(type, entry); + _graphRoot.attachChild(entry.text); + } + entry.visited = true; + + // Update text value + final double value = getBooleanConfig(type, ConfigKeys.FrameAverage.name(), false) ? val + .getAverageValue() : val.getAccumulatedValue(); + entry.text.setText(getStringConfig(type, ConfigKeys.Name.name(), type.getStatName()) + " " + + stripVal(value, type)); + + // Set font scale + final float scale = getFloatConfig(type, ConfigKeys.FontScale.name(), .80f); + entry.text.setScale(scale); + + // See if we have a defained color for this type, otherwise use + // the corresponding color from a linked line grapher, or if + // none, use white. + entry.text.setTextColor(getColorConfig(type, ConfigKeys.TextColor.name(), + _linkedGraph != null ? _linkedGraph.getColorConfig(type, LineGrapher.ConfigKeys.Color.name(), + new ColorRGBA(ColorRGBA.WHITE)) : new ColorRGBA(ColorRGBA.WHITE))); + + // Update text placement. + final double labelHeight = entry.text.getHeight(); + if (maxY < labelHeight) { + maxY = labelHeight; + } + entry.text.setTranslation(colSize * col, lastY, 0); + + // Update line key as needed + if (_linkedGraph != null && _linkedGraph.hasConfig(type) && _linkedGraph instanceof TableLinkable) { + // add line keys + entry.lineKey = ((TableLinkable) _linkedGraph).updateLineKey(type, entry.lineKey); + if (entry.lineKey.getParent() != _graphRoot) { + _graphRoot.attachChild(entry.lineKey); + } + final ReadOnlyVector3 tLoc = entry.text.getTranslation(); + entry.lineKey.setTranslation((float) (tLoc.getX() + entry.text.getWidth() + 15), (float) (tLoc + .getY() + (.5 * entry.text.getHeight())), 0); + } + + // update column / row variables + col++; + col %= getColumns(); + if (col == 0) { + lastY -= maxY; + maxY = 0; + } + } + + for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) { + final LabelEntry entry = _entries.get(i.next()); + // - Go through the entries list and remove any that were not + // visited. + if (!entry.visited) { + entry.text.removeFromParent(); + if (entry.lineKey != null) { + entry.lineKey.removeFromParent(); + } + i.remove(); + } + } + } + + _graphRoot.updateGeometricState(0, true); + + final ColorRGBA bgColor = ColorRGBA.fetchTempInstance().set(_textureRenderer.getBackgroundColor()); + if (_minimalBackground) { + bgColor.setAlpha(0); + _textureRenderer.setBackgroundColor(bgColor); + + lastY -= 3; + if (col != 0) { + lastY -= maxY; + } + _bgQuad.resize(texWidth, texHeight - lastY); + _bgQuad.setRenderState(_defBlendState); + _bgQuad.setTranslation(texWidth / 2f, texHeight - (texHeight - lastY) / 2f, 0); + _bgQuad.updateGeometricState(0, true); + + // - Draw our bg quad + _textureRenderer.render(_bgQuad, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + + // - Now, draw to texture via a TextureRenderer + _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_NONE); + } else { + bgColor.setAlpha(1); + _textureRenderer.setBackgroundColor(bgColor); + + // - Now, draw to texture via a TextureRenderer + _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + } + ColorRGBA.releaseTempInstance(bgColor); + + // Turn stat collection back on. + StatCollector.resume(); + } + + private String stripVal(double val, final StatType type) { + // scale as needed + val = val * getDoubleConfig(type, ConfigKeys.ValueScale.name(), 1.0); + + String post = ""; + // Break it down if needed. + if (getBooleanConfig(type, ConfigKeys.Abbreviate.name(), true)) { + if (val >= 1000000) { + val /= 1000000; + post = "m"; + } else if (val >= 1000) { + val /= 1000; + post = "k"; + } + } + + int decimals = getIntConfig(type, ConfigKeys.Decimals.name(), DEFAULT_DECIMALS); + if (!"".equals(post) && decimals == 0) { + decimals = 1; // use 1 spot anyway. + } + + final StringBuilder format = new StringBuilder(decimals > 0 ? "0.0" : "0"); + for (int x = 1; x < decimals; x++) { + format.append("0"); + } + + return new DecimalFormat(format.toString()).format(val) + post; + } + + public int getThreshold() { + return _threshold; + } + + public void setThreshold(final int threshold) { + _threshold = threshold; + } + + public int getColumns() { + return _columns; + } + + public void setColumns(final int columns) { + if (columns < 1) { + throw new IllegalArgumentException("columns must be >= 1 (" + columns + ")"); + } + _columns = columns; + } + + public boolean isMinimalBackground() { + return _minimalBackground; + } + + public void setMinimalBackground(final boolean minimalBackground) { + _minimalBackground = minimalBackground; + } + + public void linkTo(final AbstractStatGrapher grapher) { + _linkedGraph = grapher; + } + + class LabelEntry { + BasicText text; + Line lineKey; + boolean visited; + StatType _type; + + public LabelEntry(final StatType type) { + _type = type; + text = BasicText.createDefaultTextLabel("label", getStringConfig(type, ConfigKeys.Name.name(), type + .getStatName())); + } + } + + @Override + public void reset() { + synchronized (StatCollector.getHistorical()) { + for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) { + final LabelEntry entry = _entries.get(i.next()); + entry.text.removeFromParent(); + entry.lineKey.removeFromParent(); + i.remove(); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java new file mode 100644 index 0000000..4b0e5d2 --- /dev/null +++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java @@ -0,0 +1,323 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util.stat.graph; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.ContextCapabilities; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.util.Constants; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.stat.MultiStatSample; +import com.ardor3d.util.stat.StatCollector; +import com.ardor3d.util.stat.StatType; + +public class TimedAreaGrapher extends AbstractStatGrapher implements TableLinkable { + + public static final StatType Vertical = new StatType("_timedGrapher_vert"); + public static final StatType Horizontal = new StatType("_timedGrapher_horiz"); + + public enum ConfigKeys { + Antialias, ShowAreas, Width, Stipple, Color, + } + + protected Node _graphRoot = new Node("root"); + protected Line _horizontals, _verticals; + protected int _eventCount = 0; + protected int _threshold = 1; + protected float _startMarker = 0; + private float _off; + private float _vSpan; + private static final int majorHBar = 20; + private static final int majorVBar = 10; + + private final HashMap<StatType, AreaEntry> _entries = new HashMap<StatType, AreaEntry>(); + + private BlendState _defBlendState = null; + + public TimedAreaGrapher(final int width, final int height, final Renderer renderer, final ContextCapabilities caps) { + super(width, height, renderer, caps); + + // Setup our static horizontal graph lines + createHLines(); + + _defBlendState = new BlendState(); + _defBlendState.setEnabled(true); + _defBlendState.setBlendEnabled(true); + _defBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha); + _defBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha); + _graphRoot.setRenderState(_defBlendState); + _graphRoot.getSceneHints().setCullHint(CullHint.Never); + } + + public void statsUpdated() { + if (!isEnabled() || !Constants.updateGraphs) { + return; + } + + // Turn off stat collection while we draw this graph. + StatCollector.pause(); + + // some basic stats: + final int texWidth = _gWidth; + final int texHeight = _gHeight; + + // On stat event: + // - check if enough events have been triggered to cause an update. + _eventCount++; + _off += StatCollector.getStartOffset(); + if (_eventCount < _threshold) { + return; + } else { + _eventCount = 0; + } + + // - (Re)attach horizontal bars. + if (!_graphRoot.equals(_horizontals.getParent())) { + _graphRoot.attachChild(_horizontals); + } + + // - Check if we have valid vertical bars: + final float newVSpan = calcVSpan(); + if (_verticals == null || newVSpan != _vSpan) { + _vSpan = newVSpan; + createVLines(); + } + _off %= (StatCollector.getSampleRate() * majorVBar); + + // - (Re)attach vertical bars. + if (!_graphRoot.equals(_verticals.getParent())) { + _graphRoot.attachChild(_verticals); + } + + // - shift verticals based on current time + shiftVerticals(); + + for (final StatType type : _entries.keySet()) { + _entries.get(type).visited = false; + _entries.get(type).verts.clear(); + } + + // - For each sample, add points and extend the lines of the + // corresponding Line objects. + synchronized (StatCollector.getHistorical()) { + for (int i = 0; i < StatCollector.getHistorical().size(); i++) { + final MultiStatSample sample = StatCollector.getHistorical().get(i); + // First figure out the max value. + double max = 0; + for (final StatType type : sample.getStatTypes()) { + if (_config.containsKey(type)) { + max = Math.max(sample.getStatValue(type).getAccumulatedValue(), max); + } + } + double accum = 0; + for (final StatType type : sample.getStatTypes()) { + if (_config.containsKey(type)) { + AreaEntry entry = _entries.get(type); + // Prepare our entry object as needed. + if (entry == null || entry.maxSamples != StatCollector.getMaxSamples()) { + entry = new AreaEntry(StatCollector.getMaxSamples(), type); + _entries.put(type, entry); + } + + // average by max and bump by accumulated total. + final double value = sample.getStatValue(type).getAccumulatedValue() / max; + final Vector3 point1 = new Vector3(i, (float) (value + accum), 0); + entry.verts.add(point1); + final Vector3 point2 = new Vector3(i, (float) (accum), 0); + entry.verts.add(point2); + entry.visited = true; + accum += value; + } + } + } + } + + final float scaleWidth = texWidth / (float) (StatCollector.getMaxSamples() - 1); + final float scaleHeight = texHeight / 1.02f; + for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) { + final AreaEntry entry = _entries.get(i.next()); + // - Go through the entries list and remove any that were not + // visited. + if (!entry.visited) { + entry.area.removeFromParent(); + i.remove(); + continue; + } + + // - Update the params with the verts and count. + final FloatBuffer fb = BufferUtils.createFloatBuffer(entry.verts.toArray(new Vector3[entry.verts.size()])); + fb.rewind(); + entry.area.getMeshData().setVertexBuffer(fb); + entry.area.setScale(new Vector3(scaleWidth, scaleHeight, 1)); + entry.area.getMeshData().getIndexBuffer().limit(entry.verts.size()); + + // - attach to root as needed + if (!_graphRoot.equals(entry.area.getParent())) { + _graphRoot.attachChild(entry.area); + } + } + + _graphRoot.updateGeometricState(0, true); + + // - Now, draw to texture via a TextureRenderer + _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_COLOR_AND_DEPTH); + + // Turn stat collection back on. + StatCollector.resume(); + } + + private float calcVSpan() { + return _textureRenderer.getWidth() * majorVBar / StatCollector.getMaxSamples(); + } + + private void shiftVerticals() { + final int texWidth = _textureRenderer.getWidth(); + final double xOffset = -(_off * texWidth) / (StatCollector.getMaxSamples() * StatCollector.getSampleRate()); + final ReadOnlyVector3 trans = _verticals.getTranslation(); + _verticals.setTranslation(xOffset, trans.getY(), trans.getZ()); + } + + public int getThreshold() { + return _threshold; + } + + public void setThreshold(final int threshold) { + _threshold = threshold; + } + + // - Setup horizontal bars + private void createHLines() { + // some basic stats: + final int texWidth = _textureRenderer.getWidth(); + final int texHeight = _textureRenderer.getHeight(); + + final FloatBuffer verts = BufferUtils.createVector3Buffer((100 / majorHBar) * 2); + + final float div = texHeight * majorHBar / 100f; + + for (int y = 0, i = 0; i < verts.capacity(); i += 6, y += div) { + verts.put(0).put(y).put(0); + verts.put(texWidth).put(y).put(0); + } + + _horizontals = new Line("horiz", verts, null, null, null); + _horizontals.getMeshData().setIndexMode(IndexMode.Lines); + _horizontals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + + _horizontals.setDefaultColor(getColorConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Color.name(), + new ColorRGBA(ColorRGBA.BLUE))); + _horizontals.setLineWidth(getIntConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Width.name(), 1)); + _horizontals.setStipplePattern(getShortConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Stipple.name(), + (short) 0xFF00)); + _horizontals.setAntialiased(getBooleanConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Antialias.name(), true)); + } + + // - Setup enough vertical bars to have one at every (10 X samplerate) + // secs... we'll need +1 bar. + private void createVLines() { + // some basic stats: + final int texWidth = _textureRenderer.getWidth(); + final int texHeight = _textureRenderer.getHeight(); + + final FloatBuffer verts = BufferUtils.createVector3Buffer(((int) (texWidth / _vSpan) + 1) * 2); + + for (float x = _vSpan; x <= texWidth + _vSpan; x += _vSpan) { + verts.put(x).put(0).put(0); + verts.put(x).put(texHeight).put(0); + } + + _verticals = new Line("vert", verts, null, null, null); + _verticals.getMeshData().setIndexMode(IndexMode.Lines); + _verticals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + + _verticals.setDefaultColor(getColorConfig(TimedAreaGrapher.Vertical, ConfigKeys.Color.name(), new ColorRGBA( + ColorRGBA.RED))); + _verticals.setLineWidth(getIntConfig(TimedAreaGrapher.Vertical, ConfigKeys.Width.name(), 1)); + _verticals.setStipplePattern(getShortConfig(TimedAreaGrapher.Vertical, ConfigKeys.Stipple.name(), + (short) 0xFF00)); + _verticals.setAntialiased(getBooleanConfig(TimedAreaGrapher.Vertical, ConfigKeys.Antialias.name(), true)); + } + + class AreaEntry { + public List<Vector3> verts = new ArrayList<Vector3>(); + public int maxSamples; + public boolean visited; + public Mesh area; + + public AreaEntry(final int maxSamples, final StatType type) { + this.maxSamples = maxSamples; + + area = new Mesh("a"); + area.getMeshData().setVertexBuffer(BufferUtils.createVector3Buffer(maxSamples * 2)); + area.getMeshData().setIndexBuffer(BufferUtils.createIntBuffer(maxSamples * 2)); + for (int i = 0; i < maxSamples * 2; i++) { + area.getMeshData().getIndices().put(i); + } + area.getMeshData().getIndexBuffer().rewind(); + area.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + area.getMeshData().setIndexMode(IndexMode.LineStrip); + + area.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY))); + if (!getBooleanConfig(type, ConfigKeys.ShowAreas.name(), true)) { + area.getSceneHints().setCullHint(CullHint.Always); + } + } + } + + public Line updateLineKey(final StatType type, Line lineKey) { + if (lineKey == null) { + lineKey = new Line("lk", BufferUtils.createVector3Buffer(2), null, null, null); + final FloatBuffer fb = BufferUtils.createFloatBuffer(new Vector3[] { new Vector3(0, 0, 0), + new Vector3(30, 0, 0) }); + fb.rewind(); + lineKey.getMeshData().setVertexBuffer(fb); + } + + lineKey.getSceneHints().setRenderBucketType(RenderBucketType.Ortho); + lineKey.getMeshData().setIndexMode(IndexMode.LineLoop); + + lineKey.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY))); + lineKey.setLineWidth(getIntConfig(type, ConfigKeys.Width.name(), 3)); + lineKey.setStipplePattern(getShortConfig(type, ConfigKeys.Stipple.name(), (short) 0xFFFF)); + lineKey.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true)); + if (!getBooleanConfig(type, ConfigKeys.ShowAreas.name(), true)) { + lineKey.getSceneHints().setCullHint(CullHint.Always); + } + + return lineKey; + } + + @Override + public void reset() { + synchronized (StatCollector.getHistorical()) { + for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) { + final AreaEntry entry = _entries.get(i.next()); + entry.area.removeFromParent(); + i.remove(); + } + } + } +} diff --git a/trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF b/trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..160cd1d --- /dev/null +++ b/trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,46 @@ +Bundle-Name: Ardor3D Core +Bundle-SymbolicName: com.ardor3d.core +Bundle-ManifestVersion: 2 +Bundle-Version: 0.7 +Export-Package: com.ardor3d.annotation, + com.ardor3d.bounding, + com.ardor3d.framework, + com.ardor3d.image, + com.ardor3d.image.util, + com.ardor3d.image.util.dds, + com.ardor3d.input, + com.ardor3d.input.control, + com.ardor3d.input.logical, + com.ardor3d.intersection, + com.ardor3d.light, + com.ardor3d.math, + com.ardor3d.math.functions, + com.ardor3d.math.type, + com.ardor3d.nativeloader, + com.ardor3d.renderer, + com.ardor3d.renderer.pass, + com.ardor3d.renderer.queue, + com.ardor3d.renderer.state, + com.ardor3d.renderer.state.record, + com.ardor3d.scenegraph, + com.ardor3d.scenegraph.controller, + com.ardor3d.scenegraph.controller.interpolation, + com.ardor3d.scenegraph.event, + com.ardor3d.scenegraph.extension, + com.ardor3d.scenegraph.hint, + com.ardor3d.scenegraph.shape, + com.ardor3d.scenegraph.visitor, + com.ardor3d.spline, + com.ardor3d.ui.text, + com.ardor3d.util, + com.ardor3d.util.export, + com.ardor3d.util.export.binary, + com.ardor3d.util.export.xml, + com.ardor3d.util.geom, + com.ardor3d.util.resource, + com.ardor3d.util.scenegraph, + com.ardor3d.util.screen, + com.ardor3d.util.shader, + com.ardor3d.util.shader.uniformtypes, + com.ardor3d.util.stat, + com.ardor3d.util.stat.graph diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tga b/trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tga Binary files differnew file mode 100644 index 0000000..d4affa1 --- /dev/null +++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tga diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3d b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3d Binary files differnew file mode 100644 index 0000000..ee55979 --- /dev/null +++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3d diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt new file mode 100644 index 0000000..b8f7d49 --- /dev/null +++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt @@ -0,0 +1,1403 @@ +<?xml version="1.0"?>
+<font>
+ <info face="Arial" size="-24" bold="1" italic="0" charset="" unicode="1" stretchH="100" smooth="1" aa="2" padding="0,0,0,0" spacing="1,1" outline="0"/>
+ <common lineHeight="28" base="23" scaleW="512" scaleH="512" pages="1" packed="0" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0"/>
+ <pages>
+ <page id="0" file="arial-24-bold-regular_00.png" />
+ </pages>
+ <chars count="994">
+ <char id="32" x="510" y="44" width="1" height="1" xoffset="0" yoffset="22" xadvance="6" page="0" chnl="15" />
+ <char id="33" x="116" y="389" width="4" height="18" xoffset="2" yoffset="5" xadvance="7" page="0" chnl="15" />
+ <char id="34" x="392" y="475" width="9" height="6" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="35" x="210" y="311" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="36" x="92" y="171" width="12" height="22" xoffset="1" yoffset="3" xadvance="13" page="0" chnl="15" />
+ <char id="37" x="340" y="249" width="19" height="18" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="38" x="442" y="267" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="39" x="430" y="474" width="4" height="6" xoffset="1" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="40" x="109" y="147" width="6" height="23" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="41" x="102" y="147" width="6" height="23" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="42" x="223" y="481" width="8" height="8" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="43" x="352" y="463" width="12" height="12" xoffset="1" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="44" x="507" y="460" width="4" height="8" xoffset="1" yoffset="19" xadvance="6" page="0" chnl="15" />
+ <char id="45" x="374" y="482" width="7" height="3" xoffset="1" yoffset="15" xadvance="8" page="0" chnl="15" />
+ <char id="46" x="246" y="490" width="4" height="4" xoffset="1" yoffset="19" xadvance="6" page="0" chnl="15" />
+ <char id="47" x="504" y="305" width="7" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="48" x="347" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="49" x="430" y="362" width="8" height="18" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="50" x="373" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="51" x="386" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="52" x="322" y="307" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="53" x="399" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="54" x="412" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="55" x="425" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="56" x="438" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="57" x="451" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="58" x="99" y="471" width="4" height="14" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="59" x="121" y="389" width="4" height="18" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="60" x="169" y="452" width="12" height="14" xoffset="1" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="61" x="68" y="486" width="12" height="9" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="62" x="143" y="455" width="12" height="14" xoffset="1" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="63" x="462" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="64" x="0" y="28" width="23" height="23" xoffset="1" yoffset="5" xadvance="23" page="0" chnl="15" />
+ <char id="65" x="318" y="269" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="66" x="301" y="288" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="67" x="17" y="216" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="68" x="317" y="288" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="69" x="476" y="305" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="70" x="13" y="351" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="71" x="493" y="188" width="16" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="72" x="135" y="311" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="73" x="141" y="387" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="74" x="500" y="247" width="11" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="75" x="119" y="292" width="16" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="76" x="52" y="351" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="77" x="192" y="273" width="17" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="78" x="381" y="287" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="79" x="334" y="190" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="80" x="56" y="332" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="81" x="250" y="169" width="17" height="21" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="82" x="187" y="292" width="16" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="83" x="228" y="212" width="14" height="19" xoffset="1" yoffset="4" xadvance="16" page="0" chnl="15" />
+ <char id="84" x="180" y="311" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="85" x="195" y="311" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="86" x="459" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="87" x="469" y="229" width="23" height="18" xoffset="0" yoffset="5" xadvance="22" page="0" chnl="15" />
+ <char id="88" x="493" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="89" x="246" y="271" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="90" x="396" y="287" width="14" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="91" x="74" y="148" width="6" height="23" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="92" x="59" y="389" width="7" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="93" x="123" y="147" width="6" height="23" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="94" x="495" y="460" width="11" height="10" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="95" x="272" y="490" width="14" height="3" xoffset="0" yoffset="24" xadvance="13" page="0" chnl="15" />
+ <char id="96" x="203" y="490" width="5" height="4" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="97" x="104" y="455" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="98" x="104" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="99" x="91" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="100" x="130" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="101" x="78" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="102" x="400" y="363" width="9" height="18" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="103" x="208" y="232" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="104" x="156" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="105" x="111" y="389" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="106" x="116" y="147" width="6" height="23" xoffset="-1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="107" x="182" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="108" x="106" y="389" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="109" x="244" y="422" width="19" height="14" xoffset="1" yoffset="9" xadvance="21" page="0" chnl="15" />
+ <char id="110" x="65" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="111" x="294" y="435" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="112" x="52" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="113" x="65" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="114" x="10" y="472" width="9" height="14" xoffset="1" yoffset="9" xadvance="9" page="0" chnl="15" />
+ <char id="115" x="52" y="456" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="116" x="9" y="389" width="8" height="18" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="117" x="39" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="118" x="124" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="119" x="181" y="422" width="20" height="14" xoffset="0" yoffset="9" xadvance="19" page="0" chnl="15" />
+ <char id="120" x="139" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="121" x="348" y="210" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="122" x="269" y="452" width="11" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="123" x="0" y="148" width="9" height="23" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="124" x="169" y="147" width="3" height="23" xoffset="2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="125" x="10" y="148" width="9" height="23" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="126" x="439" y="473" width="13" height="5" xoffset="1" yoffset="12" xadvance="14" page="0" chnl="15" />
+ <char id="160" x="510" y="46" width="1" height="1" xoffset="0" yoffset="22" xadvance="6" page="0" chnl="15" />
+ <char id="161" x="281" y="231" width="4" height="19" xoffset="2" yoffset="9" xadvance="7" page="0" chnl="15" />
+ <char id="162" x="202" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="163" x="238" y="309" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="164" x="193" y="467" width="13" height="13" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="165" x="224" y="311" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="166" x="165" y="147" width="3" height="23" xoffset="2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="167" x="215" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="168" x="339" y="484" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="169" x="40" y="275" width="18" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="170" x="104" y="485" width="8" height="9" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="171" x="378" y="462" width="12" height="12" xoffset="1" yoffset="10" xadvance="13" page="0" chnl="15" />
+ <char id="172" x="55" y="486" width="12" height="9" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="173" x="366" y="483" width="7" height="3" xoffset="1" yoffset="15" xadvance="8" page="0" chnl="15" />
+ <char id="174" x="59" y="275" width="18" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="175" x="287" y="490" width="14" height="3" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="176" x="232" y="481" width="8" height="8" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="177" x="452" y="381" width="12" height="17" xoffset="0" yoffset="6" xadvance="13" page="0" chnl="15" />
+ <char id="178" x="140" y="484" width="7" height="9" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="179" x="148" y="484" width="7" height="9" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="180" x="185" y="490" width="5" height="4" xoffset="2" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="181" x="462" y="209" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="182" x="495" y="144" width="13" height="22" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="183" x="241" y="490" width="4" height="4" xoffset="1" yoffset="12" xadvance="6" page="0" chnl="15" />
+ <char id="184" x="471" y="472" width="7" height="5" xoffset="0" yoffset="22" xadvance="8" page="0" chnl="15" />
+ <char id="185" x="163" y="484" width="5" height="9" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="186" x="113" y="485" width="8" height="9" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="187" x="391" y="462" width="12" height="12" xoffset="1" yoffset="10" xadvance="13" page="0" chnl="15" />
+ <char id="188" x="420" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="189" x="480" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="190" x="153" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="191" x="91" y="235" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="192" x="252" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="193" x="270" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="194" x="288" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="195" x="306" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="196" x="468" y="48" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="197" x="292" y="145" width="17" height="22" xoffset="0" yoffset="1" xadvance="17" page="0" chnl="15" />
+ <char id="198" x="396" y="229" width="24" height="18" xoffset="-1" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="199" x="17" y="76" width="16" height="23" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="200" x="283" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="201" x="297" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="202" x="311" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="203" x="84" y="123" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="204" x="149" y="147" width="5" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="205" x="143" y="147" width="5" height="23" xoffset="1" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="206" x="38" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="207" x="503" y="48" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="208" x="336" y="268" width="17" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="209" x="75" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="210" x="342" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="211" x="360" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="212" x="378" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="213" x="396" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="214" x="414" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="215" x="365" y="462" width="12" height="12" xoffset="1" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="216" x="231" y="170" width="18" height="21" xoffset="0" yoffset="3" xadvance="18" page="0" chnl="15" />
+ <char id="217" x="45" y="100" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="218" x="210" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="219" x="233" y="74" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="220" x="248" y="74" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="221" x="54" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="222" x="0" y="332" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="223" x="416" y="344" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="224" x="429" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="225" x="442" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="226" x="455" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="227" x="468" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="228" x="481" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="229" x="473" y="167" width="12" height="20" xoffset="1" yoffset="3" xadvance="13" page="0" chnl="15" />
+ <char id="230" x="223" y="422" width="20" height="14" xoffset="1" yoffset="9" xadvance="21" page="0" chnl="15" />
+ <char id="231" x="0" y="370" width="12" height="18" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="232" x="13" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="233" x="143" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="234" x="169" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="235" x="221" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="236" x="88" y="389" width="5" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="237" x="100" y="389" width="5" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="238" x="448" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="239" x="502" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="240" x="168" y="330" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="241" x="299" y="346" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="242" x="154" y="330" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="243" x="140" y="330" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="244" x="112" y="332" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="245" x="84" y="332" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="246" x="14" y="332" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="247" x="207" y="467" width="12" height="13" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="248" x="478" y="399" width="13" height="15" xoffset="1" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="249" x="65" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="250" x="78" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="251" x="321" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="252" x="334" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="253" x="428" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="254" x="254" y="122" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="255" x="413" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="256" x="256" y="146" width="17" height="22" xoffset="0" yoffset="1" xadvance="16" page="0" chnl="15" />
+ <char id="257" x="426" y="382" width="12" height="17" xoffset="1" yoffset="6" xadvance="13" page="0" chnl="15" />
+ <char id="258" x="294" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="259" x="117" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="260" x="179" y="27" width="19" height="23" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="261" x="288" y="211" width="14" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="262" x="51" y="76" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="263" x="26" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="264" x="170" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="265" x="208" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="266" x="486" y="48" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="267" x="477" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="268" x="34" y="76" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="269" x="308" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="270" x="187" y="75" width="15" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="271" x="17" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="272" x="264" y="271" width="17" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="273" x="486" y="286" width="14" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="274" x="467" y="144" width="13" height="22" xoffset="2" yoffset="1" xadvance="16" page="0" chnl="15" />
+ <char id="275" x="465" y="381" width="12" height="17" xoffset="1" yoffset="6" xadvance="13" page="0" chnl="15" />
+ <char id="276" x="367" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="277" x="403" y="344" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="278" x="353" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="279" x="494" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="280" x="70" y="124" width="13" height="23" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="281" x="104" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="282" x="479" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="283" x="52" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="284" x="102" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="285" x="267" y="122" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="286" x="85" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="287" x="280" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="288" x="68" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="289" x="293" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="290" x="323" y="0" width="16" height="24" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="291" x="207" y="0" width="12" height="25" xoffset="1" yoffset="3" xadvance="14" page="0" chnl="15" />
+ <char id="292" x="353" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="293" x="306" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="294" x="390" y="268" width="17" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="295" x="336" y="307" width="13" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="296" x="20" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="297" x="475" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="298" x="166" y="171" width="8" height="22" xoffset="0" yoffset="1" xadvance="6" page="0" chnl="15" />
+ <char id="299" x="0" y="408" width="8" height="17" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
+ <char id="300" x="503" y="72" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="301" x="439" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="302" x="81" y="148" width="6" height="23" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="303" x="88" y="147" width="6" height="23" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="304" x="160" y="147" width="4" height="23" xoffset="1" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="305" x="119" y="470" width="4" height="14" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="306" x="408" y="267" width="16" height="18" xoffset="1" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="307" x="493" y="120" width="10" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="308" x="0" y="124" width="13" height="23" xoffset="0" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="309" x="56" y="148" width="8" height="23" xoffset="-1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="310" x="327" y="145" width="16" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="311" x="118" y="171" width="12" height="22" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="312" x="117" y="455" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="313" x="319" y="121" width="12" height="23" xoffset="2" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="314" x="137" y="147" width="5" height="23" xoffset="1" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="315" x="27" y="172" width="12" height="22" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="316" x="175" y="170" width="7" height="22" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="317" x="91" y="351" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="318" x="503" y="324" width="8" height="18" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="319" x="234" y="349" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="320" x="410" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="321" x="165" y="311" width="14" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="322" x="35" y="389" width="7" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="323" x="165" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="324" x="312" y="345" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="325" x="438" y="145" width="14" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="326" x="325" y="345" width="12" height="18" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="327" x="105" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="328" x="338" y="345" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="329" x="354" y="268" width="17" height="18" xoffset="-1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="330" x="119" y="214" width="15" height="19" xoffset="2" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="331" x="169" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="332" x="274" y="146" width="17" height="22" xoffset="1" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="333" x="358" y="383" width="13" height="17" xoffset="1" yoffset="6" xadvance="14" page="0" chnl="15" />
+ <char id="334" x="0" y="52" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="335" x="420" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="336" x="492" y="24" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="337" x="434" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="338" x="121" y="194" width="23" height="19" xoffset="1" yoffset="4" xadvance="24" page="0" chnl="15" />
+ <char id="339" x="159" y="422" width="21" height="14" xoffset="1" yoffset="9" xadvance="22" page="0" chnl="15" />
+ <char id="340" x="0" y="76" width="16" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="341" x="380" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="342" x="310" y="145" width="16" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="343" x="369" y="363" width="10" height="18" xoffset="0" yoffset="9" xadvance="9" page="0" chnl="15" />
+ <char id="344" x="136" y="75" width="16" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="345" x="420" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="346" x="15" y="100" width="14" height="23" xoffset="1" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="347" x="360" y="325" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="348" x="0" y="100" width="14" height="23" xoffset="1" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="349" x="286" y="347" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="350" x="473" y="72" width="14" height="23" xoffset="1" yoffset="4" xadvance="16" page="0" chnl="15" />
+ <char id="351" x="260" y="347" width="12" height="18" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="352" x="458" y="72" width="14" height="23" xoffset="1" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="353" x="26" y="351" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="354" x="192" y="0" width="14" height="25" xoffset="1" yoffset="3" xadvance="15" page="0" chnl="15" />
+ <char id="355" x="410" y="0" width="8" height="24" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
+ <char id="356" x="398" y="73" width="14" height="23" xoffset="1" yoffset="0" xadvance="15" page="0" chnl="15" />
+ <char id="357" x="351" y="345" width="12" height="18" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="358" x="60" y="313" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="359" x="18" y="389" width="8" height="18" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="360" x="383" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="361" x="117" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="362" x="393" y="145" width="14" height="22" xoffset="2" yoffset="1" xadvance="17" page="0" chnl="15" />
+ <char id="363" x="439" y="381" width="12" height="17" xoffset="1" yoffset="6" xadvance="14" page="0" chnl="15" />
+ <char id="364" x="368" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="365" x="247" y="347" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="366" x="338" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="367" x="486" y="167" width="12" height="20" xoffset="1" yoffset="3" xadvance="14" page="0" chnl="15" />
+ <char id="368" x="308" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="369" x="39" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="370" x="293" y="73" width="14" height="23" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="371" x="213" y="212" width="14" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="372" x="48" y="27" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="373" x="237" y="252" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="374" x="312" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="375" x="263" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="376" x="324" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="377" x="218" y="74" width="14" height="23" xoffset="0" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="378" x="238" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="379" x="150" y="99" width="14" height="23" xoffset="0" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="380" x="178" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="381" x="135" y="99" width="14" height="23" xoffset="0" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="382" x="142" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="383" x="51" y="389" width="7" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="399" x="459" y="189" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="402" x="120" y="99" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="416" x="256" y="191" width="20" height="19" xoffset="1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="417" x="468" y="415" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="431" x="97" y="275" width="18" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="432" x="485" y="415" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="461" x="474" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="462" x="65" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="463" x="29" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="464" x="0" y="389" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="465" x="450" y="48" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="466" x="280" y="309" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="467" x="195" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="468" x="273" y="347" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="469" x="225" y="98" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="470" x="384" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="471" x="240" y="98" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="472" x="131" y="171" width="12" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="473" x="443" y="72" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="474" x="79" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="475" x="30" y="100" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="476" x="105" y="171" width="12" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="506" x="287" y="0" width="17" height="24" xoffset="0" yoffset="-1" xadvance="16" page="0" chnl="15" />
+ <char id="507" x="332" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="508" x="419" y="0" width="24" height="23" xoffset="-1" yoffset="0" xadvance="24" page="0" chnl="15" />
+ <char id="509" x="258" y="252" width="20" height="18" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="510" x="249" y="0" width="18" height="24" xoffset="0" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="511" x="308" y="307" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="601" x="182" y="452" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="710" x="149" y="494" width="8" height="4" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="711" x="140" y="494" width="8" height="4" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="713" x="348" y="484" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="728" x="312" y="487" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="729" x="399" y="482" width="4" height="3" xoffset="2" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="730" x="0" y="498" width="5" height="5" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="731" x="412" y="475" width="6" height="6" xoffset="1" yoffset="22" xadvance="8" page="0" chnl="15" />
+ <char id="732" x="321" y="487" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="733" x="130" y="495" width="9" height="4" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="768" x="179" y="493" width="5" height="4" xoffset="-6" yoffset="0" xadvance="0" page="0" chnl="15" />
+ <char id="769" x="173" y="493" width="5" height="4" xoffset="-5" yoffset="0" xadvance="0" page="0" chnl="15" />
+ <char id="771" x="357" y="483" width="8" height="3" xoffset="-10" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="777" x="166" y="494" width="6" height="4" xoffset="-6" yoffset="0" xadvance="0" page="0" chnl="15" />
+ <char id="803" x="221" y="490" width="4" height="4" xoffset="-10" yoffset="24" xadvance="0" page="0" chnl="15" />
+ <char id="894" x="126" y="389" width="4" height="18" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="900" x="197" y="490" width="5" height="4" xoffset="2" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="901" x="104" y="495" width="12" height="4" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="902" x="154" y="273" width="18" height="18" xoffset="-1" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="903" x="226" y="490" width="4" height="4" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="904" x="45" y="256" width="21" height="18" xoffset="-1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="905" x="67" y="256" width="21" height="18" xoffset="-1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="906" x="334" y="364" width="11" height="18" xoffset="-1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="908" x="168" y="194" width="21" height="19" xoffset="-1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="910" x="445" y="229" width="23" height="18" xoffset="0" yoffset="5" xadvance="22" page="0" chnl="15" />
+ <char id="911" x="190" y="192" width="21" height="19" xoffset="-1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="912" x="390" y="344" width="12" height="18" xoffset="-2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="913" x="372" y="268" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="914" x="269" y="290" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="915" x="364" y="344" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="916" x="116" y="273" width="18" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="917" x="350" y="306" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="918" x="150" y="311" width="14" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="919" x="0" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="920" x="424" y="189" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="921" x="507" y="343" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="922" x="153" y="292" width="16" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="923" x="170" y="292" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="924" x="210" y="273" width="17" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="925" x="471" y="286" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="926" x="456" y="286" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="927" x="388" y="189" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="928" x="441" y="286" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="929" x="364" y="306" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="931" x="392" y="306" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="932" x="426" y="286" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="933" x="300" y="269" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="934" x="296" y="190" width="18" height="19" xoffset="1" yoffset="4" xadvance="19" page="0" chnl="15" />
+ <char id="935" x="204" y="292" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="936" x="173" y="273" width="18" height="18" xoffset="1" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="937" x="277" y="191" width="18" height="19" xoffset="0" yoffset="4" xadvance="19" page="0" chnl="15" />
+ <char id="938" x="65" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="939" x="402" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="940" x="294" y="308" width="13" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="941" x="262" y="366" width="11" height="18" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="942" x="163" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="943" x="94" y="389" width="5" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="944" x="464" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="945" x="182" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="946" x="189" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="947" x="167" y="214" width="15" height="19" xoffset="0" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="948" x="406" y="306" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="949" x="329" y="448" width="11" height="14" xoffset="1" yoffset="9" xadvance="11" page="0" chnl="15" />
+ <char id="950" x="482" y="120" width="10" height="23" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="951" x="182" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="952" x="274" y="366" width="11" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="953" x="104" y="470" width="4" height="14" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="954" x="375" y="432" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="955" x="221" y="292" width="15" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="956" x="195" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="957" x="79" y="441" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="958" x="471" y="120" width="10" height="23" xoffset="1" yoffset="5" xadvance="10" page="0" chnl="15" />
+ <char id="959" x="168" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="960" x="284" y="420" width="18" height="14" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="961" x="392" y="209" width="13" height="19" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="962" x="257" y="232" width="11" height="19" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="963" x="48" y="441" width="15" height="14" xoffset="1" yoffset="9" xadvance="16" page="0" chnl="15" />
+ <char id="964" x="418" y="446" width="10" height="14" xoffset="0" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="965" x="257" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="966" x="51" y="216" width="16" height="19" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="967" x="273" y="211" width="14" height="19" xoffset="0" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="968" x="85" y="215" width="16" height="19" xoffset="1" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="969" x="264" y="422" width="19" height="14" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="970" x="466" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="971" x="310" y="365" width="11" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="972" x="490" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="973" x="322" y="364" width="11" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="974" x="440" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1025" x="56" y="124" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="1026" x="132" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1027" x="228" y="122" width="12" height="23" xoffset="2" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="1028" x="151" y="214" width="15" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1029" x="258" y="211" width="14" height="19" xoffset="1" yoffset="4" xadvance="16" page="0" chnl="15" />
+ <char id="1030" x="131" y="387" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1031" x="47" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="1032" x="346" y="364" width="11" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1033" x="319" y="230" width="25" height="18" xoffset="0" yoffset="5" xadvance="26" page="0" chnl="15" />
+ <char id="1034" x="421" y="229" width="23" height="18" xoffset="2" yoffset="5" xadvance="25" page="0" chnl="15" />
+ <char id="1035" x="460" y="248" width="19" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1036" x="493" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="1038" x="119" y="75" width="16" height="23" xoffset="0" yoffset="0" xadvance="15" page="0" chnl="15" />
+ <char id="1039" x="423" y="145" width="14" height="22" xoffset="2" yoffset="5" xadvance="18" page="0" chnl="15" />
+ <char id="1040" x="282" y="270" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1041" x="349" y="287" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1042" x="333" y="288" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1043" x="195" y="349" width="12" height="18" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1044" x="238" y="146" width="17" height="22" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1045" x="196" y="330" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1046" x="0" y="256" width="22" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1047" x="303" y="210" width="14" height="19" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1048" x="75" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1049" x="278" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="1050" x="210" y="330" width="13" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1051" x="285" y="289" width="15" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1052" x="228" y="271" width="17" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1053" x="45" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1054" x="370" y="190" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1055" x="30" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1056" x="252" y="328" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1057" x="0" y="216" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1058" x="15" y="313" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1059" x="34" y="294" width="16" height="18" xoffset="0" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1060" x="380" y="249" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1061" x="476" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1062" x="361" y="145" width="15" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1063" x="182" y="330" width="13" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1064" x="89" y="256" width="21" height="18" xoffset="2" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="1065" x="173" y="147" width="22" height="22" xoffset="2" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="1066" x="279" y="251" width="20" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1067" x="111" y="254" width="20" height="18" xoffset="2" yoffset="5" xadvance="23" page="0" chnl="15" />
+ <char id="1068" x="365" y="287" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1069" x="135" y="214" width="15" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1070" x="145" y="194" width="22" height="19" xoffset="2" yoffset="4" xadvance="25" page="0" chnl="15" />
+ <char id="1071" x="425" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1072" x="349" y="433" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1073" x="434" y="209" width="13" height="19" xoffset="1" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1074" x="308" y="435" width="13" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1075" x="451" y="446" width="9" height="14" xoffset="1" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1076" x="299" y="385" width="15" height="17" xoffset="0" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1077" x="453" y="430" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1078" x="379" y="417" width="18" height="14" xoffset="0" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1079" x="245" y="452" width="11" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1080" x="479" y="430" width="12" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1081" x="91" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1082" x="281" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1083" x="94" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1084" x="16" y="442" width="15" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1085" x="13" y="457" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1086" x="238" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1087" x="388" y="432" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1088" x="475" y="209" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1089" x="466" y="430" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1090" x="414" y="431" width="12" height="14" xoffset="0" yoffset="9" xadvance="11" page="0" chnl="15" />
+ <char id="1091" x="183" y="214" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1092" x="492" y="0" width="19" height="23" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1093" x="109" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1094" x="372" y="382" width="13" height="17" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1095" x="233" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1096" x="398" y="416" width="17" height="14" xoffset="1" yoffset="9" xadvance="19" page="0" chnl="15" />
+ <char id="1097" x="229" y="387" width="18" height="17" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="1098" x="451" y="415" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1099" x="360" y="417" width="18" height="14" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="1100" x="196" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1101" x="26" y="457" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1102" x="322" y="418" width="18" height="14" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="1103" x="252" y="437" width="13" height="14" xoffset="0" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1105" x="104" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1106" x="14" y="124" width="13" height="23" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1107" x="390" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="10" page="0" chnl="15" />
+ <char id="1108" x="0" y="457" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1109" x="492" y="430" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1110" x="136" y="387" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1111" x="457" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1112" x="130" y="147" width="6" height="23" xoffset="-1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1113" x="135" y="425" width="23" height="14" xoffset="0" yoffset="9" xadvance="23" page="0" chnl="15" />
+ <char id="1114" x="202" y="422" width="20" height="14" xoffset="1" yoffset="9" xadvance="22" page="0" chnl="15" />
+ <char id="1115" x="126" y="330" width="13" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1116" x="226" y="368" width="11" height="18" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="1118" x="323" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1119" x="413" y="382" width="12" height="17" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1168" x="156" y="171" width="9" height="22" xoffset="2" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1169" x="490" y="381" width="9" height="17" xoffset="1" yoffset="6" xadvance="10" page="0" chnl="15" />
+ <char id="1170" x="198" y="212" width="14" height="19" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1171" x="352" y="448" width="10" height="14" xoffset="0" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1174" x="196" y="147" width="22" height="22" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1175" x="248" y="387" width="18" height="17" xoffset="0" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1178" x="339" y="97" width="13" height="23" xoffset="2" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1179" x="166" y="368" width="11" height="18" xoffset="1" yoffset="8" xadvance="12" page="0" chnl="15" />
+ <char id="1180" x="294" y="327" width="13" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1181" x="221" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1186" x="377" y="145" width="15" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1187" x="344" y="383" width="13" height="17" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1198" x="90" y="313" width="14" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1199" x="105" y="313" width="14" height="18" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1200" x="120" y="311" width="14" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1201" x="363" y="210" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1202" x="344" y="145" width="16" height="22" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1203" x="330" y="383" width="13" height="17" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1208" x="252" y="309" width="13" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1209" x="293" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1210" x="411" y="286" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1211" x="39" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1240" x="476" y="188" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1241" x="156" y="455" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1256" x="352" y="190" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1257" x="154" y="440" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1456" x="17" y="498" width="2" height="5" xoffset="3" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1457" x="462" y="473" width="8" height="5" xoffset="0" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1458" x="479" y="471" width="7" height="5" xoffset="0" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1459" x="487" y="471" width="7" height="5" xoffset="1" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1460" x="175" y="467" width="2" height="2" xoffset="3" yoffset="25" xadvance="8" page="0" chnl="15" />
+ <char id="1461" x="423" y="481" width="5" height="2" xoffset="1" yoffset="25" xadvance="8" page="0" chnl="15" />
+ <char id="1462" x="501" y="471" width="5" height="5" xoffset="1" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1463" x="183" y="192" width="5" height="1" xoffset="1" yoffset="25" xadvance="8" page="0" chnl="15" />
+ <char id="1464" x="209" y="490" width="5" height="4" xoffset="2" yoffset="24" xadvance="8" page="0" chnl="15" />
+ <char id="1465" x="414" y="482" width="2" height="3" xoffset="3" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1466" x="411" y="482" width="2" height="3" xoffset="3" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1467" x="453" y="473" width="8" height="5" xoffset="0" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1468" x="408" y="482" width="2" height="3" xoffset="3" yoffset="14" xadvance="8" page="0" chnl="15" />
+ <char id="1469" x="509" y="152" width="1" height="5" xoffset="3" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1470" x="330" y="486" width="8" height="3" xoffset="1" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1471" x="169" y="467" width="5" height="2" xoffset="1" yoffset="7" xadvance="8" page="0" chnl="15" />
+ <char id="1472" x="146" y="387" width="3" height="18" xoffset="2" yoffset="7" xadvance="7" page="0" chnl="15" />
+ <char id="1473" x="420" y="481" width="2" height="3" xoffset="9" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1474" x="417" y="482" width="2" height="3" xoffset="-3" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1475" x="109" y="470" width="4" height="14" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="1488" x="224" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1489" x="266" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1490" x="341" y="448" width="10" height="14" xoffset="0" yoffset="9" xadvance="11" page="0" chnl="15" />
+ <char id="1491" x="195" y="452" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1492" x="208" y="452" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1493" x="114" y="470" width="4" height="14" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="1494" x="0" y="472" width="9" height="14" xoffset="1" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1495" x="336" y="433" width="12" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1496" x="362" y="432" width="12" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1497" x="287" y="481" width="4" height="8" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="1498" x="245" y="232" width="11" height="19" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1499" x="429" y="446" width="10" height="14" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1500" x="501" y="286" width="10" height="18" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="1501" x="401" y="431" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1502" x="280" y="437" width="13" height="14" xoffset="0" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1503" x="286" y="231" width="4" height="19" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="1504" x="71" y="471" width="7" height="14" xoffset="1" yoffset="9" xadvance="9" page="0" chnl="15" />
+ <char id="1505" x="210" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1506" x="171" y="405" width="12" height="16" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1507" x="488" y="208" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1508" x="440" y="431" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1509" x="221" y="232" width="11" height="19" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1510" x="427" y="431" width="12" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1511" x="0" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1512" x="317" y="450" width="11" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1513" x="434" y="416" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1514" x="64" y="441" width="14" height="14" xoffset="0" yoffset="9" xadvance="16" page="0" chnl="15" />
+ <char id="1520" x="385" y="447" width="10" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1521" x="374" y="447" width="10" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1522" x="202" y="481" width="10" height="8" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1523" x="419" y="474" width="5" height="6" xoffset="1" yoffset="9" xadvance="7" page="0" chnl="15" />
+ <char id="1524" x="349" y="476" width="10" height="6" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1548" x="292" y="481" width="4" height="8" xoffset="2" yoffset="11" xadvance="7" page="0" chnl="15" />
+ <char id="1563" x="430" y="461" width="4" height="12" xoffset="1" yoffset="7" xadvance="7" page="0" chnl="15" />
+ <char id="1567" x="79" y="471" width="7" height="14" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="1569" x="404" y="462" width="9" height="12" xoffset="1" yoffset="10" xadvance="11" page="0" chnl="15" />
+ <char id="1570" x="67" y="389" width="6" height="18" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
+ <char id="1571" x="508" y="208" width="3" height="19" xoffset="1" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1572" x="298" y="366" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1573" x="65" y="195" width="3" height="20" xoffset="1" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="1574" x="40" y="425" width="12" height="15" xoffset="2" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1575" x="506" y="399" width="3" height="15" xoffset="1" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="1576" x="32" y="441" width="15" height="14" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1577" x="291" y="403" width="7" height="16" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
+ <char id="1578" x="336" y="463" width="15" height="12" xoffset="0" yoffset="7" xadvance="15" page="0" chnl="15" />
+ <char id="1579" x="0" y="442" width="15" height="14" xoffset="0" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1580" x="28" y="332" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1581" x="70" y="332" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1582" x="423" y="97" width="13" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1583" x="467" y="460" width="7" height="11" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1584" x="17" y="408" width="7" height="17" xoffset="1" yoffset="2" xadvance="8" page="0" chnl="15" />
+ <char id="1585" x="246" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1586" x="130" y="368" width="11" height="18" xoffset="0" yoffset="6" xadvance="10" page="0" chnl="15" />
+ <char id="1587" x="124" y="470" width="21" height="13" xoffset="1" yoffset="11" xadvance="21" page="0" chnl="15" />
+ <char id="1588" x="212" y="192" width="21" height="19" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1589" x="109" y="425" width="25" height="14" xoffset="1" yoffset="10" xadvance="25" page="0" chnl="15" />
+ <char id="1590" x="95" y="194" width="25" height="19" xoffset="1" yoffset="5" xadvance="25" page="0" chnl="15" />
+ <char id="1591" x="127" y="408" width="14" height="16" xoffset="-1" yoffset="3" xadvance="12" page="0" chnl="15" />
+ <char id="1592" x="142" y="406" width="14" height="16" xoffset="-1" yoffset="3" xadvance="12" page="0" chnl="15" />
+ <char id="1593" x="459" y="168" width="13" height="20" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1594" x="164" y="0" width="13" height="26" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="1600" x="382" y="482" width="6" height="3" xoffset="-1" yoffset="16" xadvance="5" page="0" chnl="15" />
+ <char id="1601" x="25" y="408" width="18" height="16" xoffset="0" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1602" x="243" y="212" width="14" height="19" xoffset="0" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="1603" x="492" y="399" width="13" height="15" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1604" x="12" y="195" width="11" height="20" xoffset="0" yoffset="4" xadvance="11" page="0" chnl="15" />
+ <char id="1605" x="282" y="403" width="8" height="16" xoffset="0" yoffset="12" xadvance="8" page="0" chnl="15" />
+ <char id="1606" x="157" y="405" width="13" height="16" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1607" x="17" y="487" width="6" height="10" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="1608" x="294" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="11" page="0" chnl="15" />
+ <char id="1609" x="233" y="467" width="12" height="13" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1610" x="184" y="405" width="12" height="16" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1611" x="251" y="490" width="4" height="4" xoffset="1" yoffset="2" xadvance="5" page="0" chnl="15" />
+ <char id="1612" x="495" y="471" width="5" height="5" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1613" x="507" y="469" width="4" height="5" xoffset="1" yoffset="21" xadvance="5" page="0" chnl="15" />
+ <char id="1614" x="429" y="481" width="4" height="2" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
+ <char id="1615" x="6" y="498" width="5" height="5" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1616" x="394" y="482" width="4" height="3" xoffset="1" yoffset="21" xadvance="5" page="0" chnl="15" />
+ <char id="1617" x="191" y="490" width="5" height="4" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
+ <char id="1618" x="256" y="490" width="3" height="4" xoffset="1" yoffset="2" xadvance="4" page="0" chnl="15" />
+ <char id="1619" x="159" y="437" width="6" height="2" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
+ <char id="1620" x="264" y="490" width="3" height="4" xoffset="1" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1621" x="268" y="490" width="3" height="4" xoffset="1" yoffset="22" xadvance="5" page="0" chnl="15" />
+ <char id="1632" x="344" y="476" width="4" height="7" xoffset="4" yoffset="11" xadvance="13" page="0" chnl="15" />
+ <char id="1633" x="93" y="471" width="5" height="14" xoffset="4" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1634" x="63" y="471" width="7" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1635" x="407" y="446" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1636" x="505" y="430" width="6" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1637" x="0" y="487" width="9" height="10" xoffset="2" yoffset="7" xadvance="13" page="0" chnl="15" />
+ <char id="1638" x="38" y="472" width="8" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1639" x="502" y="415" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1640" x="396" y="447" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1641" x="29" y="472" width="8" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1642" x="262" y="405" width="10" height="16" xoffset="1" yoffset="4" xadvance="12" page="0" chnl="15" />
+ <char id="1643" x="435" y="473" width="3" height="6" xoffset="2" yoffset="16" xadvance="7" page="0" chnl="15" />
+ <char id="1644" x="297" y="481" width="4" height="8" xoffset="2" yoffset="11" xadvance="7" page="0" chnl="15" />
+ <char id="1645" x="122" y="485" width="8" height="9" xoffset="2" yoffset="4" xadvance="12" page="0" chnl="15" />
+ <char id="1646" x="24" y="487" width="15" height="9" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1647" x="463" y="399" width="14" height="15" xoffset="0" yoffset="11" xadvance="14" page="0" chnl="15" />
+ <char id="1648" x="509" y="144" width="2" height="7" xoffset="0" yoffset="3" xadvance="0" page="0" chnl="15" />
+ <char id="1649" x="275" y="231" width="5" height="19" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1650" x="269" y="231" width="5" height="19" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1651" x="59" y="195" width="5" height="20" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="1652" x="260" y="490" width="3" height="4" xoffset="1" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1653" x="315" y="402" width="6" height="16" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
+ <char id="1654" x="13" y="236" width="12" height="19" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="1655" x="26" y="236" width="12" height="19" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="1656" x="333" y="210" width="14" height="19" xoffset="2" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1657" x="267" y="385" width="15" height="17" xoffset="0" yoffset="2" xadvance="15" page="0" chnl="15" />
+ <char id="1658" x="447" y="399" width="15" height="15" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1659" x="95" y="408" width="15" height="16" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1660" x="399" y="400" width="15" height="15" xoffset="0" yoffset="7" xadvance="15" page="0" chnl="15" />
+ <char id="1661" x="415" y="400" width="15" height="15" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1662" x="111" y="408" width="15" height="16" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1663" x="431" y="400" width="15" height="15" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1664" x="283" y="385" width="15" height="17" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1665" x="355" y="0" width="13" height="24" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1666" x="150" y="0" width="13" height="26" xoffset="1" yoffset="2" xadvance="14" page="0" chnl="15" />
+ <char id="1667" x="280" y="328" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1668" x="266" y="328" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1669" x="14" y="0" width="13" height="27" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="1670" x="238" y="328" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1671" x="224" y="330" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1672" x="43" y="389" width="7" height="18" xoffset="1" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1673" x="55" y="471" width="7" height="14" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1674" x="299" y="403" width="7" height="16" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1675" x="504" y="120" width="7" height="23" xoffset="1" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1676" x="9" y="408" width="7" height="17" xoffset="1" yoffset="2" xadvance="8" page="0" chnl="15" />
+ <char id="1677" x="307" y="403" width="7" height="16" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1678" x="27" y="389" width="7" height="18" xoffset="1" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1679" x="484" y="362" width="8" height="18" xoffset="0" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1680" x="493" y="362" width="8" height="18" xoffset="0" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1681" x="144" y="171" width="11" height="22" xoffset="0" yoffset="2" xadvance="10" page="0" chnl="15" />
+ <char id="1682" x="478" y="381" width="11" height="17" xoffset="0" yoffset="7" xadvance="10" page="0" chnl="15" />
+ <char id="1683" x="305" y="450" width="11" height="14" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1684" x="210" y="405" width="12" height="16" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1685" x="223" y="405" width="12" height="16" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1686" x="236" y="405" width="12" height="16" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1687" x="154" y="368" width="11" height="18" xoffset="0" yoffset="6" xadvance="10" page="0" chnl="15" />
+ <char id="1688" x="499" y="167" width="11" height="20" xoffset="0" yoffset="4" xadvance="10" page="0" chnl="15" />
+ <char id="1689" x="296" y="168" width="11" height="21" xoffset="0" yoffset="3" xadvance="10" page="0" chnl="15" />
+ <char id="1690" x="150" y="387" width="21" height="17" xoffset="1" yoffset="7" xadvance="21" page="0" chnl="15" />
+ <char id="1691" x="322" y="402" width="21" height="15" xoffset="1" yoffset="11" xadvance="21" page="0" chnl="15" />
+ <char id="1692" x="209" y="170" width="21" height="21" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1693" x="83" y="425" width="25" height="14" xoffset="1" yoffset="10" xadvance="25" page="0" chnl="15" />
+ <char id="1694" x="183" y="170" width="25" height="21" xoffset="1" yoffset="3" xadvance="25" page="0" chnl="15" />
+ <char id="1695" x="315" y="384" width="14" height="17" xoffset="-1" yoffset="2" xadvance="12" page="0" chnl="15" />
+ <char id="1696" x="0" y="0" width="13" height="27" xoffset="0" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="1697" x="435" y="461" width="18" height="11" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="1698" x="364" y="401" width="18" height="15" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="1699" x="381" y="168" width="18" height="20" xoffset="0" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1700" x="315" y="190" width="18" height="19" xoffset="0" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="1701" x="172" y="387" width="18" height="17" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="1702" x="78" y="275" width="18" height="18" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1703" x="416" y="168" width="14" height="20" xoffset="0" yoffset="6" xadvance="14" page="0" chnl="15" />
+ <char id="1704" x="408" y="145" width="14" height="22" xoffset="0" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1705" x="303" y="420" width="18" height="14" xoffset="0" yoffset="5" xadvance="18" page="0" chnl="15" />
+ <char id="1706" x="146" y="470" width="18" height="13" xoffset="0" yoffset="6" xadvance="18" page="0" chnl="15" />
+ <char id="1707" x="341" y="418" width="18" height="14" xoffset="0" yoffset="5" xadvance="18" page="0" chnl="15" />
+ <char id="1708" x="448" y="305" width="13" height="18" xoffset="0" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="1709" x="386" y="382" width="13" height="17" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="1710" x="453" y="145" width="13" height="22" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1711" x="191" y="387" width="18" height="17" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1712" x="210" y="387" width="18" height="17" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1713" x="135" y="273" width="18" height="18" xoffset="0" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="1714" x="219" y="147" width="18" height="22" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1715" x="268" y="0" width="18" height="24" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1716" x="493" y="228" width="18" height="18" xoffset="0" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="1717" x="423" y="121" width="11" height="23" xoffset="0" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1718" x="435" y="121" width="11" height="23" xoffset="0" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1719" x="459" y="120" width="11" height="23" xoffset="0" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1720" x="447" y="121" width="11" height="23" xoffset="0" yoffset="4" xadvance="11" page="0" chnl="15" />
+ <char id="1721" x="431" y="168" width="13" height="20" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1722" x="179" y="467" width="13" height="13" xoffset="0" yoffset="11" xadvance="13" page="0" chnl="15" />
+ <char id="1723" x="268" y="169" width="13" height="21" xoffset="0" yoffset="3" xadvance="13" page="0" chnl="15" />
+ <char id="1724" x="420" y="209" width="13" height="19" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1725" x="445" y="168" width="13" height="20" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1726" x="282" y="467" width="11" height="13" xoffset="-1" yoffset="7" xadvance="11" page="0" chnl="15" />
+ <char id="1727" x="369" y="0" width="13" height="24" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1728" x="76" y="425" width="6" height="15" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="1729" x="213" y="481" width="9" height="8" xoffset="0" yoffset="13" xadvance="9" page="0" chnl="15" />
+ <char id="1730" x="306" y="465" width="9" height="13" xoffset="0" yoffset="8" xadvance="9" page="0" chnl="15" />
+ <char id="1731" x="481" y="445" width="9" height="14" xoffset="0" yoffset="7" xadvance="9" page="0" chnl="15" />
+ <char id="1732" x="270" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="11" page="0" chnl="15" />
+ <char id="1733" x="258" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="11" page="0" chnl="15" />
+ <char id="1734" x="250" y="366" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1735" x="0" y="195" width="11" height="20" xoffset="0" yoffset="4" xadvance="11" page="0" chnl="15" />
+ <char id="1736" x="320" y="168" width="11" height="21" xoffset="0" yoffset="3" xadvance="11" page="0" chnl="15" />
+ <char id="1737" x="190" y="368" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1738" x="286" y="366" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1739" x="308" y="168" width="11" height="21" xoffset="0" yoffset="3" xadvance="11" page="0" chnl="15" />
+ <char id="1740" x="220" y="467" width="12" height="13" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1741" x="165" y="470" width="13" height="13" xoffset="1" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1742" x="27" y="425" width="12" height="15" xoffset="2" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1743" x="233" y="232" width="11" height="19" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="1744" x="249" y="405" width="12" height="16" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1745" x="197" y="405" width="12" height="16" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1746" x="316" y="465" width="19" height="12" xoffset="0" yoffset="11" xadvance="19" page="0" chnl="15" />
+ <char id="1747" x="344" y="401" width="19" height="15" xoffset="0" yoffset="8" xadvance="19" page="0" chnl="15" />
+ <char id="1748" x="389" y="482" width="4" height="3" xoffset="0" yoffset="16" xadvance="5" page="0" chnl="15" />
+ <char id="1749" x="10" y="487" width="6" height="10" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="1750" x="81" y="486" width="12" height="9" xoffset="-6" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="1751" x="94" y="486" width="9" height="9" xoffset="-5" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="1752" x="215" y="490" width="5" height="4" xoffset="-2" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="1753" x="256" y="481" width="5" height="8" xoffset="-2" yoffset="2" xadvance="0" page="0" chnl="15" />
+ <char id="1754" x="156" y="484" width="6" height="9" xoffset="-3" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="1755" x="404" y="482" width="3" height="3" xoffset="-1" yoffset="7" xadvance="0" page="0" chnl="15" />
+ <char id="1756" x="360" y="476" width="10" height="6" xoffset="-5" yoffset="4" xadvance="0" page="0" chnl="15" />
+ <char id="1757" x="55" y="0" width="26" height="26" xoffset="0" yoffset="1" xadvance="27" page="0" chnl="15" />
+ <char id="1758" x="28" y="0" width="26" height="26" xoffset="0" yoffset="1" xadvance="26" page="0" chnl="15" />
+ <char id="1759" x="440" y="479" width="2" height="2" xoffset="-1" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="1760" x="437" y="480" width="2" height="2" xoffset="-1" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="1761" x="158" y="494" width="7" height="4" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="1762" x="272" y="481" width="4" height="8" xoffset="-2" yoffset="2" xadvance="0" page="0" chnl="15" />
+ <char id="1763" x="322" y="478" width="10" height="7" xoffset="-5" yoffset="21" xadvance="0" page="0" chnl="15" />
+ <char id="1764" x="190" y="212" width="3" height="1" xoffset="-1" yoffset="9" xadvance="0" page="0" chnl="15" />
+ <char id="1765" x="333" y="478" width="5" height="7" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
+ <char id="1766" x="402" y="475" width="9" height="6" xoffset="0" yoffset="4" xadvance="9" page="0" chnl="15" />
+ <char id="1767" x="382" y="475" width="9" height="6" xoffset="-4" yoffset="4" xadvance="0" page="0" chnl="15" />
+ <char id="1768" x="249" y="481" width="6" height="8" xoffset="-3" yoffset="2" xadvance="0" page="0" chnl="15" />
+ <char id="1769" x="282" y="169" width="13" height="21" xoffset="1" yoffset="2" xadvance="14" page="0" chnl="15" />
+ <char id="1770" x="12" y="498" width="4" height="5" xoffset="-2" yoffset="22" xadvance="0" page="0" chnl="15" />
+ <char id="1771" x="231" y="490" width="4" height="4" xoffset="-2" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="1772" x="434" y="481" width="2" height="2" xoffset="-1" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="1773" x="282" y="481" width="4" height="8" xoffset="-2" yoffset="20" xadvance="0" page="0" chnl="15" />
+ <char id="1776" x="339" y="476" width="4" height="7" xoffset="4" yoffset="11" xadvance="13" page="0" chnl="15" />
+ <char id="1777" x="87" y="471" width="5" height="14" xoffset="4" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1778" x="47" y="471" width="7" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1779" x="363" y="447" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1780" x="501" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1781" x="491" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1782" x="471" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1783" x="461" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1784" x="440" y="446" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1785" x="20" y="472" width="8" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1786" x="234" y="192" width="21" height="19" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1787" x="69" y="195" width="25" height="19" xoffset="1" yoffset="5" xadvance="25" page="0" chnl="15" />
+ <char id="1788" x="178" y="0" width="13" height="26" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="1789" x="500" y="381" width="9" height="17" xoffset="1" yoffset="10" xadvance="11" page="0" chnl="15" />
+ <char id="1790" x="273" y="403" width="8" height="16" xoffset="0" yoffset="12" xadvance="8" page="0" chnl="15" />
+ <char id="7808" x="468" y="0" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="7809" x="174" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="7810" x="24" y="28" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="7811" x="195" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="7812" x="444" y="0" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="7813" x="216" y="252" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="7840" x="330" y="25" width="17" height="23" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="7841" x="39" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="7842" x="348" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7843" x="78" y="236" width="12" height="19" xoffset="1" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="7844" x="144" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7845" x="176" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7846" x="36" y="52" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7847" x="150" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7848" x="420" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7849" x="137" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7850" x="432" y="48" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7851" x="40" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="7852" x="82" y="0" width="17" height="26" xoffset="0" yoffset="2" xadvance="16" page="0" chnl="15" />
+ <char id="7853" x="124" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7854" x="198" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7855" x="111" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7856" x="384" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7857" x="98" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7858" x="456" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7859" x="410" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7860" x="438" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7861" x="53" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="7862" x="118" y="0" width="17" height="26" xoffset="0" yoffset="2" xadvance="16" page="0" chnl="15" />
+ <char id="7863" x="397" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7864" x="395" y="97" width="13" height="23" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="7865" x="117" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="7866" x="255" y="98" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7867" x="130" y="234" width="12" height="19" xoffset="1" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="7868" x="269" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7869" x="377" y="344" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7870" x="381" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7871" x="241" y="122" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7872" x="437" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7873" x="371" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7874" x="451" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7875" x="358" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7876" x="465" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7877" x="66" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="7878" x="136" y="0" width="13" height="26" xoffset="2" yoffset="2" xadvance="16" page="0" chnl="15" />
+ <char id="7879" x="345" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7880" x="95" y="147" width="6" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="7881" x="501" y="208" width="6" height="19" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
+ <char id="7882" x="507" y="96" width="4" height="23" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="7883" x="155" y="147" width="4" height="23" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="7884" x="305" y="0" width="17" height="24" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="7885" x="448" y="209" width="13" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="7886" x="180" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7887" x="378" y="210" width="13" height="19" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="7888" x="162" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7889" x="325" y="97" width="13" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="7890" x="366" y="25" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7891" x="28" y="124" width="13" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="7892" x="108" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7893" x="42" y="124" width="13" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="7894" x="90" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7895" x="0" y="172" width="13" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="7896" x="100" y="0" width="17" height="26" xoffset="1" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="7897" x="409" y="97" width="13" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="7898" x="116" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7899" x="136" y="292" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7900" x="137" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7901" x="102" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7902" x="95" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7903" x="442" y="189" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="7904" x="158" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7905" x="0" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7906" x="228" y="0" width="20" height="24" xoffset="1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="7907" x="102" y="214" width="16" height="19" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="7908" x="60" y="100" width="14" height="23" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7909" x="143" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="7910" x="90" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="7911" x="156" y="234" width="12" height="19" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="7912" x="218" y="26" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7913" x="85" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7914" x="199" y="26" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7915" x="68" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7916" x="237" y="25" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7917" x="34" y="216" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="7918" x="275" y="25" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7919" x="51" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7920" x="256" y="25" width="18" height="23" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="7921" x="68" y="216" width="16" height="19" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="7922" x="18" y="52" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7923" x="180" y="99" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7924" x="216" y="50" width="17" height="23" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="7925" x="318" y="210" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="7926" x="234" y="50" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7927" x="340" y="0" width="14" height="24" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="7928" x="126" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7929" x="488" y="72" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8204" x="510" y="24" width="1" height="19" xoffset="0" yoffset="7" xadvance="0" page="0" chnl="15" />
+ <char id="8205" x="346" y="168" width="6" height="21" xoffset="-2" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8206" x="24" y="195" width="6" height="20" xoffset="0" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8207" x="31" y="195" width="6" height="20" xoffset="-5" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8211" x="89" y="496" width="14" height="4" xoffset="0" yoffset="14" xadvance="13" page="0" chnl="15" />
+ <char id="8212" x="20" y="498" width="24" height="4" xoffset="0" yoffset="14" xadvance="24" page="0" chnl="15" />
+ <char id="8213" x="45" y="497" width="22" height="4" xoffset="1" yoffset="14" xadvance="24" page="0" chnl="15" />
+ <char id="8215" x="307" y="479" width="14" height="7" xoffset="0" yoffset="21" xadvance="13" page="0" chnl="15" />
+ <char id="8216" x="302" y="481" width="4" height="8" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8217" x="262" y="481" width="4" height="8" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8218" x="267" y="481" width="4" height="8" xoffset="1" yoffset="19" xadvance="6" page="0" chnl="15" />
+ <char id="8219" x="277" y="481" width="4" height="8" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8220" x="169" y="484" width="10" height="8" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8221" x="191" y="481" width="10" height="8" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8222" x="180" y="481" width="10" height="8" xoffset="1" yoffset="19" xadvance="12" page="0" chnl="15" />
+ <char id="8224" x="14" y="172" width="12" height="22" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8225" x="481" y="144" width="13" height="22" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8226" x="241" y="481" width="7" height="8" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="8230" x="68" y="496" width="20" height="4" xoffset="2" yoffset="19" xadvance="24" page="0" chnl="15" />
+ <char id="8234" x="81" y="389" width="6" height="18" xoffset="0" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="8235" x="74" y="389" width="6" height="18" xoffset="-5" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="8236" x="38" y="195" width="6" height="20" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8237" x="339" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8238" x="332" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8240" x="345" y="230" width="25" height="18" xoffset="0" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="8242" x="425" y="474" width="4" height="6" xoffset="2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8243" x="371" y="475" width="10" height="6" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="8249" x="422" y="461" width="7" height="12" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="8250" x="414" y="461" width="7" height="12" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="8252" x="358" y="364" width="10" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="8254" x="302" y="490" width="9" height="3" xoffset="0" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="8260" x="378" y="306" width="13" height="18" xoffset="-4" yoffset="5" xadvance="4" page="0" chnl="15" />
+ <char id="8298" x="353" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8299" x="367" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8300" x="374" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8301" x="45" y="195" width="6" height="20" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8302" x="52" y="195" width="6" height="20" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8303" x="360" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8319" x="131" y="484" width="8" height="9" xoffset="1" yoffset="8" xadvance="9" page="0" chnl="15" />
+ <char id="8355" x="42" y="332" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8356" x="266" y="309" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8359" x="291" y="231" width="27" height="18" xoffset="0" yoffset="5" xadvance="27" page="0" chnl="15" />
+ <char id="8362" x="416" y="416" width="17" height="14" xoffset="1" yoffset="9" xadvance="19" page="0" chnl="15" />
+ <char id="8363" x="65" y="425" width="10" height="15" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8364" x="406" y="209" width="13" height="19" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="8453" x="23" y="256" width="21" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="8467" x="214" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="8470" x="371" y="230" width="24" height="18" xoffset="2" yoffset="5" xadvance="27" page="0" chnl="15" />
+ <char id="8482" x="475" y="460" width="19" height="10" xoffset="2" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="8486" x="406" y="189" width="17" height="19" xoffset="0" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="8494" x="322" y="433" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="8531" x="400" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8532" x="360" y="249" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8539" x="320" y="249" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8540" x="300" y="250" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8541" x="0" y="275" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8542" x="20" y="275" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8706" x="202" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8710" x="253" y="290" width="15" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="8719" x="72" y="51" width="17" height="23" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8721" x="203" y="75" width="14" height="23" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="8722" x="117" y="495" width="12" height="4" xoffset="1" yoffset="12" xadvance="14" page="0" chnl="15" />
+ <char id="8725" x="98" y="332" width="13" height="18" xoffset="-4" yoffset="5" xadvance="4" page="0" chnl="15" />
+ <char id="8729" x="236" y="490" width="4" height="4" xoffset="1" yoffset="12" xadvance="6" page="0" chnl="15" />
+ <char id="8730" x="397" y="0" width="12" height="24" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="8734" x="40" y="487" width="14" height="9" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="8735" x="44" y="408" width="16" height="16" xoffset="4" yoffset="7" xadvance="23" page="0" chnl="15" />
+ <char id="8745" x="237" y="290" width="15" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="8747" x="220" y="0" width="7" height="25" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="8776" x="454" y="461" width="12" height="11" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="8800" x="78" y="351" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8801" x="130" y="455" width="12" height="14" xoffset="1" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="8804" x="0" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8805" x="490" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="9786" x="61" y="408" width="16" height="16" xoffset="4" yoffset="8" xadvance="24" page="0" chnl="15" />
+ <char id="9787" x="78" y="408" width="16" height="16" xoffset="5" yoffset="8" xadvance="25" page="0" chnl="15" />
+ <char id="9788" x="72" y="27" width="22" height="23" xoffset="0" yoffset="3" xadvance="22" page="0" chnl="15" />
+ <char id="9792" x="383" y="0" width="13" height="24" xoffset="2" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="9794" x="153" y="75" width="16" height="23" xoffset="1" yoffset="3" xadvance="18" page="0" chnl="15" />
+ <char id="9824" x="14" y="426" width="12" height="15" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="9827" x="383" y="400" width="15" height="15" xoffset="0" yoffset="8" xadvance="16" page="0" chnl="15" />
+ <char id="9829" x="0" y="426" width="13" height="15" xoffset="0" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="9830" x="53" y="425" width="11" height="15" xoffset="0" yoffset="8" xadvance="12" page="0" chnl="15" />
+ <char id="9834" x="400" y="382" width="12" height="17" xoffset="0" yoffset="6" xadvance="12" page="0" chnl="15" />
+ <char id="9835" x="400" y="168" width="15" height="20" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ </chars>
+ <kernings count="397">
+ <kerning first="32" second="65" amount="-1" />
+ <kerning first="32" second="902" amount="-1" />
+ <kerning first="32" second="913" amount="-1" />
+ <kerning first="32" second="916" amount="-1" />
+ <kerning first="32" second="923" amount="-1" />
+ <kerning first="49" second="49" amount="-1" />
+ <kerning first="65" second="32" amount="-1" />
+ <kerning first="65" second="84" amount="-2" />
+ <kerning first="65" second="86" amount="-2" />
+ <kerning first="65" second="87" amount="-1" />
+ <kerning first="65" second="89" amount="-2" />
+ <kerning first="65" second="118" amount="-1" />
+ <kerning first="65" second="121" amount="-1" />
+ <kerning first="65" second="8217" amount="-1" />
+ <kerning first="70" second="44" amount="-2" />
+ <kerning first="70" second="46" amount="-2" />
+ <kerning first="70" second="65" amount="-1" />
+ <kerning first="76" second="84" amount="-2" />
+ <kerning first="76" second="86" amount="-2" />
+ <kerning first="76" second="87" amount="-1" />
+ <kerning first="76" second="89" amount="-2" />
+ <kerning first="76" second="121" amount="-1" />
+ <kerning first="76" second="8217" amount="-1" />
+ <kerning first="80" second="44" amount="-3" />
+ <kerning first="80" second="46" amount="-3" />
+ <kerning first="80" second="65" amount="-2" />
+ <kerning first="82" second="89" amount="-1" />
+ <kerning first="84" second="44" amount="-2" />
+ <kerning first="84" second="45" amount="-1" />
+ <kerning first="84" second="46" amount="-2" />
+ <kerning first="84" second="58" amount="-2" />
+ <kerning first="84" second="894" amount="-2" />
+ <kerning first="84" second="65" amount="-2" />
+ <kerning first="84" second="97" amount="-2" />
+ <kerning first="84" second="99" amount="-2" />
+ <kerning first="84" second="101" amount="-2" />
+ <kerning first="84" second="111" amount="-2" />
+ <kerning first="84" second="114" amount="-1" />
+ <kerning first="84" second="115" amount="-2" />
+ <kerning first="84" second="117" amount="-2" />
+ <kerning first="84" second="119" amount="-2" />
+ <kerning first="84" second="121" amount="-2" />
+ <kerning first="86" second="44" amount="-2" />
+ <kerning first="86" second="45" amount="-1" />
+ <kerning first="86" second="46" amount="-2" />
+ <kerning first="86" second="58" amount="-1" />
+ <kerning first="86" second="894" amount="-1" />
+ <kerning first="86" second="65" amount="-2" />
+ <kerning first="86" second="97" amount="-1" />
+ <kerning first="86" second="101" amount="-1" />
+ <kerning first="86" second="111" amount="-2" />
+ <kerning first="86" second="114" amount="-1" />
+ <kerning first="86" second="117" amount="-1" />
+ <kerning first="86" second="121" amount="-1" />
+ <kerning first="87" second="44" amount="-1" />
+ <kerning first="87" second="46" amount="-1" />
+ <kerning first="87" second="65" amount="-1" />
+ <kerning first="87" second="97" amount="-1" />
+ <kerning first="89" second="44" amount="-2" />
+ <kerning first="89" second="45" amount="-1" />
+ <kerning first="89" second="46" amount="-2" />
+ <kerning first="89" second="58" amount="-2" />
+ <kerning first="89" second="894" amount="-2" />
+ <kerning first="89" second="65" amount="-2" />
+ <kerning first="89" second="97" amount="-1" />
+ <kerning first="89" second="101" amount="-1" />
+ <kerning first="89" second="105" amount="-1" />
+ <kerning first="89" second="111" amount="-2" />
+ <kerning first="89" second="112" amount="-1" />
+ <kerning first="89" second="113" amount="-2" />
+ <kerning first="89" second="117" amount="-1" />
+ <kerning first="89" second="118" amount="-1" />
+ <kerning first="114" second="44" amount="-1" />
+ <kerning first="114" second="46" amount="-1" />
+ <kerning first="114" second="8217" amount="1" />
+ <kerning first="118" second="44" amount="-2" />
+ <kerning first="118" second="46" amount="-2" />
+ <kerning first="119" second="44" amount="-1" />
+ <kerning first="119" second="46" amount="-1" />
+ <kerning first="121" second="44" amount="-2" />
+ <kerning first="121" second="46" amount="-2" />
+ <kerning first="8216" second="8216" amount="-1" />
+ <kerning first="8217" second="32" amount="-1" />
+ <kerning first="8217" second="115" amount="-1" />
+ <kerning first="8217" second="8217" amount="-1" />
+ <kerning first="8222" second="1026" amount="-2" />
+ <kerning first="8222" second="1035" amount="-2" />
+ <kerning first="8222" second="1058" amount="-2" />
+ <kerning first="8222" second="1063" amount="-2" />
+ <kerning first="8222" second="1066" amount="-2" />
+ <kerning first="915" second="44" amount="-2" />
+ <kerning first="915" second="46" amount="-2" />
+ <kerning first="915" second="913" amount="-2" />
+ <kerning first="915" second="916" amount="-2" />
+ <kerning first="915" second="923" amount="-2" />
+ <kerning first="915" second="943" amount="-1" />
+ <kerning first="915" second="953" amount="-1" />
+ <kerning first="915" second="970" amount="1" />
+ <kerning first="948" second="967" amount="-1" />
+ <kerning first="966" second="967" amount="-1" />
+ <kerning first="902" second="932" amount="-2" />
+ <kerning first="902" second="933" amount="-2" />
+ <kerning first="902" second="939" amount="-2" />
+ <kerning first="902" second="947" amount="-1" />
+ <kerning first="902" second="957" amount="-1" />
+ <kerning first="902" second="967" amount="-1" />
+ <kerning first="910" second="920" amount="-1" />
+ <kerning first="910" second="934" amount="-1" />
+ <kerning first="910" second="945" amount="-2" />
+ <kerning first="910" second="948" amount="-1" />
+ <kerning first="910" second="963" amount="-2" />
+ <kerning first="910" second="966" amount="-2" />
+ <kerning first="910" second="912" amount="3" />
+ <kerning first="910" second="913" amount="-2" />
+ <kerning first="910" second="916" amount="-2" />
+ <kerning first="910" second="923" amount="-2" />
+ <kerning first="910" second="927" amount="-1" />
+ <kerning first="910" second="937" amount="-1" />
+ <kerning first="910" second="940" amount="-2" />
+ <kerning first="910" second="942" amount="-1" />
+ <kerning first="910" second="943" amount="-1" />
+ <kerning first="910" second="951" amount="-1" />
+ <kerning first="910" second="953" amount="-1" />
+ <kerning first="910" second="954" amount="-1" />
+ <kerning first="910" second="956" amount="-1" />
+ <kerning first="910" second="959" amount="-2" />
+ <kerning first="910" second="970" amount="1" />
+ <kerning first="910" second="972" amount="-2" />
+ <kerning first="913" second="8217" amount="-1" />
+ <kerning first="913" second="932" amount="-2" />
+ <kerning first="913" second="933" amount="-2" />
+ <kerning first="913" second="939" amount="-2" />
+ <kerning first="913" second="947" amount="-1" />
+ <kerning first="913" second="957" amount="-1" />
+ <kerning first="913" second="967" amount="-1" />
+ <kerning first="916" second="932" amount="-2" />
+ <kerning first="916" second="933" amount="-2" />
+ <kerning first="916" second="939" amount="-2" />
+ <kerning first="922" second="920" amount="-1" />
+ <kerning first="922" second="934" amount="-1" />
+ <kerning first="922" second="927" amount="-1" />
+ <kerning first="923" second="932" amount="-2" />
+ <kerning first="923" second="933" amount="-2" />
+ <kerning first="923" second="939" amount="-2" />
+ <kerning first="929" second="44" amount="-3" />
+ <kerning first="929" second="46" amount="-3" />
+ <kerning first="929" second="913" amount="-2" />
+ <kerning first="929" second="916" amount="-2" />
+ <kerning first="929" second="923" amount="-2" />
+ <kerning first="932" second="44" amount="-2" />
+ <kerning first="932" second="45" amount="-1" />
+ <kerning first="932" second="46" amount="-2" />
+ <kerning first="932" second="58" amount="-2" />
+ <kerning first="932" second="894" amount="-2" />
+ <kerning first="932" second="945" amount="-2" />
+ <kerning first="932" second="948" amount="-1" />
+ <kerning first="932" second="949" amount="-2" />
+ <kerning first="932" second="963" amount="-2" />
+ <kerning first="932" second="966" amount="-2" />
+ <kerning first="932" second="912" amount="3" />
+ <kerning first="932" second="913" amount="-2" />
+ <kerning first="932" second="916" amount="-2" />
+ <kerning first="932" second="923" amount="-2" />
+ <kerning first="932" second="940" amount="-2" />
+ <kerning first="932" second="941" amount="-2" />
+ <kerning first="932" second="947" amount="-2" />
+ <kerning first="932" second="951" amount="-2" />
+ <kerning first="932" second="956" amount="-2" />
+ <kerning first="932" second="957" amount="-2" />
+ <kerning first="932" second="959" amount="-2" />
+ <kerning first="932" second="965" amount="-2" />
+ <kerning first="932" second="967" amount="-1" />
+ <kerning first="932" second="968" amount="-2" />
+ <kerning first="932" second="970" amount="1" />
+ <kerning first="932" second="971" amount="-2" />
+ <kerning first="932" second="972" amount="-2" />
+ <kerning first="932" second="973" amount="-2" />
+ <kerning first="933" second="44" amount="-2" />
+ <kerning first="933" second="45" amount="-1" />
+ <kerning first="933" second="46" amount="-2" />
+ <kerning first="933" second="58" amount="-2" />
+ <kerning first="933" second="894" amount="-2" />
+ <kerning first="933" second="920" amount="-1" />
+ <kerning first="933" second="934" amount="-1" />
+ <kerning first="933" second="945" amount="-2" />
+ <kerning first="933" second="948" amount="-1" />
+ <kerning first="933" second="963" amount="-2" />
+ <kerning first="933" second="966" amount="-2" />
+ <kerning first="933" second="912" amount="3" />
+ <kerning first="933" second="913" amount="-2" />
+ <kerning first="933" second="916" amount="-2" />
+ <kerning first="933" second="923" amount="-2" />
+ <kerning first="933" second="927" amount="-1" />
+ <kerning first="933" second="937" amount="-1" />
+ <kerning first="933" second="940" amount="-2" />
+ <kerning first="933" second="942" amount="-1" />
+ <kerning first="933" second="943" amount="-1" />
+ <kerning first="933" second="947" amount="-1" />
+ <kerning first="933" second="951" amount="-1" />
+ <kerning first="933" second="953" amount="-1" />
+ <kerning first="933" second="954" amount="-1" />
+ <kerning first="933" second="956" amount="-1" />
+ <kerning first="933" second="959" amount="-2" />
+ <kerning first="933" second="970" amount="1" />
+ <kerning first="933" second="972" amount="-2" />
+ <kerning first="939" second="920" amount="-1" />
+ <kerning first="939" second="934" amount="-1" />
+ <kerning first="939" second="945" amount="-2" />
+ <kerning first="939" second="948" amount="-1" />
+ <kerning first="939" second="963" amount="-2" />
+ <kerning first="939" second="966" amount="-2" />
+ <kerning first="939" second="912" amount="3" />
+ <kerning first="939" second="913" amount="-2" />
+ <kerning first="939" second="916" amount="-2" />
+ <kerning first="939" second="923" amount="-2" />
+ <kerning first="939" second="927" amount="-1" />
+ <kerning first="939" second="937" amount="-1" />
+ <kerning first="939" second="940" amount="-2" />
+ <kerning first="939" second="942" amount="-1" />
+ <kerning first="939" second="943" amount="-1" />
+ <kerning first="939" second="951" amount="-1" />
+ <kerning first="939" second="953" amount="-1" />
+ <kerning first="939" second="954" amount="-1" />
+ <kerning first="939" second="956" amount="-1" />
+ <kerning first="939" second="959" amount="-2" />
+ <kerning first="939" second="970" amount="1" />
+ <kerning first="939" second="972" amount="-2" />
+ <kerning first="950" second="945" amount="-1" />
+ <kerning first="950" second="948" amount="-1" />
+ <kerning first="950" second="963" amount="-1" />
+ <kerning first="950" second="964" amount="-1" />
+ <kerning first="950" second="966" amount="-1" />
+ <kerning first="950" second="940" amount="-1" />
+ <kerning first="950" second="947" amount="-1" />
+ <kerning first="950" second="952" amount="-1" />
+ <kerning first="950" second="957" amount="-1" />
+ <kerning first="950" second="959" amount="-1" />
+ <kerning first="950" second="969" amount="-1" />
+ <kerning first="950" second="972" amount="-1" />
+ <kerning first="950" second="974" amount="-1" />
+ <kerning first="950" second="960" amount="-1" />
+ <kerning first="954" second="945" amount="-1" />
+ <kerning first="954" second="948" amount="-1" />
+ <kerning first="954" second="963" amount="-1" />
+ <kerning first="954" second="966" amount="-1" />
+ <kerning first="954" second="940" amount="-1" />
+ <kerning first="954" second="950" amount="-1" />
+ <kerning first="954" second="958" amount="-1" />
+ <kerning first="954" second="959" amount="-1" />
+ <kerning first="954" second="962" amount="-1" />
+ <kerning first="954" second="969" amount="-1" />
+ <kerning first="954" second="972" amount="-1" />
+ <kerning first="954" second="974" amount="-1" />
+ <kerning first="959" second="967" amount="-1" />
+ <kerning first="967" second="945" amount="-1" />
+ <kerning first="967" second="948" amount="-1" />
+ <kerning first="967" second="963" amount="-1" />
+ <kerning first="967" second="966" amount="-1" />
+ <kerning first="967" second="940" amount="-1" />
+ <kerning first="967" second="950" amount="-1" />
+ <kerning first="967" second="959" amount="-1" />
+ <kerning first="967" second="962" amount="-1" />
+ <kerning first="967" second="972" amount="-1" />
+ <kerning first="972" second="967" amount="-1" />
+ <kerning first="1027" second="44" amount="-2" />
+ <kerning first="1027" second="46" amount="-3" />
+ <kerning first="1027" second="171" amount="-1" />
+ <kerning first="1027" second="187" amount="-1" />
+ <kerning first="1033" second="8217" amount="-2" />
+ <kerning first="1034" second="8217" amount="-2" />
+ <kerning first="1040" second="8217" amount="-1" />
+ <kerning first="1040" second="1044" amount="1" />
+ <kerning first="1040" second="1058" amount="-1" />
+ <kerning first="1040" second="1059" amount="-1" />
+ <kerning first="1040" second="1063" amount="-2" />
+ <kerning first="1041" second="1040" amount="-1" />
+ <kerning first="1041" second="1063" amount="-1" />
+ <kerning first="1041" second="1066" amount="-1" />
+ <kerning first="1042" second="1040" amount="-1" />
+ <kerning first="1042" second="1046" amount="-1" />
+ <kerning first="1042" second="1057" amount="-1" />
+ <kerning first="1042" second="1058" amount="-1" />
+ <kerning first="1042" second="1059" amount="-1" />
+ <kerning first="1042" second="1061" amount="-1" />
+ <kerning first="1042" second="1063" amount="-1" />
+ <kerning first="1042" second="1066" amount="-1" />
+ <kerning first="1042" second="1095" amount="-1" />
+ <kerning first="1043" second="44" amount="-2" />
+ <kerning first="1043" second="46" amount="-3" />
+ <kerning first="1043" second="171" amount="-1" />
+ <kerning first="1043" second="187" amount="-1" />
+ <kerning first="1043" second="1040" amount="-1" />
+ <kerning first="1043" second="1076" amount="-1" />
+ <kerning first="1043" second="1077" amount="-1" />
+ <kerning first="1043" second="1083" amount="-1" />
+ <kerning first="1043" second="1084" amount="-1" />
+ <kerning first="1043" second="1086" amount="-1" />
+ <kerning first="1043" second="1088" amount="-1" />
+ <kerning first="1043" second="1091" amount="-1" />
+ <kerning first="1043" second="1099" amount="-1" />
+ <kerning first="1043" second="1100" amount="-1" />
+ <kerning first="1043" second="1102" amount="-1" />
+ <kerning first="1043" second="1103" amount="-1" />
+ <kerning first="1046" second="1066" amount="1" />
+ <kerning first="1047" second="1058" amount="-1" />
+ <kerning first="1047" second="1059" amount="-1" />
+ <kerning first="1047" second="1063" amount="-1" />
+ <kerning first="1050" second="1047" amount="1" />
+ <kerning first="1054" second="1061" amount="-1" />
+ <kerning first="1056" second="44" amount="-3" />
+ <kerning first="1056" second="46" amount="-3" />
+ <kerning first="1056" second="1040" amount="-2" />
+ <kerning first="1056" second="1044" amount="-1" />
+ <kerning first="1056" second="1051" amount="-1" />
+ <kerning first="1056" second="1061" amount="-1" />
+ <kerning first="1056" second="1076" amount="-1" />
+ <kerning first="1057" second="1061" amount="-1" />
+ <kerning first="1058" second="44" amount="-2" />
+ <kerning first="1058" second="46" amount="-2" />
+ <kerning first="1058" second="1040" amount="-1" />
+ <kerning first="1058" second="1060" amount="-1" />
+ <kerning first="1058" second="1074" amount="-1" />
+ <kerning first="1058" second="1077" amount="-1" />
+ <kerning first="1058" second="1080" amount="-1" />
+ <kerning first="1058" second="1082" amount="-1" />
+ <kerning first="1058" second="1083" amount="-1" />
+ <kerning first="1058" second="1084" amount="-1" />
+ <kerning first="1058" second="1086" amount="-1" />
+ <kerning first="1058" second="1088" amount="-1" />
+ <kerning first="1058" second="1089" amount="-1" />
+ <kerning first="1058" second="1091" amount="-1" />
+ <kerning first="1058" second="1093" amount="-1" />
+ <kerning first="1059" second="44" amount="-2" />
+ <kerning first="1059" second="46" amount="-3" />
+ <kerning first="1059" second="171" amount="-1" />
+ <kerning first="1059" second="187" amount="-1" />
+ <kerning first="1059" second="1040" amount="-2" />
+ <kerning first="1059" second="1044" amount="-1" />
+ <kerning first="1059" second="1051" amount="-1" />
+ <kerning first="1059" second="1060" amount="-1" />
+ <kerning first="1059" second="1074" amount="-1" />
+ <kerning first="1059" second="1075" amount="-1" />
+ <kerning first="1059" second="1076" amount="-1" />
+ <kerning first="1059" second="1077" amount="-1" />
+ <kerning first="1059" second="1079" amount="-1" />
+ <kerning first="1059" second="1080" amount="-1" />
+ <kerning first="1059" second="1081" amount="-1" />
+ <kerning first="1059" second="1082" amount="-1" />
+ <kerning first="1059" second="1083" amount="-1" />
+ <kerning first="1059" second="1084" amount="-1" />
+ <kerning first="1059" second="1085" amount="-1" />
+ <kerning first="1059" second="1086" amount="-1" />
+ <kerning first="1059" second="1087" amount="-1" />
+ <kerning first="1059" second="1088" amount="-1" />
+ <kerning first="1059" second="1089" amount="-1" />
+ <kerning first="1059" second="1094" amount="-1" />
+ <kerning first="1059" second="1096" amount="-1" />
+ <kerning first="1059" second="1097" amount="-1" />
+ <kerning first="1059" second="1102" amount="-1" />
+ <kerning first="1059" second="1103" amount="-1" />
+ <kerning first="1060" second="1044" amount="-1" />
+ <kerning first="1060" second="1051" amount="-1" />
+ <kerning first="1060" second="1058" amount="-1" />
+ <kerning first="1060" second="1059" amount="-1" />
+ <kerning first="1061" second="1057" amount="-1" />
+ <kerning first="1061" second="1060" amount="-1" />
+ <kerning first="1062" second="1072" amount="1" />
+ <kerning first="1066" second="8217" amount="-2" />
+ <kerning first="1066" second="1071" amount="-1" />
+ <kerning first="1068" second="8217" amount="-2" />
+ <kerning first="1068" second="1046" amount="-1" />
+ <kerning first="1068" second="1051" amount="-1" />
+ <kerning first="1068" second="1058" amount="-2" />
+ <kerning first="1068" second="1061" amount="-1" />
+ <kerning first="1068" second="1063" amount="-2" />
+ <kerning first="1068" second="1069" amount="-1" />
+ <kerning first="1068" second="1071" amount="-1" />
+ <kerning first="1069" second="1051" amount="-1" />
+ <kerning first="1070" second="1051" amount="-1" />
+ <kerning first="1070" second="1061" amount="-1" />
+ <kerning first="1074" second="1095" amount="-1" />
+ <kerning first="1075" second="44" amount="-2" />
+ <kerning first="1075" second="46" amount="-2" />
+ <kerning first="1075" second="1076" amount="-1" />
+ <kerning first="1079" second="1095" amount="-1" />
+ <kerning first="1090" second="44" amount="-2" />
+ <kerning first="1090" second="46" amount="-2" />
+ <kerning first="1091" second="44" amount="-2" />
+ <kerning first="1091" second="46" amount="-2" />
+ <kerning first="1100" second="1090" amount="-2" />
+ <kerning first="1100" second="1095" amount="-2" />
+ <kerning first="1102" second="1095" amount="-1" />
+ <kerning first="1118" second="44" amount="-2" />
+ <kerning first="1118" second="46" amount="-2" />
+ <kerning first="1168" second="44" amount="-1" />
+ <kerning first="1168" second="46" amount="-2" />
+ <kerning first="960" second="955" amount="-1" />
+ </kernings>
+</font>
diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.png b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.png Binary files differnew file mode 100644 index 0000000..11d37da --- /dev/null +++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.png diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java new file mode 100644 index 0000000..e6f98e2 --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java @@ -0,0 +1,40 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import org.junit.Test;
+
+import com.ardor3d.math.Vector3;
+
+public class TestBounding {
+ @Test
+ public void testBoundingBoxMerge() throws Exception {
+ final BoundingBox obb = new BoundingBox();
+ obb.setCenter(Vector3.ZERO);
+ obb.setXExtent(1);
+ obb.setYExtent(1);
+ obb.setZExtent(1);
+
+ // final ReadOnlyVector3 center = sceneBounds.getCenter();
+ // for (int i = 0; i < _corners.length; i++) {
+ // _corners[i].set(center);
+ // }
+ //
+ // if (sceneBounds instanceof BoundingBox) {
+ // final BoundingBox bbox = (BoundingBox) sceneBounds;
+ // bbox.getExtent(_extents);
+ // } else if (sceneBounds instanceof BoundingSphere) {
+ // final BoundingSphere bsphere = (BoundingSphere) sceneBounds;
+ // _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius());
+ // }
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java new file mode 100644 index 0000000..347ff4e --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java @@ -0,0 +1,94 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Vector3;
+
+public class TestRayBounding {
+ @Test
+ public void testRayAABBIntersection() throws Exception {
+ final BoundingBox obb = new BoundingBox();
+ obb.setCenter(Vector3.ZERO);
+ obb.setXExtent(1);
+ obb.setYExtent(1);
+ obb.setZExtent(1);
+
+ Ray3 ray = new Ray3(new Vector3(2, -10, 0), Vector3.UNIT_Y);
+ assertFalse(obb.intersects(ray));
+ IntersectionRecord record = obb.intersectsWhere(ray);
+ assertEquals(null, record);
+
+ final Quaternion rotation = new Quaternion();
+ rotation.fromAngleAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Z);
+ final Transform transform = new Transform();
+ transform.setRotation(rotation);
+ obb.transform(transform, obb);
+
+ ray = new Ray3(new Vector3(1, -10, 0), Vector3.UNIT_Y);
+ assertTrue(obb.intersects(ray));
+ record = obb.intersectsWhere(ray);
+ assertEquals(2, record.getNumberOfIntersections());
+ }
+
+ @Test
+ public void testRayOBBIntersection() throws Exception {
+ final OrientedBoundingBox obb = new OrientedBoundingBox();
+ obb.setCenter(Vector3.ZERO);
+ obb.setExtent(Vector3.ONE);
+
+ Ray3 ray = new Ray3(new Vector3(1.2, -10, 0), Vector3.UNIT_Y);
+ assertFalse(obb.intersects(ray));
+ IntersectionRecord record = obb.intersectsWhere(ray);
+ assertEquals(null, record);
+
+ final Quaternion rotation = new Quaternion();
+ rotation.fromAngleAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Z);
+ final Transform transform = new Transform();
+ transform.setRotation(rotation);
+ obb.transform(transform, obb);
+
+ ray = new Ray3(new Vector3(1.2, -10, 0), Vector3.UNIT_Y);
+ assertTrue(obb.intersects(ray));
+ record = obb.intersectsWhere(ray);
+ assertEquals(2, record.getNumberOfIntersections());
+ }
+
+ @Test
+ public void testRaySphereIntersection() throws Exception {
+ final BoundingSphere bs = new BoundingSphere();
+ bs.setCenter(Vector3.ZERO);
+ bs.setRadius(1);
+
+ final Ray3 ray = new Ray3(new Vector3(2, -3, 0), Vector3.UNIT_Y);
+ assertFalse(bs.intersects(ray));
+ IntersectionRecord record = bs.intersectsWhere(ray);
+ assertEquals(null, record);
+
+ final Transform transform = new Transform();
+ transform.setTranslation(2, 0, .5);
+ bs.transform(transform, bs);
+
+ assertTrue(bs.intersects(ray));
+ record = bs.intersectsWhere(ray);
+ assertEquals(2, record.getNumberOfIntersections());
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java new file mode 100644 index 0000000..741d7de --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java @@ -0,0 +1,81 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.EnumSet;
+
+import org.junit.Test;
+
+public class TestKeyboardState {
+ KeyboardState ks1, ks2;
+
+ @Test
+ public void testKeysReleased1() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.B), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A, Key.E), KeyEvent.NOTHING);
+
+ final EnumSet<Key> released = ks2.getKeysReleasedSince(ks1);
+
+ assertEquals("1 key", 1, released.size());
+ assertTrue("b released", released.contains(Key.B));
+ }
+
+ @Test
+ public void testKeysReleased2() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.B), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+
+ final EnumSet<Key> released = ks2.getKeysReleasedSince(ks1);
+
+ assertEquals("2 key", 2, released.size());
+ assertTrue("a released", released.contains(Key.A));
+ assertTrue("b released", released.contains(Key.B));
+ }
+
+ @Test
+ public void testKeysPressed1() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.B), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A, Key.C, Key.D), KeyEvent.NOTHING);
+
+ final EnumSet<Key> pressed = ks2.getKeysPressedSince(ks1);
+
+ assertEquals("2 key", 2, pressed.size());
+ assertTrue("c pressed", pressed.contains(Key.C));
+ assertTrue("d pressed", pressed.contains(Key.D));
+ }
+
+ @Test
+ public void testKeysPressed2() throws Exception {
+ ks1 = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A, Key.C, Key.D), KeyEvent.NOTHING);
+
+ final EnumSet<Key> pressed = ks2.getKeysPressedSince(ks1);
+
+ assertEquals("2 key", 3, pressed.size());
+ assertTrue("a pressed", pressed.contains(Key.A));
+ assertTrue("c pressed", pressed.contains(Key.C));
+ assertTrue("d pressed", pressed.contains(Key.D));
+ }
+
+ @Test
+ public void testKeysPressed3() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.C, Key.D), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A), KeyEvent.NOTHING);
+
+ final EnumSet<Key> pressed = ks2.getKeysPressedSince(ks1);
+
+ assertEquals("0 key", 0, pressed.size());
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java new file mode 100644 index 0000000..2b769ea --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java @@ -0,0 +1,389 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.PeekingIterator;
+
+public class TestPhysicalLayer {
+ KeyboardWrapper keyboardWrapper;
+ MouseWrapper mouseWrapper;
+ FocusWrapper focusWrapper;
+ ControllerWrapper controllerWrapper;
+ PhysicalLayer pl;
+
+ Object[] mocks;
+
+ List<KeyEvent> noKeys = new LinkedList<KeyEvent>();
+ List<KeyEvent> Adown = new LinkedList<KeyEvent>();
+ List<KeyEvent> AdownBdown = new LinkedList<KeyEvent>();
+ List<KeyEvent> AdownAup = new LinkedList<KeyEvent>();
+
+ List<ControllerEvent> nothing = new LinkedList<ControllerEvent>();
+
+ List<MouseState> buttonDown = new LinkedList<MouseState>();
+ List<MouseState> noMice = new LinkedList<MouseState>();
+
+ List<InputState> inputStates;
+ InputState is;
+
+ // @SuppressWarnings( { "unchecked" })
+ @Before
+ public void setup() throws Exception {
+ keyboardWrapper = createMock("KeyboardWrapper", KeyboardWrapper.class);
+ mouseWrapper = createMock("MouseWrapper", MouseWrapper.class);
+ controllerWrapper = createMock("ControllerWrapper", ControllerWrapper.class);
+ focusWrapper = createMock("FocusWrapper", FocusWrapper.class);
+
+ pl = new PhysicalLayer(keyboardWrapper, mouseWrapper, controllerWrapper, focusWrapper);
+
+ mocks = new Object[] { keyboardWrapper, mouseWrapper, controllerWrapper, focusWrapper };
+
+ Adown.add(new KeyEvent(Key.A, KeyState.DOWN, 'a'));
+
+ AdownBdown.add(new KeyEvent(Key.A, KeyState.DOWN, 'a'));
+ AdownBdown.add(new KeyEvent(Key.B, KeyState.DOWN, 'b'));
+
+ AdownAup.add(new KeyEvent(Key.A, KeyState.DOWN, 'a'));
+ AdownAup.add(new KeyEvent(Key.A, KeyState.UP, 'a'));
+
+ buttonDown.add(new MouseState(0, 0, 0, 0, 0, MouseButton.makeMap(ButtonState.DOWN, ButtonState.UP,
+ ButtonState.UP), null));
+ }
+
+ @After
+ public void verifyMocks() throws Exception {
+ verify(mocks);
+ }
+
+ @Test
+ public void testKeyboardBasic1() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ controllerWrapper.init();
+ focusWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(Adown.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(3);
+
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(3);
+
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).atLeastOnce();
+
+ replay(mocks);
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("1 state", 1, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("0 states", 0, inputStates.size());
+ }
+
+ @Test
+ public void testKeyboardBasic2() throws Exception {
+ final PeekingIterator<KeyEvent> adau = Iterators.peekingIterator(AdownAup.iterator());
+
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ controllerWrapper.init();
+ focusWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(adau);
+ expect(keyboardWrapper.getEvents()).andReturn(adau);
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(4);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(4);
+
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).times(2);
+
+ replay(mocks);
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ is = inputStates.get(1);
+
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("0 states", 0, inputStates.size());
+ }
+
+ @Test
+ public void testKeyboardBasic3() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ final PeekingIterator<KeyEvent> keyIterator = Iterators.peekingIterator(AdownBdown.iterator());
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(keyIterator).times(4);
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(4);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(4);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).times(2);
+
+ replay(mocks);
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ is = inputStates.get(1);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertTrue("b down", is.getKeyboardState().isDown(Key.B));
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("0 states", 0, inputStates.size());
+ }
+
+ @Test
+ public void testTooManyChanges1() throws Exception {
+ final PeekingIterator<KeyEvent> iter = new NeverEndingKeyIterator();
+
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(iter).atLeastOnce();
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).atLeastOnce();
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).atLeastOnce();
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).atLeastOnce();
+
+ replay(mocks);
+
+ pl.readState();
+ }
+
+ @Test
+ public void testTooManyChanges2() throws Exception {
+ final PeekingIterator<MouseState> iter = new NeverEndingMouseIterator();
+
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator())).atLeastOnce();
+ expect(mouseWrapper.getEvents()).andReturn(iter).atLeastOnce();
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).atLeastOnce();
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).atLeastOnce();
+
+ replay(mocks);
+
+ pl.readState();
+ }
+
+ @Test
+ public void testLostFocus1() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(Adown.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(2);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(2);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(true);
+
+ replay(mocks);
+
+ pl.readState();
+
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ assertTrue("lost focus", InputState.LOST_FOCUS == inputStates.get(1));
+ }
+
+ @Test
+ public void testLostFocus2() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(Adown.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(3);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(3);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(true);
+
+ replay(mocks);
+
+ pl.readState();
+ pl.readState();
+
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ is = inputStates.get(1);
+
+ assertEquals("lost focus", InputState.LOST_FOCUS, is);
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+ }
+
+ @Test
+ public void testLostFocus3() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator())).times(3);
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(buttonDown.iterator()));
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(2);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(3);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(true);
+
+ replay(mocks);
+
+ pl.readState();
+ pl.readState();
+
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+ assertEquals("mb down", ButtonState.DOWN, is.getMouseState().getButtonState(MouseButton.LEFT));
+
+ is = inputStates.get(1);
+
+ assertEquals("lost focus", InputState.LOST_FOCUS, is);
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+ assertEquals("mb up", ButtonState.UP, is.getMouseState().getButtonState(MouseButton.LEFT));
+ }
+
+ private static class NeverEndingKeyIterator extends AbstractIterator<KeyEvent> implements PeekingIterator<KeyEvent> {
+ final KeyEvent aUp = new KeyEvent(Key.A, KeyState.UP, 'a');
+ final KeyEvent aDown = new KeyEvent(Key.A, KeyState.DOWN, 'a');
+
+ int count = 0;
+
+ @Override
+ protected KeyEvent computeNext() {
+ count++;
+
+ if (count % 2 == 0) {
+ return aUp;
+ }
+
+ return aDown;
+ }
+ }
+
+ private static class NeverEndingMouseIterator extends AbstractIterator<MouseState> implements
+ PeekingIterator<MouseState> {
+ final MouseState m1 = new MouseState(0, 0, 0, 0, 0, null, null);
+ final MouseState m2 = new MouseState(0, 1, 2, 0, 0, null, null);
+
+ int count = 0;
+
+ @Override
+ protected MouseState computeNext() {
+ count++;
+
+ if (count % 2 == 0) {
+ return m1;
+ }
+
+ return m2;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java new file mode 100644 index 0000000..1003e11 --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java @@ -0,0 +1,249 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+
+import java.util.EnumSet;
+import java.util.LinkedList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.ControllerState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.KeyEvent;
+import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.PhysicalLayer;
+import com.google.common.base.Predicate;
+
+/**
+ * Tests for the LogicalLayer
+ */
+public class TestLogicalLayer {
+ LogicalLayer ll;
+ PhysicalLayer pl;
+
+ TriggerAction ta1, ta2;
+
+ Predicate<TwoInputStates> p1;
+ Predicate<TwoInputStates> p2;
+
+ KeyboardState ks;
+ MouseState ms;
+ ControllerState cs;
+
+ Canvas canvas;
+
+ Object[] mocks;
+
+ @SuppressWarnings( { "unchecked" })
+ @Before
+ public void setup() throws Exception {
+ pl = createMock("Physicallayer", PhysicalLayer.class);
+
+ ta1 = createMock("TA1", TriggerAction.class);
+ ta2 = createMock("TA2", TriggerAction.class);
+
+ p1 = createMock("P1", Predicate.class);
+ p2 = createMock("P2", Predicate.class);
+
+ canvas = createMock("canvas", Canvas.class);
+
+ ll = new LogicalLayer();
+
+ ll.registerInput(canvas, pl);
+
+ ks = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+ ms = new MouseState(0, 0, 0, 0, 0, MouseButton.makeMap(ButtonState.UP, ButtonState.UP, ButtonState.UP), null);
+ cs = new ControllerState();
+
+ mocks = new Object[] { pl, ta1, ta2, p1, p2, canvas };
+ }
+
+ @After
+ public void verifyMocks() throws Exception {
+ verify(mocks);
+ }
+
+ @Test
+ public void testTriggers1() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state2), tpf);
+
+ replay(mocks);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testTriggers2() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state2), tpf);
+ ta2.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+ ta2.perform(canvas, new TwoInputStates(state1, state2), tpf);
+
+ replay(mocks);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+ ll.registerTrigger(new InputTrigger(p2, ta2));
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testTriggers3() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state2), tpf);
+ ta2.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+
+ replay(mocks);
+
+ final InputTrigger trigger2 = new InputTrigger(p2, ta2);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+ ll.registerTrigger(trigger2);
+
+ ll.checkTriggers(tpf);
+ ll.deregisterTrigger(trigger2);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testTriggers4() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state1), tpf);
+ ta2.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+ ta2.perform(canvas, new TwoInputStates(state1, state1), tpf);
+
+ replay(mocks);
+
+ final InputTrigger trigger2 = new InputTrigger(p2, ta2);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+ ll.registerTrigger(trigger2);
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testLostFocus() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(InputState.LOST_FOCUS);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ ta1.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+
+ replay(mocks);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java new file mode 100644 index 0000000..8da445d --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java @@ -0,0 +1,202 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import java.util.EnumMap;
+import java.util.EnumSet;
+
+import org.junit.Test;
+
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.ControllerState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.KeyEvent;
+import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseState;
+
+public class TestStandardConditions {
+ final KeyboardState ks = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+ final MouseState ms = new MouseState(0, 0, 0, 0, 0, MouseButton.makeMap(ButtonState.UP, ButtonState.UP,
+ ButtonState.UP), null);
+ final ControllerState cs = new ControllerState();
+ InputState is1, is2, is3, is4, is5;
+
+ KeyboardState aDown = new KeyboardState(EnumSet.of(Key.A), KeyEvent.NOTHING);
+ KeyboardState bDown = new KeyboardState(EnumSet.of(Key.B), KeyEvent.NOTHING);
+
+ EnumMap<MouseButton, ButtonState> bothUp = MouseButton.makeMap(ButtonState.UP, ButtonState.UP, ButtonState.UP);
+ EnumMap<MouseButton, ButtonState> upDown = MouseButton.makeMap(ButtonState.UP, ButtonState.DOWN, ButtonState.UP);
+ EnumMap<MouseButton, ButtonState> downUp = MouseButton.makeMap(ButtonState.DOWN, ButtonState.UP, ButtonState.UP);
+ EnumMap<MouseButton, ButtonState> bothDown = MouseButton
+ .makeMap(ButtonState.DOWN, ButtonState.DOWN, ButtonState.UP);
+
+ @Test
+ public void testKeyHeld1() throws Exception {
+ final KeyHeldCondition kh = new KeyHeldCondition(Key.A);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(aDown, ms, cs);
+ is3 = new InputState(bDown, ms, cs);
+
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is1)));
+ assertTrue("down", kh.apply(new TwoInputStates(is1, is2)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is3)));
+ assertTrue("not down", kh.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", kh.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", kh.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", kh.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", kh.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testKeyHeld2() throws Exception {
+ new KeyHeldCondition(null);
+ }
+
+ @Test
+ public void testKeyPressed() throws Exception {
+ final KeyPressedCondition kh = new KeyPressedCondition(Key.A);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(aDown, ms, cs);
+ is3 = new InputState(bDown, ms, cs);
+
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is1)));
+ assertTrue("down", kh.apply(new TwoInputStates(is1, is2)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", kh.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", kh.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", kh.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", kh.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testKeyPressedNull() throws Exception {
+ new KeyPressedCondition(null);
+ }
+
+ @Test
+ public void testKeyReleased() throws Exception {
+ final KeyReleasedCondition kh = new KeyReleasedCondition(Key.A);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(aDown, ms, cs);
+ is3 = new InputState(bDown, ms, cs);
+
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is1)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is2)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is3)));
+ assertTrue("not down", kh.apply(new TwoInputStates(is2, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", kh.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", kh.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", kh.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertFalse("empty4", kh.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testKeyReleasedNull() throws Exception {
+ new KeyReleasedCondition(null);
+ }
+
+ @Test
+ public void testMouseMove() throws Exception {
+ final MouseMovedCondition mm = TriggerConditions.mouseMoved();
+
+ final MouseState ms2 = new MouseState(1, 0, 1, 0, 0, bothUp, null);
+ final MouseState ms3 = new MouseState(1, 0, 0, 0, 0, bothDown, null);
+ final MouseState ms4 = new MouseState(3, 1, 2, 1, 0, bothDown, null);
+ final MouseState ms5 = new MouseState(3, 0, 0, -1, 0, bothDown, null);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(ks, ms2, cs);
+ is3 = new InputState(ks, ms3, cs);
+ is4 = new InputState(ks, ms4, cs);
+ is5 = new InputState(ks, ms5, cs);
+
+ assertFalse("mm1", mm.apply(new TwoInputStates(is1, is1)));
+ assertTrue("mm2", mm.apply(new TwoInputStates(is1, is2)));
+ assertFalse("mm3", mm.apply(new TwoInputStates(is2, is3)));
+ assertTrue("mm4", mm.apply(new TwoInputStates(is3, is4)));
+ assertTrue("mm5", mm.apply(new TwoInputStates(is4, is5)));
+ assertFalse("mm6", mm.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", mm.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", mm.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", mm.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", mm.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test
+ public void testMouseButton1() throws Exception {
+ final MouseButtonCondition mm = TriggerConditions.leftButtonDown();
+
+ final MouseState ms2 = new MouseState(1, 0, 1, 0, 0, bothUp, null);
+ final MouseState ms3 = new MouseState(1, 0, 0, 0, 0, bothDown, null);
+ final MouseState ms4 = new MouseState(3, 1, 2, 1, 0, upDown, null);
+ final MouseState ms5 = new MouseState(3, 0, 0, -1, 0, downUp, null);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(ks, ms2, cs);
+ is3 = new InputState(ks, ms3, cs);
+ is4 = new InputState(ks, ms4, cs);
+ is5 = new InputState(ks, ms5, cs);
+
+ assertFalse("mm1", mm.apply(new TwoInputStates(is1, is1)));
+ assertFalse("mm2", mm.apply(new TwoInputStates(is1, is2)));
+ assertTrue("mm3", mm.apply(new TwoInputStates(is2, is3)));
+ assertFalse("mm4", mm.apply(new TwoInputStates(is3, is4)));
+ assertTrue("mm5", mm.apply(new TwoInputStates(is4, is5)));
+
+ assertFalse("empty1", mm.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", mm.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", mm.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", mm.apply(new TwoInputStates(InputState.EMPTY, is3)));
+ }
+
+ @Test
+ public void testMouseButton2() throws Exception {
+ final MouseButtonCondition mm = TriggerConditions.rightButtonDown();
+
+ final MouseState ms2 = new MouseState(1, 0, 1, 0, 0, bothUp, null);
+ final MouseState ms3 = new MouseState(1, 0, 0, 0, 0, bothDown, null);
+ final MouseState ms4 = new MouseState(3, 1, 2, 1, 0, upDown, null);
+ final MouseState ms5 = new MouseState(3, 0, 0, -1, 0, downUp, null);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(ks, ms2, cs);
+ is3 = new InputState(ks, ms3, cs);
+ is4 = new InputState(ks, ms4, cs);
+ is5 = new InputState(ks, ms5, cs);
+
+ assertFalse("mm1", mm.apply(new TwoInputStates(is1, is1)));
+ assertFalse("mm2", mm.apply(new TwoInputStates(is1, is2)));
+ assertTrue("mm3", mm.apply(new TwoInputStates(is2, is3)));
+ assertTrue("mm4", mm.apply(new TwoInputStates(is3, is4)));
+ assertFalse("mm5", mm.apply(new TwoInputStates(is4, is5)));
+
+ assertFalse("empty1", mm.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", mm.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", mm.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", mm.apply(new TwoInputStates(InputState.EMPTY, is3)));
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java new file mode 100644 index 0000000..c0525b4 --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import java.io.IOException; +import java.io.InputStream; + +public class MockInputStream extends InputStream { + private int bytesAvailable = 0; + private boolean eof = false; + + @Override + public int read() throws IOException { + while (true) { + final int result = returnSomething(); + + if (result != 0) { + return result; + } + + try { + Thread.sleep(2); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private synchronized int returnSomething() { + if (eof) { + return -1; + } + + if (bytesAvailable > 0) { + bytesAvailable--; + return 1; + } + + return 0; + } + + @Override + public synchronized int available() throws IOException { + return bytesAvailable; + } + + public synchronized void addBytesAvailable(final int bytesAvailable) { + this.bytesAvailable += bytesAvailable; + } + + public synchronized void setEof(final boolean eof) { + this.eof = eof; + } +} diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java new file mode 100644 index 0000000..b779190 --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.EOFException; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +/** + * These tests are fairly brittle, since they rely on the implementation of BufferedInputStream. + * It is necessary for the bytes available to always be larger than the buffer size of the buffered + * input stream for the tests to work. This size is currently 8192, but if it changes, or if the + * implementation changes, these tests can break. + */ +public class TestLittleEndianDataInput { + MockInputStream in; + byte[] array; + LittleEndianDataInput littleEndien; + + + @Before + public void setup() throws Exception { + in = new MockInputStream(); + + array = new byte[10]; + + littleEndien = new LittleEndianDataInput(in); + } + + + @Test + public void testReadFully1() throws Exception { + in.addBytesAvailable(11111); + + littleEndien.readFully(array); + + // not caring about whether the bytes were actually copied successfully in this test + } + + @Test + public void testReadFully2() throws Exception { + in.addBytesAvailable(11240); + + littleEndien.readFully(array, 0, 4); + + // not caring about whether the bytes were actually copied successfully in this test + } + + @Test + public void testReadFully3() throws Exception { + array = new byte[30003]; + + Thread testThread = new Thread(new Runnable() { + public void run() { + try { + littleEndien.readFully(array); + } catch (IOException e) { + e.printStackTrace(); + fail("ioexception"); + } + } + }); + + testThread.start(); + + in.addBytesAvailable(10000); + + assertTrue("still alive", testThread.isAlive()); + + Thread.sleep(6); + + in.addBytesAvailable(10001); + assertTrue("still alive", testThread.isAlive()); + + in.addBytesAvailable(10002); // now the test thread can die + + testThread.join(); + + // not caring about whether the bytes were actually copied successfully in this test + } + + @Test (expected = EOFException.class) + public void testReadFully4() throws Exception { + in.setEof(true); + + littleEndien.readFully(array); + + // not caring about whether the bytes were actually copied successfully in this test + } +} diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java new file mode 100644 index 0000000..dee87f2 --- /dev/null +++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.util; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; + +import org.junit.Test; + +/** + * Some tests for our random access little endian data input + */ +public class TestLittleEndianRandomAccessDataInput { + + @Test + public void testReadUint() throws Exception { + // test reading of uint vs int. + final byte[] data = new byte[4]; + data[0] = (byte) 0xff; + data[1] = (byte) 0xff; + data[2] = (byte) 0xff; + data[3] = (byte) 0xff; + + final ByteArrayInputStream bais = new ByteArrayInputStream(data); + final LittleEndianRandomAccessDataInput littleEndien = new LittleEndianRandomAccessDataInput(bais); + + final long val = littleEndien.readUnsignedInt(); + assertTrue(val == 4294967295L); + + littleEndien.seek(0); + final int val2 = littleEndien.readInt(); + assertTrue(val2 == -1); + } +} |