diff options
author | neothemachine <[email protected]> | 2012-12-05 17:03:16 +0100 |
---|---|---|
committer | neothemachine <[email protected]> | 2012-12-05 17:03:16 +0100 |
commit | 9dd02f103042cb8a196f8a3ed2278da443e345bf (patch) | |
tree | 422449f0c62ff9518316ce5d4219bb2b12f0ed15 /ardor3d-effects | |
parent | 2b26b12fd794de0f03a064a10024a3d9f5583756 (diff) |
move all files from trunk to root folder
Diffstat (limited to 'ardor3d-effects')
80 files changed, 11089 insertions, 0 deletions
diff --git a/ardor3d-effects/.classpath b/ardor3d-effects/.classpath new file mode 100644 index 0000000..b2417a3 --- /dev/null +++ b/ardor3d-effects/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src/main/java"/> + <classpathentry kind="src" path="src/test/java"/> + <classpathentry kind="src" path="src/main/resources"/> + <classpathentry combineaccessrules="false" kind="src" path="/ardor3d-core"/> + <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/ardor3d-effects/.project b/ardor3d-effects/.project new file mode 100644 index 0000000..a38130a --- /dev/null +++ b/ardor3d-effects/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>ardor3d-effects</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/ardor3d-effects/.settings/org.eclipse.core.resources.prefs b/ardor3d-effects/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e21a61e --- /dev/null +++ b/ardor3d-effects/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Wed Jan 07 11:32:43 PST 2009 +eclipse.preferences.version=1 +encoding/<project>=UTF-8 diff --git a/ardor3d-effects/.settings/org.eclipse.jdt.core.prefs b/ardor3d-effects/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..e914104 --- /dev/null +++ b/ardor3d-effects/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,276 @@ +#Tue Apr 06 11:29:47 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.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/ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs b/ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..f479490 --- /dev/null +++ b/ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,114 @@ +#Sun Jan 04 11:43:23 CST 2009
+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">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param} the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="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">/**\r\n * ${tags}\r\n * ${see_to_target}\r\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">/**\r\n * Copyright (c) 2008-2010 Ardor Labs, Inc.\r\n *\r\n * This file is part of Ardor3D.\r\n *\r\n * Ardor3D is free software\: you can redistribute it and/or modify it \r\n * under the terms of its license which may be found in the accompanying\r\n * LICENSE file or at <http\://www.ardor3d.com/LICENSE>.\r\n */\r\n\r\n${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\r\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_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/ardor3d-effects/pom.xml b/ardor3d-effects/pom.xml new file mode 100644 index 0000000..123dbe3 --- /dev/null +++ b/ardor3d-effects/pom.xml @@ -0,0 +1,32 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ardor3d</groupId>
+ <artifactId>ardor3d</artifactId>
+ <version>0.9-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ardor3d-effects</artifactId>
+ <packaging>bundle</packaging>
+ <name>Ardor 3D Effects</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.java new file mode 100644 index 0000000..dcd3b80 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.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.extension.effect; + +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture.WrapMode; +import com.ardor3d.renderer.effect.EffectManager; +import com.ardor3d.renderer.effect.EffectStep_RenderScreenOverlay; +import com.ardor3d.renderer.effect.EffectStep_SetRenderTarget; +import com.ardor3d.renderer.effect.RenderEffect; +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.util.resource.ResourceLocatorTool; + +public class ColorReplaceEffect extends RenderEffect { + + private String shaderDirectory = "com/ardor3d/extension/effect/"; + private float _redWeight = 0.3086f; + private float _greenWeight = 0.6094f; + private float _blueWeight = 0.0820f; + private Texture _colorRampTexture; + + public ColorReplaceEffect(final Texture colorRampTexture) { + _colorRampTexture = colorRampTexture; + _colorRampTexture.setWrap(WrapMode.EdgeClamp); + } + + private GLSLShaderObjectsState getColorizeShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class, + shaderDirectory + "color_replace.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("inputTex", 0); + shader.setUniform("colorRampTex", 1); + shader.setUniform("redWeight", _redWeight); + shader.setUniform("greenWeight", _greenWeight); + shader.setUniform("blueWeight", _blueWeight); + return shader; + } + + @Override + public void prepare(final EffectManager manager) { + _steps.clear(); + _steps.add(new EffectStep_SetRenderTarget("*Next")); + + final EffectStep_RenderScreenOverlay colorizeStep = new EffectStep_RenderScreenOverlay(); + colorizeStep.getTextureState().setTexture(_colorRampTexture, 1); + colorizeStep.getTargetMap().put("*Previous", 0); + colorizeStep.getEnforcedStates().put(StateType.GLSLShader, getColorizeShader()); + _steps.add(colorizeStep); + + super.prepare(manager); + } + + public float getRedWeight() { + return _redWeight; + } + + public void setRedWeight(final float redWeight) { + _redWeight = redWeight; + } + + public float getGreenWeight() { + return _greenWeight; + } + + public void setGreenWeight(final float greenWeight) { + _greenWeight = greenWeight; + } + + public float getBlueWeight() { + return _blueWeight; + } + + public void setBlueWeight(final float blueWeight) { + _blueWeight = blueWeight; + } + + public String getShaderDirectory() { + return shaderDirectory; + } + + public void setShaderDirectory(final String shaderDirectory) { + this.shaderDirectory = shaderDirectory; + } + + public Texture getColorRampTexture() { + return _colorRampTexture; + } + + public void setColorRampTexture(final Texture colorRampTexture) { + _colorRampTexture = colorRampTexture; + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java new file mode 100644 index 0000000..27cb7db --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect; + +import com.ardor3d.framework.DisplaySettings; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.image.Texture.MagnificationFilter; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.image.Texture.WrapMode; +import com.ardor3d.renderer.effect.EffectManager; +import com.ardor3d.renderer.effect.EffectStep_RenderScreenOverlay; +import com.ardor3d.renderer.effect.EffectStep_SetRenderTarget; +import com.ardor3d.renderer.effect.RenderEffect; +import com.ardor3d.renderer.effect.RenderTarget; +import com.ardor3d.renderer.effect.RenderTarget_Texture2D; +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.util.resource.ResourceLocatorTool; + +public class HDREffect extends RenderEffect { + private String shaderDirectory = "com/ardor3d/extension/effect/"; + + protected static final String RT_DOWNSAMPLED = "HDREffect.DOWNSAMPLED"; + protected static final String RT_LUM64x64 = "HDREffect.LUM64x64"; + protected static final String RT_LUM16x16 = "HDREffect.LUM16x16"; + protected static final String RT_LUM1x1 = "HDREffect.LUM1x1"; + protected static final String RT_BRIGHTMAP = "HDREffect.BRIGHTMAP"; + protected static final String RT_BLOOM_HORIZONTAL = "HDREffect.BLOOM_HORIZONTAL"; + protected static final String RT_BLOOM = "HDREffect.BLOOM"; + + protected float _downsampleRatio = 0.25f; + + @Override + public void prepare(final EffectManager manager) { + // init targets used in this effect + initTargets(manager); + + _steps.clear(); + + // step 1: downsample previous rendering in chain + { + _steps.add(new EffectStep_SetRenderTarget(RT_DOWNSAMPLED)); + final EffectStep_RenderScreenOverlay downsample = new EffectStep_RenderScreenOverlay(); + downsample.getTargetMap().put("*Previous", 0); + _steps.add(downsample); + } + + // step 2: extract our average luminance value + { + _steps.add(new EffectStep_SetRenderTarget(RT_LUM64x64)); + final EffectStep_RenderScreenOverlay extract64 = new EffectStep_RenderScreenOverlay(); + extract64.getEnforcedStates().put(StateType.GLSLShader, getLuminanceExtractionShader()); + extract64.getTargetMap().put(RT_DOWNSAMPLED, 0); + _steps.add(extract64); + + _steps.add(new EffectStep_SetRenderTarget(RT_LUM16x16)); + final EffectStep_RenderScreenOverlay extract4 = new EffectStep_RenderScreenOverlay(); + extract4.getTargetMap().put(RT_LUM64x64, 0); + _steps.add(extract4); + + _steps.add(new EffectStep_SetRenderTarget(RT_LUM1x1)); + final EffectStep_RenderScreenOverlay extract1 = new EffectStep_RenderScreenOverlay(); + extract1.getTargetMap().put(RT_LUM16x16, 0); + _steps.add(extract1); + } + + // step 3: apply bright pass, extracting the brighter than normal portions of the scene + { + _steps.add(new EffectStep_SetRenderTarget(RT_BRIGHTMAP)); + final EffectStep_RenderScreenOverlay bright = new EffectStep_RenderScreenOverlay(); + bright.getEnforcedStates().put(StateType.GLSLShader, getBrightMapShader()); + bright.getTargetMap().put(RT_DOWNSAMPLED, 0); + bright.getTargetMap().put(RT_LUM1x1, 1); + _steps.add(bright); + } + + // finally: draw bloom texture and previous texture on fsq, blended. + _steps.add(new EffectStep_SetRenderTarget("*Next")); + + final EffectStep_RenderScreenOverlay blendOverlay = new EffectStep_RenderScreenOverlay(); + blendOverlay.getTargetMap().put(RT_BRIGHTMAP, 0); + _steps.add(blendOverlay); + + super.prepare(manager); + } + + private RenderState getLuminanceExtractionShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class, + shaderDirectory + "luminance.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("inputTex", 0); + return shader; + } + + private RenderState getBrightMapShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class, + shaderDirectory + "brightmap.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("inputTex", 0); + shader.setUniform("lum1x1Tex", 1); + shader.setUniform("exposurePow", 3.0f); + shader.setUniform("exposureCutoff", 0.0f); + return shader; + } + + private void initTargets(final EffectManager manager) { + final DisplaySettings canvas = manager.getCanvasSettings(); + final int downsampledHeight = Math.round(canvas.getHeight() * _downsampleRatio); + final int downsampledWidth = Math.round(canvas.getWidth() * _downsampleRatio); + + final RenderTarget_Texture2D downsampled = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, + TextureStoreFormat.RGBA16F); + downsampled.getTexture().setWrap(WrapMode.Clamp); + manager.getRenderTargetMap().put(RT_DOWNSAMPLED, downsampled); + + manager.getRenderTargetMap().put(RT_LUM64x64, getLuminanceDownsampleTexture(64)); + manager.getRenderTargetMap().put(RT_LUM16x16, getLuminanceDownsampleTexture(16)); + manager.getRenderTargetMap().put(RT_LUM1x1, getLuminanceDownsampleTexture(1)); + + final RenderTarget_Texture2D brightmap = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, + TextureStoreFormat.RGBA16F); + brightmap.getTexture().setWrap(WrapMode.Clamp); + manager.getRenderTargetMap().put(RT_BRIGHTMAP, brightmap); + + final RenderTarget_Texture2D bloomHoriz = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, + TextureStoreFormat.RGBA8); + bloomHoriz.getTexture().setWrap(WrapMode.Clamp); + manager.getRenderTargetMap().put(RT_BLOOM_HORIZONTAL, bloomHoriz); + + final RenderTarget_Texture2D bloom = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, + TextureStoreFormat.RGBA8); + bloom.getTexture().setWrap(WrapMode.Clamp); + manager.getRenderTargetMap().put(RT_BLOOM, bloom); + } + + private RenderTarget getLuminanceDownsampleTexture(final int size) { + final RenderTarget_Texture2D target = new RenderTarget_Texture2D(size, size, TextureStoreFormat.RGBA16F); + if (size != 1) { + target.getTexture().setMinificationFilter(MinificationFilter.Trilinear); + target.getTexture().setMagnificationFilter(MagnificationFilter.Bilinear); + } else { + target.getTexture().setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps); + target.getTexture().setMagnificationFilter(MagnificationFilter.NearestNeighbor); + } + + target.getTexture().setWrap(WrapMode.Clamp); + return target; + } + + public float getDownsampleRatio() { + return _downsampleRatio; + } + + public void setDownsampleRatio(final float downsampleRatio) { + _downsampleRatio = downsampleRatio; + } + + public String getShaderDirectory() { + return shaderDirectory; + } + + public void setShaderDirectory(final String shaderDirectory) { + this.shaderDirectory = shaderDirectory; + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.java new file mode 100644 index 0000000..bca0512 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.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.extension.effect; + +import java.util.List; + +import com.ardor3d.extension.effect.bloom.BloomRenderPass; +import com.ardor3d.framework.DisplaySettings; +import com.ardor3d.image.Texture.WrapMode; +import com.ardor3d.renderer.effect.EffectManager; +import com.ardor3d.renderer.effect.EffectStep_RenderScreenOverlay; +import com.ardor3d.renderer.effect.EffectStep_RenderSpatials; +import com.ardor3d.renderer.effect.EffectStep_SetRenderTarget; +import com.ardor3d.renderer.effect.RenderEffect; +import com.ardor3d.renderer.effect.RenderTarget_Texture2D; +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.scenegraph.Spatial; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.google.common.collect.Lists; + +public class SimpleBloomEffect extends RenderEffect { + protected static final String RT_MAIN = "LDRBloomEffect.MAIN"; + protected static final String RT_SECONDARY = "LDRBloomEffect.SECONDARY"; + + protected String shaderDirectory = "com/ardor3d/extension/effect/"; + protected final List<Spatial> _bloomItems = Lists.newArrayList(); + + protected float _downsampleRatio = .33f; + private final GLSLShaderObjectsState _extractionShader, _blurHorizShader, _blurVertShader; + + public SimpleBloomEffect() { + _extractionShader = getExtractionShader(); + _blurHorizShader = getBlurHorizShader(); + _blurVertShader = getBlurVertShader(); + setExposureIntensity(1.3f); + setExposureCutoff(0.15f); + setSampleDistance(0.02f); + } + + @Override + public void prepare(final EffectManager manager) { + // init targets used in this effect + initTargets(manager); + + final boolean useBloomItems = !_bloomItems.isEmpty(); + + _steps.clear(); + // step 1: pick whether we are blooming the whole previous render buffer or just specific items + if (useBloomItems) { + // render these items to a texture + _steps.add(new EffectStep_SetRenderTarget(RT_MAIN)); + _steps.add(new EffectStep_RenderSpatials(null)); + } + + // step 2: extract intensity + { + _steps.add(new EffectStep_SetRenderTarget(RT_SECONDARY)); + final EffectStep_RenderScreenOverlay extract = new EffectStep_RenderScreenOverlay(); + extract.getEnforcedStates().put(StateType.GLSLShader, _extractionShader); + extract.getTargetMap().put(useBloomItems ? RT_MAIN : "*Previous", 0); + _steps.add(extract); + } + + // step 3: blur + { + _steps.add(new EffectStep_SetRenderTarget(RT_MAIN)); + final EffectStep_RenderScreenOverlay blurHoriz = new EffectStep_RenderScreenOverlay(); + blurHoriz.getEnforcedStates().put(StateType.GLSLShader, _blurHorizShader); + blurHoriz.getTargetMap().put(RT_SECONDARY, 0); + _steps.add(blurHoriz); + + _steps.add(new EffectStep_SetRenderTarget(RT_SECONDARY)); + final EffectStep_RenderScreenOverlay blurVert = new EffectStep_RenderScreenOverlay(); + blurVert.getEnforcedStates().put(StateType.GLSLShader, _blurVertShader); + blurVert.getTargetMap().put(RT_MAIN, 0); + _steps.add(blurVert); + } + + // finally: draw bloom texture and previous texture on fsq, blended. + _steps.add(new EffectStep_SetRenderTarget("*Next")); + + final EffectStep_RenderScreenOverlay blendOverlay = new EffectStep_RenderScreenOverlay(); + blendOverlay.getEnforcedStates().put(StateType.GLSLShader, getBlendShader()); + blendOverlay.getTargetMap().put("*Previous", 0); + blendOverlay.getTargetMap().put(RT_SECONDARY, 1); + _steps.add(blendOverlay); + + super.prepare(manager); + } + + protected void initTargets(final EffectManager manager) { + final DisplaySettings canvas = manager.getCanvasSettings(); + final int downsampledHeight = Math.round(canvas.getHeight() * _downsampleRatio); + final int downsampledWidth = Math.round(canvas.getWidth() * _downsampleRatio); + + final RenderTarget_Texture2D main = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, manager + .getOutputFormat()); + main.getTexture().setWrap(WrapMode.Clamp); + manager.getRenderTargetMap().put(RT_MAIN, main); + + final RenderTarget_Texture2D secondary = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, + manager.getOutputFormat()); + secondary.getTexture().setWrap(WrapMode.Clamp); + manager.getRenderTargetMap().put(RT_SECONDARY, secondary); + } + + protected GLSLShaderObjectsState getExtractionShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "bloom_extract.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("inputTex", 0); + return shader; + } + + protected GLSLShaderObjectsState getBlurHorizShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "gausian_blur_horizontal9.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("inputTex", 0); + return shader; + } + + protected GLSLShaderObjectsState getBlurVertShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "gausian_blur_vertical9.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("inputTex", 0); + return shader; + } + + protected GLSLShaderObjectsState getBlendShader() { + final GLSLShaderObjectsState shader = new GLSLShaderObjectsState(); + try { + shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "fsq.vert")); + shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class, + shaderDirectory + "add2textures.frag")); + } catch (final Exception e) { + e.printStackTrace(); + } + shader.setUniform("tex1", 0); + shader.setUniform("tex2", 1); + return shader; + } + + public void setExposureIntensity(final float value) { + _extractionShader.setUniform("exposureIntensity", value); + } + + public void setExposureCutoff(final float value) { + _extractionShader.setUniform("exposureCutoff", value); + } + + public void setSampleDistance(final float value) { + _blurHorizShader.setUniform("sampleDist", value); + _blurVertShader.setUniform("sampleDist", value); + } + + public float getDownsampleRatio() { + return _downsampleRatio; + } + + public void setDownsampleRatio(final float downsampleRatio) { + _downsampleRatio = downsampleRatio; + } + + public String getShaderDirectory() { + return shaderDirectory; + } + + public void setShaderDirectory(final String shaderDirectory) { + this.shaderDirectory = shaderDirectory; + } + + public List<Spatial> getBloomItems() { + return _bloomItems; + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java new file mode 100644 index 0000000..1ef32bd --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java @@ -0,0 +1,440 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.bloom;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.pass.Pass;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Renderable;
+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.resource.ResourceLocatorTool;
+
+/**
+ * GLSL bloom effect pass. - Render supplied source to a texture - Extract intensity - Blur intensity - Blend with first
+ * pass
+ */
+public class BloomRenderPass extends Pass {
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(BloomRenderPass.class.getName());
+
+ private static final long serialVersionUID = 1L;
+
+ private double throttle = 0;
+ private double sinceLast = 1;
+
+ private TextureRenderer tRenderer = null;
+ private TextureRenderer fullTRenderer = null;
+ private Texture2D mainTexture = null;
+ private Texture2D secondTexture = null;
+ private Texture2D screenTexture = null;
+
+ private Quad fullScreenQuad = null;
+
+ private GLSLShaderObjectsState extractionShader = null;
+ private GLSLShaderObjectsState blurShader = null;
+ private GLSLShaderObjectsState blurShaderHorizontal = null;
+ private GLSLShaderObjectsState blurShaderVertical = null;
+ private GLSLShaderObjectsState finalShader = null;
+
+ private final Camera cam;
+ private final int renderScale;
+
+ private int nrBlurPasses;
+ private float blurSize;
+ private float blurIntensityMultiplier;
+ private float exposurePow;
+ private float exposureCutoff;
+ private boolean supported = true;
+ private boolean useCurrentScene = false;
+
+ private boolean useSeparateConvolution = false;
+
+ public static String shaderDirectory = "com/ardor3d/extension/effect/bloom/";
+
+ private boolean initialized = false;
+
+ /**
+ * Reset bloom parameters to default
+ */
+ public void resetParameters() {
+ nrBlurPasses = 2;
+ blurSize = 0.02f;
+ blurIntensityMultiplier = 1.3f;
+ exposurePow = 3.0f;
+ exposureCutoff = 0.0f;
+ }
+
+ /**
+ * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
+ */
+ @Override
+ public void cleanUp() {
+ super.cleanUp();
+ if (tRenderer != null) {
+ tRenderer.cleanup();
+ }
+ if (fullTRenderer != null) {
+ fullTRenderer.cleanup();
+ }
+ }
+
+ public boolean isSupported() {
+ return supported;
+ }
+
+ /**
+ * Creates a new bloom renderpass
+ *
+ * @param cam
+ * Camera used for rendering the bloomsource
+ * @param renderScale
+ * Scale of bloom texture
+ */
+ public BloomRenderPass(final Camera cam, final int renderScale) {
+ this.cam = cam;
+ this.renderScale = renderScale;
+ resetParameters();
+ }
+
+ @Override
+ protected void doUpdate(final double tpf) {
+ super.doUpdate(tpf);
+ sinceLast += tpf;
+ }
+
+ @Override
+ public void doRender(final Renderer r) {
+ if (!initialized) {
+ doInit(r);
+ }
+
+ if (!isSupported() || !useCurrentScene && _spatials.size() == 0) {
+ return;
+ }
+
+ final BlendState blend = (BlendState) fullScreenQuad.getWorldRenderState(RenderState.StateType.Blend);
+
+ if (sinceLast > throttle) {
+ sinceLast = 0;
+
+ tRenderer.getCamera().setLocation(cam.getLocation());
+ tRenderer.getCamera().setDirection(cam.getDirection());
+ tRenderer.getCamera().setUp(cam.getUp());
+ tRenderer.getCamera().setLeft(cam.getLeft());
+
+ blend.setEnabled(false);
+ final TextureState ts = (TextureState) fullScreenQuad.getWorldRenderState(RenderState.StateType.Texture);
+
+ // see if we should use the current scene to bloom, or only things added to the pass.
+ if (useCurrentScene) {
+ // grab backbuffer to texture
+ if (screenTexture == null) {
+ final DisplaySettings settings = new DisplaySettings(cam.getWidth(), cam.getHeight(), 24, 0, 0, 8,
+ 0, 0, false, false);
+ fullTRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(settings, false, r,
+ ContextManager.getCurrentContext().getCapabilities());
+ screenTexture = new Texture2D();
+ screenTexture.setWrap(Texture.WrapMode.Clamp);
+ screenTexture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ fullTRenderer.setupTexture(screenTexture);
+ }
+ fullTRenderer.copyToTexture(screenTexture, 0, 0, cam.getWidth(), cam.getHeight(), 0, 0);
+ ts.setTexture(screenTexture, 0);
+ } else {
+ // Render scene to texture
+ tRenderer.render(_spatials, mainTexture, Renderer.BUFFER_COLOR_AND_DEPTH);
+ ts.setTexture(mainTexture, 0);
+ }
+
+ // Extract intensity
+ extractionShader.setUniform("exposurePow", getExposurePow());
+ extractionShader.setUniform("exposureCutoff", getExposureCutoff());
+
+ fullScreenQuad.setRenderState(extractionShader);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = extractionShader;
+ tRenderer.render(fullScreenQuad, secondTexture, Renderer.BUFFER_NONE);
+
+ if (!useSeparateConvolution) {
+ blurShader.setUniform("sampleDist", getBlurSize());
+ blurShader.setUniform("blurIntensityMultiplier", getBlurIntensityMultiplier());
+
+ ts.setTexture(secondTexture, 0);
+ fullScreenQuad.setRenderState(blurShader);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = blurShader;
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+
+ // Extra blur passes
+ for (int i = 1; i < getNrBlurPasses(); i++) {
+ blurShader.setUniform("sampleDist", getBlurSize() - i * getBlurSize() / getNrBlurPasses());
+ if (i % 2 == 1) {
+ ts.setTexture(mainTexture, 0);
+ tRenderer.render(fullScreenQuad, secondTexture, Renderer.BUFFER_NONE);
+ } else {
+ ts.setTexture(secondTexture, 0);
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+ }
+ }
+ if (getNrBlurPasses() % 2 == 1) {
+ ts.setTexture(mainTexture, 0);
+ } else {
+ ts.setTexture(secondTexture, 0);
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+ ts.setTexture(mainTexture, 0);
+ }
+ } else {
+ blurShaderVertical.setUniform("blurIntensityMultiplier", getBlurIntensityMultiplier());
+
+ for (int i = 0; i < getNrBlurPasses(); i++) {
+ blurShaderHorizontal
+ .setUniform("sampleDist", getBlurSize() - i * getBlurSize() / getNrBlurPasses());
+ blurShaderVertical.setUniform("sampleDist", getBlurSize() - i * getBlurSize() / getNrBlurPasses());
+
+ ts.setTexture(secondTexture, 0);
+ fullScreenQuad.setRenderState(blurShaderHorizontal);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = blurShaderHorizontal;
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+ ts.setTexture(mainTexture, 0);
+ fullScreenQuad.setRenderState(blurShaderVertical);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = blurShaderVertical;
+ tRenderer.render(fullScreenQuad, secondTexture, Renderer.BUFFER_NONE);
+ }
+ ts.setTexture(secondTexture, 0);
+ }
+ }
+
+ // Final blend
+ blend.setEnabled(true);
+
+ fullScreenQuad.setRenderState(finalShader);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = finalShader;
+ r.draw((Renderable) fullScreenQuad);
+ }
+
+ private void doInit(final Renderer r) {
+ initialized = true;
+
+ cleanUp();
+
+ // Test for glsl support
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+ if (!caps.isGLSLSupported() || !(caps.isPbufferSupported() || caps.isFBOSupported())) {
+ supported = false;
+ return;
+ }
+
+ // Create texture renderers and rendertextures(alternating between two not to overwrite pbuffers)
+ final DisplaySettings settings = new DisplaySettings(cam.getWidth() / renderScale, cam.getHeight()
+ / renderScale, 24, 0, 0, 8, 0, 0, false, false);
+ tRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(settings, false, r, ContextManager
+ .getCurrentContext().getCapabilities());
+
+ if (tRenderer == null) {
+ supported = false;
+ return;
+ }
+ tRenderer.setMultipleTargets(true);
+ tRenderer.setBackgroundColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f));
+ tRenderer.getCamera().setFrustum(cam.getFrustumNear(), cam.getFrustumFar(), cam.getFrustumLeft(),
+ cam.getFrustumRight(), cam.getFrustumTop(), cam.getFrustumBottom());
+
+ mainTexture = new Texture2D();
+ mainTexture.setWrap(Texture.WrapMode.Clamp);
+ mainTexture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ tRenderer.setupTexture(mainTexture);
+
+ secondTexture = new Texture2D();
+ secondTexture.setWrap(Texture.WrapMode.Clamp);
+ secondTexture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ tRenderer.setupTexture(secondTexture);
+
+ extractionShader = new GLSLShaderObjectsState();
+ try {
+ extractionShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_extract.vert"));
+ extractionShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_extract.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ extractionShader.setUniform("RT", 0);
+
+ // Create blur shader
+ blurShader = new GLSLShaderObjectsState();
+ try {
+ blurShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_blur.vert"));
+ blurShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_blur.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShader.setUniform("RT", 0);
+
+ // Create blur shader horizontal
+ blurShaderHorizontal = new GLSLShaderObjectsState();
+ try {
+ blurShaderHorizontal.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ BloomRenderPass.class, shaderDirectory + "bloom_blur.vert"));
+ blurShaderHorizontal.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ BloomRenderPass.class, shaderDirectory + "bloom_blur_horizontal7.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShaderHorizontal.setUniform("RT", 0);
+
+ // Create blur shader vertical
+ blurShaderVertical = new GLSLShaderObjectsState();
+ try {
+ blurShaderVertical.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_blur.vert"));
+ blurShaderVertical.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ BloomRenderPass.class, shaderDirectory + "bloom_blur_vertical7.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShaderVertical.setUniform("RT", 0);
+
+ // Create final shader(basic texturing)
+ finalShader = new GLSLShaderObjectsState();
+ try {
+ finalShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_final.vert"));
+ finalShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_final.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+
+ // Create fullscreen quad
+ fullScreenQuad = new Quad("FullScreenQuad", cam.getWidth() / 4, cam.getHeight() / 4);
+ fullScreenQuad.setTranslation(cam.getWidth() / 2, cam.getHeight() / 2, 0);
+ fullScreenQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ fullScreenQuad.getSceneHints().setCullHint(CullHint.Never);
+ fullScreenQuad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ fullScreenQuad.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ final TextureState ts = new TextureState();
+ ts.setEnabled(true);
+ fullScreenQuad.setRenderState(ts);
+
+ final BlendState as = new BlendState();
+ as.setBlendEnabled(true);
+ as.setSourceFunction(BlendState.SourceFunction.One);
+ as.setDestinationFunction(BlendState.DestinationFunction.One);
+ as.setEnabled(true);
+ fullScreenQuad.setRenderState(as);
+
+ fullScreenQuad.updateGeometricState(0.0f, true);
+ }
+
+ /**
+ * @return The throttle amount - or in other words, how much time in seconds must pass before the bloom effect is
+ * updated.
+ */
+ public double getThrottle() {
+ return throttle;
+ }
+
+ /**
+ * @param throttle
+ * The throttle amount - or in other words, how much time in seconds must pass before the bloom effect is
+ * updated.
+ */
+ public void setThrottle(final float throttle) {
+ this.throttle = throttle;
+ }
+
+ public float getBlurSize() {
+ return blurSize;
+ }
+
+ public void setBlurSize(final float blurSize) {
+ this.blurSize = blurSize;
+ }
+
+ public float getExposurePow() {
+ return exposurePow;
+ }
+
+ public void setExposurePow(final float exposurePow) {
+ this.exposurePow = exposurePow;
+ }
+
+ public float getExposureCutoff() {
+ return exposureCutoff;
+ }
+
+ public void setExposureCutoff(final float exposureCutoff) {
+ this.exposureCutoff = exposureCutoff;
+ }
+
+ public float getBlurIntensityMultiplier() {
+ return blurIntensityMultiplier;
+ }
+
+ public void setBlurIntensityMultiplier(final float blurIntensityMultiplier) {
+ this.blurIntensityMultiplier = blurIntensityMultiplier;
+ }
+
+ public int getNrBlurPasses() {
+ return nrBlurPasses;
+ }
+
+ public void setNrBlurPasses(final int nrBlurPasses) {
+ this.nrBlurPasses = nrBlurPasses;
+ }
+
+ public boolean useCurrentScene() {
+ return useCurrentScene;
+ }
+
+ public void setUseCurrentScene(final boolean useCurrentScene) {
+ this.useCurrentScene = useCurrentScene;
+ }
+
+ public void setUseSeparateConvolution(final boolean useSeparateConvolution) {
+ this.useSeparateConvolution = useSeparateConvolution;
+ }
+
+ public boolean isUseSeparateConvolution() {
+ return useSeparateConvolution;
+ }
+
+ public void markNeedsRefresh() {
+ initialized = false;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.java new file mode 100644 index 0000000..56e5121 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.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.extension.effect.particle; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +public class AnimationEntry implements Savable { + protected double _offset = 0.05; // 5% of life from previous entry + protected double _rate = 0.2; // 5 fps + protected int[] _frames = new int[1]; + + public AnimationEntry() {} + + public AnimationEntry(final double offset) { + _offset = offset; + } + + public int[] getFrames() { + return _frames; + } + + public void setFrames(final int[] frames) { + _frames = frames; + } + + public double getOffset() { + return _offset; + } + + public void setOffset(final double offset) { + _offset = offset; + } + + public double getRate() { + return _rate; + } + + public void setRate(final double rate) { + _rate = rate; + } + + public Class<? extends AnimationEntry> getClassTag() { + return getClass(); + } + + public void read(final InputCapsule capsule) throws IOException { + _offset = capsule.readDouble("offsetMS", 0.05); + _rate = capsule.readDouble("rate", 0.2); + _frames = capsule.readIntArray("frames", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_offset, "offsetMS", 0.05); + capsule.write(_rate, "rate", 0.2); + capsule.write(_frames, "frames", null); + } + + private static String makeText(final int[] frames) { + if (frames == null || frames.length == 0) { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + for (final int frame : frames) { + sb.append(frame); + sb.append(","); + } + return sb.substring(0, sb.length() - 1); + } + + @Override + public String toString() { + + final StringBuilder builder = new StringBuilder(); + + builder.append("prev+"); + builder.append((int) (_offset * 100)); + builder.append("% age..."); + + builder.append(" rate: " + _rate); + + builder.append(" sequence: " + makeText(_frames)); + + return builder.toString(); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.java new file mode 100644 index 0000000..5891b13 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.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.extension.effect.particle; + +import java.io.IOException; + +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.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class FloorInfluence extends ParticleInfluence { + + /** + * Bounciness is the factor of multiplication when bouncing off the floor. A bounciness factor of 1 means the + * particle leaves the floor with the same velocity as it hit the floor. + */ + private double _bounciness = 1; + + /** + * Our imaginary floor + */ + private final Plane _floor = new Plane(); + + public FloorInfluence() {} + + /** + * @param plane + * The imaginary floor plane + * @param bounciness + * Bounciness is the factor of multiplication when bouncing off the floor. A bounciness factor of 1 means + * the ball leaves the floor with the same velocity as it hit the floor, much like a rubber ball. + */ + public FloorInfluence(final ReadOnlyPlane plane, final double bounciness) { + _bounciness = bounciness; + _floor.set(plane); + } + + @Override + public void apply(final double dt, final Particle particle, final int index) { + // Is particle alive, AND "under" our floor? + if (particle.getStatus() == Particle.Status.Alive && _floor.pseudoDistance(particle.getPosition()) <= 0) { + final Vector3 tempVect1 = Vector3.fetchTempInstance(); + final double scale = particle.getVelocity().length(); + tempVect1.set(particle.getVelocity()).divideLocal(scale); // normalize + + // Is the particle moving further into the floor? + if (_floor.getNormal().smallestAngleBetween(tempVect1) > MathUtils.HALF_PI) { + // reflect our velocity vector across the floor plane + _floor.reflectVector(tempVect1, tempVect1); + + // apply the "bounciness" factor + tempVect1.multiplyLocal(scale * _bounciness); + + // write back to particle + particle.setVelocity(tempVect1); + } + Vector3.releaseTempInstance(tempVect1); + } + } + + public double getBounciness() { + return _bounciness; + } + + public void setBounciness(final double bounciness) { + _bounciness = bounciness; + } + + public ReadOnlyPlane getFloor() { + return _floor; + } + + public void setFloor(final ReadOnlyPlane floor) { + + _floor.set(floor); + } + + public ReadOnlyVector3 getNormal() { + return _floor.getNormal(); + } + + public void setNormal(final Vector3 normal) { + _floor.setNormal(normal.normalize(null)); + } + + public double getConstant() { + return _floor.getConstant(); + } + + public void setConstant(final double constant) { + _floor.setConstant(constant); + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + @Override + public Class<? extends FloorInfluence> getClassTag() { + return this.getClass(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_bounciness, "bounciness", 1.0); + capsule.write(_floor, "floor", new Plane()); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _bounciness = capsule.readDouble("bounciness", 1.0); + _floor.set((Plane) capsule.readSavable("floor", new Plane())); + } + +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java new file mode 100644 index 0000000..48ff5ff --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java @@ -0,0 +1,496 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; +import java.nio.FloatBuffer; + +import com.ardor3d.extension.effect.particle.ParticleSystem.ParticleType; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Triangle; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>Particle</code> defines a single Particle of a Particle system. Generally, you would not interact with this + * class directly. + */ +public class Particle implements Savable { + + public enum Status { + /** Particle is dead -- not in play. */ + Dead, + /** Particle is currently active. */ + Alive, + /** Particle is available for spawning. */ + Available; + } + + static final int VAL_CURRENT_SIZE = 0; + static final int VAL_CURRENT_SPIN = 1; + static final int VAL_CURRENT_MASS = 2; + + private int startIndex; + private final Vector3 _position = new Vector3(); + private final Vector3 _velocity = new Vector3(); + private final ColorRGBA currColor = new ColorRGBA(ColorRGBA.BLACK); + private Status status = Status.Available; + private double lifeSpan; + private final double[] values = new double[3]; + private int currentAge; + private int currentTexIndex = -1; + private ParticleSystem parent; + private final Vector3 bbX = new Vector3(), bbY = new Vector3(); + + // colors + private ParticleType type = ParticleSystem.ParticleType.Quad; + + private Triangle triModel; + + /** + * Empty constructor - mostly for use with Savable interface + */ + public Particle() {} + + /** + * Normal use constructor. Sets up the parent and particle type for this particle. + * + * @param parent + * the particle collection this particle belongs to + */ + public Particle(final ParticleSystem parent) { + this.parent = parent; + type = parent.getParticleType(); + } + + /** + * Cause this particle to reset it's lifespan, velocity, color, age and size per the parent's settings. status is + * set to Status.Available and location is set to 0,0,0. Actual geometry data is not affected by this call, only + * particle params. + */ + public void init() { + init(parent.getRandomVelocity(_velocity), Vector3.ZERO, parent.getRandomLifeSpan()); + } + + /** + * Cause this particle to reset it's color, age and size per the parent's settings. status is set to + * Status.Available. Location, velocity and lifespan are set as given. Actual geometry data is not affected by this + * call, only particle params. + * + * @param velocity + * new initial particle velocity + * @param position + * new initial particle position + * @param lifeSpan + * new particle lifespan in ms + */ + public void init(final ReadOnlyVector3 velocity, final ReadOnlyVector3 position, final double lifeSpan) { + this.lifeSpan = lifeSpan; + _velocity.set(velocity); + _position.set(position); + + currColor.set(parent.getStartColor()); + currentAge = 0; + status = Status.Available; + values[VAL_CURRENT_SIZE] = parent.getStartSize(); + } + + /** + * Reset particle conditions. Besides the passed lifespan, we also reset color, size, and spin angle to their + * starting values (as given by parent.) Status is set to Status.Available. + * + * @param lifeSpan + * the recreated particle's new lifespan + */ + public void recreateParticle(final double lifeSpan) { + this.lifeSpan = lifeSpan; + + final int verts = ParticleSystem.getVertsForParticleType(type); + currColor.set(parent.getStartColor()); + for (int x = 0; x < verts; x++) { + BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getMeshData().getColorBuffer(), startIndex + + x); + } + values[VAL_CURRENT_SIZE] = parent.getStartSize(); + currentAge = 0; + values[VAL_CURRENT_MASS] = 1; + status = Status.Available; + } + + /** + * Update the vertices for this particle, taking size, spin and viewer into consideration. In the case of particle + * type ParticleType.GeomMesh, the original triangle normal is maintained rather than rotating it to face the camera + * or parent vectors. + * + * @param cam + * Camera to use in determining viewer aspect. If null, or if parent is not set to camera facing, + * parent's left and up vectors are used. + */ + public void updateVerts(final Camera cam) { + final double orient = parent.getParticleOrientation() + values[VAL_CURRENT_SPIN]; + final double currSize = values[VAL_CURRENT_SIZE]; + + if (type == ParticleSystem.ParticleType.GeomMesh || type == ParticleSystem.ParticleType.Point) { + ; // nothing to do + } else if (cam != null && parent.isCameraFacing()) { + final ReadOnlyVector3 camUp = cam.getUp(); + final ReadOnlyVector3 camLeft = cam.getLeft(); + final ReadOnlyVector3 camDir = cam.getDirection(); + if (parent.isVelocityAligned()) { + bbX.set(_velocity).normalizeLocal().multiplyLocal(currSize); + camDir.cross(bbX, bbY).normalizeLocal().multiplyLocal(currSize); + } else if (orient == 0) { + bbX.set(camLeft).multiplyLocal(currSize); + bbY.set(camUp).multiplyLocal(currSize); + } else { + final double cA = MathUtils.cos(orient) * currSize; + final double sA = MathUtils.sin(orient) * currSize; + bbX.set(camLeft).multiplyLocal(cA).addLocal(camUp.getX() * sA, camUp.getY() * sA, camUp.getZ() * sA); + bbY.set(camLeft).multiplyLocal(-sA).addLocal(camUp.getX() * cA, camUp.getY() * cA, camUp.getZ() * cA); + } + } else { + final ReadOnlyVector3 left = parent.getFacingLeftVector(); + final ReadOnlyVector3 up = parent.getFacingUpVector(); + + if (parent.isVelocityAligned()) { + bbX.set(_velocity).normalizeLocal().multiplyLocal(currSize); + up.cross(bbX, bbY).normalizeLocal().multiplyLocal(currSize); + } else if (orient == 0) { + bbX.set(left).multiplyLocal(currSize); + bbY.set(up).multiplyLocal(currSize); + } else { + final double cA = MathUtils.cos(orient) * currSize; + final double sA = MathUtils.sin(orient) * currSize; + bbX.set(left).multiplyLocal(cA).addLocal(up.getX() * sA, up.getY() * sA, up.getZ() * sA); + bbY.set(left).multiplyLocal(-sA).addLocal(up.getX() * cA, up.getY() * cA, up.getZ() * cA); + } + } + + final Vector3 tempVec3 = Vector3.fetchTempInstance(); + final FloatBuffer vertexBuffer = parent.getParticleGeometry().getMeshData().getVertexBuffer(); + switch (type) { + case Quad: { + _position.subtract(bbX, tempVec3).subtractLocal(bbY); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 0); + + _position.subtract(bbX, tempVec3).addLocal(bbY); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1); + + _position.add(bbX, tempVec3).addLocal(bbY); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 2); + + _position.add(bbX, tempVec3).subtractLocal(bbY); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 3); + break; + } + case GeomMesh: { + final Quaternion tempQuat = Quaternion.fetchTempInstance(); + final ReadOnlyVector3 norm = triModel.getNormal(); + if (orient != 0) { + tempQuat.fromAngleNormalAxis(orient, norm); + } + + for (int x = 0; x < 3; x++) { + if (orient != 0) { + tempQuat.apply(triModel.get(x), tempVec3); + } else { + tempVec3.set(triModel.get(x)); + } + tempVec3.multiplyLocal(currSize).addLocal(_position); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + x); + } + Quaternion.releaseTempInstance(tempQuat); + break; + } + case Triangle: { + _position.subtract(3 * bbX.getX(), 3 * bbX.getY(), 3 * bbX.getZ(), tempVec3).subtractLocal(bbY); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 0); + + _position.add(bbX, tempVec3).addLocal(3 * bbY.getX(), 3 * bbY.getY(), 3 * bbY.getZ()); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1); + + _position.add(bbX, tempVec3).subtractLocal(bbY); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 2); + break; + } + case Line: { + _position.subtract(bbX, tempVec3); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex); + + _position.add(bbX, tempVec3); + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1); + break; + } + case Point: { + BufferUtils.setInBuffer(_position, vertexBuffer, startIndex); + break; + } + } + Vector3.releaseTempInstance(tempVec3); + } + + /** + * <p> + * update position (using current position and velocity), color (interpolating between start and end color), size + * (interpolating between start and end size), spin (using parent's spin speed) and current age of particle. If this + * particle's age is greater than its lifespan, it is set to status DEAD. + * </p> + * <p> + * Note that this only changes the parameters of the Particle, not the geometry the particle is associated with. + * </p> + * + * @param secondsPassed + * number of seconds passed since last update. + * @return true if this particle is not ALIVE (in other words, if it is ready to be reused.) + */ + public boolean updateAndCheck(final double secondsPassed) { + if (status != Status.Alive) { + return true; + } + currentAge += secondsPassed * 1000; // add ms time to age + if (currentAge > lifeSpan) { + killParticle(); + return true; + } + + final Vector3 temp = Vector3.fetchTempInstance(); + _position.addLocal(_velocity.multiply(secondsPassed * 1000f, temp)); + Vector3.releaseTempInstance(temp); + + // get interpolated values from appearance ramp: + parent.getRamp().getValuesAtAge(currentAge, lifeSpan, currColor, values, parent); + + // interpolate colors + final int verts = ParticleSystem.getVertsForParticleType(type); + for (int x = 0; x < verts; x++) { + BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getMeshData().getColorBuffer(), startIndex + + x); + } + + // check for tex animation + final int newTexIndex = parent.getTexAnimation().getTexIndexAtAge(currentAge, lifeSpan, parent); + // Update tex coords if applicable + if (currentTexIndex != newTexIndex) { + // Only supported in Quad type for now. + if (ParticleType.Quad.equals(parent.getParticleType())) { + // determine side + final float side = (float) Math.sqrt(parent.getTexQuantity()); + int index = newTexIndex; + if (index >= parent.getTexQuantity()) { + index %= parent.getTexQuantity(); + } + // figure row / col + final float row = side - (int) (index / side) - 1; + final float col = index % side; + // set texcoords + final float sU = col / side, eU = (col + 1) / side; + final float sV = row / side, eV = (row + 1) / side; + final FloatBuffer texs = parent.getParticleGeometry().getMeshData().getTextureCoords(0).getBuffer(); + texs.position(startIndex * 2); + texs.put(eU).put(sV); + texs.put(eU).put(eV); + texs.put(sU).put(eV); + texs.put(sU).put(sV); + texs.clear(); + } + currentTexIndex = newTexIndex; + } + + return false; + } + + public void killParticle() { + setStatus(Status.Dead); + + final Vector3 tempVec3 = Vector3.fetchTempInstance(); + final FloatBuffer vertexBuffer = parent.getParticleGeometry().getMeshData().getVertexBuffer(); + BufferUtils.populateFromBuffer(tempVec3, vertexBuffer, startIndex); + final int verts = ParticleSystem.getVertsForParticleType(type); + for (int x = 1; x < verts; x++) { + BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + x); + } + Vector3.releaseTempInstance(tempVec3); + + } + + /** + * Resets current age to 0 + */ + public void resetAge() { + currentAge = 0; + } + + /** + * @return the current age of the particle in ms + */ + public int getCurrentAge() { + return currentAge; + } + + /** + * @return the current position of the particle in space + */ + public Vector3 getPosition() { + return _position; + } + + /** + * Set the position of the particle in space. + * + * @param position + * the new position in world coordinates + */ + public void setPosition(final Vector3 position) { + _position.set(position); + } + + /** + * @return the current status of this particle. + * @see Status + */ + public Status getStatus() { + return status; + } + + /** + * Set the status of this particle. + * + * @param status + * new status of this particle + * @see Status + */ + public void setStatus(final Status status) { + this.status = status; + } + + /** + * @return the current velocity of this particle + */ + public Vector3 getVelocity() { + return _velocity; + } + + /** + * Set the current velocity of this particle + * + * @param velocity + * the new velocity + */ + public void setVelocity(final Vector3 velocity) { + _velocity.set(velocity); + } + + /** + * @return the current color applied to this particle + */ + public ColorRGBA getCurrentColor() { + return currColor; + } + + /** + * @return the start index of this particle in relation to where it exists in its parent's geometry data. + */ + public int getStartIndex() { + return startIndex; + } + + /** + * Set the starting index where this particle is represented in its parent's geometry data + * + * @param index + */ + public void setStartIndex(final int index) { + startIndex = index; + } + + /** + * @return the mass of this particle. Only used by ParticleInfluences such as drag. + */ + public double getMass() { + return values[VAL_CURRENT_MASS]; + } + + /** + * @return the inverse mass of this particle. Often useful for skipping constant division by mass calculations. If + * the mass is 0, the inverse mass is considered to be positive infinity. Conversely, if the mass is + * positive infinity, the inverse is 0. The inverse of negative infinity is considered to be -0. + */ + public double getInvMass() { + final double mass = values[VAL_CURRENT_MASS]; + if (mass == 0) { + return Float.POSITIVE_INFINITY; + } else if (mass == Float.POSITIVE_INFINITY) { + return 0; + } else if (mass == Float.NEGATIVE_INFINITY) { + return -0; + } else { + return 1f / mass; + } + } + + /** + * Sets a triangle model to use for particle calculations when using particle type ParticleType.GeomMesh. The + * particle will maintain the triangle's ratio and plane of orientation. It will spin (if applicable) around the + * triangle's normal axis. The triangle should already have its center and normal fields calculated before calling + * this method. + * + * @param t + * the triangle to model this particle after. + */ + public void setTriangleModel(final Triangle t) { + triModel = t; + } + + /** + * @return the triangle model used by this particle + * @see #setTriangleModel(Triangle) + */ + public Triangle getTriangleModel() { + return triModel; + } + + // ///// + // Savable interface methods + // ///// + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(startIndex, "startIndex", 0); + capsule.write(_position, "position", new Vector3(Vector3.ZERO)); + capsule.write(status, "status", Status.Available); + capsule.write(lifeSpan, "lifeSpan", 0); + capsule.write(currentAge, "currentAge", 0); + capsule.write(parent, "parent", null); + capsule.write(_velocity, "velocity", new Vector3()); + capsule.write(type, "type", ParticleSystem.ParticleType.Quad); + } + + public void read(final InputCapsule capsule) throws IOException { + startIndex = capsule.readInt("startIndex", 0); + _position.set((Vector3) capsule.readSavable("position", new Vector3(Vector3.ZERO))); + status = capsule.readEnum("status", Status.class, Status.Available); + lifeSpan = capsule.readDouble("lifeSpan", 0); + currentAge = capsule.readInt("currentAge", 0); + parent = (ParticleSystem) capsule.readSavable("parent", null); + _velocity.set((Vector3) capsule.readSavable("velocity", new Vector3())); + type = capsule.readEnum("type", ParticleSystem.ParticleType.class, ParticleSystem.ParticleType.Quad); + } + + public Class<? extends Particle> getClassTag() { + return this.getClass(); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java new file mode 100644 index 0000000..c55f504 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +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; + +public class ParticleAppearanceRamp implements Savable { + + protected List<RampEntry> _entries = new ArrayList<RampEntry>(); + + public void addEntry(final RampEntry entry) { + _entries.add(entry); + } + + public void addEntry(final int index, final RampEntry entry) { + _entries.add(index, entry); + } + + public void clearEntries() { + _entries.clear(); + } + + public Iterator<RampEntry> getEntries() { + return _entries.iterator(); + } + + public void removeEntry(final RampEntry entry) { + _entries.remove(entry); + } + + public void removeEntry(final int index) { + _entries.remove(index); + } + + public void getValuesAtAge(final double age, final double maxAge, final ColorRGBA store, final double[] fStore, + final ParticleSystem particles) { + double prevCAge = 0, prevMAge = 0, prevSiAge = 0, prevSpAge = 0; + double nextCAge = maxAge, nextMAge = maxAge, nextSiAge = maxAge, nextSpAge = maxAge; + double trAge = 0; + RampEntry prevCEntry = null, prevMEntry = null, prevSiEntry = null, prevSpEntry = null; + RampEntry nextCEntry = null, nextMEntry = null, nextSiEntry = null, nextSpEntry = null; + for (int i = 0; i < _entries.size(); i++) { + final RampEntry entry = _entries.get(i); + trAge += entry.getOffset() * maxAge; + // Color + if (nextCEntry == null) { + if (trAge > age) { + if (entry.hasColorSet()) { + nextCAge = trAge; + nextCEntry = entry; + } + } else { + if (entry.hasColorSet()) { + prevCAge = trAge; + prevCEntry = entry; + } + } + } + + // mass + if (nextMEntry == null) { + if (trAge > age) { + if (entry.hasMassSet()) { + nextMAge = trAge; + nextMEntry = entry; + } + } else { + if (entry.hasMassSet()) { + prevMAge = trAge; + prevMEntry = entry; + } + } + } + + // size + if (nextSiEntry == null) { + if (trAge > age) { + if (entry.hasSizeSet()) { + nextSiAge = trAge; + nextSiEntry = entry; + } + } else { + if (entry.hasSizeSet()) { + prevSiAge = trAge; + prevSiEntry = entry; + } + } + } + + // spin + if (nextSpEntry == null) { + if (trAge > age) { + if (entry.hasSpinSet()) { + nextSpAge = trAge; + nextSpEntry = entry; + } + } else { + if (entry.hasSpinSet()) { + prevSpAge = trAge; + prevSpEntry = entry; + } + } + } + + } + + // color + { + final float lifeCRatio = (float) ((age - prevCAge) / (nextCAge - prevCAge)); + final ReadOnlyColorRGBA start = prevCEntry != null ? prevCEntry.getColor() : particles.getStartColor(); + final ReadOnlyColorRGBA end = nextCEntry != null ? nextCEntry.getColor() : particles.getEndColor(); + ColorRGBA.lerp(start, end, lifeCRatio, store); + } + + // mass + { + final double lifeMRatio = (age - prevMAge) / (nextMAge - prevMAge); + final double start = prevMEntry != null ? prevMEntry.getMass() : particles.getStartMass(); + final double end = nextMEntry != null ? nextMEntry.getMass() : particles.getEndMass(); + fStore[Particle.VAL_CURRENT_MASS] = (1 - lifeMRatio) * start + lifeMRatio * end; + } + + // Size + { + final double lifeSiRatio = (age - prevSiAge) / (nextSiAge - prevSiAge); + final double start = prevSiEntry != null ? prevSiEntry.getSize() : particles.getStartSize(); + final double end = nextSiEntry != null ? nextSiEntry.getSize() : particles.getEndSize(); + fStore[Particle.VAL_CURRENT_SIZE] = (1 - lifeSiRatio) * start + lifeSiRatio * end; + } + + // Spin + { + final double lifeSpRatio = (age - prevSpAge) / (nextSpAge - prevSpAge); + final double start = prevSpEntry != null ? prevSpEntry.getSpin() : particles.getStartSpin(); + final double end = nextSpEntry != null ? nextSpEntry.getSpin() : particles.getEndSpin(); + fStore[Particle.VAL_CURRENT_SPIN] = (1 - lifeSpRatio) * start + lifeSpRatio * end; + } + } + + public Class<? extends ParticleAppearanceRamp> getClassTag() { + return getClass(); + } + + public void read(final InputCapsule capsule) throws IOException { + _entries = capsule.readSavableList("entries", null); + if (_entries == null) { + _entries = new ArrayList<RampEntry>(); + } + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.writeSavableList(_entries, "entries", null); + } + +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java new file mode 100644 index 0000000..f91e9c0 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java @@ -0,0 +1,517 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.Camera.FrustumIntersect; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.controller.ComplexSpatialController; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * <code>ParticleController</code> controls and maintains the parameters of a particle system over time. + */ +public class ParticleController extends ComplexSpatialController<ParticleSystem> { + + private static final long serialVersionUID = 1L; + + private int _particlesToCreate = 0; + private double _releaseVariance; + private double _currentTime; + private double _prevTime; + private double _releaseParticles; + private double _timePassed; + private double _precision; + private boolean _controlFlow; + private boolean _updateOnlyInView; + private Camera _viewCamera; + + private int iterations; + private List<ParticleInfluence> influences; + protected List<ParticleControllerListener> listeners; + + /** + * ParticleController constructor + */ + public ParticleController() { + + setMinTime(0); + setMaxTime(Float.MAX_VALUE); + setRepeatType(RepeatType.WRAP); + setSpeed(1.0f); + + _releaseVariance = 0; + _controlFlow = false; + _updateOnlyInView = false; + _precision = .01f; // 10ms + } + + protected boolean _ignoreOneUpdate = false; + + protected void ignoreNextUpdate() { + _ignoreOneUpdate = true; + } + + /** + * Update the particles managed by this manager. If any particles are "dead" recreate them at the origin position + * (which may be a point, line or rectangle.) + * + * @param secondsPassed + * double precision time + * @param particles + * the particles we are updating + */ + @Override + public void update(final double secondsPassed, final ParticleSystem particles) { + + if (_ignoreOneUpdate) { + _ignoreOneUpdate = false; + return; + } + + // If instructed, check to see if our last frustum check passed + if (isUpdateOnlyInView()) { + final Camera cam = _viewCamera != null ? _viewCamera : ContextManager.getCurrentContext() + .getCurrentCamera(); + if (cam != null) { + final int state = cam.getPlaneState(); + final boolean out = cam.contains(particles.getWorldBound()).equals(FrustumIntersect.Outside); + cam.setPlaneState(state); + if (out) { + return; + } + } + } + + // Add time and unless we have more than precision time passed + // since last real update, do nothing + _currentTime += secondsPassed * getSpeed(); + + // Check precision passes + _timePassed = _currentTime - _prevTime; + if (_timePassed < _precision * getSpeed()) { + return; + } + + // We are actually going to do a real update, + // so this is our new previous time + _prevTime = _currentTime; + + // Update the current rotation matrix if needed. + particles.updateRotationMatrix(); + + // If we are in the time window where this controller is active + // (defaults to 0 to Float.MAX_VALUE for ParticleController) + if (_currentTime >= getMinTime() && _currentTime <= getMaxTime()) { + + // If we are controlling the flow (ie the rate of particle spawning.) + if (_controlFlow) { + // Release a number of particles based on release rate, + // timePassed (already scaled for speed) and variance. This + // is added to any current value Note this is a double value, + // so we will keep adding up partial particles + + _releaseParticles += (particles.getReleaseRate() * _timePassed * (1.0 + _releaseVariance + * (MathUtils.nextRandomFloat() - 0.5))); + + // Try to create all "whole" particles we have added up + _particlesToCreate = (int) _releaseParticles; + + // If we have any whole particles, then subtract them from + // releaseParticles + if (_particlesToCreate > 0) { + _releaseParticles -= _particlesToCreate; + } else { + _particlesToCreate = 0; + } + } + + particles.updateInvScale(); + + // If we have any influences, prepare them all + if (influences != null) { + for (int x = 0; x < influences.size(); x++) { + final ParticleInfluence inf = influences.get(x); + inf.prepare(particles); + } + } + + // Track particle index + int i = 0; + + // Track whether the whole set of particles is "dead" - if any + // particles are still alive, this will be set to false + boolean dead = true; + + // opposite of above boolean, but tracked separately + boolean anyAlive = false; + + // i is index through all particles + while (i < particles.getNumParticles()) { + // Current particle + final Particle p = particles.getParticle(i); + + // If we have influences and particle is alive + if (influences != null && p.getStatus() == Particle.Status.Alive) { + // Apply each enabled influence to the current particle + for (int x = 0; x < influences.size(); x++) { + final ParticleInfluence inf = influences.get(x); + if (inf.isEnabled()) { + inf.apply(_timePassed, p, i); + } + } + } + + // Update and check the particle. + // If this returns true, indicating particle is ready to be + // reused, we may reuse it. Do so if we are not using + // control flow, OR we intend to create particles based on + // control flow count calculated above + final boolean reuse = p.updateAndCheck(_timePassed); + if (reuse && (!_controlFlow || _particlesToCreate > 0)) { + + // Don't recreate the particle if it is dead, and we are clamped + if (p.getStatus() == Particle.Status.Dead && getRepeatType() == RepeatType.CLAMP) { + ; + + // We plan to reuse the particle + } else { + // Not all particles are dead (this one will be reused) + dead = false; + + // If we are doing flow control, decrement + // particlesToCreate, since we are about to create + // one + if (_controlFlow) { + _particlesToCreate--; + } + + // Recreate the particle + p.recreateParticle(particles.getRandomLifeSpan()); + p.setStatus(Particle.Status.Alive); + particles.initParticleLocation(i); + particles.resetParticleVelocity(i); + p.updateVerts(null); + } + + } else if (!reuse || (_controlFlow && particles.getReleaseRate() > 0)) { + // The particle wasn't dead, or we expect more particles + // later, so we're not dead! + dead = false; + } + + // Check for living particles so we know when to update our boundings. + if (p.getStatus() == Particle.Status.Alive) { + anyAlive = true; + } + + // Next particle + i++; + } + + // If we are dead, deactivate and tell our listeners + if (dead) { + setActive(false); + if (listeners != null && listeners.size() > 0) { + for (final ParticleControllerListener listener : listeners) { + listener.onDead(particles); + } + } + } else { + // if not dead make sure our particles refresh their vbos, etc. + final MeshData md = particles.getParticleGeometry().getMeshData(); + md.getVertexCoords().setNeedsRefresh(true); + md.getColorCoords().setNeedsRefresh(true); + md.getTextureCoords(0).setNeedsRefresh(true); + } + + // If we have any live particles and are offscreen, update it + if (anyAlive) { + boolean updateMB = true; + final Camera cam = _viewCamera != null ? _viewCamera + : (ContextManager.getCurrentContext() != null ? ContextManager.getCurrentContext() + .getCurrentCamera() : null); + if (cam != null) { + final int state = cam.getPlaneState(); + updateMB = cam.contains(particles.getWorldBound()).equals(FrustumIntersect.Outside); + cam.setPlaneState(state); + } + if (updateMB) { + particles.getParticleGeometry().updateModelBound(); + particles.updateWorldBoundManually(); + } + } + } + } + + /** + * Get how soon after the last update the manager will send updates to the particles. + * + * @return The precision. + */ + public double getPrecision() { + return _precision; + } + + /** + * Set how soon after the last update the manager will send updates to the particles. Defaults to .01f (10ms)<br> + * <br> + * This means that if an update is called every 2ms (e.g. running at 500 FPS) the particles position and stats will + * be updated every fifth frame with the elapsed time (in this case, 10ms) since previous update. + * + * @param precision + * in seconds + */ + public void setPrecision(final double precision) { + _precision = precision; + } + + /** + * Get the variance possible on the release rate. 0.0f = no variance 0.5f = between releaseRate / 2f and 1.5f * + * releaseRate + * + * @return release variance as a percent. + */ + public double getReleaseVariance() { + return _releaseVariance; + } + + /** + * Set the variance possible on the release rate. + * + * @param variance + * release rate +/- variance as a percent (eg. .5 = 50%) + */ + public void setReleaseVariance(final double variance) { + _releaseVariance = variance; + } + + /** + * Does this manager regulate the particle flow? + * + * @return true if this manager regulates how many particles per sec are emitted. + */ + public boolean isControlFlow() { + return _controlFlow; + } + + /** + * Set the regulate flow property on the manager. + * + * @param regulate + * regulate particle flow. + */ + public void setControlFlow(final boolean regulate) { + _controlFlow = regulate; + } + + /** + * Does this manager use the particle's bounding volume to limit updates? + * + * @return true if this manager only updates the particles when they are in view. + */ + public boolean isUpdateOnlyInView() { + return _updateOnlyInView; + } + + /** + * Set the updateOnlyInView property on the manager. + * + * @param updateOnlyInView + * use the particle's bounding volume to limit updates. + */ + public void setUpdateOnlyInView(final boolean updateOnlyInView) { + _updateOnlyInView = updateOnlyInView; + } + + /** + * @return the camera to be used in updateOnlyInView situations. If null, the current displaySystem's renderer + * camera is used. + */ + public Camera getViewCamera() { + return _viewCamera; + } + + /** + * @param viewCamera + * sets the camera to be used in updateOnlyInView situations. If null, the current displaySystem's + * renderer camera is used. + */ + public void setViewCamera(final Camera viewCamera) { + _viewCamera = viewCamera; + } + + /** + * Return the number this manager has warmed up + * + * @return int + */ + public int getIterations() { + return iterations; + } + + /** + * Sets the iterations for the warmup and calls warmUp with the number of iterations as the argument + * + * @param iterations + */ + public void setIterations(final int iterations) { + this.iterations = iterations; + } + + /** + * Add an external influence to this particle controller. + * + * @param influence + * ParticleInfluence + */ + public void addInfluence(final ParticleInfluence influence) { + if (influences == null) { + influences = new ArrayList<ParticleInfluence>(1); + } + influences.add(influence); + } + + /** + * Remove an influence from this particle controller. + * + * @param influence + * ParticleInfluence + * @return true if found and removed. + */ + public boolean removeInfluence(final ParticleInfluence influence) { + if (influences == null) { + return false; + } + return influences.remove(influence); + } + + /** + * Returns the list of influences acting on this particle controller. + * + * @return ArrayList + */ + public List<ParticleInfluence> getInfluences() { + return influences; + } + + public void clearInfluences() { + if (influences != null) { + influences.clear(); + } + } + + /** + * Subscribe a listener to receive mouse events. Enable event generation. + * + * @param listener + * to be subscribed + */ + public void addListener(final ParticleControllerListener listener) { + if (listeners == null) { + listeners = new ArrayList<ParticleControllerListener>(); + } + + listeners.add(listener); + } + + /** + * Unsubscribe a listener. Disable event generation if no more listeners. + * + * @param listener + * to be unsuscribed + * @see #addListener(ParticleControllerListener) + */ + public void removeListener(final ParticleControllerListener listener) { + if (listeners != null) { + listeners.remove(listener); + } + } + + /** + * Remove all listeners and disable event generation. + */ + public void removeListeners() { + if (listeners != null) { + listeners.clear(); + } + } + + /** + * Check if a listener is allready added to this ParticleController + * + * @param listener + * listener to check for + * @return true if listener is contained in the listenerlist + */ + public boolean containsListener(final ParticleControllerListener listener) { + if (listeners != null) { + return listeners.contains(listener); + } + return false; + } + + /** + * Get all added ParticleController listeners + * + * @return ArrayList of listeners added to this ParticleController + */ + public List<ParticleControllerListener> getListeners() { + return listeners; + } + + /** + * Runs the update method of this particle manager X number of times passing .1 seconds for each call. This is used + * to "warm up" and get the particle manager going. + * + * @param iterations + * The number of iterations to warm up. + */ + public void warmUp(int iterations, final ParticleSystem particles) { + // first set the initial positions of all the verts + particles.initAllParticlesLocation(); + + iterations *= 10; + for (int i = iterations; --i >= 0;) { + particles.updateGeometricState(0.1, false); + } + ignoreNextUpdate(); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_releaseVariance, "releaseVariance", 0); + capsule.write(_precision, "precision", 0); + capsule.write(_controlFlow, "controlFlow", false); + capsule.write(_updateOnlyInView, "updateOnlyInView", false); + capsule.write(iterations, "iterations", 0); + capsule.writeSavableList(influences, "influences", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _releaseVariance = capsule.readDouble("releaseVariance", 0); + _precision = capsule.readDouble("precision", 0); + _controlFlow = capsule.readBoolean("controlFlow", false); + _updateOnlyInView = capsule.readBoolean("updateOnlyInView", false); + iterations = capsule.readInt("iterations", 0); + influences = capsule.readSavableList("influences", null); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.java new file mode 100644 index 0000000..1b5b40b --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.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.extension.effect.particle;
+
+/**
+ * ParticleControllerListener This interface is used to receive key events from ParticleController
+ */
+public interface ParticleControllerListener {
+ void onDead(ParticleSystem particles);
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.java new file mode 100644 index 0000000..d212e6b --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.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.extension.effect.particle;
+
+import com.ardor3d.extension.effect.particle.ParticleSystem.ParticleType;
+import com.ardor3d.scenegraph.Mesh;
+
+public class ParticleFactory {
+
+ public static ParticleSystem buildParticles(final String name, final int number) {
+ return buildParticles(name, number, ParticleSystem.ParticleType.Quad);
+ }
+
+ public static ParticleSystem buildParticles(final String name, final int number,
+ final ParticleSystem.ParticleType particleType) {
+ if (particleType == ParticleSystem.ParticleType.GeomMesh) {
+ throw new IllegalArgumentException("particleType can not be GeomMesh");
+ }
+ ParticleSystem system;
+ if (particleType == ParticleType.Point) {
+ system = new ParticlePoints(name, number);
+ } else if (particleType == ParticleType.Line) {
+ system = new ParticleLines(name, number);
+ } else {
+ system = new ParticleMesh(name, number, particleType);
+ }
+ final ParticleController particleController = new ParticleController();
+ system.addController(particleController);
+ return system;
+ }
+
+ public static ParticleMesh buildMeshParticles(final String name, final Mesh mesh) {
+ final ParticleMesh particleMesh = new ParticleMesh(name, mesh);
+ final ParticleController particleController = new ParticleController();
+ particleMesh.addController(particleController);
+ return particleMesh;
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.java new file mode 100644 index 0000000..3cd6906 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.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.extension.effect.particle; + +import java.io.IOException; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +/** + * <code>ParticleInfluence</code> is an abstract class defining an external influence to be used with the ParticleMesh + * class. + */ +public abstract class ParticleInfluence implements Savable { + + /** + * Is this influence enabled? ie, should it be used when updating particles. + */ + private boolean _enabled = true; + + /** + * Set this influence enabled or not. + * + * @param enabled + * boolean + */ + public void setEnabled(final boolean enabled) { + _enabled = enabled; + } + + /** + * Return whether or not this influence is enabled. + * + * @return boolean + */ + public boolean isEnabled() { + return _enabled; + } + + /** + * Gives the influence a chance to perform any necessary initialization immediately before {@link #apply} is called + * on each particle for the current frame. + * + * @param particleGeom + * the particle system containing the influence + */ + public void prepare(final ParticleSystem particleGeom) {} + + /** + * Apply the influence defined by this class on a given particle. Should probably do this by making a call to + * <i>particle.getSpeed().addLocal(....);</i> etc. + * + * @param dt + * amount of time since last apply call in ms. + * @param particle + * the particle to apply the influence to. + * @param index + * the index of the particle we are working with. This is useful for adding small steady amounts of + * variation, or remembering information. + */ + public abstract void apply(double dt, Particle particle, int index); + + 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 ParticleInfluence> getClassTag() { + return this.getClass(); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.java new file mode 100644 index 0000000..5bc25a5 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.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.extension.effect.particle; + +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.scenegraph.Line; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.util.geom.BufferUtils; + +/** + * ParticleLines is a particle system that uses Line as its underlying geometric data. + */ +public class ParticleLines extends ParticleSystem { + + public ParticleLines() {} + + public ParticleLines(final String name, final int numParticles) { + super(name, numParticles); + } + + @Override + protected void initializeParticles(final int numParticles) { + + // setup texture coords + final Vector2[] sharedTextureData = new Vector2[] { new Vector2(0.0, 0.0), new Vector2(1.0, 1.0) }; + + final int verts = getVertsForParticleType(getParticleType()); + + _geometryCoordinates = BufferUtils.createVector3Buffer(numParticles * verts); + + // setup indices for PT_LINES + + _appearanceColors = BufferUtils.createColorBuffer(numParticles * verts); + _particles = new Particle[numParticles]; + + if (_particleMesh != null) { + detachChild(_particleMesh); + } + final Line line = new Line(getName() + "_lines") { + + @Override + public void updateWorldTransform(final boolean recurse) { + ; // Do nothing. + } + + @Override + public void updateWorldBound(final boolean recurse) { + super.updateWorldTransform(recurse); + super.updateWorldBound(recurse); + } + }; + _particleMesh = line; + attachChild(line); + line.getMeshData().setVertexBuffer(_geometryCoordinates); + line.getMeshData().setColorBuffer(_appearanceColors); + line.getMeshData().setTextureBuffer(BufferUtils.createVector2Buffer(numParticles * 2), 0); + getSceneHints().setRenderBucketType(RenderBucketType.Opaque); + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + + for (int k = 0; k < numParticles; k++) { + _particles[k] = new Particle(this); + _particles[k].init(); + _particles[k].setStartIndex(k * verts); + for (int a = verts - 1; a >= 0; a--) { + final int ind = (k * verts) + a; + BufferUtils.setInBuffer(sharedTextureData[a], line.getMeshData().getTextureCoords(0).getBuffer(), ind); + BufferUtils.setInBuffer(_particles[k].getCurrentColor(), _appearanceColors, (ind)); + } + + } + updateWorldRenderStates(true); + _particleMesh.getSceneHints().setCastsShadows(false); + } + + @Override + public ParticleType getParticleType() { + return ParticleSystem.ParticleType.Line; + } + + @Override + public void draw(final Renderer r) { + final Camera camera = Camera.getCurrentCamera(); + boolean anyAlive = false; + for (int i = 0; i < _particles.length; i++) { + final Particle particle = _particles[i]; + if (particle.getStatus() == Particle.Status.Alive) { + particle.updateVerts(camera); + anyAlive = true; + } + } + + // Since we've updated our verts, update the model boundary where applicable + if (getParticleGeometry().getWorldBound() != null && anyAlive) { + getParticleGeometry().updateModelBound(); + } + + if (!_particlesInWorldCoords) { + getParticleGeometry().setWorldTransform(getWorldTransform()); + } else { + getParticleGeometry().setWorldTranslation(Vector3.ZERO); + getParticleGeometry().setWorldRotation(Matrix3.IDENTITY); + getParticleGeometry().setWorldScale(getWorldScale()); + } + + getParticleGeometry().draw(r); + } + + @Override + public Line getParticleGeometry() { + return (Line) _particleMesh; + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java new file mode 100644 index 0000000..c700471 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; + +import com.ardor3d.extension.effect.particle.emitter.MeshEmitter; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * ParticleMesh is a particle system that uses Mesh as its underlying geometric data. + */ +public class ParticleMesh extends ParticleSystem { + + private boolean _useMeshTexCoords = true; + private boolean _useTriangleNormalEmit = true; + + public ParticleMesh() {} + + public ParticleMesh(final String name, final int numParticles) { + super(name, numParticles); + getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + } + + public ParticleMesh(final String name, final int numParticles, final ParticleSystem.ParticleType type) { + super(name, numParticles, type); + getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + } + + public ParticleMesh(final String name, final Mesh sourceMesh) { + super(name, 0, ParticleSystem.ParticleType.GeomMesh); + _numParticles = sourceMesh.getMeshData().getTotalPrimitiveCount(); + setParticleEmitter(new MeshEmitter(sourceMesh, false)); + getSceneHints().setRenderBucketType(RenderBucketType.Transparent); + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + initializeParticles(_numParticles); + } + + @Override + protected void initializeParticles(final int numParticles) { + + if (_particleMesh != null) { + detachChild(_particleMesh); + } + final Mesh mesh = new Mesh(getName() + "_mesh") { + + @Override + public void updateWorldTransform(final boolean recurse) { + ; // Do nothing. + } + + @Override + public void updateWorldBound(final boolean recurse) { + super.updateWorldTransform(recurse); + super.updateWorldBound(recurse); + } + }; + _particleMesh = mesh; + attachChild(mesh); + _particles = new Particle[numParticles]; + if (numParticles == 0) { + return; + } + Vector2 sharedTextureData[]; + + // setup texture coords and index mode + final MeshData meshData = mesh.getMeshData(); + switch (getParticleType()) { + case GeomMesh: + case Triangle: + sharedTextureData = new Vector2[] { new Vector2(2.0, 0.0), new Vector2(0.0, 2.0), new Vector2(0.0, 0.0) }; + meshData.setIndexMode(IndexMode.Triangles); + break; + case Quad: + sharedTextureData = new Vector2[] { new Vector2(1.0, 0.0), new Vector2(1.0, 1.0), + new Vector2(0.0, 1.0), new Vector2(0.0, 0.0) }; + meshData.setIndexMode(IndexMode.Quads); + break; + default: + throw new IllegalStateException( + "Particle Mesh may only have particle type of ParticleType.Quad, ParticleType.GeomMesh or ParticleType.Triangle"); + } + + final int verts = getVertsForParticleType(getParticleType()); + + _geometryCoordinates = BufferUtils.createVector3Buffer(numParticles * verts); + _appearanceColors = BufferUtils.createColorBuffer(numParticles * verts); + + meshData.setVertexBuffer(_geometryCoordinates); + meshData.setColorBuffer(_appearanceColors); + meshData.setTextureBuffer(BufferUtils.createVector2Buffer(numParticles * verts), 0); + + final Vector2 temp = Vector2.fetchTempInstance(); + for (int k = 0; k < numParticles; k++) { + _particles[k] = new Particle(this); + _particles[k].init(); + _particles[k].setStartIndex(k * verts); + for (int a = verts - 1; a >= 0; a--) { + final int ind = (k * verts) + a; + if (_particleType == ParticleSystem.ParticleType.GeomMesh && _useMeshTexCoords) { + final MeshEmitter source = (MeshEmitter) getParticleEmitter(); + final Mesh sourceMesh = source.getSource(); + final int index = sourceMesh.getMeshData().getIndexBuffer() != null ? sourceMesh.getMeshData() + .getIndices().get(ind) : ind; + BufferUtils.populateFromBuffer(temp, sourceMesh.getMeshData().getTextureCoords(0).getBuffer(), + index); + BufferUtils.setInBuffer(temp, meshData.getTextureCoords(0).getBuffer(), ind); + } else { + BufferUtils.setInBuffer(sharedTextureData[a], meshData.getTextureCoords(0).getBuffer(), ind); + } + BufferUtils.setInBuffer(_particles[k].getCurrentColor(), _appearanceColors, (ind)); + } + + } + Vector2.releaseTempInstance(temp); + updateWorldRenderStates(true); + _particleMesh.getSceneHints().setCastsShadows(false); + } + + @Override + public void draw(final Renderer r) { + final Camera camera = Camera.getCurrentCamera(); + boolean anyAlive = false; + for (int i = 0; i < _particles.length; i++) { + final Particle particle = _particles[i]; + if (particle.getStatus() == Particle.Status.Alive) { + particle.updateVerts(camera); + anyAlive = true; + } + } + + // Since we've updated our verts, update the model boundary where applicable + if (getParticleGeometry().getWorldBound() != null && anyAlive) { + getParticleGeometry().updateModelBound(); + } + + if (!_particlesInWorldCoords) { + getParticleGeometry().setWorldTransform(getWorldTransform()); + } else { + getParticleGeometry().setWorldTranslation(Vector3.ZERO); + getParticleGeometry().setWorldRotation(Matrix3.IDENTITY); + getParticleGeometry().setWorldScale(getWorldScale()); + } + + getParticleGeometry().draw(r); + } + + @Override + public void resetParticleVelocity(final int i) { + if (_particleType == ParticleSystem.ParticleType.GeomMesh && _useTriangleNormalEmit) { + _particles[i].getVelocity().set(_particles[i].getTriangleModel().getNormal()); + _particles[i].getVelocity().multiplyLocal(_emissionDirection); + _particles[i].getVelocity().multiplyLocal(getInitialVelocity()); + } else { + super.resetParticleVelocity(i); + } + } + + public boolean isUseMeshTexCoords() { + return _useMeshTexCoords; + } + + public void setUseMeshTexCoords(final boolean useMeshTexCoords) { + _useMeshTexCoords = useMeshTexCoords; + } + + public boolean isUseTriangleNormalEmit() { + return _useTriangleNormalEmit; + } + + public void setUseTriangleNormalEmit(final boolean useTriangleNormalEmit) { + _useTriangleNormalEmit = useTriangleNormalEmit; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_useMeshTexCoords, "useMeshTexCoords", true); + capsule.write(_useTriangleNormalEmit, "useTriangleNormalEmit", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _useMeshTexCoords = capsule.readBoolean("useMeshTexCoords", true); + _useTriangleNormalEmit = capsule.readBoolean("useTriangleNormalEmit", true); + } + + @Override + public Mesh getParticleGeometry() { + return _particleMesh; + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.java new file mode 100644 index 0000000..c14ff07 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.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.extension.effect.particle; + +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.scenegraph.Point; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.util.geom.BufferUtils; + +/** + * ParticlePoints is a particle system that uses Point as its underlying geometric data. + */ +public class ParticlePoints extends ParticleSystem { + + public ParticlePoints() {} + + public ParticlePoints(final String name, final int numParticles) { + super(name, numParticles); + } + + @Override + protected void initializeParticles(final int numParticles) { + Vector2 sharedTextureData[]; + + // setup texture coords + sharedTextureData = new Vector2[] { new Vector2(0.0, 0.0) }; + + final int verts = getVertsForParticleType(getParticleType()); + + _geometryCoordinates = BufferUtils.createVector3Buffer(numParticles * verts); + + _appearanceColors = BufferUtils.createColorBuffer(numParticles * verts); + _particles = new Particle[numParticles]; + + if (_particleMesh != null) { + detachChild(_particleMesh); + } + _particleMesh = new Point(getName() + "_points", _geometryCoordinates, null, _appearanceColors, null) { + + @Override + public void updateWorldTransform(final boolean recurse) { + ; // Do nothing. + } + + @Override + public void updateWorldBound(final boolean recurse) { + super.updateWorldTransform(recurse); + super.updateWorldBound(recurse); + } + }; + _particleMesh.getMeshData().setTextureBuffer(BufferUtils.createVector2Buffer(numParticles), 0); + attachChild(_particleMesh); + getSceneHints().setRenderBucketType(RenderBucketType.Opaque); + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + + for (int k = 0; k < numParticles; k++) { + _particles[k] = new Particle(this); + _particles[k].init(); + _particles[k].setStartIndex(k * verts); + for (int a = verts - 1; a >= 0; a--) { + final int ind = (k * verts) + a; + BufferUtils.setInBuffer(sharedTextureData[a], getParticleGeometry().getMeshData().getTextureCoords(0) + .getBuffer(), ind); + BufferUtils.setInBuffer(_particles[k].getCurrentColor(), _appearanceColors, (ind)); + } + + } + updateWorldRenderStates(true); + _particleMesh.getSceneHints().setCastsShadows(false); + } + + @Override + public ParticleType getParticleType() { + return ParticleSystem.ParticleType.Point; + } + + @Override + public void draw(final Renderer r) { + final Camera camera = Camera.getCurrentCamera(); + boolean anyAlive = false; + for (int i = 0; i < _particles.length; i++) { + final Particle particle = _particles[i]; + if (particle.getStatus() == Particle.Status.Alive) { + particle.updateVerts(camera); + anyAlive = true; + } + } + + // Since we've updated our verts, update the model boundary where applicable + if (getParticleGeometry().getWorldBound() != null && anyAlive) { + getParticleGeometry().updateModelBound(); + } + + if (!_particlesInWorldCoords) { + getParticleGeometry().setWorldTransform(getWorldTransform()); + } else { + getParticleGeometry().setWorldTranslation(Vector3.ZERO); + getParticleGeometry().setWorldRotation(Matrix3.IDENTITY); + getParticleGeometry().setWorldScale(getWorldScale()); + } + + getParticleGeometry().draw(r); + } + + /** + * @return true if points are to be drawn antialiased + */ + public boolean isAntialiased() { + return getParticleGeometry().isAntialiased(); + } + + /** + * Sets whether the points 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) { + getParticleGeometry().setAntialiased(antialiased); + } + + /** + * @return the pixel size of each point. + */ + public float getPointSize() { + return getParticleGeometry().getPointSize(); + } + + /** + * Sets the pixel width of the points 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) { + getParticleGeometry().setPointSize(size); + } + + @Override + public Point getParticleGeometry() { + return (Point) _particleMesh; + } + +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java new file mode 100644 index 0000000..351470e --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java @@ -0,0 +1,1084 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.ardor3d.extension.effect.particle.emitter.MeshEmitter;
+import com.ardor3d.extension.effect.particle.emitter.SavableParticleEmitter;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Triangle;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.ComplexSpatialController.RepeatType;
+import com.ardor3d.scenegraph.controller.SpatialController;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * ParticleSystem is an abstract class representing a particle system. A ParticleController must be attached for the
+ * effect to be complete.
+ */
+public abstract class ParticleSystem extends Node {
+ private static final Logger logger = Logger.getLogger(ParticleSystem.class.getName());
+
+ protected static final long serialVersionUID = 2L;
+
+ public enum ParticleType {
+ Quad, Triangle, Point, Line, GeomMesh;
+ }
+
+ protected static final double DEFAULT_END_SIZE = 4;
+ protected static final double DEFAULT_START_SIZE = 20;
+ protected static final double DEFAULT_MAX_ANGLE = 0.7853982;
+ protected static final double DEFAULT_MAX_LIFE = 3000;
+ protected static final double DEFAULT_MIN_LIFE = 2000;
+
+ protected static final ReadOnlyColorRGBA DEFAULT_START_COLOR = new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f);
+ protected static final ReadOnlyColorRGBA DEFAULT_END_COLOR = new ColorRGBA(1.0f, 1.0f, 0.0f, 0.0f);
+
+ protected ParticleType _particleType;
+ protected SavableParticleEmitter _particleEmitter;
+ protected boolean _cameraFacing = true;
+ protected boolean _velocityAligned = false;
+ protected boolean _particlesInWorldCoords = true;
+
+ protected double _startSize, _endSize;
+ protected final ColorRGBA _startColor = new ColorRGBA(DEFAULT_START_COLOR);
+ protected final ColorRGBA _endColor = new ColorRGBA(DEFAULT_END_COLOR);
+ protected ParticleAppearanceRamp _ramp = new ParticleAppearanceRamp();
+ protected TexAnimation _texAnimation = new TexAnimation();
+ protected double _initialVelocity;
+ protected double _minimumLifeTime, _maximumLifeTime;
+ protected double _minimumAngle, _maximumAngle;
+ protected double _startSpin, _endSpin;
+ protected double _startMass, _endMass;
+ protected int _startTexIndex, _texQuantity;
+ protected final Vector3 _emissionDirection = new Vector3(Vector3.UNIT_Y);
+ protected final Transform _emitterTransform = new Transform();
+ protected final Vector3 _worldEmit = new Vector3();
+ protected int _numParticles;
+ protected boolean _rotateWithScene = false;
+ protected final Matrix3 _rotMatrix = new Matrix3();
+ protected double _particleOrientation;
+
+ protected FloatBuffer _geometryCoordinates;
+ protected FloatBuffer _appearanceColors;
+
+ // vectors to prevent repeated object creation:
+ protected final Vector3 _upXemit = new Vector3(), _absUpVector = new Vector3(), _abUpMinUp = new Vector3();
+ protected final Vector3 _upVector = new Vector3(Vector3.UNIT_Y);
+ protected final Vector3 _leftVector = new Vector3(-1, 0, 0);
+ protected final Vector3 _invScale = new Vector3();
+
+ // These vectors are used for determining particle orientation if you turn off camera facing
+ protected final Vector3 _facingUpVector = new Vector3(Vector3.UNIT_Y);
+ protected final Vector3 _facingLeftVector = new Vector3(-1, 0, 0);
+
+ protected Particle _particles[];
+
+ // protected Vector3 particleSpeed;
+ protected int _releaseRate; // particles per second
+ protected final Vector3 _originOffset = new Vector3();
+ protected final Vector3 _originCenter = new Vector3();
+
+ protected Mesh _particleMesh;
+ protected ParticleController _controller;
+
+ protected Vector3 _oldEmit = new Vector3(Float.NaN, Float.NaN, Float.NaN);
+ protected double _matData[] = new double[9];
+
+ public ParticleSystem() {}
+
+ public ParticleSystem(final String name, final int numParticles) {
+ this(name, numParticles, ParticleType.Quad);
+ }
+
+ public ParticleSystem(final String name, final int numParticles, final ParticleType particleType) {
+ super(name);
+ _numParticles = numParticles;
+ _particleType = particleType;
+ _minimumLifeTime = DEFAULT_MIN_LIFE;
+ _maximumLifeTime = DEFAULT_MAX_LIFE;
+ _maximumAngle = DEFAULT_MAX_ANGLE;
+ _startSize = DEFAULT_START_SIZE;
+ _endSize = DEFAULT_END_SIZE;
+ _startSpin = 0;
+ _endSpin = 0;
+ _startMass = 1;
+ _endMass = 1;
+ _startTexIndex = 0;
+ _texQuantity = 1;
+ _releaseRate = numParticles;
+ _initialVelocity = 1.0;
+
+ initializeParticles(numParticles);
+ }
+
+ protected abstract void initializeParticles(int numParticles);
+
+ public static int getVertsForParticleType(final ParticleType type) {
+ if (type == null) {
+ throw new NullPointerException("type is null");
+ }
+ switch (type) {
+ case Triangle:
+ case GeomMesh:
+ return 3;
+ case Point:
+ return 1;
+ case Line:
+ return 2;
+ case Quad:
+ return 4;
+ }
+ throw new IllegalArgumentException("Invalid ParticleType: " + type);
+ }
+
+ public void forceRespawn() {
+ for (final Particle p : _particles) {
+ p.recreateParticle(0);
+ p.setStatus(Particle.Status.Alive);
+ p.updateAndCheck(1);
+ p.setStatus(Particle.Status.Available);
+ }
+
+ if (_controller != null) {
+ _controller.setActive(true);
+ }
+ }
+
+ /**
+ * Setup the rotation matrix used to determine initial particle velocity based on emission angle and emission
+ * direction. called automatically by the set* methods for those parameters.
+ */
+ public void updateRotationMatrix() {
+
+ if (_oldEmit.equals(_worldEmit)) {
+ return;
+ }
+
+ final double upDotEmit = _upVector.dot(_worldEmit);
+ if (Math.abs(upDotEmit) > 1.0 - MathUtils.EPSILON) {
+ _absUpVector.setX(_upVector.getX() <= 0.0 ? -_upVector.getX() : _upVector.getX());
+ _absUpVector.setY(_upVector.getY() <= 0.0 ? -_upVector.getY() : _upVector.getY());
+ _absUpVector.setZ(_upVector.getZ() <= 0.0 ? -_upVector.getZ() : _upVector.getZ());
+ if (_absUpVector.getX() < _absUpVector.getY()) {
+ if (_absUpVector.getX() < _absUpVector.getZ()) {
+ _absUpVector.set(Vector3.UNIT_X);
+ } else {
+ _absUpVector.set(Vector3.UNIT_Z);
+ }
+ } else if (_absUpVector.getY() < _absUpVector.getZ()) {
+ _absUpVector.set(Vector3.UNIT_Y);
+ } else {
+ _absUpVector.set(Vector3.UNIT_Z);
+ }
+ _absUpVector.subtract(_upVector, _abUpMinUp);
+ _absUpVector.subtract(_worldEmit, _upXemit);
+ final double f4 = 2.0 / _abUpMinUp.dot(_abUpMinUp);
+ final double f6 = 2.0 / _upXemit.dot(_upXemit);
+ final double f8 = f4 * f6 * _abUpMinUp.dot(_upXemit);
+ final double af1[] = { _abUpMinUp.getX(), _abUpMinUp.getY(), _abUpMinUp.getZ() };
+ final double af2[] = { _upXemit.getX(), _upXemit.getY(), _upXemit.getZ() };
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ _matData[(i * 3) + j] = (-f4 * af1[i] * af1[j] - f6 * af2[i] * af2[j]) + f8 * af2[i] * af1[j];
+
+ }
+ _matData[(i * 3) + i]++;
+ }
+
+ } else {
+ _upVector.cross(_worldEmit, _upXemit);
+ final double f2 = 1.0 / (1.0 + upDotEmit);
+ final double f5 = f2 * _upXemit.getX();
+ final double f7 = f2 * _upXemit.getZ();
+ final double f9 = f5 * _upXemit.getY();
+ final double f10 = f5 * _upXemit.getZ();
+ final double f11 = f7 * _upXemit.getY();
+ _matData[0] = upDotEmit + f5 * _upXemit.getX();
+ _matData[1] = f9 - _upXemit.getZ();
+ _matData[2] = f10 + _upXemit.getY();
+ _matData[3] = f9 + _upXemit.getZ();
+ _matData[4] = upDotEmit + f2 * _upXemit.getY() * _upXemit.getY();
+ _matData[5] = f11 - _upXemit.getX();
+ _matData[6] = f10 - _upXemit.getY();
+ _matData[7] = f11 + _upXemit.getX();
+ _matData[8] = upDotEmit + f7 * _upXemit.getZ();
+ }
+ _rotMatrix.fromArray(_matData);
+ _oldEmit.set(_worldEmit);
+ }
+
+ public abstract Mesh getParticleGeometry();
+
+ public ParticleController getParticleController() {
+ return _controller;
+ }
+
+ @Override
+ public void addController(final SpatialController<?> c) {
+ super.addController(c);
+ if (c instanceof ParticleController) {
+ _controller = (ParticleController) c;
+ }
+ }
+
+ public Vector3 getEmissionDirection() {
+ return _emissionDirection;
+ }
+
+ public void setEmissionDirection(final ReadOnlyVector3 emissionDirection) {
+ _emissionDirection.set(emissionDirection);
+ _worldEmit.set(emissionDirection);
+ }
+
+ public double getEndSize() {
+ return _endSize;
+ }
+
+ public void setEndSize(final double size) {
+ _endSize = size >= 0.0 ? size : 0.0;
+ }
+
+ public double getStartSize() {
+ return _startSize;
+ }
+
+ public void setStartSize(final double size) {
+ _startSize = size >= 0.0 ? size : 0.0;
+ }
+
+ /**
+ * Set the start color for particles.
+ *
+ * @param color
+ * The new start color.
+ */
+ public void setStartColor(final ReadOnlyColorRGBA color) {
+ _startColor.set(color);
+ }
+
+ /**
+ * @return The start color.
+ */
+ public ReadOnlyColorRGBA getStartColor() {
+ return _startColor;
+ }
+
+ /**
+ * Set the end color for particles. The base color of the particle will linearly approach this color from the start
+ * color over the lifetime of the particle.
+ *
+ * @param color
+ * ColorRGBA The ending color.
+ */
+ public void setEndColor(final ReadOnlyColorRGBA color) {
+ _endColor.set(color);
+ }
+
+ /**
+ * getEndColor returns the ending color.
+ *
+ * @return The ending color
+ */
+ public ReadOnlyColorRGBA getEndColor() {
+ return _endColor;
+ }
+
+ /**
+ * Set the start and end spinSpeed of particles managed by this manager. Setting it to 0 means no spin.
+ *
+ * @param speed
+ * double
+ */
+ public void setParticleSpinSpeed(final double speed) {
+ _startSpin = speed;
+ _endSpin = speed;
+ }
+
+ public Vector3 getInvScale() {
+ return _invScale;
+ }
+
+ public void setInvScale(final ReadOnlyVector3 invScale) {
+ _invScale.set(invScale);
+ }
+
+ public void updateInvScale() {
+ _invScale.set(1.0 / getWorldScale().getX(), 1.0 / getWorldScale().getY(), 1.0 / getWorldScale().getZ());
+ }
+
+ /**
+ * Add an external influence to the particle controller for this mesh.
+ *
+ * @param influence
+ * ParticleInfluence
+ */
+ public void addInfluence(final ParticleInfluence influence) {
+ _controller.addInfluence(influence);
+ }
+
+ /**
+ * Remove an influence from the particle controller for this mesh.
+ *
+ * @param influence
+ * ParticleInfluence
+ * @return true if found and removed.
+ */
+ public boolean removeInfluence(final ParticleInfluence influence) {
+ return _controller.removeInfluence(influence);
+ }
+
+ /**
+ * Returns the list of influences acting on this particle controller.
+ *
+ * @return ArrayList
+ */
+ public List<ParticleInfluence> getInfluences() {
+ return _controller.getInfluences();
+ }
+
+ public void clearInfluences() {
+ _controller.clearInfluences();
+ }
+
+ public void setParticleMass(final double mass) {
+ _startMass = _endMass = mass;
+ }
+
+ /**
+ * Set the minimum angle (in radians) that particles can be emitted away from the emission direction. Any angle less
+ * than 0 is trimmed to 0.
+ *
+ * @param f
+ * The new emission minimum angle.
+ */
+ public void setMinimumAngle(final double f) {
+ _minimumAngle = f >= 0.0 ? f : 0.0;
+ }
+
+ /**
+ * getEmissionMinimumAngle returns the minimum emission angle.
+ *
+ * @return The minimum emission angle.
+ */
+ public double getMinimumAngle() {
+ return _minimumAngle;
+ }
+
+ /**
+ * Set the maximum angle (in radians) that particles can be emitted away from the emission direction. Any angle less
+ * than 0 is trimmed to 0.
+ *
+ * @param f
+ * The new emission maximum angle.
+ */
+ public void setMaximumAngle(final double f) {
+ _maximumAngle = f >= 0.0 ? f : 0.0;
+ }
+
+ /**
+ * getEmissionMaximumAngle returns the maximum emission angle.
+ *
+ * @return The maximum emission angle.
+ */
+ public double getMaximumAngle() {
+ return _maximumAngle;
+ }
+
+ /**
+ * Set the minimum lifespan of new particles (or recreated) managed by this manager. if a value less than zero is
+ * given, 1.0 is used.
+ *
+ * @param lifeSpan
+ * in ms
+ */
+ public void setMinimumLifeTime(final double lifeSpan) {
+ _minimumLifeTime = lifeSpan >= 0.0 ? lifeSpan : 1.0;
+ }
+
+ /**
+ * getParticlesMinimumLifeTime returns the minimum life time of a particle.
+ *
+ * @return The current minimum life time in ms.
+ */
+ public double getMinimumLifeTime() {
+ return _minimumLifeTime;
+ }
+
+ /**
+ * Set the maximum lifespan of new particles (or recreated) managed by this manager. if a value less than zero is
+ * given, 1.0 is used.
+ *
+ * @param lifeSpan
+ * in ms
+ */
+ public void setMaximumLifeTime(final double lifeSpan) {
+ _maximumLifeTime = lifeSpan >= 0.0 ? lifeSpan : 1.0;
+ }
+
+ /**
+ * getParticlesMaximumLifeTime returns the maximum life time of a particle.
+ *
+ * @return The current maximum life time in ms.
+ */
+ public double getMaximumLifeTime() {
+ return _maximumLifeTime;
+ }
+
+ public ReadOnlyMatrix3 getRotMatrix() {
+ return _rotMatrix;
+ }
+
+ public void setRotMatrix(final ReadOnlyMatrix3 rotMatrix) {
+ _rotMatrix.set(rotMatrix);
+ }
+
+ public ReadOnlyTransform getEmitterTransform() {
+ return _emitterTransform;
+ }
+
+ public void setEmitterTransform(final ReadOnlyTransform emitterTransform) {
+ _emitterTransform.set(emitterTransform);
+ }
+
+ public double getParticleOrientation() {
+ return _particleOrientation;
+ }
+
+ public void setParticleOrientation(final double orient) {
+ _particleOrientation = orient;
+ }
+
+ /**
+ * Set the acceleration for any new particles created (or recreated) by this manager.
+ *
+ * @param velocity
+ * particle v0
+ */
+ public void setInitialVelocity(final double velocity) {
+ _initialVelocity = velocity;
+ }
+
+ /**
+ * Get the acceleration set in this manager.
+ *
+ * @return The initialVelocity
+ */
+ public double getInitialVelocity() {
+ return _initialVelocity;
+ }
+
+ /**
+ * Set the offset for any new particles created (or recreated) by this manager. This is applicable only to managers
+ * generating from a point (not a line, rectangle, etc..)
+ *
+ * @param offset
+ * new offset position
+ */
+ public void setOriginOffset(final ReadOnlyVector3 offset) {
+ _originOffset.set(offset);
+ }
+
+ /**
+ * Get the offset point set in this manager.
+ *
+ * @return origin
+ */
+ public ReadOnlyVector3 getOriginOffset() {
+ return _originOffset;
+ }
+
+ public ReadOnlyVector3 getWorldEmit() {
+ return _worldEmit;
+ }
+
+ public void setWorldEmit(final ReadOnlyVector3 worldEmit) {
+ _worldEmit.set(worldEmit);
+ }
+
+ /**
+ * Get the number of particles the manager should release per second.
+ *
+ * @return The number of particles that should be released per second.
+ */
+ public int getReleaseRate() {
+ return _releaseRate;
+ }
+
+ /**
+ * Set the number of particles the manager should release per second.
+ *
+ * @param particlesPerSecond
+ * number of particles per second
+ */
+ public void setReleaseRate(final int particlesPerSecond) {
+ final int oldRate = _releaseRate;
+ _releaseRate = particlesPerSecond;
+ if (_controller != null && !_controller.isActive() && _controller.isControlFlow() && oldRate == 0) {
+ _controller.setActive(true);
+ }
+ }
+
+ public double getEndMass() {
+ return _endMass;
+ }
+
+ public void setEndMass(final double endMass) {
+ _endMass = endMass;
+ }
+
+ public double getEndSpin() {
+ return _endSpin;
+ }
+
+ public void setEndSpin(final double endSpin) {
+ _endSpin = endSpin;
+ }
+
+ public double getStartMass() {
+ return _startMass;
+ }
+
+ public void setStartMass(final double startMass) {
+ _startMass = startMass;
+ }
+
+ public double getStartSpin() {
+ return _startSpin;
+ }
+
+ public void setStartSpin(final double startSpin) {
+ _startSpin = startSpin;
+ }
+
+ public int getTexQuantity() {
+ return _texQuantity;
+ }
+
+ public void setTexQuantity(final int quantity) {
+ _texQuantity = quantity;
+ }
+
+ public int getStartTexIndex() {
+ return _startTexIndex;
+ }
+
+ public void setStartTexIndex(final int startTexIndex) {
+ _startTexIndex = startTexIndex;
+ }
+
+ /**
+ * Get which emittype method is being used by the underlying system. One of ParticleType.Quad,
+ * ParticleType.Triangle, ParticleType.Point, ParticleType.Line, ParticleType.GeomMesh
+ *
+ * @return An int representing the type of particle we are emitting.
+ */
+ public ParticleType getParticleType() {
+ return _particleType;
+ }
+
+ /**
+ * Set what type of particle to emit from this sytem. Does not have an effect unless recreate is called.
+ *
+ * @param type
+ * particle type to use, should be one of ParticleType.Quad, ParticleType.Triangle, ParticleType.Point,
+ * ParticleType.Line, ParticleType.GeomMesh
+ */
+ public void setParticleType(final ParticleType type) {
+ _particleType = type;
+ }
+
+ /**
+ * Set our particle emitter.
+ *
+ * @param emitter
+ * New emitter or null for default point emitter.
+ */
+ public void setParticleEmitter(final SavableParticleEmitter emitter) {
+ _particleEmitter = emitter;
+ }
+
+ /**
+ * @return the set particle emitter, or null if none is set.
+ */
+ public SavableParticleEmitter getParticleEmitter() {
+ return _particleEmitter;
+ }
+
+ public void initAllParticlesLocation() {
+ for (int i = _particles.length; --i >= 0;) {
+ initParticleLocation(i);
+ _particles[i].updateVerts(null);
+ }
+ getParticleGeometry().updateModelBound();
+ }
+
+ @Override
+ public void onDraw(final Renderer r) {
+ // make sure our particle world bound is correct
+ updateWorldBoundManually();
+
+ super.onDraw(r);
+ };
+
+ public void initParticleLocation(final int index) {
+ final Particle p = _particles[index];
+ if (getParticleType() == ParticleType.GeomMesh && getParticleEmitter() instanceof MeshEmitter) {
+ final MeshEmitter emitter = (MeshEmitter) getParticleEmitter();
+ final Mesh mesh = emitter.getSource();
+
+ // Update the triangle model on each new particle creation.
+ final Vector3[] vertices = new Vector3[3];
+ final MeshData mData = mesh.getMeshData();
+ for (int x = 0; x < 3; x++) {
+ vertices[x] = new Vector3();
+
+ final int vertIndex = mData.getVertexIndex(index, x, 0);
+ BufferUtils.populateFromBuffer(vertices[x], mData.getVertexBuffer(),
+ mData.getIndexBuffer() != null ? mData.getIndices().get(vertIndex) : vertIndex);
+ }
+ Triangle t = p.getTriangleModel();
+ if (t == null) {
+ t = new Triangle(vertices[0], vertices[1], vertices[2]);
+ } else {
+ t.setA(vertices[0]);
+ t.setB(vertices[1]);
+ t.setC(vertices[2]);
+ }
+ // turn the triangle corners into vector offsets from center
+ for (int x = 0; x < 3; x++) {
+ vertices[x].subtract(t.getCenter(), vertices[x]);
+ t.set(x, vertices[x]);
+ }
+ p.setTriangleModel(t);
+ mesh.localToWorld(t.getCenter(), p.getPosition());
+ p.getPosition().multiplyLocal(getInvScale());
+
+ } else if (getParticleEmitter() instanceof MeshEmitter) {
+ final MeshEmitter emitter = (MeshEmitter) getParticleEmitter();
+ final Mesh mesh = emitter.getSource();
+ mesh.getMeshData().randomPointOnPrimitives(p.getPosition());
+ mesh.localToWorld(p.getPosition(), p.getPosition());
+ p.getPosition().multiplyLocal(getInvScale());
+ } else {
+ if (getParticleEmitter() != null) {
+ getParticleEmitter().randomEmissionPoint(p.getPosition());
+ } else {
+ p.getPosition().set(_originOffset);
+ }
+
+ _emitterTransform.applyForward(p.getPosition());
+ p.getPosition().divideLocal(_emitterTransform.getScale());
+ }
+ }
+
+ public boolean isCameraFacing() {
+ return _cameraFacing;
+ }
+
+ public void setCameraFacing(final boolean cameraFacing) {
+ _cameraFacing = cameraFacing;
+ }
+
+ public boolean isVelocityAligned() {
+ return _velocityAligned;
+ }
+
+ public void setVelocityAligned(final boolean velocityAligned) {
+ _velocityAligned = velocityAligned;
+ }
+
+ public Particle getParticle(final int i) {
+ return _particles[i];
+ }
+
+ public boolean isActive() {
+ return _controller.isActive();
+ }
+
+ public void setSpeed(final double f) {
+ _controller.setSpeed(f);
+ }
+
+ public void setRepeatType(final RepeatType type) {
+ _controller.setRepeatType(type);
+ }
+
+ public void setControlFlow(final boolean b) {
+ _controller.setControlFlow(b);
+ }
+
+ public ReadOnlyVector3 getOriginCenter() {
+ return _originCenter;
+ }
+
+ public ReadOnlyVector3 getUpVector() {
+ return _upVector;
+ }
+
+ public void setUpVector(final ReadOnlyVector3 vector) {
+ _upVector.set(vector);
+ }
+
+ public ReadOnlyVector3 getLeftVector() {
+ return _leftVector;
+ }
+
+ public void setLeftVector(final ReadOnlyVector3 vector) {
+ _leftVector.set(vector);
+ }
+
+ public ReadOnlyVector3 getFacingUpVector() {
+ return _facingUpVector;
+ }
+
+ /**
+ * Used to determine particle orientation (only if cameraFacing is false.)
+ *
+ * @param vector
+ */
+ public void setFacingUpVector(final ReadOnlyVector3 vector) {
+ _facingUpVector.set(vector);
+ }
+
+ public ReadOnlyVector3 getFacingLeftVector() {
+ return _facingLeftVector;
+ }
+
+ /**
+ * Used to determine particle orientation (only if cameraFacing is false.)
+ *
+ * @param vector
+ */
+ public void setFacingLeftVector(final ReadOnlyVector3 vector) {
+ _facingLeftVector.set(vector);
+ }
+
+ public boolean isRotateWithScene() {
+ return _rotateWithScene;
+ }
+
+ public void setRotateWithScene(final boolean rotate) {
+ _rotateWithScene = rotate;
+ }
+
+ public void resetParticleVelocity(final int i) {
+ getRandomVelocity(_particles[i].getVelocity());
+ }
+
+ /**
+ * Returns a random angle between the min and max angles.
+ *
+ * @return the random angle.
+ */
+ public double getRandomAngle() {
+ return getMinimumAngle() + MathUtils.nextRandomFloat() * (getMaximumAngle() - getMinimumAngle());
+ }
+
+ /**
+ * generate a random lifespan between the min and max lifespan of the particle system.
+ *
+ * @return the generated lifespan value
+ */
+ public double getRandomLifeSpan() {
+ return getMinimumLifeTime() + ((getMaximumLifeTime() - getMinimumLifeTime()) * MathUtils.nextRandomFloat());
+ }
+
+ /**
+ * Generate a random velocity within the parameters of max angle and the rotation matrix.
+ *
+ * @param store
+ * a vector to store the results in.
+ */
+ protected Vector3 getRandomVelocity(final Vector3 store) {
+ final double randDir = MathUtils.TWO_PI * MathUtils.nextRandomFloat();
+ final double randAngle = getRandomAngle();
+ Vector3 result = store;
+ if (result == null) {
+ result = new Vector3();
+ }
+ result.setX(MathUtils.cos(randDir) * MathUtils.sin(randAngle));
+ result.setY(MathUtils.cos(randAngle));
+ result.setZ(MathUtils.sin(randDir) * MathUtils.sin(randAngle));
+ rotateVectorSpeed(result);
+ result.multiplyLocal(getInitialVelocity());
+ return result;
+ }
+
+ /**
+ * Apply the rotation matrix to a given vector representing a particle velocity.
+ *
+ * @param pSpeed
+ * the velocity vector to be modified.
+ */
+ protected void rotateVectorSpeed(final Vector3 pSpeed) {
+
+ final double x = pSpeed.getX(), y = pSpeed.getY(), z = pSpeed.getZ();
+
+ pSpeed.setX(-1 * ((_rotMatrix.getM00() * x) + (_rotMatrix.getM10() * y) + (_rotMatrix.getM20() * z)));
+ pSpeed.setY((_rotMatrix.getM01() * x) + (_rotMatrix.getM11() * y) + (_rotMatrix.getM21() * z));
+ pSpeed.setZ(-1 * ((_rotMatrix.getM02() * x) + (_rotMatrix.getM12() * y) + (_rotMatrix.getM22() * z)));
+ }
+
+ public void warmUp(final int iterations) {
+ if (_controller != null) {
+ _controller.warmUp(iterations, this);
+ }
+ }
+
+ public int getNumParticles() {
+ return _numParticles;
+ }
+
+ public void setNumParticles(final int numParticles) {
+ _numParticles = numParticles;
+ }
+
+ public double getReleaseVariance() {
+ if (_controller != null) {
+ return _controller.getReleaseVariance();
+ }
+ return 0;
+ }
+
+ public void setReleaseVariance(final double var) {
+ if (_controller != null) {
+ _controller.setReleaseVariance(var);
+ }
+ }
+
+ public ParticleAppearanceRamp getRamp() {
+ return _ramp;
+ }
+
+ public void setRamp(final ParticleAppearanceRamp ramp) {
+ if (ramp == null) {
+ logger.warning("Can not set a null ParticleAppearanceRamp.");
+ return;
+ }
+ _ramp = ramp;
+ }
+
+ public TexAnimation getTexAnimation() {
+ return _texAnimation;
+ }
+
+ public void setTexAnimation(final TexAnimation texAnimation) {
+ if (texAnimation == null) {
+ logger.warning("Can not set a null TexAnimation.");
+ return;
+ }
+ _texAnimation = texAnimation;
+ }
+
+ /**
+ * @return true if the particles are already in world coordinate space (default). When true, scene-graph transforms
+ * will only affect the emission of particles, not particles that are already living.
+ */
+ public boolean isParticlesInWorldCoords() {
+ return _particlesInWorldCoords;
+ }
+
+ public void setParticlesInWorldCoords(final boolean particlesInWorldCoords) {
+ _particlesInWorldCoords = particlesInWorldCoords;
+ }
+
+ /**
+ * Changes the number of particles in this particle mesh.
+ *
+ * @param count
+ * the desired number of particles to change to.
+ */
+ public void recreate(final int count) {
+ _numParticles = count;
+ initializeParticles(_numParticles);
+ }
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ ; // ignore this since we want it to happen only when we say it can
+ // happen due to world vectors not being used
+ }
+
+ public void updateWorldBoundManually() {
+ super.updateWorldBound(true);
+ }
+
+ @Override
+ public void updateGeometricState(final double time, final boolean initiator) {
+ super.updateGeometricState(time, initiator);
+ if (isRotateWithScene()) {
+ // XXX: Perhaps we can avoid this special case via an addition to the interface?
+ if (getParticleEmitter() instanceof MeshEmitter) {
+ ((MeshEmitter) getParticleEmitter()).getSource().getWorldRotation()
+ .applyPost(_emissionDirection, _worldEmit);
+ } else {
+ getWorldRotation().applyPost(_emissionDirection, _worldEmit);
+ }
+ } else {
+ _worldEmit.set(_emissionDirection);
+ }
+
+ if (_particlesInWorldCoords) {
+ final Transform t = Transform.fetchTempInstance();
+ t.setIdentity();
+ t.setTranslation(getWorldTranslation());
+ t.setScale(getScale());
+ if (getParent() != null) {
+ t.setRotation(getParent().getWorldRotation());
+ }
+ _emitterTransform.set(t);
+ Transform.releaseTempInstance(t);
+
+ _originCenter.set(getWorldTranslation()).addLocal(_originOffset);
+
+ setWorldTranslation(Vector3.ZERO);
+ } else {
+ _originCenter.set(_originOffset);
+ }
+
+ setWorldRotation(Matrix3.IDENTITY);
+ setWorldScale(getScale());
+ markDirty(DirtyType.Transform);
+ }
+
+ @Override
+ public ParticleSystem makeCopy(final boolean shareGeometricData) {
+ synchronized (this) {
+ // Do not call make copy on the "generated" particle geometry.
+ final Spatial geom = getParticleGeometry();
+ detachChild(geom);
+ final ParticleSystem copy = (ParticleSystem) super.makeCopy(shareGeometricData);
+ // Reattach
+ attachChild(geom);
+ return copy;
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ synchronized (this) {
+ // Do not save the "generated" particle geometry.
+ final Spatial geom = getParticleGeometry();
+ detachChild(geom);
+ super.write(capsule);
+ // Reattach
+ attachChild(geom);
+ }
+
+ capsule.write(_particleType, "particleType", ParticleType.Quad);
+ capsule.write(_particleEmitter, "particleEmitter", null);
+ capsule.write(_startSize, "startSize", DEFAULT_START_SIZE);
+ capsule.write(_endSize, "endSize", DEFAULT_END_SIZE);
+ capsule.write(_startColor, "startColor", new ColorRGBA(DEFAULT_START_COLOR));
+ capsule.write(_endColor, "endColor", new ColorRGBA(DEFAULT_END_COLOR));
+ capsule.write(_startSpin, "startSpin", 0);
+ capsule.write(_endSpin, "endSpin", 0);
+ capsule.write(_startMass, "startMass", 0);
+ capsule.write(_endMass, "endMass", 0);
+ capsule.write(_startTexIndex, "startTexIndex", 0);
+ capsule.write(_texQuantity, "texQuantity", 1);
+ capsule.write(_initialVelocity, "initialVelocity", 1);
+ capsule.write(_minimumLifeTime, "minimumLifeTime", DEFAULT_MIN_LIFE);
+ capsule.write(_maximumLifeTime, "maximumLifeTime", DEFAULT_MAX_LIFE);
+ capsule.write(_minimumAngle, "minimumAngle", 0);
+ capsule.write(_maximumAngle, "maximumAngle", DEFAULT_MAX_ANGLE);
+ capsule.write(_emissionDirection, "emissionDirection", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_worldEmit, "worldEmit", new Vector3(Vector3.ZERO));
+ capsule.write(_upVector, "upVector", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_leftVector, "leftVector", new Vector3(-1, 0, 0));
+ capsule.write(_facingUpVector, "facingUpVector", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_facingLeftVector, "facingLeftVector", new Vector3(-1, 0, 0));
+ capsule.write(_numParticles, "numParticles", 0);
+ capsule.write(_particleOrientation, "particleOrientation", 0);
+ capsule.write(_rotateWithScene, "rotateWithScene", false);
+ capsule.write(_geometryCoordinates, "geometryCoordinates", null);
+ capsule.write(_appearanceColors, "appearanceColors", null);
+ capsule.write(_releaseRate, "releaseRate", _numParticles);
+ capsule.write(_originCenter, "originCenter", new Vector3(Vector3.ZERO));
+ capsule.write(_originOffset, "originOffset", new Vector3(Vector3.ZERO));
+ capsule.write(_controller, "controller", null);
+ capsule.write(_cameraFacing, "cameraFacing", true);
+ capsule.write(_velocityAligned, "velocityAligned", false);
+ capsule.write(_particlesInWorldCoords, "particlesInWorldCoords", true);
+ capsule.write(_ramp, "ramp", new ParticleAppearanceRamp());
+ capsule.write(_texAnimation, "texAnimation", new TexAnimation());
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _particleType = capsule.readEnum("particleType", ParticleType.class, ParticleType.Quad);
+ _particleEmitter = (SavableParticleEmitter) capsule.readSavable("particleEmitter", null);
+ _startSize = capsule.readDouble("startSize", DEFAULT_START_SIZE);
+ _endSize = capsule.readDouble("endSize", DEFAULT_END_SIZE);
+ _startColor.set((ColorRGBA) capsule.readSavable("startColor", new ColorRGBA(DEFAULT_START_COLOR)));
+ _endColor.set((ColorRGBA) capsule.readSavable("endColor", new ColorRGBA(DEFAULT_END_COLOR)));
+ _startSpin = capsule.readDouble("startSpin", 0);
+ _endSpin = capsule.readDouble("endSpin", 0);
+ _startMass = capsule.readDouble("startMass", 0);
+ _endMass = capsule.readDouble("endMass", 0);
+ _startTexIndex = capsule.readInt("startTexIndex", 0);
+ _texQuantity = capsule.readInt("texQuantity", 1);
+ _initialVelocity = capsule.readDouble("initialVelocity", 1);
+ _minimumLifeTime = capsule.readDouble("minimumLifeTime", DEFAULT_MIN_LIFE);
+ _maximumLifeTime = capsule.readDouble("maximumLifeTime", DEFAULT_MAX_LIFE);
+ _minimumAngle = capsule.readDouble("minimumAngle", 0);
+ _maximumAngle = capsule.readDouble("maximumAngle", DEFAULT_MAX_ANGLE);
+ _emissionDirection.set((Vector3) capsule.readSavable("emissionDirection", new Vector3(Vector3.UNIT_Y)));
+ _worldEmit.set((Vector3) capsule.readSavable("worldEmit", new Vector3(Vector3.ZERO)));
+ _upVector.set((Vector3) capsule.readSavable("upVector", new Vector3(Vector3.UNIT_Y)));
+ _leftVector.set((Vector3) capsule.readSavable("leftVector", new Vector3(-1, 0, 0)));
+ _facingUpVector.set((Vector3) capsule.readSavable("facingUpVector", new Vector3(Vector3.UNIT_Y)));
+ _facingLeftVector.set((Vector3) capsule.readSavable("facingLeftVector", new Vector3(-1, 0, 0)));
+ _numParticles = capsule.readInt("numParticles", 0);
+ _rotateWithScene = capsule.readBoolean("rotateWithScene", false);
+ _geometryCoordinates = capsule.readFloatBuffer("geometryCoordinates", null);
+ _appearanceColors = capsule.readFloatBuffer("appearanceColors", null);
+
+ _releaseRate = capsule.readInt("releaseRate", _numParticles);
+ _particleOrientation = capsule.readDouble("particleOrientation", 0);
+ _originCenter.set((Vector3) capsule.readSavable("originCenter", new Vector3()));
+ _originOffset.set((Vector3) capsule.readSavable("originOffset", new Vector3()));
+ _controller = (ParticleController) capsule.readSavable("controller", null);
+ _cameraFacing = capsule.readBoolean("cameraFacing", true);
+ _velocityAligned = capsule.readBoolean("velocityAligned", false);
+ _particlesInWorldCoords = capsule.readBoolean("particlesInWorldCoords", true);
+ _ramp = (ParticleAppearanceRamp) capsule.readSavable("ramp", new ParticleAppearanceRamp());
+ _texAnimation = (TexAnimation) capsule.readSavable("texAnimation", new TexAnimation());
+
+ _invScale.zero();
+ _upXemit.zero();
+ _absUpVector.zero();
+ _abUpMinUp.zero();
+ _rotMatrix.setIdentity();
+ initializeParticles(_numParticles);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java new file mode 100644 index 0000000..2e014d7 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; + +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>RampEntry</code> defines an entry for a ParticleAppearanceRamp. + * + * @see ParticleAppearanceRamp + */ +public class RampEntry implements Savable { + + public static final double DEFAULT_OFFSET = 0.05f; // (5% of lifetime) + public static final double DEFAULT_SIZE = -1; // special case -> negative = no size change at this entry + public static final double DEFAULT_SPIN = Float.MAX_VALUE; // special case -> no spin change + public static final double DEFAULT_MASS = Float.MAX_VALUE; // special case -> no mass change + public static final ColorRGBA DEFAULT_COLOR = null; // special case -> no color change + + protected double _offset = DEFAULT_OFFSET; + protected ColorRGBA _color = DEFAULT_COLOR; // no color change at this entry + protected double _size = DEFAULT_SIZE; + protected double _spin = DEFAULT_SPIN; + protected double _mass = DEFAULT_MASS; + + public RampEntry() {} + + /** + * Construct new addition to color ramp + * + * @param offset + * amount of time (as a percent of total lifetime) between the last appearance and this one. + */ + public RampEntry(final double offset) { + setOffset(offset); + } + + public ReadOnlyColorRGBA getColor() { + return _color; + } + + public void setColor(final ReadOnlyColorRGBA color) { + if (color != null) { + if (_color != null) { + _color.set(color); + } else { + _color = new ColorRGBA(color); + } + } else { + _color = null; + } + } + + public boolean hasColorSet() { + return _color != null; // DEFAULT_COLOR is null + } + + public double getSize() { + return _size; + } + + public void setSize(final double size) { + _size = size; + } + + public boolean hasSizeSet() { + return _size != DEFAULT_SIZE; + } + + public double getSpin() { + return _spin; + } + + public void setSpin(final double spin) { + _spin = spin; + } + + public boolean hasSpinSet() { + return _spin != DEFAULT_SPIN; + } + + public double getMass() { + return _mass; + } + + public void setMass(final double mass) { + _mass = mass; + } + + public boolean hasMassSet() { + return _mass != DEFAULT_MASS; + } + + public double getOffset() { + return _offset; + } + + public void setOffset(final double offset) { + _offset = offset; + } + + public Class<? extends RampEntry> getClassTag() { + return getClass(); + } + + public void read(final InputCapsule capsule) throws IOException { + _offset = capsule.readDouble("offsetMS", DEFAULT_OFFSET); + _size = capsule.readDouble("size", DEFAULT_SIZE); + _spin = capsule.readDouble("spin", DEFAULT_SPIN); + _mass = capsule.readDouble("mass", DEFAULT_MASS); + _color = (ColorRGBA) capsule.readSavable("color", DEFAULT_COLOR); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_offset, "offsetMS", DEFAULT_OFFSET); + capsule.write(_size, "size", DEFAULT_SIZE); + capsule.write(_spin, "spin", DEFAULT_SPIN); + capsule.write(_mass, "mass", DEFAULT_MASS); + capsule.write(_color, "color", DEFAULT_COLOR); + } + + private static String convColorToHex(final ColorRGBA color) { + if (color == null) { + return null; + } + String sRed = Integer.toHexString((int) (color.getRed() * 255 + .5f)); + if (sRed.length() == 1) { + sRed = "0" + sRed; + } + String sGreen = Integer.toHexString((int) (color.getGreen() * 255 + .5f)); + if (sGreen.length() == 1) { + sGreen = "0" + sGreen; + } + String sBlue = Integer.toHexString((int) (color.getBlue() * 255 + .5f)); + if (sBlue.length() == 1) { + sBlue = "0" + sBlue; + } + return "#" + sRed + sGreen + sBlue; + } + + @Override + public String toString() { + + final StringBuilder builder = new StringBuilder(); + if (_offset > 0) { + builder.append("prev+"); + builder.append((int) (_offset * 100)); + builder.append("% age..."); + } + if (_color != DEFAULT_COLOR) { + builder.append(" color:"); + builder.append(convColorToHex(_color).toUpperCase()); + builder.append(" a: "); + builder.append((int) (_color.getAlpha() * 100)); + builder.append("%"); + } + + if (_size != DEFAULT_SIZE) { + builder.append(" size: " + _size); + } + + if (_mass != DEFAULT_MASS) { + builder.append(" mass: " + _spin); + } + + if (_spin != DEFAULT_SPIN) { + builder.append(" spin: " + _spin); + } + + return builder.toString(); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java new file mode 100644 index 0000000..09b422c --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java @@ -0,0 +1,453 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; + +import com.ardor3d.math.Line3; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyLine3; +import com.ardor3d.math.type.ReadOnlyMatrix3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public final class SimpleParticleInfluenceFactory { + + public static class BasicWind extends ParticleInfluence { + private double _strength; + private final Vector3 _windDirection = new Vector3(); + private boolean _random, _rotateWithScene; + private final Vector3 _vector = new Vector3(); + + public BasicWind() {} + + public BasicWind(final double windStr, final ReadOnlyVector3 windDir, final boolean addRandom, + final boolean rotateWithScene) { + _strength = windStr; + _windDirection.set(windDir); + _random = addRandom; + _rotateWithScene = rotateWithScene; + } + + public double getStrength() { + return _strength; + } + + public void setStrength(final double windStr) { + _strength = windStr; + } + + public ReadOnlyVector3 getWindDirection() { + return _windDirection; + } + + public void setWindDirection(final ReadOnlyVector3 windDir) { + _windDirection.set(windDir); + } + + public boolean isRandom() { + return _random; + } + + public void setRandom(final boolean addRandom) { + _random = addRandom; + } + + public boolean isRotateWithScene() { + return _rotateWithScene; + } + + public void setRotateWithScene(final boolean rotateWithScene) { + _rotateWithScene = rotateWithScene; + } + + @Override + public void prepare(final ParticleSystem system) { + _vector.set(_windDirection); + final ReadOnlyMatrix3 mat = system.getEmitterTransform().getMatrix(); + if (_rotateWithScene && !mat.isIdentity()) { + mat.applyPost(_vector, _vector); + } + } + + @Override + public void apply(final double dt, final Particle p, final int index) { + final double tStr = (_random ? MathUtils.nextRandomFloat() * _strength : _strength); + _vector.scaleAdd(tStr * dt, p.getVelocity(), p.getVelocity()); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_strength, "strength", 1f); + capsule.write(_windDirection, "windDirection", new Vector3(Vector3.UNIT_X)); + capsule.write(_random, "random", false); + capsule.write(_rotateWithScene, "rotateWithScene", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _strength = capsule.readDouble("strength", 1.0); + _windDirection.set((Vector3) capsule.readSavable("windDirection", new Vector3(Vector3.UNIT_X))); + _random = capsule.readBoolean("random", false); + _rotateWithScene = capsule.readBoolean("rotateWithScene", true); + } + + @Override + public Class<? extends BasicWind> getClassTag() { + return this.getClass(); + } + } + + public static class BasicGravity extends ParticleInfluence { + private final Vector3 gravity = new Vector3(); + private boolean rotateWithScene; + private final Vector3 vector = new Vector3(); + + public BasicGravity() {} + + public BasicGravity(final ReadOnlyVector3 gravForce, final boolean rotateWithScene) { + gravity.set(gravForce); + this.rotateWithScene = rotateWithScene; + } + + public ReadOnlyVector3 getGravityForce() { + return gravity; + } + + public void setGravityForce(final ReadOnlyVector3 gravForce) { + gravity.set(gravForce); + } + + public boolean isRotateWithScene() { + return rotateWithScene; + } + + public void setRotateWithScene(final boolean rotateWithScene) { + this.rotateWithScene = rotateWithScene; + } + + @Override + public void prepare(final ParticleSystem system) { + vector.set(gravity); + final ReadOnlyMatrix3 mat = system.getEmitterTransform().getMatrix(); + if (rotateWithScene && !mat.isIdentity()) { + mat.applyPost(vector, vector); + } + } + + @Override + public void apply(final double dt, final Particle p, final int index) { + vector.scaleAdd(dt, p.getVelocity(), p.getVelocity()); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(gravity, "gravity", new Vector3(Vector3.ZERO)); + capsule.write(rotateWithScene, "rotateWithScene", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + gravity.set((Vector3) capsule.readSavable("gravity", new Vector3(Vector3.ZERO))); + rotateWithScene = capsule.readBoolean("rotateWithScene", true); + } + + @Override + public Class<? extends BasicGravity> getClassTag() { + return this.getClass(); + } + } + + public static class BasicDrag extends ParticleInfluence { + private final Vector3 velocity = new Vector3(); + private double dragCoefficient; + + public BasicDrag() {} + + public BasicDrag(final double dragCoef) { + dragCoefficient = dragCoef; + } + + public double getDragCoefficient() { + return dragCoefficient; + } + + public void setDragCoefficient(final double dragCoef) { + dragCoefficient = dragCoef; + } + + @Override + public void apply(final double dt, final Particle p, final int index) { + // viscous drag + velocity.set(p.getVelocity()); + p.getVelocity().addLocal(velocity.multiplyLocal(-dragCoefficient * dt * p.getInvMass())); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(dragCoefficient, "dragCoefficient", 1.0); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + dragCoefficient = capsule.readDouble("dragCoefficient", 1.0); + } + + @Override + public Class<? extends BasicDrag> getClassTag() { + return this.getClass(); + } + } + + public static class BasicVortex extends ParticleInfluence { + + public static final int VT_CYLINDER = 0; + public static final int VT_TORUS = 1; + + private int _type = VT_CYLINDER; + private double _strength, _divergence, _height, _radius; + private final Line3 _axis = new Line3(); + private boolean _random, _transformWithScene; + private final Vector3 _v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3(); + private final Quaternion _rot = new Quaternion(); + private final Line3 _line = new Line3(); + + public BasicVortex() {} + + public BasicVortex(final double strength, final double divergence, final ReadOnlyLine3 axis, + final boolean random, final boolean transformWithScene) { + _strength = strength; + _divergence = divergence; + _axis.set(axis); + _height = 0f; + _radius = 1f; + _random = random; + _transformWithScene = transformWithScene; + } + + public int getType() { + return _type; + } + + public void setType(final int type) { + _type = type; + } + + public double getStrength() { + return _strength; + } + + public void setStrength(final double strength) { + _strength = strength; + } + + public double getDivergence() { + return _divergence; + } + + public void setDivergence(final double divergence) { + _divergence = divergence; + } + + public ReadOnlyLine3 getAxis() { + return _axis; + } + + public void setAxis(final ReadOnlyLine3 axis) { + _axis.set(axis); + } + + public double getHeight() { + return _height; + } + + public void setHeight(final double height) { + _height = height; + } + + public double getRadius() { + return _radius; + } + + public void setRadius(final double radius) { + _radius = radius; + } + + public boolean isRandom() { + return _random; + } + + public void setRandom(final boolean random) { + _random = random; + } + + public boolean isTransformWithScene() { + return _transformWithScene; + } + + public void setTransformWithScene(final boolean transformWithScene) { + _transformWithScene = transformWithScene; + } + + @Override + public void prepare(final ParticleSystem system) { + _line.setOrigin(_axis.getOrigin()); + _line.setDirection(_axis.getDirection()); + final ReadOnlyMatrix3 mat = system.getEmitterTransform().getMatrix(); + if (_transformWithScene && !mat.isIdentity()) { + final Vector3 temp = Vector3.fetchTempInstance(); + mat.applyPost(_line.getOrigin(), temp); + _line.setOrigin(temp); + mat.applyPost(_line.getDirection(), temp); + _line.setDirection(temp); + Vector3.releaseTempInstance(temp); + } + if (_type == VT_CYLINDER) { + _rot.fromAngleAxis(-_divergence, _line.getDirection()); + } + } + + @Override + public void apply(final double dt, final Particle p, final int index) { + final double dtStr = dt * _strength * (_random ? MathUtils.nextRandomFloat() : 1f); + p.getPosition().subtract(_line.getOrigin(), _v1); + _line.getDirection().cross(_v1, v2); + if (v2.length() == 0) { // particle is on the axis + return; + } + v2.normalizeLocal(); + if (_type == VT_CYLINDER) { + _rot.apply(v2, v2); + v2.scaleAdd(dtStr, p.getVelocity(), p.getVelocity()); + return; + } + v2.cross(_line.getDirection(), _v1); + _v1.multiplyLocal(_radius); + _line.getDirection().scaleAdd(_height, _v1, _v1); + _v1.addLocal(_line.getOrigin()); + _v1.subtractLocal(p.getPosition()); + if (_v1.length() == 0) { // particle is on the ring + return; + } + _v1.normalizeLocal(); + _v1.cross(v2, v3); + _rot.fromAngleAxis(-_divergence, v2); + _rot.apply(v3, v3); + v3.scaleAdd(dtStr, p.getVelocity(), p.getVelocity()); + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_type, "type", VT_CYLINDER); + capsule.write(_strength, "strength", 1.0); + capsule.write(_divergence, "divergence", 0.0); + capsule.write(_axis, "axis", new Line3(new Vector3(), new Vector3(Vector3.UNIT_Y))); + capsule.write(_height, "height", 0.0); + capsule.write(_radius, "radius", 1.0); + capsule.write(_random, "random", false); + capsule.write(_transformWithScene, "transformWithScene", true); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _type = capsule.readInt("type", VT_CYLINDER); + _strength = capsule.readDouble("strength", 1.0); + _divergence = capsule.readDouble("divergence", 0.0); + _axis.set((Line3) capsule.readSavable("axis", new Line3(new Vector3(), new Vector3(Vector3.UNIT_Y)))); + _height = capsule.readDouble("height", 0.0); + _radius = capsule.readDouble("radius", 1.0); + _random = capsule.readBoolean("random", false); + _transformWithScene = capsule.readBoolean("transformWithScene", true); + } + + @Override + public Class<? extends BasicVortex> getClassTag() { + return this.getClass(); + } + } + + /** + * Not used. + */ + private SimpleParticleInfluenceFactory() {} + + /** + * Creates a basic wind that always blows in a single direction. + * + * @param windStr + * Max strength of wind. + * @param windDir + * Direction wind should blow. + * @param addRandom + * randomly alter the strength of the wind by 0-100% + * @param rotateWithScene + * rotate the wind direction with the particle system + * @return ParticleInfluence + */ + public static ParticleInfluence createBasicWind(final double windStr, final ReadOnlyVector3 windDir, + final boolean addRandom, final boolean rotateWithScene) { + return new BasicWind(windStr, windDir, addRandom, rotateWithScene); + } + + /** + * Create a basic gravitational force. + * + * @param rotateWithScene + * rotate the gravity vector with the particle system + * @return ParticleInfluence + */ + public static ParticleInfluence createBasicGravity(final ReadOnlyVector3 gravForce, final boolean rotateWithScene) { + return new BasicGravity(gravForce, rotateWithScene); + } + + /** + * Create a basic drag force that will use the given drag coefficient. Drag is determined by figuring the current + * velocity and reversing it, then multiplying by the drag coefficient and dividing by the particle mass. + * + * @param dragCoef + * Should be positive. Larger values mean more drag but possibly more instability. + * @return ParticleInfluence + */ + public static ParticleInfluence createBasicDrag(final double dragCoef) { + return new BasicDrag(dragCoef); + } + + /** + * Creates a basic vortex. + * + * @param strength + * Max strength of vortex. + * @param divergence + * The divergence in radians from the tangent vector + * @param axis + * The center of the vortex. + * @param random + * randomly alter the strength of the vortex by 0-100% + * @param transformWithScene + * transform the axis with the particle system + * @return ParticleInfluence + */ + public static ParticleInfluence createBasicVortex(final double strength, final double divergence, + final ReadOnlyLine3 axis, final boolean random, final boolean transformWithScene) { + return new BasicVortex(strength, divergence, axis, random, transformWithScene); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java new file mode 100644 index 0000000..08e4ffb --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +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.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * Simple swarming influence for use with particles. + */ +public class SwarmInfluence extends ParticleInfluence { + + private double _swarmRangeSQ; + private final Vector3 _swarmOffset = new Vector3(); + private final Vector3 _swarmPoint = new Vector3(); + + public static final double DEFAULT_SWARM_RANGE_SQ = 0.01; + public static final double DEFAULT_DEVIANCE = MathUtils.DEG_TO_RAD * 15; + public static final double DEFAULT_TURN_SPEED = MathUtils.DEG_TO_RAD * 180; + public static final double DEFAULT_SPEED_BUMP = .1; + public static final double DEFAULT_MAX_SPEED = .2; + + private double _deviance = DEFAULT_DEVIANCE; + private double _turnSpeed = DEFAULT_TURN_SPEED; + private double _speedBump = DEFAULT_SPEED_BUMP; + private double _maxSpeed = DEFAULT_MAX_SPEED; + + private transient double _maxSpeedSQ = DEFAULT_MAX_SPEED * DEFAULT_MAX_SPEED; + + public SwarmInfluence() { + _swarmRangeSQ = DEFAULT_SWARM_RANGE_SQ; + } + + public SwarmInfluence(final ReadOnlyVector3 offset, final double swarmRange) { + super(); + _swarmRangeSQ = swarmRange * swarmRange; + _swarmOffset.set(offset); + } + + @Override + public void prepare(final ParticleSystem system) { + super.prepare(system); + _swarmPoint.set(system.getOriginCenter()).addLocal(_swarmOffset); + } + + @Override + public void apply(final double dt, final Particle particle, final int index) { + final Vector3 pVelocity = particle.getVelocity(); + // determine if the particle is in the inner or outer zone + final double pDist = particle.getPosition().distanceSquared(_swarmPoint); + final Vector3 workVect = Vector3.fetchTempInstance(); + final Vector3 workVect2 = Vector3.fetchTempInstance(); + final Matrix3 workMat = Matrix3.fetchTempInstance(); + workVect.set(_swarmPoint).subtractLocal(particle.getPosition()).normalizeLocal(); + workVect2.set(pVelocity).normalizeLocal(); + if (pDist > _swarmRangeSQ) { + // IN THE OUTER ZONE... + // Determine if the angle between particle velocity and a vector to + // the swarmPoint is less than the accepted deviance + final double angle = workVect.smallestAngleBetween(workVect2); + if (angle < _deviance) { + // if it is, increase the speed speedBump over time + if (pVelocity.lengthSquared() < _maxSpeedSQ) { + final double change = _speedBump * dt; + workVect2.multiplyLocal(change); // where workVector2 = pVelocity.normalizeLocal() + pVelocity.addLocal(workVect2); + } + } else { + final Vector3 axis = workVect2.crossLocal(workVect); + // if it is not, shift the velocity to bring it back in line + if ((Double.doubleToLongBits(pVelocity.lengthSquared()) & 0x1d) != 0) { + workMat.fromAngleAxis(_turnSpeed * dt, axis); + } else { + workMat.fromAngleAxis(-_turnSpeed * dt, axis); + } + workMat.applyPost(pVelocity, pVelocity); + } + } else { + final Vector3 axis = workVect2.crossLocal(workVect); + // IN THE INNER ZONE... + // Alter the heading based on how fast we are going + if ((index & 0x1f) != 0) { + workMat.fromAngleAxis(_turnSpeed * dt, axis); + } else { + workMat.fromAngleAxis(-_turnSpeed * dt, axis); + } + workMat.applyPost(pVelocity, pVelocity); + } + Vector3.releaseTempInstance(workVect); + Vector3.releaseTempInstance(workVect2); + Matrix3.releaseTempInstance(workMat); + } + + public double getSwarmRange() { + return Math.sqrt(_swarmRangeSQ); + } + + public void setSwarmRange(final double swarmRange) { + _swarmRangeSQ = swarmRange * swarmRange; + } + + public ReadOnlyVector3 getSwarmOffset() { + return _swarmOffset; + } + + public void setSwarmOffset(final ReadOnlyVector3 offset) { + _swarmOffset.set(offset); + } + + public double getDeviance() { + return _deviance; + } + + public void setDeviance(final double deviance) { + _deviance = deviance; + } + + public double getSpeedBump() { + return _speedBump; + } + + public void setSpeedBump(final double speedVariance) { + _speedBump = speedVariance; + } + + public double getTurnSpeed() { + return _turnSpeed; + } + + public void setTurnSpeed(final double turnSpeed) { + _turnSpeed = turnSpeed; + } + + public double getMaxSpeed() { + return _maxSpeed; + } + + public void setMaxSpeed(final double maxSpeed) { + _maxSpeed = maxSpeed; + _maxSpeedSQ = maxSpeed * maxSpeed; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + final OutputCapsule cap = capsule; + cap.write(_swarmRangeSQ, "swarmRangeSQ", DEFAULT_SWARM_RANGE_SQ); + cap.write(_deviance, "deviance", DEFAULT_DEVIANCE); + cap.write(_turnSpeed, "turnSpeed", DEFAULT_TURN_SPEED); + cap.write(_speedBump, "speedBump", DEFAULT_SPEED_BUMP); + cap.write(_maxSpeed, "maxSpeed", DEFAULT_MAX_SPEED); + cap.write(_swarmOffset, "swarmOffset", new Vector3()); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + final InputCapsule cap = capsule; + _swarmRangeSQ = cap.readDouble("swarmRangeSQ", DEFAULT_SWARM_RANGE_SQ); + _deviance = cap.readDouble("deviance", DEFAULT_DEVIANCE); + _turnSpeed = cap.readDouble("turnSpeed", DEFAULT_TURN_SPEED); + _speedBump = cap.readDouble("speedBump", DEFAULT_SPEED_BUMP); + _maxSpeed = cap.readDouble("maxSpeed", DEFAULT_MAX_SPEED); + _swarmOffset.set((Vector3) cap.readSavable("swarmOffset", new Vector3())); + } + + @Override + public Class<? extends SwarmInfluence> getClassTag() { + return getClass(); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.java new file mode 100644 index 0000000..3b73b03 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.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.extension.effect.particle; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.export.Savable; + +public class TexAnimation implements Savable { + + protected List<AnimationEntry> _entries = new ArrayList<AnimationEntry>(); + + public void addEntry(final AnimationEntry entry) { + _entries.add(entry); + } + + public void addEntry(final int index, final AnimationEntry entry) { + _entries.add(index, entry); + } + + public void clearEntries() { + _entries.clear(); + } + + public Iterator<AnimationEntry> getEntries() { + return _entries.iterator(); + } + + public void removeEntry(final AnimationEntry entry) { + _entries.remove(entry); + } + + public void removeEntry(final int index) { + _entries.remove(index); + } + + public int getTexIndexAtAge(double age, double maxAge, final ParticleSystem particles) { + // find what AnimationEntry we last passed... + double trAge = 0, lastAge = 0; + AnimationEntry latest = null; + maxAge /= 1000f; + age /= 1000f; + for (int i = 0; i < _entries.size(); i++) { + final AnimationEntry entry = _entries.get(i); + trAge += (entry.getOffset() * maxAge); + if (trAge <= age) { + latest = entry; + lastAge = trAge; + } else { + break; + } + } + + if (latest == null) { + return particles.getStartTexIndex(); + } else { + int index = (int) ((age - lastAge) / latest._rate); + index %= latest._frames.length; + return latest._frames[index]; + } + } + + public Class<? extends TexAnimation> getClassTag() { + return getClass(); + } + + public void read(final InputCapsule capsule) throws IOException { + _entries = capsule.readSavableList("entries", null); + if (_entries == null) { + _entries = new ArrayList<AnimationEntry>(); + } + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.writeSavableList(_entries, "entries", null); + } + +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java new file mode 100644 index 0000000..5a0481a --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.effect.particle; + +import java.io.IOException; +import java.util.ArrayList; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class WanderInfluence extends ParticleInfluence { + + public static final double DEFAULT_RADIUS = .03f; + public static final double DEFAULT_DISTANCE = .2f; + public static final double DEFAULT_JITTER = .005f; + + private double _wanderRadius = DEFAULT_RADIUS; + private double _wanderDistance = DEFAULT_DISTANCE; + private double _wanderJitter = DEFAULT_JITTER; + + private ArrayList<Vector3> _wanderTargets = new ArrayList<Vector3>(1); + private final Vector3 _workVect = new Vector3(); + + @Override + public void prepare(final ParticleSystem system) { + if (_wanderTargets.size() != system.getNumParticles()) { + _wanderTargets = new ArrayList<Vector3>(system.getNumParticles()); + for (int x = system.getNumParticles(); --x >= 0;) { + _wanderTargets.add(new Vector3(system.getEmissionDirection()).normalizeLocal()); + } + } + } + + @Override + public void apply(final double dt, final Particle particle, final int index) { + if (_wanderRadius == 0 && _wanderDistance == 0 && _wanderJitter == 0) { + return; + } + + final Vector3 wanderTarget = _wanderTargets.get(index); + + wanderTarget.addLocal(calcNewJitter(), calcNewJitter(), calcNewJitter()); + wanderTarget.normalizeLocal(); + wanderTarget.multiplyLocal(_wanderRadius); + + _workVect.set(particle.getVelocity()).normalizeLocal().multiplyLocal(_wanderDistance); + _workVect.addLocal(wanderTarget).normalizeLocal(); + _workVect.multiplyLocal(particle.getVelocity().length()); + particle.getVelocity().set(_workVect); + } + + private double calcNewJitter() { + return ((MathUtils.nextRandomFloat() * 2.0f) - 1.0f) * _wanderJitter; + } + + public double getWanderDistance() { + return _wanderDistance; + } + + public void setWanderDistance(final double wanderDistance) { + _wanderDistance = wanderDistance; + } + + public double getWanderJitter() { + return _wanderJitter; + } + + public void setWanderJitter(final double wanderJitter) { + _wanderJitter = wanderJitter; + } + + public double getWanderRadius() { + return _wanderRadius; + } + + public void setWanderRadius(final double wanderRadius) { + _wanderRadius = wanderRadius; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + final OutputCapsule cap = capsule; + cap.write(_wanderRadius, "wanderRadius", DEFAULT_RADIUS); + cap.write(_wanderDistance, "wanderDistance", DEFAULT_DISTANCE); + cap.write(_wanderJitter, "wanderJitter", DEFAULT_JITTER); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + final InputCapsule cap = capsule; + _wanderRadius = cap.readDouble("wanderRadius", DEFAULT_RADIUS); + _wanderDistance = cap.readDouble("wanderDistance", DEFAULT_DISTANCE); + _wanderJitter = cap.readDouble("wanderJitter", DEFAULT_JITTER); + } + + @Override + public Class<? extends WanderInfluence> getClassTag() { + return getClass(); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.java new file mode 100644 index 0000000..9e2a75c --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.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.extension.effect.particle.emitter; + +import java.io.IOException; + +import com.ardor3d.math.LineSegment3; +import com.ardor3d.math.Vector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class LineSegmentEmitter extends SavableParticleEmitter { + + private LineSegment3 _source; + + public LineSegmentEmitter() { + _source = new LineSegment3(); + } + + /** + * @param source + * the segment to use as our source + */ + public LineSegmentEmitter(final LineSegment3 source) { + _source = source; + } + + public void setSource(final LineSegment3 source) { + _source = source; + } + + public LineSegment3 getSource() { + return _source; + } + + public Vector3 randomEmissionPoint(final Vector3 store) { + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + + getSource().random(rVal); + return rVal; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public void read(final InputCapsule capsule) throws IOException { + _source = (LineSegment3) capsule.readSavable("source", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_source, "source", null); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.java new file mode 100644 index 0000000..48fb32d --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.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.extension.effect.particle.emitter; + +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; + +public class MeshEmitter extends SavableParticleEmitter { + + private Mesh _source; + private boolean _onlyVertices; + + public MeshEmitter() {} + + /** + * @param source + * the mesh to use as our source + * @param onlyVertices + * if true, only the vertices of the emitter should be used for spawning particles. Otherwise, the mesh's + * face surfaces should be used. + */ + public MeshEmitter(final Mesh source, final boolean onlyVertices) { + _source = source; + _onlyVertices = onlyVertices; + } + + public void setSource(final Mesh source) { + _source = source; + } + + public Mesh getSource() { + return _source; + } + + public void setOnlyVertices(final boolean onlyVertices) { + _onlyVertices = onlyVertices; + } + + public boolean isOnlyVertices() { + return _onlyVertices; + } + + public Vector3 randomEmissionPoint(final Vector3 store) { + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + + if (_onlyVertices) { + getSource().getMeshData().randomVertex(rVal); + } else { + getSource().getMeshData().randomPointOnPrimitives(rVal); + } + return rVal; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public void read(final InputCapsule capsule) throws IOException { + _source = (Mesh) capsule.readSavable("source", null); + _onlyVertices = capsule.readBoolean("onlyVertices", false); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_source, "source", null); + capsule.write(_onlyVertices, "onlyVertices", false); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.java new file mode 100644 index 0000000..b382596 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.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.extension.effect.particle.emitter; + +import com.ardor3d.math.Vector3; + +public interface ParticleEmitter { + + /** + * Get the next point from this emitter. + * + * @param store + * the vector to store our point in. If null, a new one is created. + * @return the vector we stored in + */ + public Vector3 randomEmissionPoint(final Vector3 store); + +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.java new file mode 100644 index 0000000..9e6b84d --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.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.extension.effect.particle.emitter; + +import java.io.IOException; + +import com.ardor3d.math.Vector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class PointEmitter extends SavableParticleEmitter { + + public PointEmitter() {} + + public Vector3 randomEmissionPoint(final Vector3 store) { + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + + rVal.set(0, 0, 0); + return rVal; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public void read(final InputCapsule capsule) throws IOException {} + + public void write(final OutputCapsule capsule) throws IOException {} +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.java new file mode 100644 index 0000000..18264f4 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.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.extension.effect.particle.emitter; + +import java.io.IOException; + +import com.ardor3d.math.Rectangle3; +import com.ardor3d.math.Vector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class RectangleEmitter extends SavableParticleEmitter { + + private Rectangle3 _source; + + public RectangleEmitter() { + _source = new Rectangle3(); + } + + /** + * @param source + * the rectangle to use as our source + */ + public RectangleEmitter(final Rectangle3 source) { + _source = source; + } + + public void setSource(final Rectangle3 source) { + _source = source; + } + + public Rectangle3 getSource() { + return _source; + } + + public Vector3 randomEmissionPoint(final Vector3 store) { + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + + getSource().random(rVal); + return rVal; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public void read(final InputCapsule capsule) throws IOException { + _source = (Rectangle3) capsule.readSavable("source", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_source, "source", null); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.java new file mode 100644 index 0000000..738bb60 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.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.extension.effect.particle.emitter; + +import java.io.IOException; + +import com.ardor3d.math.Ring; +import com.ardor3d.math.Vector3; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +public class RingEmitter extends SavableParticleEmitter { + + private Ring _source; + + public RingEmitter() { + _source = new Ring(); + } + + /** + * @param source + * the ring to use as our source + */ + public RingEmitter(final Ring source) { + _source = source; + } + + public void setSource(final Ring source) { + _source = source; + } + + public Ring getSource() { + return _source; + } + + public Vector3 randomEmissionPoint(final Vector3 store) { + Vector3 rVal = store; + if (rVal == null) { + rVal = new Vector3(); + } + + getSource().random(rVal); + return rVal; + } + + // ///////////////// + // Methods for Savable + // ///////////////// + + public void read(final InputCapsule capsule) throws IOException { + _source = (Ring) capsule.readSavable("source", null); + } + + public void write(final OutputCapsule capsule) throws IOException { + capsule.write(_source, "source", null); + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.java new file mode 100644 index 0000000..a11413a --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.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.extension.effect.particle.emitter; + +import com.ardor3d.util.export.Savable; + +public abstract class SavableParticleEmitter implements Savable, ParticleEmitter { + + // ///////////////// + // Methods for Savable + // ///////////////// + + public Class<? extends SavableParticleEmitter> getClassTag() { + return this.getClass(); + } + +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java new file mode 100644 index 0000000..7df9833 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java @@ -0,0 +1,31 @@ +/**
+ * Copyright (c) 2008 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+/**
+ * <code>HeightGenerator</code> Base interface for all waterheight generators used by the projected grid mesh.
+ */
+public interface HeightGenerator {
+ /**
+ * How to animate/set heights on a grid
+ *
+ * @param x
+ * x position to get height for
+ * @param z
+ * z position to get height for
+ * @param time
+ * time to get height for
+ * @return height for specified position
+ */
+ public double getHeight(double x, double z, double time);
+
+ public double getMaximumHeight();
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.java new file mode 100644 index 0000000..9599306 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.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.extension.effect.water;
+
+import com.ardor3d.math.MathUtils;
+
+/**
+ * <code>ImprovedNoise</code> Fast perlin noise.
+ *
+ * @author Ken Perlin
+ */
+public final class ImprovedNoise {
+ public static double noise(double x, double y, double z) {
+ final int X = (int) MathUtils.floor(x) & 255, // FIND UNIT CUBE THAT
+ Y = (int) MathUtils.floor(y) & 255, // CONTAINS POINT.
+ Z = (int) MathUtils.floor(z) & 255;
+ x -= MathUtils.floor(x); // FIND RELATIVE X,Y,Z
+ y -= MathUtils.floor(y); // OF POINT IN CUBE.
+ z -= MathUtils.floor(z);
+ final double u = fade(x), // COMPUTE FADE CURVES
+ v = fade(y), // FOR EACH OF X,Y,Z.
+ w = fade(z);
+ final int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF
+ B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS,
+
+ return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), // AND ADD
+ grad(p[BA], x - 1, y, z)), // BLENDED
+ lerp(u, grad(p[AB], x, y - 1, z), // RESULTS
+ grad(p[BB], x - 1, y - 1, z))),// FROM 8
+ lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), // CORNERS
+ grad(p[BA + 1], x - 1, y, z - 1)), // OF CUBE
+ lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))));
+ }
+
+ private static double fade(final double t) {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+ }
+
+ private static double lerp(final double t, final double a, final double b) {
+ return a + t * (b - a);
+ }
+
+ private static double grad(final int hash, final double x, final double y, final double z) {
+ final int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
+ final double u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
+ v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+ }
+
+ private static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53,
+ 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0,
+ 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171,
+ 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
+ 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187,
+ 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226,
+ 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
+ 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129,
+ 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
+ 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199,
+ 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
+ 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 };
+
+ static {
+ for (int i = 0; i < 256; i++) {
+ p[256 + i] = p[i] = permutation[i];
+ }
+ }
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java new file mode 100644 index 0000000..9667b81 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java @@ -0,0 +1,662 @@ +/**
+ * Copyright (c) 2008 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Stack;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.ExtendedCamera;
+import com.ardor3d.util.Timer;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.geom.Debugger;
+
+/**
+ * <code>ProjectedGrid</code> Projected grid mesh
+ */
+public class ProjectedGrid extends Mesh {
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(ProjectedGrid.class.getName());
+
+ private final int sizeX;
+ private final int sizeY;
+
+ private FloatBuffer vertBuf;
+ private final FloatBuffer normBuf;
+ private final FloatBuffer texs;
+ private IntBuffer indexBuffer;
+
+ private final ExtendedCamera mainCamera = new ExtendedCamera();
+ private final Camera projectorCamera = new Camera();
+
+ private final Vector4 origin = new Vector4();
+ private final Vector4 direction = new Vector4();
+ private final Vector2 source = new Vector2();
+ private final Matrix4 rangeMatrix = new Matrix4();
+
+ private final Vector4 intersectBottomLeft = new Vector4();
+ private final Vector4 intersectTopLeft = new Vector4();
+ private final Vector4 intersectTopRight = new Vector4();
+ private final Vector4 intersectBottomRight = new Vector4();
+
+ private final Vector3 planeIntersection = new Vector3();
+
+ public boolean freezeProjector = false;
+ private final Timer timer;
+ private final Camera camera;
+
+ private final HeightGenerator heightGenerator;
+ private final float textureScale;
+
+ private double projectorMinHeight = 100.0;
+ private final Vector3[] intersections = new Vector3[24];
+
+ private final float[] vertBufArray;
+ private final float[] normBufArray;
+ private final float[] texBufArray;
+
+ private int nrUpdateThreads = 1;
+ private final ExecutorService executorService = Executors.newCachedThreadPool(new DeamonThreadFactory());
+ private final Stack<Future<?>> futureStack = new Stack<Future<?>>();
+
+ private final int connections[] = { 0, 1, 2, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 5, 6, 7, };
+
+ // Debug drawing
+ private boolean drawDebug = false;
+
+ public ProjectedGrid(final String name, final Camera camera, final int sizeX, final int sizeY,
+ final float textureScale, final HeightGenerator heightGenerator, final Timer timer) {
+ super(name);
+ this.sizeX = sizeX;
+ this.sizeY = sizeY;
+ this.textureScale = textureScale;
+ this.heightGenerator = heightGenerator;
+ this.camera = camera;
+ this.timer = timer;
+
+ buildVertices(sizeX * sizeY);
+ texs = BufferUtils.createVector2Buffer(_meshData.getVertexCount());
+ _meshData.setTextureBuffer(texs, 0);
+ normBuf = BufferUtils.createVector3Buffer(_meshData.getVertexCount());
+ _meshData.setNormalBuffer(normBuf);
+
+ vertBufArray = new float[_meshData.getVertexCount() * 3];
+ normBufArray = new float[_meshData.getVertexCount() * 3];
+ texBufArray = new float[_meshData.getVertexCount() * 2];
+
+ for (int i = 0; i < 24; i++) {
+ intersections[i] = new Vector3();
+ }
+ }
+
+ public void setNrUpdateThreads(final int nrUpdateThreads) {
+ this.nrUpdateThreads = nrUpdateThreads;
+ if (this.nrUpdateThreads < 1) {
+ this.nrUpdateThreads = 1;
+ }
+ }
+
+ public int getNrUpdateThreads() {
+ return nrUpdateThreads;
+ }
+
+ public void setFreezeUpdate(final boolean freeze) {
+ freezeProjector = freeze;
+ }
+
+ public boolean isFreezeUpdate() {
+ return freezeProjector;
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ final boolean doDraw = update();
+ if (doDraw) {
+ super.render(renderer);
+ }
+
+ if (drawDebug) {
+ Debugger.drawCameraFrustum(renderer, mainCamera, new ColorRGBA(1, 0, 0, 1), (short) 0xFFFF, true);
+ Debugger.drawCameraFrustum(renderer, projectorCamera, new ColorRGBA(0, 1, 1, 1), (short) 0xFFFF, true);
+ }
+ }
+
+ public boolean update() {
+ final double upperBound = heightGenerator.getMaximumHeight();
+
+ if (!freezeProjector) {
+ mainCamera.set(camera);
+
+ final Vector3 tmp = new Vector3();
+ getWorldTransform().applyInverse(mainCamera.getLocation(), tmp);
+ mainCamera.setLocation(tmp);
+ getWorldTransform().applyInverseVector(mainCamera.getLeft(), tmp);
+ mainCamera.setLeft(tmp);
+ getWorldTransform().applyInverseVector(mainCamera.getUp(), tmp);
+ mainCamera.setUp(tmp);
+ getWorldTransform().applyInverseVector(mainCamera.getDirection(), tmp);
+ mainCamera.setDirection(tmp);
+ }
+
+ final ReadOnlyVector3 mainCameraLocation = mainCamera.getLocation();
+ if (mainCameraLocation.getY() > 0.0 && mainCameraLocation.getY() < upperBound + mainCamera.getFrustumNear()) {
+ mainCamera.setLocation(mainCameraLocation.getX(), upperBound + mainCamera.getFrustumNear(),
+ mainCameraLocation.getZ());
+ } else if (mainCameraLocation.getY() < 0.0
+ && mainCameraLocation.getY() > -upperBound - mainCamera.getFrustumNear()) {
+ mainCamera.setLocation(mainCameraLocation.getX(), -upperBound - mainCamera.getFrustumNear(),
+ mainCameraLocation.getZ());
+ }
+ mainCamera.calculateFrustum();
+ final Vector3[] corners = mainCamera.getCorners();
+
+ int nrPoints = 0;
+
+ // check intersections of frustum connections with upper and lower bound
+ final Vector3 tmpStorage = Vector3.fetchTempInstance();
+ for (int i = 0; i < 8; i++) {
+ final int source = connections[i * 2];
+ final int destination = connections[i * 2 + 1];
+
+ if (corners[source].getY() > upperBound && corners[destination].getY() < upperBound
+ || corners[source].getY() < upperBound && corners[destination].getY() > upperBound) {
+ getWorldIntersection(upperBound, corners[source], corners[destination], intersections[nrPoints++],
+ tmpStorage);
+ }
+ if (corners[source].getY() > -upperBound && corners[destination].getY() < -upperBound
+ || corners[source].getY() < -upperBound && corners[destination].getY() > -upperBound) {
+ getWorldIntersection(-upperBound, corners[source], corners[destination], intersections[nrPoints++],
+ tmpStorage);
+ }
+ }
+ // check if any of the frustums corner vertices lie between the upper and lower bound planes
+ for (int i = 0; i < 8; i++) {
+ if (corners[i].getY() < upperBound && corners[i].getY() > -upperBound) {
+ intersections[nrPoints++].set(corners[i]);
+ }
+ }
+
+ if (nrPoints == 0) {
+ // No intersection, grid not visible
+ return false;
+ }
+
+ // set projector
+ projectorCamera.set(mainCamera);
+
+ // force the projector to point at the plane
+ if (projectorCamera.getLocation().getY() > 0.0 && projectorCamera.getDirection().getY() > 0.0
+ || projectorCamera.getLocation().getY() < 0.0 && projectorCamera.getDirection().getY() < 0.0) {
+ projectorCamera.setDirection(new Vector3(projectorCamera.getDirection().getX(), -projectorCamera
+ .getDirection().getY(), projectorCamera.getDirection().getZ()));
+ projectorCamera.setUp(projectorCamera.getDirection().cross(projectorCamera.getLeft(), null)
+ .normalizeLocal());
+ }
+
+ // find the plane intersection point
+ source.set(0.5, 0.5);
+ getWorldIntersection(0.0, source, projectorCamera.getModelViewProjectionInverseMatrix(), planeIntersection);
+
+ // force the projector to be a certain distance above the plane
+ final ReadOnlyVector3 cameraLocation = projectorCamera.getLocation();
+ if (cameraLocation.getY() > 0.0 && cameraLocation.getY() < projectorMinHeight * 2) {
+ final double delta = (projectorMinHeight * 2 - cameraLocation.getY()) / (projectorMinHeight * 2);
+
+ projectorCamera.setLocation(cameraLocation.getX(), projectorMinHeight * 2 - projectorMinHeight * delta,
+ cameraLocation.getZ());
+ } else if (cameraLocation.getY() < 0.0 && cameraLocation.getY() > -projectorMinHeight * 2) {
+ final double delta = (-projectorMinHeight * 2 - cameraLocation.getY()) / (-projectorMinHeight * 2);
+
+ projectorCamera.setLocation(cameraLocation.getX(), -projectorMinHeight * 2 + projectorMinHeight * delta,
+ cameraLocation.getZ());
+ }
+
+ // restrict the intersection point to be a certain distance from the camera in plane coords
+ planeIntersection.subtractLocal(projectorCamera.getLocation());
+ planeIntersection.setY(0.0);
+ final double length = planeIntersection.length();
+ if (length > Math.abs(projectorCamera.getLocation().getY())) {
+ planeIntersection.normalizeLocal();
+ planeIntersection.multiplyLocal(Math.abs(projectorCamera.getLocation().getY()));
+ } else if (length < MathUtils.EPSILON) {
+ planeIntersection.addLocal(projectorCamera.getUp());
+ planeIntersection.setY(0.0);
+ planeIntersection.normalizeLocal();
+ planeIntersection.multiplyLocal(0.1); // TODO: magic number
+ }
+ planeIntersection.addLocal(projectorCamera.getLocation());
+ planeIntersection.setY(0.0);
+
+ // point projector at the new intersection point
+ projectorCamera.lookAt(planeIntersection, Vector3.UNIT_Y);
+
+ // transform points to projector space
+ final ReadOnlyMatrix4 modelViewProjectionMatrix = projectorCamera.getModelViewProjectionMatrix();
+ final Vector4 spaceTransformation = new Vector4();
+ for (int i = 0; i < nrPoints; i++) {
+ spaceTransformation.set(intersections[i].getX(), 0.0, intersections[i].getZ(), 1.0);
+ modelViewProjectionMatrix.applyPre(spaceTransformation, spaceTransformation);
+ intersections[i].set(spaceTransformation.getX(), spaceTransformation.getY(), 0);
+ intersections[i].divideLocal(spaceTransformation.getW());
+ }
+
+ // find min/max in projector space
+ double minX = Double.MAX_VALUE;
+ double maxX = -Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+ for (int i = 0; i < nrPoints; i++) {
+ if (intersections[i].getX() < minX) {
+ minX = intersections[i].getX();
+ }
+ if (intersections[i].getX() > maxX) {
+ maxX = intersections[i].getX();
+ }
+ if (intersections[i].getY() < minY) {
+ minY = intersections[i].getY();
+ }
+ if (intersections[i].getY() > maxY) {
+ maxY = intersections[i].getY();
+ }
+ }
+
+ // create range matrix
+ rangeMatrix.setIdentity();
+ rangeMatrix.setM00(maxX - minX);
+ rangeMatrix.setM11(maxY - minY);
+ rangeMatrix.setM30(minX);
+ rangeMatrix.setM31(minY);
+
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix = projectorCamera.getModelViewProjectionInverseMatrix();
+ rangeMatrix.multiplyLocal(modelViewProjectionInverseMatrix);
+
+ // convert screen coords to homogenous world coords with new range matrix
+ source.set(0.5, 0.5);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectBottomLeft);
+ source.set(0.5, 1);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectTopLeft);
+ source.set(1, 1);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectTopRight);
+ source.set(1, 0.5);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectBottomRight);
+
+ // update data
+ if (nrUpdateThreads <= 1) {
+ updateGrid(0, sizeY);
+ } else {
+ for (int i = 0; i < nrUpdateThreads; i++) {
+ final int from = sizeY * i / (nrUpdateThreads);
+ final int to = sizeY * (i + 1) / (nrUpdateThreads);
+ final Future<?> future = executorService.submit(new Runnable() {
+ public void run() {
+ updateGrid(from, to);
+ }
+ });
+ futureStack.push(future);
+ }
+ try {
+ while (!futureStack.isEmpty()) {
+ futureStack.pop().get();
+ }
+ } catch (final InterruptedException ex) {
+ logger.log(Level.SEVERE, "InterruptedException in thread execution", ex);
+ } catch (final ExecutionException ex) {
+ logger.log(Level.SEVERE, "ExecutionException in thread execution", ex);
+ }
+ }
+
+ vertBuf.rewind();
+ vertBuf.put(vertBufArray);
+
+ texs.rewind();
+ texs.put(texBufArray);
+
+ normBuf.rewind();
+ normBuf.put(normBufArray);
+
+ return true;
+ }
+
+ private boolean getWorldIntersection(final double planeHeight, final Vector3 source, final Vector3 destination,
+ final Vector3 store, final Vector3 tmpStorage) {
+ final Vector3 origin = store.set(source);
+ final Vector3 direction = tmpStorage.set(destination).subtractLocal(origin);
+
+ final double t = (planeHeight - origin.getY()) / (direction.getY());
+
+ direction.multiplyLocal(t);
+ origin.addLocal(direction);
+
+ return t >= 0.0 && t <= 1.0;
+ }
+
+ private void updateGrid(final int from, final int to) {
+ final double time = timer.getTimeInSeconds();
+ final double du = 1.0f / (double) (sizeX - 1);
+ final double dv = 1.0f / (double) (sizeY - 1);
+
+ final Vector4 pointTop = Vector4.fetchTempInstance();
+ final Vector4 pointFinal = Vector4.fetchTempInstance();
+ final Vector4 pointBottom = Vector4.fetchTempInstance();
+
+ int smallerFrom = from;
+ if (smallerFrom > 0) {
+ smallerFrom--;
+ }
+ int biggerTo = to;
+ if (biggerTo < sizeY) {
+ biggerTo++;
+ }
+ double u = 0, v = smallerFrom * dv;
+ int index = smallerFrom * sizeX * 3;
+ for (int y = smallerFrom; y < biggerTo; y++) {
+ for (int x = 0; x < sizeX; x++) {
+ pointTop.lerpLocal(intersectTopLeft, intersectTopRight, u);
+ pointBottom.lerpLocal(intersectBottomLeft, intersectBottomRight, u);
+ pointFinal.lerpLocal(pointTop, pointBottom, v);
+
+ pointFinal.setX(pointFinal.getX() / pointFinal.getW());
+ pointFinal.setZ(pointFinal.getZ() / pointFinal.getW());
+ pointFinal.setY(heightGenerator.getHeight(pointFinal.getX(), pointFinal.getZ(), time));
+
+ vertBufArray[index++] = pointFinal.getXf();
+ vertBufArray[index++] = pointFinal.getYf();
+ vertBufArray[index++] = pointFinal.getZf();
+
+ u += du;
+ }
+ v += dv;
+ u = 0;
+ }
+
+ Vector4.releaseTempInstance(pointTop);
+ Vector4.releaseTempInstance(pointFinal);
+ Vector4.releaseTempInstance(pointBottom);
+
+ final Vector3 oppositePoint = Vector3.fetchTempInstance();
+ final Vector3 adjacentPoint = Vector3.fetchTempInstance();
+ final Vector3 rootPoint = Vector3.fetchTempInstance();
+
+ int adj = 0, opp = 0;
+ int normalIndex = from * sizeX;
+ for (int row = from; row < to; row++) {
+ for (int col = 0; col < sizeX; col++) {
+ if (row == sizeY - 1) {
+ if (col == sizeX - 1) { // last row, last col
+ // up cross left
+ adj = normalIndex - sizeX;
+ opp = normalIndex - 1;
+ } else { // last row, except for last col
+ // right cross up
+ adj = normalIndex + 1;
+ opp = normalIndex - sizeX;
+ }
+ } else {
+ if (col == sizeX - 1) { // last column except for last row
+ // left cross down
+ adj = normalIndex - 1;
+ opp = normalIndex + sizeX;
+ } else { // most cases
+ // down cross right
+ adj = normalIndex + sizeX;
+ opp = normalIndex + 1;
+ }
+ }
+
+ final float x = vertBufArray[normalIndex * 3];
+ final float y = vertBufArray[normalIndex * 3 + 1];
+ final float z = vertBufArray[normalIndex * 3 + 2];
+
+ texBufArray[normalIndex * 2] = x * textureScale;
+ texBufArray[normalIndex * 2 + 1] = z * textureScale;
+
+ rootPoint.set(x, y, z);
+ adjacentPoint.set(vertBufArray[adj * 3], vertBufArray[adj * 3 + 1], vertBufArray[adj * 3 + 2]);
+ adjacentPoint.subtractLocal(rootPoint);
+ oppositePoint.set(vertBufArray[opp * 3], vertBufArray[opp * 3 + 1], vertBufArray[opp * 3 + 2]);
+ oppositePoint.subtractLocal(rootPoint);
+
+ adjacentPoint.crossLocal(oppositePoint).normalizeLocal();
+
+ normBufArray[normalIndex * 3] = adjacentPoint.getXf();
+ normBufArray[normalIndex * 3 + 1] = adjacentPoint.getYf();
+ normBufArray[normalIndex * 3 + 2] = adjacentPoint.getZf();
+
+ normalIndex++;
+ }
+ }
+
+ Vector3.releaseTempInstance(oppositePoint);
+ Vector3.releaseTempInstance(adjacentPoint);
+ Vector3.releaseTempInstance(rootPoint);
+ }
+
+ private void getWorldIntersectionHomogenous(final double planeHeight, final ReadOnlyVector2 screenPosition,
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix, final Vector4 store) {
+ calculateIntersection(planeHeight, screenPosition, modelViewProjectionInverseMatrix);
+ store.set(origin);
+ }
+
+ private void getWorldIntersection(final double planeHeight, final ReadOnlyVector2 screenPosition,
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix, final Vector3 store) {
+ calculateIntersection(planeHeight, screenPosition, modelViewProjectionInverseMatrix);
+ store.set(origin.getX(), origin.getY(), origin.getZ()).divideLocal(origin.getW());
+ }
+
+ private void calculateIntersection(final double planeHeight, final ReadOnlyVector2 screenPosition,
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix) {
+ origin.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, -1, 1);
+ direction.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, 1, 1);
+
+ modelViewProjectionInverseMatrix.applyPre(origin, origin);
+ modelViewProjectionInverseMatrix.applyPre(direction, direction);
+
+ direction.subtractLocal(origin);
+
+ // final double t = (planeHeight * origin.getW() - origin.getY())
+ // / (direction.getY() - planeHeight * direction.getW());
+
+ if (Math.abs(direction.getY()) > MathUtils.EPSILON) {
+ final double t = (planeHeight - origin.getY()) / direction.getY();
+ direction.multiplyLocal(t);
+ } else {
+ direction.normalizeLocal();
+ direction.multiplyLocal(mainCamera.getFrustumFar());
+ }
+
+ origin.addLocal(direction);
+ }
+
+ /**
+ * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
+ * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
+ * the height map, null is returned.
+ *
+ * @param position
+ * the vector representing the location to find a normal at.
+ * @param store
+ * the Vector3 object to store the result in. If null, a new one is created.
+ * @return the normal vector at the provided location.
+ */
+ public Vector3 getSurfaceNormal(final Vector2 position, final Vector3 store) {
+ return getSurfaceNormal(position.getX(), position.getY(), store);
+ }
+
+ /**
+ * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
+ * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
+ * the height map, null is returned.
+ *
+ * @param position
+ * the vector representing the location to find a normal at. Only the x and z values are used.
+ * @param store
+ * the Vector3 object to store the result in. If null, a new one is created.
+ * @return the normal vector at the provided location.
+ */
+ public Vector3 getSurfaceNormal(final Vector3 position, final Vector3 store) {
+ return getSurfaceNormal(position.getX(), position.getZ(), store);
+ }
+
+ /**
+ * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
+ * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
+ * the height map, null is returned.
+ *
+ * @param x
+ * the x coordinate to check.
+ * @param z
+ * the z coordinate to check.
+ * @param store
+ * the Vector3 object to store the result in. If null, a new one is created.
+ * @return the normal unit vector at the provided location.
+ */
+ public Vector3 getSurfaceNormal(final double x, final double z, Vector3 store) {
+ final double col = MathUtils.floor(x);
+ final double row = MathUtils.floor(z);
+
+ if (col < 0 || row < 0 || col >= sizeX - 1 || row >= sizeY - 1) {
+ return null;
+ }
+ final double intOnX = x - col, intOnZ = z - row;
+
+ if (store == null) {
+ store = new Vector3();
+ }
+
+ final Vector3 topLeft = store, topRight = new Vector3(), bottomLeft = new Vector3(), bottomRight = new Vector3();
+
+ final int focalSpot = (int) (col + row * sizeX);
+
+ // find the heightmap point closest to this position (but will always
+ // be to the left ( < x) and above (< z) of the spot.
+ BufferUtils.populateFromBuffer(topLeft, normBuf, focalSpot);
+
+ // now find the next point to the right of topLeft's position...
+ BufferUtils.populateFromBuffer(topRight, normBuf, focalSpot + 1);
+
+ // now find the next point below topLeft's position...
+ BufferUtils.populateFromBuffer(bottomLeft, normBuf, focalSpot + sizeX);
+
+ // now find the next point below and to the right of topLeft's
+ // position...
+ BufferUtils.populateFromBuffer(bottomRight, normBuf, focalSpot + sizeX + 1);
+
+ // Use linear interpolation to find the height.
+ topLeft.lerpLocal(topRight, intOnX);
+ bottomLeft.lerpLocal(bottomRight, intOnX);
+ topLeft.lerpLocal(bottomLeft, intOnZ);
+ return topLeft.normalizeLocal();
+ }
+
+ /**
+ * <code>buildVertices</code> sets up the vertex and index arrays of the TriMesh.
+ */
+ private void buildVertices(final int vertexCount) {
+ vertBuf = BufferUtils.createVector3Buffer(vertBuf, vertexCount);
+ _meshData.setVertexBuffer(vertBuf);
+
+ final Vector3 point = new Vector3();
+ for (int x = 0; x < sizeX; x++) {
+ for (int y = 0; y < sizeY; y++) {
+ point.set(x, 0, y);
+ BufferUtils.setInBuffer(point, vertBuf, (x + (y * sizeX)));
+ }
+ }
+
+ // set up the indices
+ final int triangleQuantity = ((sizeX - 1) * (sizeY - 1)) * 2;
+ indexBuffer = BufferUtils.createIntBuffer(triangleQuantity * 3);
+ _meshData.setIndexBuffer(indexBuffer);
+
+ // go through entire array up to the second to last column.
+ for (int i = 0; i < (sizeX * (sizeY - 1)); i++) {
+ // we want to skip the top row.
+ if (i % ((sizeX * (i / sizeX + 1)) - 1) == 0 && i != 0) {
+ // logger.info("skip row: "+i+" cause: "+((sizeY * (i / sizeX + 1)) - 1));
+ continue;
+ } else {
+ // logger.info("i: "+i);
+ }
+ // set the top left corner.
+ indexBuffer.put(i);
+ // set the bottom right corner.
+ indexBuffer.put((1 + sizeX) + i);
+ // set the top right corner.
+ indexBuffer.put(1 + i);
+ // set the top left corner
+ indexBuffer.put(i);
+ // set the bottom left corner
+ indexBuffer.put(sizeX + i);
+ // set the bottom right corner
+ indexBuffer.put((1 + sizeX) + i);
+ }
+ }
+
+ static class DeamonThreadFactory implements ThreadFactory {
+ static final AtomicInteger poolNumber = new AtomicInteger(1);
+ final ThreadGroup group;
+ final AtomicInteger threadNumber = new AtomicInteger(1);
+ final String namePrefix;
+
+ DeamonThreadFactory() {
+ final SecurityManager s = System.getSecurityManager();
+ group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ namePrefix = "ProjectedGrid Pool-" + poolNumber.getAndIncrement() + "-thread-";
+ }
+
+ public Thread newThread(final Runnable r) {
+ final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
+ if (!t.isDaemon()) {
+ t.setDaemon(true);
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+ }
+
+ public double getProjectorMinHeight() {
+ return projectorMinHeight;
+ }
+
+ public void setProjectorMinHeight(final double projectorMinHeight) {
+ this.projectorMinHeight = projectorMinHeight;
+ }
+
+ public boolean isDrawDebug() {
+ return drawDebug;
+ }
+
+ public void setDrawDebug(final boolean drawDebug) {
+ this.drawDebug = drawDebug;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.java new file mode 100644 index 0000000..46c348a --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.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.extension.effect.water;
+
+/**
+ * Sample implementation of a water height generator.
+ */
+public class WaterHeightGenerator implements HeightGenerator {
+ private double scalexsmall = 0.04;
+ private double scaleysmall = 0.02;
+ private double scalexbig = 0.015;
+ private double scaleybig = 0.01;
+ private double heightsmall = 3.0;
+ private double heightbig = 10.0;
+ private double speedsmall = 1.0;
+ private double speedbig = 0.5;
+ private int octaves = 2;
+
+ public double getHeight(final double x, final double z, final double time) {
+ final double zval = z * scaleybig * 4 + time * speedbig * 4;
+ double height = Math.sin(zval);
+ height *= heightbig;
+
+ if (octaves > 0) {
+ final double height2 = ImprovedNoise.noise(x * scaleybig, z * scalexbig, time * speedbig) * heightbig;
+ height = height * 0.4 + height2 * 0.6;
+ }
+ if (octaves > 1) {
+ height += ImprovedNoise.noise(x * scaleysmall, z * scalexsmall, time * speedsmall) * heightsmall;
+ }
+ if (octaves > 2) {
+ height += ImprovedNoise.noise(x * scaleysmall * 2.0, z * scalexsmall * 2.0, time * speedsmall * 1.5)
+ * heightsmall * 0.5;
+ }
+ if (octaves > 3) {
+ height += ImprovedNoise.noise(x * scaleysmall * 4.0, z * scalexsmall * 4.0, time * speedsmall * 2.0)
+ * heightsmall * 0.25;
+ }
+
+ return height; // + waterHeight
+ }
+
+ public double getScalexsmall() {
+ return scalexsmall;
+ }
+
+ public void setScalexsmall(final double scalexsmall) {
+ this.scalexsmall = scalexsmall;
+ }
+
+ public double getScaleysmall() {
+ return scaleysmall;
+ }
+
+ public void setScaleysmall(final double scaleysmall) {
+ this.scaleysmall = scaleysmall;
+ }
+
+ public double getScalexbig() {
+ return scalexbig;
+ }
+
+ public void setScalexbig(final double scalexbig) {
+ this.scalexbig = scalexbig;
+ }
+
+ public double getScaleybig() {
+ return scaleybig;
+ }
+
+ public void setScaleybig(final double scaleybig) {
+ this.scaleybig = scaleybig;
+ }
+
+ public double getHeightsmall() {
+ return heightsmall;
+ }
+
+ public void setHeightsmall(final double heightsmall) {
+ this.heightsmall = heightsmall;
+ }
+
+ public double getHeightbig() {
+ return heightbig;
+ }
+
+ public void setHeightbig(final double heightbig) {
+ this.heightbig = heightbig;
+ }
+
+ public double getSpeedsmall() {
+ return speedsmall;
+ }
+
+ public void setSpeedsmall(final double speedsmall) {
+ this.speedsmall = speedsmall;
+ }
+
+ public double getSpeedbig() {
+ return speedbig;
+ }
+
+ public void setSpeedbig(final double speedbig) {
+ this.speedbig = speedbig;
+ }
+
+ public int getOctaves() {
+ return octaves;
+ }
+
+ public void setOctaves(final int octaves) {
+ this.octaves = octaves;
+ }
+
+ @Override
+ public double getMaximumHeight() {
+ return 15.0;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java new file mode 100644 index 0000000..1db0cdb --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java @@ -0,0 +1,1085 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Plane;
+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;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.ContextCapabilities;
+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.ClipState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.TextureState;
+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.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.google.common.collect.Lists;
+
+/**
+ * The WaterNode handles rendering of a water effect on all of it's children. What is reflected in the water is
+ * controlled through setReflectedScene/addReflectedScene. The skybox (if any) needs to be explicitly set through
+ * setSkybox since it needs to be relocated when rendering the reflection. The water is treated as a plane no matter
+ * what the geometry is, which is controlled through the water node plane equation settings.
+ */
+public class WaterNode extends Node {
+ private static final Logger logger = Logger.getLogger(WaterNode.class.getName());
+
+ protected Camera cam;
+ protected double tpf;
+ protected double reflectionThrottle = 0f, refractionThrottle = 0f;
+ protected double reflectionTime = 0, refractionTime = 0;
+ protected boolean useFadeToFogColor = false;
+
+ protected TextureRenderer tRenderer;
+ protected Texture2D textureReflect;
+ protected Texture2D textureReflectBlur;
+ protected Texture2D textureRefract;
+ protected Texture2D textureDepth;
+
+ protected ArrayList<Spatial> renderList = Lists.newArrayList();
+ protected ArrayList<Texture> texArray = Lists.newArrayList();
+ protected Node skyBox;
+
+ protected GLSLShaderObjectsState waterShader;
+ protected CullState cullBackFace;
+ protected TextureState textureState;
+ protected TextureState fallbackTextureState;
+
+ private Texture normalmapTexture;
+ private Texture dudvTexture;
+ private Texture foamTexture;
+ private Texture fallbackTexture;
+ private Matrix4 fallbackTextureStateMatrix;
+
+ protected BlendState as1;
+ protected FogState noFog;
+
+ protected Plane waterPlane;
+ protected Vector3 tangent;
+ protected Vector3 binormal;
+ protected Vector3 calcVect = new Vector3();
+ protected double clipBias;
+ protected ColorRGBA waterColorStart;
+ protected ColorRGBA waterColorEnd;
+ protected double heightFalloffStart;
+ protected double heightFalloffSpeed;
+ protected double waterMaxAmplitude;
+ protected double speedReflection;
+ protected double speedRefraction;
+
+ protected boolean aboveWater;
+ protected double normalTranslation = 0.0;
+ protected double refractionTranslation = 0.0;
+ protected boolean supported = true;
+ protected boolean useProjectedShader = false;
+ protected boolean useRefraction = false;
+ protected boolean useReflection = true;
+ protected int renderScale;
+
+ protected String simpleShaderStr = "com/ardor3d/extension/effect/water/flatwatershader";
+ protected String simpleShaderRefractionStr = "com/ardor3d/extension/effect/water/flatwatershader_refraction";
+ protected String projectedShaderStr = "com/ardor3d/extension/effect/water/projectedwatershader";
+ protected String projectedShaderRefractionStr = "com/ardor3d/extension/effect/water/projectedwatershader_refraction";
+ protected String currentShaderStr;
+
+ protected String normalMapTextureString = "";
+ protected String dudvMapTextureString = "";
+ protected String foamMapTextureString = "";
+ protected String fallbackMapTextureString = "";
+
+ private GLSLShaderObjectsState blurShaderVertical = null;
+ private float blurSampleDistance = 0.002f;
+ private Quad fullScreenQuad = null;
+ private boolean doBlurReflection = true;
+
+ private boolean initialized;
+
+ /**
+ * Resets water parameters to default values
+ *
+ */
+ public void resetParameters() {
+ waterPlane = new Plane(new Vector3(0.0, 1.0, 0.0), 0.0);
+ tangent = new Vector3(1.0, 0.0, 0.0);
+ binormal = new Vector3(0.0, 0.0, 1.0);
+
+ waterMaxAmplitude = 0.0;
+ clipBias = 1.0;
+ waterColorStart = new ColorRGBA(0.0f, 0.0f, 0.1f, 1.0f);
+ waterColorEnd = new ColorRGBA(0.0f, 0.3f, 0.1f, 1.0f);
+ heightFalloffStart = 400.0;
+ heightFalloffSpeed = 500.0;
+ speedReflection = 0.1;
+ speedRefraction = -0.05;
+ }
+
+ /**
+ * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
+ */
+ public void cleanup() {
+ if (isSupported() && tRenderer != null) {
+ tRenderer.cleanup();
+ }
+ }
+
+ public boolean isSupported() {
+ return supported;
+ }
+
+ /**
+ * Creates a new WaterRenderPass
+ *
+ * @param cam
+ * main rendercam to use for reflection settings etc
+ * @param renderScale
+ * how many times smaller the reflection/refraction textures should be compared to the main display
+ * @param useProjectedShader
+ * true - use the projected setup for variable height water meshes, false - use the flast shader setup
+ * @param useRefraction
+ * enable/disable rendering of refraction textures
+ */
+ public WaterNode(final Camera cam, final int renderScale, final boolean useProjectedShader,
+ final boolean useRefraction) {
+ this.cam = cam;
+ this.useProjectedShader = useProjectedShader;
+ this.useRefraction = useRefraction;
+ this.renderScale = renderScale;
+ resetParameters();
+
+ waterShader = new GLSLShaderObjectsState();
+ blurShaderVertical = new GLSLShaderObjectsState();
+
+ cullBackFace = new CullState();
+ cullBackFace.setEnabled(true);
+ cullBackFace.setCullFace(CullState.Face.None);
+ }
+
+ /**
+ * Initialize texture renderers. Load water textures. Create shaders.
+ *
+ * @param r
+ */
+ private void initialize(final Renderer r) {
+ if (cam == null || initialized) {
+ return;
+ }
+ initialized = true;
+
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+
+ if (useRefraction && useProjectedShader && caps.getNumberOfFragmentTextureUnits() < 6 || useRefraction
+ && caps.getNumberOfFragmentTextureUnits() < 5) {
+ useRefraction = false;
+ logger.info("Not enough textureunits, falling back to non refraction water");
+ }
+
+ if (!caps.isGLSLSupported()) {
+ supported = false;
+ }
+ if (!(caps.isPbufferSupported() || caps.isFBOSupported())) {
+ supported = false;
+ }
+
+ if (isSupported()) {
+ tRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer( //
+ cam.getWidth() / renderScale, // width
+ cam.getHeight() / renderScale, // height
+ 8, // Depth bits... TODO: Make configurable?
+ 0, // Samples... TODO: Make configurable?
+ r, caps);
+
+ // blurSampleDistance = 1f / ((float) cam.getHeight() / renderScale);
+
+ tRenderer.setMultipleTargets(true);
+ tRenderer.setBackgroundColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f));
+ tRenderer.getCamera().setFrustum(cam.getFrustumNear(), cam.getFrustumFar(), cam.getFrustumLeft(),
+ cam.getFrustumRight(), cam.getFrustumTop(), cam.getFrustumBottom());
+
+ textureState = new TextureState();
+ textureState.setEnabled(true);
+
+ setupTextures();
+
+ fullScreenQuad = new Quad("FullScreenQuad", cam.getWidth() / 4, cam.getHeight() / 4);
+ fullScreenQuad.setTranslation(cam.getWidth() / 2, cam.getHeight() / 2, 0);
+ fullScreenQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ fullScreenQuad.getSceneHints().setCullHint(CullHint.Never);
+ fullScreenQuad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ fullScreenQuad.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ final TextureState ts = new TextureState();
+ ts.setTexture(textureReflect);
+ fullScreenQuad.setRenderState(ts);
+ fullScreenQuad.setRenderState(blurShaderVertical);
+ fullScreenQuad.updateWorldRenderStates(false);
+ }
+
+ if (!isSupported()) {
+ createFallbackData();
+ } else {
+ noFog = new FogState();
+ noFog.setEnabled(false);
+ }
+
+ getSceneHints().setCullHint(CullHint.Never);
+
+ setWaterEffectOnSpatial(this);
+ }
+
+ /**
+ * Load water textures.
+ */
+ protected void setupTextures() {
+ textureReflect = new Texture2D();
+ textureReflect.setWrap(Texture.WrapMode.EdgeClamp);
+ textureReflect.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ Matrix4 matrix = new Matrix4();
+ matrix.setM00(-1.0);
+ matrix.setM30(1.0);
+ textureReflect.setTextureMatrix(matrix);
+ tRenderer.setupTexture(textureReflect);
+
+ normalmapTexture = TextureManager.load(normalMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessCompressedFormat, true);
+ textureState.setTexture(normalmapTexture, 0);
+ normalmapTexture.setWrap(Texture.WrapMode.Repeat);
+
+ textureReflectBlur = new Texture2D();
+ textureReflectBlur.setWrap(Texture.WrapMode.EdgeClamp);
+ textureReflectBlur.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ textureReflectBlur.setTextureMatrix(matrix);
+ tRenderer.setupTexture(textureReflectBlur);
+
+ textureState.setTexture(textureReflectBlur, 1);
+
+ dudvTexture = TextureManager.load(dudvMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessNoCompressedFormat, true);
+ matrix = new Matrix4();
+ matrix.setM00(0.8);
+ matrix.setM11(0.8);
+ dudvTexture.setTextureMatrix(matrix);
+ textureState.setTexture(dudvTexture, 2);
+ dudvTexture.setWrap(Texture.WrapMode.Repeat);
+
+ if (useRefraction) {
+ textureRefract = new Texture2D();
+ textureRefract.setWrap(Texture.WrapMode.EdgeClamp);
+ textureRefract.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ tRenderer.setupTexture(textureRefract);
+
+ textureDepth = new Texture2D();
+ textureDepth.setWrap(Texture.WrapMode.EdgeClamp);
+ textureDepth.setMagnificationFilter(Texture.MagnificationFilter.NearestNeighbor);
+ textureDepth.setTextureStoreFormat(TextureStoreFormat.Depth24);
+ tRenderer.setupTexture(textureDepth);
+
+ textureState.setTexture(textureRefract, 3);
+ textureState.setTexture(textureDepth, 4);
+ }
+
+ if (useProjectedShader) {
+ foamTexture = TextureManager.load(foamMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessCompressedFormat, true);
+ if (useRefraction) {
+ textureState.setTexture(foamTexture, 5);
+ } else {
+ textureState.setTexture(foamTexture, 3);
+ }
+ foamTexture.setWrap(Texture.WrapMode.Repeat);
+ }
+
+ reloadShader();
+ }
+
+ /**
+ * Create setup to use as fallback if fancy water is not supported.
+ */
+ private void createFallbackData() {
+ fallbackTextureState = new TextureState();
+ fallbackTextureState.setEnabled(true);
+
+ fallbackTexture = TextureManager.load(fallbackMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessCompressedFormat, true);
+ fallbackTextureState.setTexture(fallbackTexture, 0);
+ fallbackTexture.setWrap(Texture.WrapMode.Repeat);
+
+ fallbackTextureStateMatrix = new Matrix4();
+
+ as1 = new BlendState();
+ as1.setBlendEnabled(true);
+ as1.setTestEnabled(true);
+ as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ as1.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ as1.setEnabled(true);
+ }
+
+ public void update(final double tpf) {
+ this.tpf = tpf;
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ initialize(r);
+
+ updateTranslations();
+
+ final double camWaterDist = waterPlane.pseudoDistance(cam.getLocation());
+ aboveWater = camWaterDist >= 0;
+
+ if (isSupported()) {
+ waterShader.setUniform("tangent", tangent);
+ waterShader.setUniform("binormal", binormal);
+ waterShader.setUniform("useFadeToFogColor", useFadeToFogColor);
+ waterShader.setUniform("waterColor", waterColorStart);
+ waterShader.setUniform("waterColorEnd", waterColorEnd);
+ waterShader.setUniform("normalTranslation", (float) normalTranslation);
+ waterShader.setUniform("refractionTranslation", (float) refractionTranslation);
+ waterShader.setUniform("abovewater", aboveWater);
+ if (useProjectedShader) {
+ waterShader.setUniform("cameraPos", cam.getLocation());
+ waterShader.setUniform("waterHeight", (float) waterPlane.getConstant());
+ waterShader.setUniform("amplitude", (float) waterMaxAmplitude);
+ waterShader.setUniform("heightFalloffStart", (float) heightFalloffStart);
+ waterShader.setUniform("heightFalloffSpeed", (float) heightFalloffSpeed);
+ }
+
+ final double heightTotal = clipBias + waterMaxAmplitude - waterPlane.getConstant();
+ final Vector4 clipPlane = Vector4.fetchTempInstance();
+
+ if (useReflection) {
+ clipPlane.set(waterPlane.getNormal().getX(), waterPlane.getNormal().getY(), waterPlane.getNormal()
+ .getZ(), heightTotal);
+ renderReflection(clipPlane);
+ }
+
+ if (useRefraction && aboveWater) {
+ clipPlane.set(-waterPlane.getNormal().getX(), -waterPlane.getNormal().getY(), -waterPlane.getNormal()
+ .getZ(), -waterPlane.getConstant());
+ renderRefraction(clipPlane);
+ }
+ }
+
+ if (fallbackTextureState != null) {
+ fallbackTextureStateMatrix.setM31(normalTranslation);
+ fallbackTexture.setTextureMatrix(fallbackTextureStateMatrix);
+ }
+
+ super.draw(r);
+ }
+
+ protected void updateTranslations() {
+ normalTranslation += speedReflection * tpf;
+ refractionTranslation += speedRefraction * tpf;
+ }
+
+ public void reloadShader() {
+ if (useProjectedShader) {
+ if (useRefraction) {
+ currentShaderStr = projectedShaderRefractionStr;
+ } else {
+ currentShaderStr = projectedShaderStr;
+ }
+ } else {
+ if (useRefraction) {
+ currentShaderStr = simpleShaderRefractionStr;
+ } else {
+ currentShaderStr = simpleShaderStr;
+ }
+ }
+
+ try {
+ logger.info("loading " + currentShaderStr);
+ waterShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ currentShaderStr + ".vert"));
+ waterShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ currentShaderStr + ".frag"));
+ } catch (final IOException e) {
+ logger.log(Level.WARNING, "Error loading shader", e);
+ return;
+ }
+
+ waterShader.setUniform("normalMap", 0);
+ waterShader.setUniform("reflection", 1);
+ waterShader.setUniform("dudvMap", 2);
+ if (useRefraction) {
+ waterShader.setUniform("refraction", 3);
+ waterShader.setUniform("depthMap", 4);
+ }
+ if (useProjectedShader) {
+ if (useRefraction) {
+ waterShader.setUniform("foamMap", 5);
+ } else {
+ waterShader.setUniform("foamMap", 3);
+ }
+ }
+
+ waterShader._needSendShader = true;
+
+ try {
+ blurShaderVertical.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ "com/ardor3d/extension/effect/bloom/bloom_blur.vert"));
+ blurShaderVertical.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ "com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShaderVertical.setUniform("RT", 0);
+ blurShaderVertical.setUniform("sampleDist", blurSampleDistance);
+ blurShaderVertical._needSendShader = true;
+
+ logger.info("Shader reloaded...");
+ }
+
+ /**
+ * Sets a spatial up for being rendered with the watereffect
+ *
+ * @param spatial
+ * Spatial to use as base for the watereffect
+ */
+ public void setWaterEffectOnSpatial(final Spatial spatial) {
+ spatial.setRenderState(cullBackFace);
+ if (isSupported()) {
+ // spatial.setRenderBucketType(RenderBucketType.Skip);
+ spatial.setRenderState(waterShader);
+ spatial.setRenderState(textureState);
+ } else {
+ spatial.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ spatial.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ spatial.setRenderState(fallbackTextureState);
+ spatial.setRenderState(as1);
+ }
+ }
+
+ // temporary vectors for mem opt.
+ private final Vector3 tmpLocation = new Vector3();
+ private final Vector3 camReflectPos = new Vector3();
+ private final Vector3 camReflectDir = new Vector3();
+ private final Vector3 camReflectUp = new Vector3();
+ private final Vector3 camReflectLeft = new Vector3();
+ private final Vector3 camLocation = new Vector3();
+
+ /**
+ * Render water reflection RTT
+ */
+ private void renderReflection(final Vector4 clipPlane) {
+ if (renderList.isEmpty()) {
+ return;
+ }
+
+ reflectionTime += tpf;
+ if (reflectionTime < reflectionThrottle) {
+ return;
+ }
+ reflectionTime = 0;
+
+ if (aboveWater) {
+ camLocation.set(cam.getLocation());
+
+ double planeDistance = waterPlane.pseudoDistance(camLocation);
+ calcVect.set(waterPlane.getNormal()).multiplyLocal(planeDistance * 2.0f);
+ camReflectPos.set(camLocation.subtractLocal(calcVect));
+
+ camLocation.set(cam.getLocation()).addLocal(cam.getDirection());
+ planeDistance = waterPlane.pseudoDistance(camLocation);
+ calcVect.set(waterPlane.getNormal()).multiplyLocal(planeDistance * 2.0f);
+ camReflectDir.set(camLocation.subtractLocal(calcVect)).subtractLocal(camReflectPos).normalizeLocal();
+
+ camLocation.set(cam.getLocation()).addLocal(cam.getUp());
+ planeDistance = waterPlane.pseudoDistance(camLocation);
+ calcVect.set(waterPlane.getNormal()).multiplyLocal(planeDistance * 2.0f);
+ camReflectUp.set(camLocation.subtractLocal(calcVect)).subtractLocal(camReflectPos).normalizeLocal();
+
+ camReflectLeft.set(camReflectUp).crossLocal(camReflectDir).normalizeLocal();
+
+ tRenderer.getCamera().setLocation(camReflectPos);
+ tRenderer.getCamera().setDirection(camReflectDir);
+ tRenderer.getCamera().setUp(camReflectUp);
+ tRenderer.getCamera().setLeft(camReflectLeft);
+ } else {
+ tRenderer.getCamera().setLocation(cam.getLocation());
+ tRenderer.getCamera().setDirection(cam.getDirection());
+ tRenderer.getCamera().setUp(cam.getUp());
+ tRenderer.getCamera().setLeft(cam.getLeft());
+ }
+
+ if (skyBox != null) {
+ tmpLocation.set(skyBox.getTranslation());
+ skyBox.setTranslation(tRenderer.getCamera().getLocation());
+ skyBox.updateGeometricState(0.0f);
+ skyBox.getSceneHints().setCullHint(CullHint.Never);
+ }
+
+ texArray.clear();
+ if (doBlurReflection) {
+ texArray.add(textureReflect);
+ } else {
+ texArray.add(textureReflectBlur);
+ }
+
+ tRenderer.getCamera().setProjectionMode(ProjectionMode.Custom);
+ tRenderer.getCamera().setProjectionMatrix(cam.getProjectionMatrix());
+ tRenderer.render(skyBox, texArray, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ if (skyBox != null) {
+ skyBox.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ modifyProjectionMatrix(clipPlane);
+
+ tRenderer.render(renderList, texArray, Renderer.BUFFER_NONE);
+
+ if (doBlurReflection) {
+ blurReflectionTexture();
+ }
+
+ if (skyBox != null) {
+ skyBox.setTranslation(tmpLocation);
+ skyBox.updateGeometricState(0.0f);
+ skyBox.getSceneHints().setCullHint(CullHint.Never);
+ }
+ }
+
+ private void blurReflectionTexture() {
+ tRenderer.render(fullScreenQuad, textureReflectBlur, Renderer.BUFFER_NONE);
+ }
+
+ /**
+ * Render water refraction RTT
+ */
+ private void renderRefraction(final Vector4 clipPlane) {
+ if (renderList.isEmpty()) {
+ return;
+ }
+
+ refractionTime += tpf;
+ if (refractionTime < refractionThrottle) {
+ return;
+ }
+ refractionTime = 0;
+
+ // tRenderer.getCamera().set(cam);
+ tRenderer.getCamera().setLocation(cam.getLocation());
+ tRenderer.getCamera().setDirection(cam.getDirection());
+ tRenderer.getCamera().setUp(cam.getUp());
+ tRenderer.getCamera().setLeft(cam.getLeft());
+
+ CullHint cullMode = CullHint.Dynamic;
+ if (skyBox != null) {
+ cullMode = skyBox.getSceneHints().getCullHint();
+ skyBox.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ tRenderer.getCamera().setProjectionMatrix(cam.getProjectionMatrix());
+
+ texArray.clear();
+ texArray.add(textureRefract);
+ texArray.add(textureDepth);
+
+ tRenderer.getCamera().update();
+ tRenderer.getCamera().getModelViewMatrix();
+ tRenderer.getCamera().getProjectionMatrix();
+
+ tRenderer.render(renderList, texArray, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ if (skyBox != null) {
+ skyBox.getSceneHints().setCullHint(cullMode);
+ }
+ }
+
+ private double sign(final double a) {
+ if (a > 0.0) {
+ return 1.0;
+ }
+ if (a < 0.0) {
+ return -1.0;
+ }
+ return 0.0;
+ }
+
+ private double projectionMatrix[] = new double[16];
+ private final Vector4 cornerPoint = new Vector4();
+ private final Matrix4 tmpMatrix = new Matrix4();
+
+ private void modifyProjectionMatrix(final Vector4 clipPlane) {
+ // Get the current projection matrix
+ projectionMatrix = cam.getProjectionMatrix().toArray(projectionMatrix);
+
+ // Get the inverse transpose of the current modelview matrix
+ final ReadOnlyMatrix4 modelViewMatrixInvTrans = tRenderer.getCamera().getModelViewMatrix().invert(tmpMatrix)
+ .transposeLocal();
+ modelViewMatrixInvTrans.applyPre(clipPlane, clipPlane);
+
+ // Calculate the clip-space corner point opposite the clipping plane
+ // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
+ // transform it into camera space by multiplying it
+ // by the inverse of the projection matrix
+ cornerPoint.setX((sign(clipPlane.getX()) + projectionMatrix[8]) / projectionMatrix[0]);
+ cornerPoint.setY((sign(clipPlane.getY()) + projectionMatrix[9]) / projectionMatrix[5]);
+ cornerPoint.setZ(-1.0);
+ cornerPoint.setW((1.0 + projectionMatrix[10]) / projectionMatrix[14]);
+
+ // Calculate the scaled plane vector
+ final Vector4 scaledPlaneVector = clipPlane.multiply((2.0 / clipPlane.dot(cornerPoint)), cornerPoint);
+
+ // Replace the third row of the projection matrix
+ projectionMatrix[2] = scaledPlaneVector.getX();
+ projectionMatrix[6] = scaledPlaneVector.getY();
+ projectionMatrix[10] = scaledPlaneVector.getZ() + 1.0;
+ projectionMatrix[14] = scaledPlaneVector.getW();
+
+ // Load it back into OpenGL
+ final Matrix4 newProjectionMatrix = tmpMatrix.fromArray(projectionMatrix);
+ tRenderer.getCamera().setProjectionMatrix(newProjectionMatrix);
+ }
+
+ public void removeReflectedScene(final Spatial renderNode) {
+ if (logger.isLoggable(Level.INFO)) {
+ logger.info("Removed reflected scene: " + renderList.remove(renderNode));
+ }
+ }
+
+ public void clearReflectedScene() {
+ renderList.clear();
+ }
+
+ /**
+ * Adds a spatial to the list of spatials used as reflection in the water
+ *
+ * @param renderNode
+ * Spatial to add to the list of objects used as reflection in the water
+ */
+ public void addReflectedScene(final Spatial renderNode) {
+ if (renderNode == null) {
+ return;
+ }
+
+ if (!renderList.contains(renderNode)) {
+ renderList.add(renderNode);
+ }
+ }
+
+ /**
+ * Sets up a node to be transformed and clipped for skybox usage
+ *
+ * @param skyBox
+ * Handle to a node to use as skybox
+ */
+ public void setSkybox(final Node skyBox) {
+ if (skyBox != null) {
+ final ClipState skyboxClipState = new ClipState();
+ skyboxClipState.setEnabled(false);
+ skyBox.setRenderState(skyboxClipState);
+ }
+
+ this.skyBox = skyBox;
+ }
+
+ public Camera getCam() {
+ return cam;
+ }
+
+ public void setCam(final Camera cam) {
+ this.cam = cam;
+ }
+
+ public ColorRGBA getWaterColorStart() {
+ return waterColorStart;
+ }
+
+ /**
+ * Color to use when the incident angle to the surface is low
+ */
+ public void setWaterColorStart(final ColorRGBA waterColorStart) {
+ this.waterColorStart = waterColorStart;
+ }
+
+ public ColorRGBA getWaterColorEnd() {
+ return waterColorEnd;
+ }
+
+ /**
+ * Color to use when the incident angle to the surface is high
+ */
+ public void setWaterColorEnd(final ColorRGBA waterColorEnd) {
+ this.waterColorEnd = waterColorEnd;
+ }
+
+ public double getHeightFalloffStart() {
+ return heightFalloffStart;
+ }
+
+ /**
+ * Set at what distance the waveheights should start to fade out(for projected water only)
+ *
+ * @param heightFalloffStart
+ */
+ public void setHeightFalloffStart(final double heightFalloffStart) {
+ this.heightFalloffStart = heightFalloffStart;
+ }
+
+ public double getHeightFalloffSpeed() {
+ return heightFalloffSpeed;
+ }
+
+ /**
+ * Set the fadeout length of the waveheights, when over falloff start(for projected water only)
+ *
+ * @param heightFalloffStart
+ */
+ public void setHeightFalloffSpeed(final double heightFalloffSpeed) {
+ this.heightFalloffSpeed = heightFalloffSpeed;
+ }
+
+ public double getWaterHeight() {
+ return waterPlane.getConstant();
+ }
+
+ /**
+ * Set base height of the waterplane(Used for reflecting the camera for rendering reflection)
+ *
+ * @param waterHeight
+ * Waterplane height
+ */
+ public void setWaterHeight(final double waterHeight) {
+ waterPlane.setConstant(waterHeight);
+ }
+
+ public ReadOnlyVector3 getNormal() {
+ return waterPlane.getNormal();
+ }
+
+ /**
+ * Set the normal of the waterplane(Used for reflecting the camera for rendering reflection)
+ *
+ * @param normal
+ * Waterplane normal
+ */
+ public void setNormal(final Vector3 normal) {
+ waterPlane.setNormal(normal);
+ }
+
+ public double getSpeedReflection() {
+ return speedReflection;
+ }
+
+ /**
+ * Set the movement speed of the reflectiontexture
+ *
+ * @param speedReflection
+ * Speed of reflectiontexture
+ */
+ public void setSpeedReflection(final double speedReflection) {
+ this.speedReflection = speedReflection;
+ }
+
+ public double getSpeedRefraction() {
+ return speedRefraction;
+ }
+
+ /**
+ * Set the movement speed of the refractiontexture
+ *
+ * @param speedRefraction
+ * Speed of refractiontexture
+ */
+ public void setSpeedRefraction(final double speedRefraction) {
+ this.speedRefraction = speedRefraction;
+ }
+
+ public double getWaterMaxAmplitude() {
+ return waterMaxAmplitude;
+ }
+
+ /**
+ * Maximum amplitude of the water, used for clipping correctly(projected water only)
+ *
+ * @param waterMaxAmplitude
+ * Maximum amplitude
+ */
+ public void setWaterMaxAmplitude(final double waterMaxAmplitude) {
+ this.waterMaxAmplitude = waterMaxAmplitude;
+ }
+
+ public double getClipBias() {
+ return clipBias;
+ }
+
+ public void setClipBias(final double clipBias) {
+ this.clipBias = clipBias;
+ }
+
+ public Plane getWaterPlane() {
+ return waterPlane;
+ }
+
+ public void setWaterPlane(final Plane waterPlane) {
+ this.waterPlane = waterPlane;
+ }
+
+ public Vector3 getTangent() {
+ return tangent;
+ }
+
+ public void setTangent(final Vector3 tangent) {
+ this.tangent = tangent;
+ }
+
+ public Vector3 getBinormal() {
+ return binormal;
+ }
+
+ public void setBinormal(final Vector3 binormal) {
+ this.binormal = binormal;
+ }
+
+ public Texture getTextureReflect() {
+ return textureReflect;
+ }
+
+ public Texture getTextureRefract() {
+ return textureRefract;
+ }
+
+ public Texture getTextureDepth() {
+ return textureDepth;
+ }
+
+ /**
+ * If true, fade to fogcolor. If false, fade to 100% reflective surface
+ *
+ * @param value
+ */
+ public void useFadeToFogColor(final boolean value) {
+ useFadeToFogColor = value;
+ }
+
+ public boolean isUseFadeToFogColor() {
+ return useFadeToFogColor;
+ }
+
+ public boolean isUseReflection() {
+ return useReflection;
+ }
+
+ /**
+ * Turn reflection on and off
+ *
+ * @param useReflection
+ */
+ public void setUseReflection(final boolean useReflection) {
+ if (useReflection == this.useReflection) {
+ return;
+ }
+ this.useReflection = useReflection;
+ reloadShader();
+ }
+
+ public boolean isUseRefraction() {
+ return useRefraction;
+ }
+
+ /**
+ * Turn refraction on and off
+ *
+ * @param useRefraction
+ */
+ public void setUseRefraction(final boolean useRefraction) {
+ if (useRefraction == this.useRefraction) {
+ return;
+ }
+ this.useRefraction = useRefraction;
+ reloadShader();
+ }
+
+ public int getRenderScale() {
+ return renderScale;
+ }
+
+ public void setRenderScale(final int renderScale) {
+ this.renderScale = renderScale;
+ }
+
+ public boolean isUseProjectedShader() {
+ return useProjectedShader;
+ }
+
+ public void setUseProjectedShader(final boolean useProjectedShader) {
+ if (useProjectedShader == this.useProjectedShader) {
+ return;
+ }
+ this.useProjectedShader = useProjectedShader;
+ reloadShader();
+ }
+
+ public double getReflectionThrottle() {
+ return reflectionThrottle;
+ }
+
+ public void setReflectionThrottle(final double reflectionThrottle) {
+ this.reflectionThrottle = reflectionThrottle;
+ }
+
+ public double getRefractionThrottle() {
+ return refractionThrottle;
+ }
+
+ public void setRefractionThrottle(final double refractionThrottle) {
+ this.refractionThrottle = refractionThrottle;
+ }
+
+ public TextureState getTextureState() {
+ return textureState;
+ }
+
+ public void setTextureState(final TextureState textureState) {
+ this.textureState = textureState;
+ }
+
+ public void updateCamera() {
+ if (isSupported()) {
+ tRenderer.getCamera().setFrustum(cam.getFrustumNear(), cam.getFrustumFar(), cam.getFrustumLeft(),
+ cam.getFrustumRight(), cam.getFrustumTop(), cam.getFrustumBottom());
+ }
+ }
+
+ public void setFallbackTexture(final Texture fallbackTexture) {
+ this.fallbackTexture = fallbackTexture;
+ }
+
+ public Texture getFallbackTexture() {
+ return fallbackTexture;
+ }
+
+ public void setNormalmapTexture(final Texture normalmapTexture) {
+ this.normalmapTexture = normalmapTexture;
+ }
+
+ public Texture getNormalmapTexture() {
+ return normalmapTexture;
+ }
+
+ public void setDudvTexture(final Texture dudvTexture) {
+ this.dudvTexture = dudvTexture;
+ }
+
+ public Texture getDudvTexture() {
+ return dudvTexture;
+ }
+
+ public void setFoamTexture(final Texture foamTexture) {
+ this.foamTexture = foamTexture;
+ }
+
+ public Texture getFoamTexture() {
+ return foamTexture;
+ }
+
+ /**
+ * @return the normalMapTextureString
+ */
+ public String getNormalMapTextureString() {
+ return normalMapTextureString;
+ }
+
+ /**
+ * @param normalMapTextureString
+ * the normalMapTextureString to set
+ */
+ public void setNormalMapTextureString(final String normalMapTextureString) {
+ this.normalMapTextureString = normalMapTextureString;
+ }
+
+ /**
+ * @return the dudvMapTextureString
+ */
+ public String getDudvMapTextureString() {
+ return dudvMapTextureString;
+ }
+
+ /**
+ * @param dudvMapTextureString
+ * the dudvMapTextureString to set
+ */
+ public void setDudvMapTextureString(final String dudvMapTextureString) {
+ this.dudvMapTextureString = dudvMapTextureString;
+ }
+
+ /**
+ * @return the foamMapTextureString
+ */
+ public String getFoamMapTextureString() {
+ return foamMapTextureString;
+ }
+
+ /**
+ * @param foamMapTextureString
+ * the foamMapTextureString to set
+ */
+ public void setFoamMapTextureString(final String foamMapTextureString) {
+ this.foamMapTextureString = foamMapTextureString;
+ }
+
+ /**
+ * @return the fallbackMapTextureString
+ */
+ public String getFallbackMapTextureString() {
+ return fallbackMapTextureString;
+ }
+
+ /**
+ * @param fallbackMapTextureString
+ * the fallbackMapTextureString to set
+ */
+ public void setFallbackMapTextureString(final String fallbackMapTextureString) {
+ this.fallbackMapTextureString = fallbackMapTextureString;
+ }
+
+ public boolean isDoBlurReflection() {
+ return doBlurReflection;
+ }
+
+ public void setDoBlurReflection(final boolean doBlurReflection) {
+ this.doBlurReflection = doBlurReflection;
+ }
+
+ public float getBlurSampleDistance() {
+ return blurSampleDistance;
+ }
+
+ public void setBlurSampleDistance(final float blurSampleDistance) {
+ this.blurSampleDistance = blurSampleDistance;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java new file mode 100644 index 0000000..b3e7b82 --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java @@ -0,0 +1,285 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.shadow.map; + +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 PSSMCamera extends Camera { + + /** The storage place for calculated split distances. */ + protected double _splitDistances[] = new double[2]; + + /** The lambda value used in split distance calculations. */ + protected double _lambda = 0.5; + + /** The corners of the camera frustum. */ + protected final Vector3[] _corners = new Vector3[8]; + + /** The center of the camera frustum. */ + protected final Vector3 _center = new Vector3(); + + /** Temporary vector used for storing extents during corner calculations. */ + protected final Vector3 _extents = new Vector3(); + + /** The maximum far plane distance used when packing the frustum. */ + protected double _maxFarPlaneDistance = 2000.0; + + /** + * Instantiates a new PSSM camera. + */ + public PSSMCamera() { + super(0, 0); // copy later + init(); + } + + /** + * Instantiates a new PSSM camera. + * + * @param width + * the width + * @param height + * the height + */ + public PSSMCamera(final int width, final int height) { + super(width, height); + init(); + } + + /** + * Instantiates a new PSSM camera. + * + * @param source + * the source + */ + public PSSMCamera(final Camera source) { + super(source); + init(); + } + + /** + * Initialize structures. + */ + private void init() { + for (int i = 0; i < _corners.length; i++) { + _corners[i] = new Vector3(); + } + } + + /** + * Calculates the distances from view location for view frustum splits using the "practical split scheme". + * + * @param splitCount + * the split count + * + * @see <a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/keypoint1.htm">technique paper</a> + */ + public void calculateSplitDistances(final int splitCount) { + // ensure correct size. + if (_splitDistances.length != splitCount + 1) { + _splitDistances = new double[splitCount + 1]; + } + + final double nearPlane = getFrustumNear(); + final double farPlane = getFrustumFar(); + + // setup intermediate splits + for (int i = 1; i < splitCount; i++) { + final double part = i / (double) splitCount; + final double logsplit = nearPlane * Math.pow((farPlane / nearPlane), part); + final double uniformSplit = nearPlane + (farPlane - nearPlane) * part; + _splitDistances[i] = logsplit * _lambda + uniformSplit * (1 - _lambda); + } + + // setup first and last split (near/far planes) + _splitDistances[0] = nearPlane; + _splitDistances[splitCount] = farPlane; + } + + /** + * 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(_maxFarPlaneDistance, optimalCameraFar)); + + final double change = optimalCameraNear / _frustumNear; + setFrustumLeft(getFrustumLeft() * change); + setFrustumRight(getFrustumRight() * change); + setFrustumTop(getFrustumTop() * change); + setFrustumBottom(getFrustumBottom() * change); + + setFrustumNear(optimalCameraNear); + setFrustumFar(optimalCameraFar); + } + + public void updateMaxCameraFar() { + double optimalCameraFar = getFrustumFar(); + optimalCameraFar = Math.max(getFrustumNear() + 1.0, Math.min(_maxFarPlaneDistance, optimalCameraFar)); + setFrustumFar(optimalCameraFar); + } + + /** + * Calculate frustum corners and center. + * + * @param fNear + * the near distance + * @param fFar + * the far distance + */ + public void calculateFrustum(final double fNear, final double fFar) { + final double fNearPlaneHeight, fNearPlaneWidth, fFarPlaneHeight, fFarPlaneWidth; + if (getProjectionMode() == ProjectionMode.Parallel) { + fNearPlaneHeight = (_frustumTop - _frustumBottom) * 0.5; + fNearPlaneWidth = (_frustumRight - _frustumLeft) * 0.5; + + fFarPlaneHeight = (_frustumTop - _frustumBottom) * 0.5; + fFarPlaneWidth = (_frustumRight - _frustumLeft) * 0.5; + } else { + fNearPlaneHeight = (_frustumTop - _frustumBottom) * fNear * 0.5 / _frustumNear; + fNearPlaneWidth = (_frustumRight - _frustumLeft) * fNear * 0.5 / _frustumNear; + + fFarPlaneHeight = (_frustumTop - _frustumBottom) * fFar * 0.5 / _frustumNear; + fFarPlaneWidth = (_frustumRight - _frustumLeft) * fFar * 0.5 / _frustumNear; + } + + 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); + + direction.set(getDirection()).multiplyLocal((fFar + fNear) * 0.5); + _center.set(getLocation()).addLocal(direction); + + Vector3.releaseTempInstance(vNearPlaneCenter); + Vector3.releaseTempInstance(vFarPlaneCenter); + Vector3.releaseTempInstance(direction); + Vector3.releaseTempInstance(left); + Vector3.releaseTempInstance(up); + } + + /** + * Gets the lambda. + * + * @return the lambda + */ + public double getLambda() { + return _lambda; + } + + /** + * Sets the lambda. + * + * @param lambda + * the new lambda + */ + public void setLambda(final double lambda) { + _lambda = lambda; + } + + /** + * Gets the split distances. + * + * @return the split distances + */ + public double[] getSplitDistances() { + return _splitDistances; + } + + /** + * Gets the max far plane distance. + * + * @return the max far plane distance + */ + public double getMaxFarPlaneDistance() { + return _maxFarPlaneDistance; + } + + /** + * Sets the max far plane distance. + * + * @param maxFarPlaneDistance + * the new max far plane distance + */ + public void setMaxFarPlaneDistance(final double maxFarPlaneDistance) { + _maxFarPlaneDistance = maxFarPlaneDistance; + } +} diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java new file mode 100644 index 0000000..cf07f8a --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java @@ -0,0 +1,1365 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.shadow.map;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.bounding.OrientedBoundingBox;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.DepthTextureCompareFunc;
+import com.ardor3d.image.Texture.DepthTextureCompareMode;
+import com.ardor3d.image.Texture.DepthTextureMode;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.light.DirectionalLight;
+import com.ardor3d.light.Light;
+import com.ardor3d.light.PointLight;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.RenderLogic;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.pass.Pass;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.ClipState;
+import com.ardor3d.renderer.state.ColorMaskState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.CullState.Face;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.LightState;
+import com.ardor3d.renderer.state.OffsetState;
+import com.ardor3d.renderer.state.OffsetState.OffsetType;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.renderer.state.ShadingState;
+import com.ardor3d.renderer.state.ShadingState.ShadingMode;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.WireframeState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Renderable;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.shape.Sphere;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.google.common.collect.Lists;
+
+/**
+ * A pass providing a parallel split shadow mapping (PSSM) layer across the top of an existing scene.
+ */
+public class ParallelSplitShadowMapPass extends Pass {
+
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(ParallelSplitShadowMapPass.class.getName());
+
+ /** The Constant serialVersionUID. */
+ private static final long serialVersionUID = 1L;
+
+ /** Bias matrix from [-1, 1] to [0, 1]. */
+ public static final ReadOnlyMatrix4 SCALE_BIAS_MATRIX = 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);//
+
+ /** The renderer used to produce the shadow map. */
+ private TextureRenderer _shadowMapRenderer;
+
+ /** The textures storing the shadow maps. */
+ private Texture2D _shadowMapTexture[];
+
+ /** The list of occluding nodes. */
+ private final List<Spatial> _occluderNodes = Lists.newArrayList();
+
+ /** Extra bounds receivers, when rendering shadows other ways than through overlay */
+ private final List<Spatial> _boundsReceiver = Lists.newArrayList();
+
+ // Various optimizations for rendering shadow maps...
+ /** Culling front faces when rendering shadow maps. */
+ private final CullState _cullFrontFace;
+
+ /** Turn off textures when rendering shadow maps. */
+ private final TextureState _noTexture;
+
+ /** Turn off lighting when rendering shadow maps. */
+ private final LightState _noLights;
+
+ /** set flat shading when rendering shadow maps. */
+ private final ShadingState _flat;
+
+ // Important pieces for rendering shadow maps
+ /** Turn off colors when rendering shadow maps. */
+ private final ColorMaskState _colorDisabled;
+
+ /** The state applying the depth offset for the shadow. */
+ private final OffsetState _shadowOffsetState;
+
+ /**
+ * The blending to both discard the fragments that have been determined to be free of shadows and to blend into the
+ * background scene.
+ */
+ private final BlendState _discardShadowFragments;
+
+ /** The state applying the shadow map. */
+ private final TextureState _shadowTextureState;
+
+ /** Don't perform any plane clipping when rendering the shadowed scene. */
+ private final ClipState _noClip;
+
+ /** True once the pass has been initialized. */
+ protected boolean _initialised = false;
+
+ /** Flag for updating texture splits when number of splits changed. */
+ protected boolean _reinitSplitsDirty = true;
+
+ /** Flag for updating texture renderer when texture size changed. */
+ protected boolean _reinitTextureSizeDirty = true;
+
+ /** The size of the shadow map texture. */
+ private int _shadowMapSize;
+
+ /** Minimum number of splits allowed */
+ public static final int _MIN_SPLITS = 1;
+
+ /** Maximum number of splits allowed */
+ public static final int _MAX_SPLITS = 4;
+
+ /** Number of splits used. */
+ protected int _numOfSplits;
+
+ /** Minimum light z distance from target. */
+ private double _minimumLightDistance = 1000.0;
+
+ /** Shadow color and transparency. */
+ private final ColorRGBA _shadowColor = new ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f);
+
+ /** Light -> Camera transformation matrix. */
+ private final Matrix4 _shadowMatrix = new Matrix4();
+
+ /** Bounding of scene for packing frustum. */
+ protected BoundingBox _receiverBounds = new BoundingBox();
+
+ /** Indicates if we have any valid reciever bounds to use for frustum packing */
+ protected boolean hasValidBounds = false;
+
+ /** Special camera with functionality for packing frustum etc. */
+ protected PSSMCamera _pssmCam;
+
+ /** Light that casts the shadow. */
+ protected Light _light;
+
+ /** Shader for rendering pssm shadows in one pass. */
+ private GLSLShaderObjectsState _pssmShader;
+
+ /** Might need to keep main shader when doing both multitexturing shadows and overlays */
+ private GLSLShaderObjectsState _mainShader;
+
+ /** Shader for debugging pssm shadows drawing splits in different colors. */
+ private GLSLShaderObjectsState _pssmDebugShader;
+
+ /** Debug for stopping camera update. */
+ private boolean _updateMainCamera = true;
+
+ /** Debug drawing frustums. */
+ private boolean _drawDebug = false;
+
+ /** Debug drawing shader. */
+ private boolean _drawShaderDebug = false;
+
+ /** Do we want to keep the main shader to do both multitexturing shadows and overlays? */
+ private boolean _keepMainShader = false;
+
+ /**
+ * True if we want to factor in texturing to shadows; useful for casting shadows against alpha-tested textures.
+ * Default is false.
+ */
+ private boolean _useSceneTexturing = false;
+
+ /**
+ * True if we want to use the culling set on the objects instead of always culling front face (which is done for
+ * shadow precision). Default is false.
+ */
+ private boolean _useObjectCullFace = false;
+
+ /**
+ * When true (the default) the generated shadow map textures are drawn over the scene in a separate blend pass. If
+ * false, the shadows are generated, but not applied.
+ */
+ private boolean _renderShadowedScene = true;
+
+ /** Store format to use for the shadow textures. */
+ protected TextureStoreFormat _shadowTextureStoreFormat = TextureStoreFormat.Depth16;
+
+ /** Shadow filter techniques */
+ public enum Filter {
+ None, Pcf
+ }
+
+ private Filter filter = Filter.None;
+
+ private ShadowRenderCallback shadowRenderCallback;
+
+ /**
+ * Create a pssm shadow map pass casting shadows from a light with the direction given.
+ *
+ * @param shadowMapSize
+ * The size of the shadow map texture
+ * @param numOfSplits
+ * the num of splits
+ */
+ public ParallelSplitShadowMapPass(final Light light, final int shadowMapSize, final int numOfSplits) {
+ _light = light;
+ _shadowMapSize = shadowMapSize;
+ setNumOfSplits(numOfSplits);
+ _pssmCam = new PSSMCamera();
+
+ _noClip = new ClipState();
+ _noClip.setEnabled(false);
+ _noTexture = new TextureState();
+ _noTexture.setEnabled(false);
+ _colorDisabled = new ColorMaskState();
+ _colorDisabled.setAll(false);
+ _cullFrontFace = new CullState();
+ _cullFrontFace.setEnabled(true);
+ _cullFrontFace.setCullFace(CullState.Face.Front);
+ _noLights = new LightState();
+ _noLights.setEnabled(false);
+
+ _shadowOffsetState = new OffsetState();
+ _shadowOffsetState.setEnabled(true);
+ _shadowOffsetState.setTypeEnabled(OffsetType.Fill, true);
+ _shadowOffsetState.setFactor(1.1f);
+ _shadowOffsetState.setUnits(4.0f);
+
+ _flat = new ShadingState();
+ _flat.setShadingMode(ShadingMode.Flat);
+
+ // When rendering and comparing the shadow map with the current depth, the result will be set to alpha 1 if in
+ // shadow and to 0 if not in shadow.
+ _discardShadowFragments = new BlendState();
+ _discardShadowFragments.setEnabled(true);
+ _discardShadowFragments.setBlendEnabled(true);
+ _discardShadowFragments.setTestEnabled(true);
+ _discardShadowFragments.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ _discardShadowFragments.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+
+ _shadowTextureState = new TextureState();
+ }
+
+ /**
+ * Add a spatial that will occlude light and hence cast a shadow.
+ *
+ * @param occluder
+ * The spatial to add as an occluder
+ */
+ public void addOccluder(final Spatial occluder) {
+ if (!_occluderNodes.contains(occluder)) {
+ _occluderNodes.add(occluder);
+ }
+ }
+
+ /**
+ * Remove a spatial from the list of occluders.
+ *
+ * @param occluder
+ * The spatial to remove from the occluderlist
+ */
+ public void removeOccluder(final Spatial occluder) {
+ _occluderNodes.remove(occluder);
+ }
+
+ /**
+ * Initialize the pass render states.
+ *
+ * @param r
+ * the r
+ */
+ public void init(final Renderer r) {
+ if (_initialised) {
+ return;
+ }
+
+ _initialised = true; // now it's initialized
+
+ // render states to use when rendering into the shadow map, no textures or colors are required since we're only
+ // interested in recording depth. Also only need back faces when rendering the shadow maps
+
+ // Load PSSM shader.
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+ if (caps.isGLSLSupported()) {
+ _pssmShader = new GLSLShaderObjectsState();
+ try {
+ _pssmShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssm.vert"));
+ if (filter == Filter.None) {
+ _pssmShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssm.frag"));
+ } else if (filter == Filter.Pcf) {
+ _pssmShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmPCF.frag"));
+ }
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ _mainShader = _pssmShader;
+
+ _pssmDebugShader = new GLSLShaderObjectsState();
+ try {
+ _pssmDebugShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmDebug.vert"));
+ _pssmDebugShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmDebug.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ }
+
+ // Setup texture renderer.
+ reinitTextureSize(r);
+
+ // Setup textures and shader for all splits
+ reinitSplits();
+ }
+
+ public void reinit(final Renderer r) {
+ reinitTextureSize(r);
+ reinitSplits();
+ }
+
+ /**
+ * Reinit texture size. Sets up texture renderer.
+ *
+ * @param r
+ * the Renderer
+ */
+ private void reinitTextureSize(final Renderer r) {
+ if (!_reinitTextureSizeDirty) {
+ return;
+ }
+
+ _reinitTextureSizeDirty = false;
+
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+
+ // Create texture renderer
+ _shadowMapRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(_shadowMapSize, _shadowMapSize, r,
+ caps);
+
+ // Enforce performance enhancing states on the renderer.
+ _shadowMapRenderer.enforceState(_noClip);
+ _shadowMapRenderer.enforceState(_colorDisabled);
+ if (!_useObjectCullFace) {
+ _shadowMapRenderer.enforceState(_cullFrontFace);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(StateType.Cull);
+ }
+ _shadowMapRenderer.enforceState(_noLights);
+ _shadowMapRenderer.enforceState(_flat);
+ _shadowMapRenderer.enforceState(_shadowOffsetState);
+ if (!_useSceneTexturing) {
+ _shadowMapRenderer.enforceState(_noTexture);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(RenderState.StateType.Texture);
+ }
+
+ if (_light instanceof DirectionalLight) {
+ _shadowMapRenderer.getCamera().setProjectionMode(ProjectionMode.Parallel);
+ }
+ }
+
+ /**
+ * Reinit splits. Setup textures and shader for all splits
+ */
+ private void reinitSplits() {
+ if (!_reinitSplitsDirty) {
+ return;
+ }
+
+ _reinitSplitsDirty = false;
+
+ // render state to apply the shadow map texture
+ _shadowMapTexture = new Texture2D[_numOfSplits];
+ for (int i = 0; i < _numOfSplits; i++) {
+ _shadowMapTexture[i] = new Texture2D();
+ _shadowMapTexture[i].setWrap(Texture.WrapMode.BorderClamp);
+ _shadowMapTexture[i].setMinificationFilter(Texture.MinificationFilter.BilinearNoMipMaps);
+ _shadowMapTexture[i].setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ _shadowMapTexture[i].setBorderColor(ColorRGBA.WHITE);
+
+ _shadowMapTexture[i].setEnvironmentalMapMode(Texture.EnvironmentalMapMode.EyeLinear);
+
+ _shadowMapTexture[i].setTextureStoreFormat(_shadowTextureStoreFormat);
+ _shadowMapTexture[i].setDepthCompareMode(DepthTextureCompareMode.RtoTexture);
+ _shadowMapTexture[i].setDepthCompareFunc(DepthTextureCompareFunc.GreaterThanEqual);
+ _shadowMapTexture[i].setDepthMode(DepthTextureMode.Intensity);
+
+ _shadowMapRenderer.setupTexture(_shadowMapTexture[i]);
+ _shadowTextureState.setTexture(_shadowMapTexture[i], i);
+ }
+ if (_pssmShader != null) {
+ for (int i = 0; i < _MAX_SPLITS; i++) {
+ _pssmShader.setUniform("shadowMap" + i, i);
+ _pssmDebugShader.setUniform("shadowMap" + i, i);
+ _mainShader.setUniform("shadowMap" + i, i);
+ }
+ }
+ }
+
+ /**
+ * Render the pass.
+ *
+ * @param r
+ * the Renderer
+ */
+ @Override
+ protected void doRender(final Renderer r) {
+ updateShadowMaps(r);
+
+ if (_renderShadowedScene) {
+ // Render overlay scene
+ renderShadowedScene(r);
+ }
+ }
+
+ public void updateShadowMaps(final Renderer r) {
+ init(r);
+ reinitTextureSize(r);
+ reinitSplits();
+
+ // Update receiving scene bounds to prepare for frustum packing
+ updateReceiverBounds();
+
+ // If updating camera is true (can be turned off for debugging), copy from main
+ // camera and pack frustum.
+ if (_updateMainCamera) {
+ // Copy our active camera to our working pssm camera.
+ final Camera cam = ContextManager.getCurrentContext().getCurrentCamera();
+ _pssmCam.set(cam);
+
+ // Calculate the closest fitting near and far planes.
+ if (hasValidBounds) {
+ _pssmCam.pack(_receiverBounds);
+ } else {
+ _pssmCam.updateMaxCameraFar();
+ }
+ }
+
+ // Calculate the split distances
+ _pssmCam.calculateSplitDistances(_numOfSplits);
+
+ // Render our scene in sections.
+ // For each part...
+ for (int iSplit = 0; iSplit < _numOfSplits; iSplit++) {
+ // Figure out the appropriate frustum.
+ final double fNear = _pssmCam.getSplitDistances()[iSplit];
+ final double fFar = _pssmCam.getSplitDistances()[iSplit + 1];
+
+ // Calculate the frustum corners for the current split
+ _pssmCam.calculateFrustum(fNear, fFar);
+
+ // Debug draw main camera frustum for current split
+ if (_drawDebug) {
+ drawFrustum(r, _pssmCam, fNear, fFar, new ColorRGBA(0, 1, (float) (iSplit + 1) / _numOfSplits, 1),
+ (short) 0xF000, iSplit == 0);
+ }
+
+ // Calculate the appropriate lightview and projection matrices for the current split
+ if (_light instanceof DirectionalLight) {
+ calculateOptimalLightFrustum(_pssmCam._corners, _pssmCam._center);
+
+ if (_drawDebug) {
+ boundingSphere
+ .setData(frustumBoundingSphere.getCenter(), 10, 10, frustumBoundingSphere.getRadius());
+ boundingSphere.draw(r);
+ }
+ } else if (_light instanceof PointLight) {
+ calculateOptimalLightFrustumOld(_pssmCam._corners, _pssmCam._center);
+ }
+
+ // Debug draw light frustum for current split
+ if (_drawDebug) {
+ drawFrustum(r, _shadowMapRenderer.getCamera(), new ColorRGBA(1, 1, (iSplit + 1) / (float) _numOfSplits,
+ 1), (short) 0xFFFF, true);
+ }
+
+ // Render shadowmap from light view for current split
+ updateShadowMap(iSplit, r);
+
+ // Update texture matrix for shadowmap
+ updateTextureMatrix(iSplit);
+ }
+
+ updateShaderVariables();
+ }
+
+ private void updateShaderVariables() {
+ if (_pssmShader != null && ContextManager.getCurrentContext().getCapabilities().isGLSLSupported()) {
+ final float split1 = (float) _pssmCam.getSplitDistances()[1];
+ final float split2 = (float) (_pssmCam.getSplitDistances().length > 2 ? _pssmCam.getSplitDistances()[2]
+ : 0f);
+ final float split3 = (float) (_pssmCam.getSplitDistances().length > 3 ? _pssmCam.getSplitDistances()[3]
+ : 0f);
+ final float split4 = (float) (_pssmCam.getSplitDistances().length > 4 ? _pssmCam.getSplitDistances()[4]
+ : 0f);
+
+ GLSLShaderObjectsState currentShader = _drawShaderDebug ? _pssmDebugShader : _pssmShader;
+ if (_drawShaderDebug) {
+ currentShader = _pssmDebugShader;
+ }
+
+ currentShader.setUniform("sampleDist", split1, split2, split3, split4);
+ if (filter == Filter.Pcf) {
+ // TODO
+ // currentShader.setUniform("_shadowSize", 1f / _shadowMapSize);
+ }
+
+ if (!_drawShaderDebug) {
+ currentShader.setUniform("shadowColor", _shadowColor);
+ }
+
+ if (_keepMainShader) {
+ _mainShader.setUniform("sampleDist", split1, split2, split3, split4);
+ if (filter == Filter.Pcf) {
+ // TODO
+ // _mainShader.setUniform("_shadowSize", 1f / _shadowMapSize);
+ }
+
+ if (!_drawShaderDebug) {
+ _mainShader.setUniform("shadowColor", _shadowColor);
+ }
+ }
+ }
+ }
+
+ // TODO
+ final FloatBuffer frustumBoundingBuffer = BufferUtils.createVector3Buffer(8);
+ final BoundingSphere frustumBoundingSphere = new BoundingSphere();
+
+ /**
+ * Calculate optimal light frustum perspective.
+ *
+ * @param frustumCorners
+ * the frustum corners
+ * @param center
+ * the center
+ */
+ private void calculateOptimalLightFrustum(final Vector3[] frustumCorners, final ReadOnlyVector3 centerFrustum) {
+ for (int i = 0; i < 8; i++) {
+ BufferUtils.setInBuffer(frustumCorners[i], frustumBoundingBuffer, i);
+ }
+ frustumBoundingSphere.computeFromPoints(frustumBoundingBuffer);
+
+ final Camera shadowCam = _shadowMapRenderer.getCamera();
+
+ final ReadOnlyVector3 center = frustumBoundingSphere.getCenter();
+ final double radius = frustumBoundingSphere.getRadius();
+
+ Vector3 direction = new Vector3();
+ final DirectionalLight dl = (DirectionalLight) _light;
+ direction = direction.set(dl.getDirection());
+ final double distance = Math.max(radius, _minimumLightDistance);
+
+ final Vector3 tmpVec = Vector3.fetchTempInstance();
+ tmpVec.set(direction);
+ tmpVec.negateLocal();
+ tmpVec.multiplyLocal(distance);
+ tmpVec.addLocal(center);
+
+ // temporary location
+ shadowCam.setLocation(tmpVec);
+ shadowCam.lookAt(center, Vector3.UNIT_Y);
+
+ {
+ // determine
+ final double texelSizeW = (2 * radius) / _shadowMapRenderer.getWidth();
+ final double texelSizeH = (2 * radius) / _shadowMapRenderer.getHeight();
+
+ // build a Quaternion from camera axes to move
+ final Quaternion q = Quaternion.fetchTempInstance();
+ q.fromAxes(shadowCam.getLeft(), shadowCam.getUp(), shadowCam.getDirection());
+
+ // invert to calculate in light space
+ final Vector3 lightSpace = q.invert(null).apply(tmpVec, Vector3.fetchTempInstance());
+
+ // snap to nearest texel
+ lightSpace.setX(lightSpace.getX() - (lightSpace.getX() % texelSizeW));
+ lightSpace.setY(lightSpace.getY() - (lightSpace.getY() % texelSizeH));
+
+ // convert back
+ q.apply(lightSpace, tmpVec);
+ Vector3.releaseTempInstance(lightSpace);
+
+ Quaternion.releaseTempInstance(q);
+ }
+
+ // updated location
+ final double x = tmpVec.getX();
+ final double y = tmpVec.getY();
+ final double z = tmpVec.getZ();
+ final double farZ = tmpVec.subtractLocal(center).length() + radius;
+ Vector3.releaseTempInstance(tmpVec);
+
+ // set frustum, then location
+ shadowCam.setFrustum(1, farZ, -radius, radius, radius, -radius);
+ shadowCam.setLocation(x, y, z);
+ }
+
+ /**
+ * Saving this around until we fully support a good solution for non-directional lights. Like dual paraboloid shadow
+ * maps...
+ *
+ * @param frustumCorners
+ * @param center
+ */
+ private void calculateOptimalLightFrustumOld(final Vector3[] frustumCorners, final ReadOnlyVector3 center) {
+ final Camera shadowCam = _shadowMapRenderer.getCamera();
+
+ double distance = _minimumLightDistance;
+
+ final Vector3 tmpVec = Vector3.fetchTempInstance();
+
+ // Update shadow camera from light
+ final PointLight pl = (PointLight) _light;
+
+ shadowCam.setLocation(pl.getLocation());
+
+ // Point light at split center
+ shadowCam.lookAt(center, Vector3.UNIT_Y);
+
+ // Reset frustum
+ distance = center.subtract(shadowCam.getLocation(), tmpVec).length();
+ shadowCam.setFrustum(1, distance, -1, 1, 1, -1);
+
+ double fMinX = Double.POSITIVE_INFINITY;
+ double fMaxX = Double.NEGATIVE_INFINITY;
+ double fMinY = Double.POSITIVE_INFINITY;
+ double fMaxY = Double.NEGATIVE_INFINITY;
+ double fMinZ = Double.POSITIVE_INFINITY;
+ double fMaxZ = Double.NEGATIVE_INFINITY;
+
+ final ReadOnlyMatrix4 lightviewproj = shadowCam.getModelViewProjectionMatrix();
+
+ final Vector4 position = Vector4.fetchTempInstance();
+ for (final Vector3 frustumCorner : frustumCorners) {
+ position.set(frustumCorner.getX(), frustumCorner.getY(), frustumCorner.getZ(), 1);
+ lightviewproj.applyPre(position, position);
+
+ position.setX(position.getX() / position.getW());
+ position.setY(position.getY() / position.getW());
+ position.setZ(position.getZ());
+
+ fMinX = Math.min(position.getX(), fMinX);
+ fMaxX = Math.max(position.getX(), fMaxX);
+
+ fMinY = Math.min(position.getY(), fMinY);
+ fMaxY = Math.max(position.getY(), fMaxY);
+
+ fMinZ = Math.min(position.getZ(), fMinZ);
+ fMaxZ = Math.max(position.getZ(), fMaxZ);
+ }
+
+ double width = 0;
+ double height = 0;
+ fMinX = clamp(fMinX, -1.0, 1.0);
+ fMaxX = clamp(fMaxX, -1.0, 1.0);
+ fMinY = clamp(fMinY, -1.0, 1.0);
+ fMaxY = clamp(fMaxY, -1.0, 1.0);
+
+ // Make sure the minimum z is at least a specified distance from
+ // the target.
+ fMinZ = Math.min(fMinZ, distance - _minimumLightDistance);
+ fMinZ = Math.max(10.0, fMinZ);
+
+ width = fMinZ * (fMaxX - fMinX) * 0.5;
+ height = fMinZ * (fMaxY - fMinY) * 0.5;
+
+ final Vector3 newCenter = Vector3.fetchTempInstance();
+ position.set((fMinX + fMaxX) * 0.5, (fMinY + fMaxY) * 0.5, 1.0, 1);
+ shadowCam.getModelViewProjectionInverseMatrix().applyPre(position, position);
+ position.divideLocal(position.getW());
+ newCenter.set(position.getX(), position.getY(), position.getZ());
+
+ shadowCam.lookAt(newCenter, Vector3.UNIT_Y);
+
+ Vector3.releaseTempInstance(newCenter);
+ Vector4.releaseTempInstance(position);
+
+ shadowCam.setFrustum(fMinZ, fMaxZ, -width, width, height, -height);
+ shadowCam.update();
+ }
+
+ /**
+ * Render the overlay scene with shadows.
+ *
+ * @param r
+ * The renderer to use
+ */
+ public void renderShadowedScene(final Renderer r) {
+ boolean reset = false;
+ if (_context == null) {
+ _context = ContextManager.getCurrentContext();
+ reset = true;
+ }
+
+ _context.pushEnforcedStates();
+ _context.enforceState(_shadowTextureState);
+ _context.enforceState(_discardShadowFragments);
+
+ if (_pssmShader != null && _context.getCapabilities().isGLSLSupported()) {
+ GLSLShaderObjectsState currentShader = _drawShaderDebug ? _pssmDebugShader : _pssmShader;
+ if (_drawShaderDebug) {
+ currentShader = _pssmDebugShader;
+ }
+ if (_keepMainShader) {
+ currentShader = _mainShader;
+ }
+ _context.enforceState(currentShader);
+ }
+
+ for (final Spatial spat : _spatials) {
+ spat.onDraw(r);
+ }
+ r.renderBuckets();
+
+ _context.popEnforcedStates();
+
+ if (reset) {
+ // _context = null;
+ }
+ }
+
+ private static RenderLogic logic = new RenderLogic() {
+ private CullState cullState;
+ private Face cullFace;
+ private boolean isVisible;
+
+ public void apply(final Renderable renderable) {
+ if (renderable instanceof Mesh) {
+ final Mesh mesh = (Mesh) renderable;
+
+ isVisible = mesh.isVisible();
+ if (!mesh.getSceneHints().isCastsShadows()) {
+ mesh.setVisible(false);
+ }
+
+ cullState = (CullState) mesh.getWorldRenderState(StateType.Cull);
+ if (cullState != null) {
+ cullFace = cullState.getCullFace();
+ if (cullFace != Face.None) {
+ cullState.setCullFace(Face.Front);
+ }
+ }
+ }
+ }
+
+ public void restore(final Renderable renderable) {
+ if (renderable instanceof Mesh) {
+ final Mesh mesh = (Mesh) renderable;
+
+ mesh.setVisible(isVisible);
+
+ if (cullState != null) {
+ cullState.setCullFace(cullFace);
+ }
+ }
+ }
+ };
+
+ /**
+ * Update the shadow map.
+ *
+ * @param index
+ * shadow map texture index to update
+ */
+ private void updateShadowMap(final int index, final Renderer r) {
+
+ if (shadowRenderCallback != null) {
+ shadowRenderCallback.onRender(index, r, this, _shadowMapRenderer.getCamera());
+ }
+
+ r.setRenderLogic(logic);
+ if (!_useSceneTexturing) {
+ Mesh.RENDER_VERTEX_ONLY = true;
+ }
+ _shadowMapRenderer.render(_occluderNodes, _shadowMapTexture[index], Renderer.BUFFER_COLOR_AND_DEPTH);
+ if (!_useSceneTexturing) {
+ Mesh.RENDER_VERTEX_ONLY = false;
+ }
+ r.setRenderLogic(null);
+ }
+
+ /**
+ * Update texture matrix.
+ *
+ * @param index
+ * the index
+ */
+ private void updateTextureMatrix(final int index) {
+ // Create a matrix going from light to camera space
+ final Camera cam = ContextManager.getCurrentContext().getCurrentCamera();
+ _shadowMatrix.set(cam.getModelViewMatrix()).invertLocal();
+ _shadowMatrix.multiplyLocal(_shadowMapRenderer.getCamera().getModelViewProjectionMatrix()).multiplyLocal(
+ SCALE_BIAS_MATRIX);
+ _shadowMapTexture[index].setTextureMatrix(_shadowMatrix);
+ }
+
+ /**
+ * Checks if this pass is initialized.
+ *
+ * @return true, if is initialized
+ */
+ public boolean isInitialised() {
+ return _initialised;
+ }
+
+ /**
+ *
+ * @return the offset state used for drawing the shadow textures.
+ */
+ public OffsetState getShadowOffsetState() {
+ return _shadowOffsetState;
+ }
+
+ /**
+ * Update receiver bounds.
+ */
+ private void updateReceiverBounds() {
+ hasValidBounds = false;
+ boolean firstRun = true;
+
+ for (int i = 0, cSize = _boundsReceiver.size(); i < cSize; i++) {
+ final Spatial child = _boundsReceiver.get(i);
+ if (child != null && child.getWorldBound() != null && boundIsValid(child.getWorldBound())) {
+ if (firstRun) {
+ _receiverBounds.setCenter(child.getWorldBound().getCenter());
+ _receiverBounds.setXExtent(0);
+ _receiverBounds.setYExtent(0);
+ _receiverBounds.setZExtent(0);
+ firstRun = false;
+ }
+ _receiverBounds.mergeLocal(child.getWorldBound());
+ hasValidBounds = true;
+ }
+ }
+
+ for (int i = 0, cSize = _spatials.size(); i < cSize; i++) {
+ final Spatial child = _spatials.get(i);
+ if (child != null && child.getWorldBound() != null && boundIsValid(child.getWorldBound())) {
+ if (firstRun) {
+ _receiverBounds.setCenter(child.getWorldBound().getCenter());
+ _receiverBounds.setXExtent(0);
+ _receiverBounds.setYExtent(0);
+ _receiverBounds.setZExtent(0);
+ firstRun = false;
+ }
+ _receiverBounds.mergeLocal(child.getWorldBound());
+ hasValidBounds = true;
+ }
+ }
+ }
+
+ /**
+ * Checks if a bounding volume is valid.
+ *
+ * @param volume
+ * @return
+ */
+ private boolean boundIsValid(final BoundingVolume volume) {
+ if (!Vector3.isValid(volume.getCenter())) {
+ return false;
+ }
+ switch (volume.getType()) {
+ case AABB: {
+ final BoundingBox vBox = (BoundingBox) volume;
+ return !(Double.isInfinite(vBox.getXExtent()) || Double.isInfinite(vBox.getYExtent())
+ || Double.isInfinite(vBox.getZExtent()) || Double.isNaN(vBox.getXExtent())
+ || Double.isNaN(vBox.getYExtent()) || Double.isNaN(vBox.getZExtent()));
+ }
+
+ case Sphere: {
+ final BoundingSphere vSphere = (BoundingSphere) volume;
+ return !(Double.isInfinite(vSphere.getRadius()) || Double.isNaN(vSphere.getRadius()));
+ }
+
+ case OBB: {
+ final OrientedBoundingBox obb = (OrientedBoundingBox) volume;
+ return Vector3.isValid(obb.getExtent());
+ }
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Gets the shadow map texture.
+ *
+ * @param index
+ * the index
+ *
+ * @return the shadow map texture
+ */
+ public Texture2D getShadowMapTexture(final int index) {
+ return _shadowMapTexture[index];
+ }
+
+ /**
+ * Gets the number of splits.
+ *
+ * @return the number of splits
+ */
+ public int getNumOfSplits() {
+ return _numOfSplits;
+ }
+
+ /**
+ * Sets the number of frustum splits and thus the number of shadow textures created by this pass. More splits
+ * creates crisper shadows at the cost of increased texture memory.
+ *
+ * @param numOfSplits
+ * the new number of splits
+ */
+ public void setNumOfSplits(final int numOfSplits) {
+ _numOfSplits = Math.min(Math.max(_MIN_SPLITS, numOfSplits), _MAX_SPLITS);
+ _reinitSplitsDirty = true;
+
+ if (_numOfSplits != numOfSplits) {
+ logger.warning("Valid range for number of splits is " + _MIN_SPLITS + " to " + _MAX_SPLITS
+ + ". Tried to set it to " + numOfSplits);
+ }
+ }
+
+ /**
+ * Gets the shadow map size.
+ *
+ * @return the shadow map size
+ */
+ public int getShadowMapSize() {
+ return _shadowMapSize;
+ }
+
+ /**
+ * Sets the shadow map size.
+ *
+ * @param shadowMapSize
+ * the new shadow map size
+ */
+ public void setShadowMapSize(final int shadowMapSize) {
+ _shadowMapSize = shadowMapSize;
+ _reinitTextureSizeDirty = true;
+ _reinitSplitsDirty = true;
+ }
+
+ /**
+ * Gets the maximum distance for shadowing.
+ *
+ * @return max distance
+ * @see com.ardor3d.extension.shadow.map.PSSMCamera#getMaxFarPlaneDistance()
+ */
+ public double getMaxShadowDistance() {
+ return _pssmCam.getMaxFarPlaneDistance();
+ }
+
+ /**
+ * Sets the maximum distance for shadowing.
+ *
+ * @param maxShadowDistance
+ * distance to set
+ * @see com.ardor3d.extension.shadow.map.PSSMCamera#setMaxFarPlaneDistance(double)
+ */
+ public void setMaxShadowDistance(final double maxShadowDistance) {
+ _pssmCam.setMaxFarPlaneDistance(maxShadowDistance);
+ }
+
+ /**
+ * Gets the minimum z distance for the light.
+ *
+ * @return the minimumLightDistance
+ */
+ public double getMinimumLightDistance() {
+ return _minimumLightDistance;
+ }
+
+ /**
+ * Sets the minimum z distance for the light.
+ *
+ * @param minimumLightDistance
+ * the minimumLightDistance to set
+ */
+ public void setMinimumLightDistance(final double minimumLightDistance) {
+ _minimumLightDistance = minimumLightDistance;
+ }
+
+ /**
+ * Gets shadow color and transparency.
+ *
+ * @return the shadowColor
+ */
+ public ReadOnlyColorRGBA getShadowColor() {
+ return _shadowColor;
+ }
+
+ /**
+ * Sets shadow color and transparency.
+ *
+ * @param shadowColor
+ * the shadowColor to set
+ */
+ public void setShadowColor(final ReadOnlyColorRGBA shadowColor) {
+ _shadowColor.set(shadowColor);
+ }
+
+ public ShadowRenderCallback getShadowRenderCallback() {
+ return shadowRenderCallback;
+ }
+
+ public void setShadowRenderCallback(final ShadowRenderCallback callback) {
+ shadowRenderCallback = callback;
+ }
+
+ /**
+ * Clean up.
+ *
+ * @see com.ardor3d.renderer.pass.Pass#cleanUp()
+ */
+ @Override
+ public void cleanUp() {
+ super.cleanUp();
+
+ if (_shadowMapRenderer != null) {
+ _shadowMapRenderer.cleanup();
+ }
+ }
+
+ /**
+ * Remove the contents of the pass.
+ */
+ public void clear() {
+ _occluderNodes.clear();
+ _spatials.clear();
+ }
+
+ /**
+ * Simple clamp.
+ *
+ * @param val
+ * value to clamp
+ * @param from
+ * minimum value after clamping
+ * @param to
+ * maximum value after clamping
+ * @return Math.min(to, Math.max(from, val))
+ */
+ private double clamp(final double val, final double from, final double to) {
+ return Math.min(to, Math.max(from, val));
+ }
+
+ /**
+ * @return the updateMainCamera
+ */
+ public boolean isUpdateMainCamera() {
+ return _updateMainCamera;
+ }
+
+ /**
+ * @param updateMainCamera
+ * True (the default) if we want to copy the current rendering camera into this pass for use in shadow
+ * generation. False if we will manage our shadow camera elsewhere.
+ * @see #getPssmCam()
+ */
+ public void setUpdateMainCamera(final boolean updateMainCamera) {
+ _updateMainCamera = updateMainCamera;
+ }
+
+ /**
+ * @return the drawDebug
+ */
+ public boolean isDrawDebug() {
+ return _drawDebug;
+ }
+
+ /**
+ * @param drawDebug
+ * True if we want to draw camera and light frustums for debugging purposes. Default is false.
+ */
+ public void setDrawDebug(final boolean drawDebug) {
+ _drawDebug = drawDebug;
+ }
+
+ /**
+ * @return the drawShaderDebug
+ */
+ public boolean isDrawShaderDebug() {
+ return _drawShaderDebug;
+ }
+
+ /**
+ * @param drawShaderDebug
+ * True if we want to draw debug colors over the shadows, showing which level they come from.
+ */
+ public void setDrawShaderDebug(final boolean drawShaderDebug) {
+ _drawShaderDebug = drawShaderDebug;
+ }
+
+ /**
+ * @return the useSceneTexturing
+ */
+ public boolean isUseSceneTexturing() {
+ return _useSceneTexturing;
+ }
+
+ /**
+ * @param useSceneTexturing
+ * True if we want to factor in texturing to shadows; useful for casting shadows against alpha-tested
+ * textures. Default is false.
+ */
+ public void setUseSceneTexturing(final boolean useSceneTexturing) {
+ _useSceneTexturing = useSceneTexturing;
+ if (_shadowMapRenderer != null) {
+ if (!_useSceneTexturing) {
+ _shadowMapRenderer.enforceState(_noTexture);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(RenderState.StateType.Texture);
+ }
+ }
+
+ }
+
+ public boolean isUseObjectCullFace() {
+ return _useObjectCullFace;
+ }
+
+ /**
+ * @param useObjectCullFace
+ * True if we want to use the culling set on the objects instead of always culling front face (which is
+ * done for shadow precision). Default is false.
+ */
+ public void setUseObjectCullFace(final boolean useObjectCullFace) {
+ _useObjectCullFace = useObjectCullFace;
+ if (_shadowMapRenderer != null) {
+ if (!_useObjectCullFace) {
+ _shadowMapRenderer.enforceState(_cullFrontFace);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(StateType.Cull);
+ }
+ }
+ }
+
+ public boolean isRenderShadowedScene() {
+ return _renderShadowedScene;
+ }
+
+ /**
+ * @param renderShadowedScene
+ * When true (the default) the generated shadow map textures are drawn over the scene in a separate blend
+ * pass. If false, the shadows are generated, but not applied.
+ */
+ public void setRenderShadowedScene(final boolean renderShadowedScene) {
+ _renderShadowedScene = renderShadowedScene;
+ }
+
+ /**
+ * @return the camera used internally to generate shadows.
+ */
+ public PSSMCamera getPssmCam() {
+ return _pssmCam;
+ }
+
+ /**
+ * @return the texture store format to use for the shadow textures on the next call to init/setNumOfSplits.
+ */
+ public TextureStoreFormat getShadowTextureStoreFormat() {
+ return _shadowTextureStoreFormat;
+ }
+
+ /**
+ * @param shadowTextureStoreFormat
+ * - the texture store format to use for the shadow textures. Only has an affect if called prior to
+ * calling init on this pass (or if called before calling {@link #setNumOfSplits(int)}).
+ */
+ public void setShadowTextureStoreFormat(final TextureStoreFormat shadowTextureStoreFormat) {
+ _shadowTextureStoreFormat = shadowTextureStoreFormat;
+ }
+
+ /** The debug line frustum. */
+ private static Line lineFrustum;
+
+ /** The debug test cam. */
+ private static final PSSMCamera testCam = new PSSMCamera();
+
+ /** Debug bounding sphere */
+ private static final Sphere boundingSphere = new Sphere("bsphere", 10, 10, 1);
+ static {
+ boundingSphere.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ boundingSphere.setRenderState(new WireframeState());
+ boundingSphere.setRenderState(new ZBufferState());
+ boundingSphere.updateWorldRenderStates(false);
+ }
+
+ /**
+ * Draw debug frustum.
+ *
+ * @param r
+ * the r
+ * @param cam
+ * the cam
+ * @param color
+ * the color
+ * @param drawOriginConnector
+ * whether or not to draw a connector
+ */
+ private static void drawFrustum(final Renderer r, final Camera cam, final ReadOnlyColorRGBA color,
+ final short pattern, final boolean drawOriginConnector) {
+ drawFrustum(r, cam, cam.getFrustumNear(), cam.getFrustumFar(), color, pattern, drawOriginConnector);
+ }
+
+ /**
+ * Draw debug frustum.
+ *
+ * @param r
+ * the r
+ * @param cam
+ * the cam
+ * @param fNear
+ * the f near
+ * @param fFar
+ * the f far
+ * @param color
+ * the color
+ * @param drawOriginConnector
+ * whether or not to draw a connector
+ */
+ private static void drawFrustum(final Renderer r, final Camera cam, 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);
+
+ testCam.set(cam);
+ testCam.update();
+ testCam.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 FloatBuffer verts = lineFrustum.getMeshData().getVertexBuffer();
+ BufferUtils.setInBuffer(testCam._corners[0], verts, 0);
+ BufferUtils.setInBuffer(testCam._corners[1], verts, 1);
+ BufferUtils.setInBuffer(testCam._corners[2], verts, 2);
+ BufferUtils.setInBuffer(testCam._corners[3], verts, 3);
+
+ BufferUtils.setInBuffer(testCam._corners[4], verts, 4);
+ BufferUtils.setInBuffer(testCam._corners[5], verts, 5);
+ BufferUtils.setInBuffer(testCam._corners[6], verts, 6);
+ BufferUtils.setInBuffer(testCam._corners[7], verts, 7);
+
+ BufferUtils.setInBuffer(testCam._corners[0], verts, 8);
+ BufferUtils.setInBuffer(testCam._corners[4], verts, 9);
+ BufferUtils.setInBuffer(testCam._corners[1], verts, 10);
+ BufferUtils.setInBuffer(testCam._corners[5], verts, 11);
+ BufferUtils.setInBuffer(testCam._corners[2], verts, 12);
+ BufferUtils.setInBuffer(testCam._corners[6], verts, 13);
+ BufferUtils.setInBuffer(testCam._corners[3], verts, 14);
+ BufferUtils.setInBuffer(testCam._corners[7], verts, 15);
+
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 16);
+ BufferUtils.setInBuffer(testCam._corners[0], verts, 17);
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 18);
+ BufferUtils.setInBuffer(testCam._corners[1], verts, 19);
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 20);
+ BufferUtils.setInBuffer(testCam._corners[2], verts, 21);
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 22);
+ BufferUtils.setInBuffer(testCam._corners[3], verts, 23);
+
+ lineFrustum.draw(r);
+ }
+
+ public void setPssmShader(final GLSLShaderObjectsState pssmShader) {
+ _pssmShader = pssmShader;
+ }
+
+ public boolean isKeepMainShader() {
+ return _keepMainShader;
+ }
+
+ public void setKeepMainShader(final boolean keepMainShader) {
+ _keepMainShader = keepMainShader;
+ }
+
+ public void addBoundsReceiver(final Spatial spatial) {
+ _boundsReceiver.add(spatial);
+ }
+
+ public void setFiltering(final Filter filter) {
+ this.filter = filter;
+ }
+
+ public BlendState getDiscardShadowFragmentsBlendState() {
+ return _discardShadowFragments;
+ }
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.java new file mode 100644 index 0000000..931f3ca --- /dev/null +++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.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.extension.shadow.map;
+
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+
+public interface ShadowRenderCallback {
+
+ void onRender(int splitIndex, Renderer renderer, ParallelSplitShadowMapPass pass, Camera renderCamera);
+
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag new file mode 100644 index 0000000..e88109e --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag @@ -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>.
+ */
+
+uniform sampler2D tex1;
+uniform sampler2D tex2;
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec4 color1 = texture2D(tex1, texCoord);
+ vec4 color2 = texture2D(tex2, texCoord);
+
+ gl_FragColor = vec4(color1.rgb + color2.rgb, min(color1.a + color2.a, 1.0));
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag new file mode 100644 index 0000000..09ec326 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag @@ -0,0 +1,74 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec2 samples00 = vec2(-0.326212, -0.405805);
+ vec2 samples01 = vec2(-0.840144, -0.073580);
+ vec2 samples02 = vec2(-0.695914, 0.457137);
+ vec2 samples03 = vec2(-0.203345, 0.620716);
+ vec2 samples04 = vec2( 0.962340, -0.194983);
+ vec2 samples05 = vec2( 0.473434, -0.480026);
+ vec2 samples06 = vec2( 0.519456, 0.767022);
+ vec2 samples07 = vec2( 0.185461, -0.893124);
+ vec2 samples08 = vec2( 0.507431, 0.064425);
+ vec2 samples09 = vec2( 0.896420, 0.412458);
+ vec2 samples10 = vec2(-0.321940, -0.932615);
+ vec2 samples11 = vec2(-0.791559, -0.597705);
+
+ vec2 newCoord;
+ vec4 sum = texture2D(RT, texCoord);
+
+ newCoord = texCoord + sampleDist * samples00;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples01;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples02;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples03;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples04;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples05;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples06;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples07;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples08;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples09;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples10;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples11;
+ sum += texture2D(RT, newCoord);
+
+ sum /= 13.0;
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert new file mode 100644 index 0000000..02bda22 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert @@ -0,0 +1,19 @@ +/**
+ * 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>.
+ */
+
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ texCoord.x = 0.5 * (1.0 + Pos.x);
+ texCoord.y = 0.5 * (1.0 + Pos.y);
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag new file mode 100644 index 0000000..bb8d7db --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag @@ -0,0 +1,26 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x - 1.0*sampleDist, texCoord.y)) * 1.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.5*sampleDist, texCoord.y)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 3.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.5*sampleDist, texCoord.y)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x + 1.0*sampleDist, texCoord.y)) * 1.0/9.0;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag new file mode 100644 index 0000000..6ee9508 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag @@ -0,0 +1,28 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x - 1.0*sampleDist, texCoord.y)) * 0.25/4.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.666*sampleDist, texCoord.y)) * 0.5/4.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.333*sampleDist, texCoord.y)) * 0.75/4.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 1.0/4.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.333*sampleDist, texCoord.y)) * 0.75/4.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.666*sampleDist, texCoord.y)) * 0.5/4.0;
+ sum += texture2D(RT, vec2(texCoord.x + 1.0*sampleDist, texCoord.y)) * 0.25/4.0;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag new file mode 100644 index 0000000..d447dfa --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag @@ -0,0 +1,30 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x - 1.0*sampleDist, texCoord.y)) * 1.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.75*sampleDist, texCoord.y)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.5*sampleDist, texCoord.y)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.25*sampleDist, texCoord.y)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 5.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.25*sampleDist, texCoord.y)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.5*sampleDist, texCoord.y)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.75*sampleDist, texCoord.y)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 1.0*sampleDist, texCoord.y)) * 1.0/25.0;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag new file mode 100644 index 0000000..ca44225 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag @@ -0,0 +1,29 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 1.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.5*sampleDist)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 3.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.5*sampleDist)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 1.0*sampleDist)) * 1.0/9.0;
+
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag new file mode 100644 index 0000000..08f7fa1 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag @@ -0,0 +1,26 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 6.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 4.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 2.0*sampleDist)) * 3.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 3.0*sampleDist)) * 2.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 4.0*sampleDist)) * 1.0/16.0;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag new file mode 100644 index 0000000..385afc8 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag @@ -0,0 +1,31 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 1.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.666*sampleDist)) * 2.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.333*sampleDist)) * 3.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 4.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.333*sampleDist)) * 3.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.666*sampleDist)) * 2.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 1.0*sampleDist)) * 1.0/16.0;
+
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag new file mode 100644 index 0000000..55fbf26 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag @@ -0,0 +1,33 @@ +/**
+ * 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>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 1.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.75*sampleDist)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.5*sampleDist)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.25*sampleDist)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 5.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.25*sampleDist)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.5*sampleDist)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.75*sampleDist)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 1.0*sampleDist)) * 1.0/25.0;
+
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag new file mode 100644 index 0000000..88e4630 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag @@ -0,0 +1,25 @@ +/**
+ * 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>.
+ */
+
+uniform float exposurePow;
+uniform float exposureCutoff;
+uniform sampler2D RT;
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ vec4 sum = texture2D(RT, vTexCoord);
+ if ( (sum.r+sum.g+sum.b)/3.0 < exposureCutoff ) {
+ sum = vec4(0.0);
+ }
+ sum = pow(sum,vec4(exposurePow));
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert new file mode 100644 index 0000000..1ed3f16 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert @@ -0,0 +1,19 @@ +/**
+ * 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>.
+ */
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ vTexCoord.x = 0.5 * (1.0 + Pos.x);
+ vTexCoord.y = 0.5 * (1.0 + Pos.y);
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag new file mode 100644 index 0000000..e33042b --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag @@ -0,0 +1,18 @@ +/**
+ * 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>.
+ */
+
+uniform sampler2D RT;
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ gl_FragColor = texture2D(RT, vTexCoord);
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert new file mode 100644 index 0000000..1ed3f16 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert @@ -0,0 +1,19 @@ +/**
+ * 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>.
+ */
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ vTexCoord.x = 0.5 * (1.0 + Pos.x);
+ vTexCoord.y = 0.5 * (1.0 + Pos.y);
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag new file mode 100644 index 0000000..42ccb9c --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag @@ -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>.
+ */
+
+uniform sampler2D inputTex;
+uniform float exposureIntensity;
+uniform float exposureCutoff;
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec4 color = texture2D(inputTex, texCoord);
+ float lum = dot(vec3(0.3086, 0.6094, 0.0820), color.rgb);
+
+ if (lum < exposureCutoff ) {
+ color = vec4(0.0, 0.0, 0.0, color.a);
+ }
+
+ gl_FragColor = vec4(color.rgb * exposureIntensity, color.a);
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag new file mode 100644 index 0000000..7caf050 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag @@ -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>. + */ + +uniform sampler2D inputTex; +uniform sampler2D colorRampTex; + +uniform float redWeight; +uniform float greenWeight; +uniform float blueWeight; + +varying vec2 texCoord; + +void main(void) { + vec4 color = texture2D(inputTex, texCoord); + vec3 convert = vec3(redWeight, greenWeight, blueWeight); + + float luminance = dot(convert, color.rgb); + + vec4 finalColor = texture2D( colorRampTex, vec2(luminance, .5) ); + finalColor.a = color.a; + gl_FragColor = finalColor; +}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert new file mode 100644 index 0000000..9396569 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert @@ -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>. + */ + +varying vec2 texCoord; + +void main(void) { + vec2 Pos = sign(gl_Vertex.xy); + gl_Position = vec4(Pos.xy, 0, 1); + texCoord.x = 0.5 * (1.0 + Pos.x); + texCoord.y = 0.5 * (1.0 + Pos.y); +} + diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag new file mode 100644 index 0000000..4ed36dc --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag @@ -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>.
+ */
+
+uniform sampler2D inputTex;
+uniform float sampleDist;
+
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(inputTex, vec2(texCoord.x - 1.0 * sampleDist, texCoord.y)) * 0.04;
+ sum += texture2D(inputTex, vec2(texCoord.x - 0.75 * sampleDist, texCoord.y)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x - 0.5 * sampleDist, texCoord.y)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x - 0.25 * sampleDist, texCoord.y)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x , texCoord.y)) * 0.20;
+ sum += texture2D(inputTex, vec2(texCoord.x + 0.25 * sampleDist, texCoord.y)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x + 0.5 * sampleDist, texCoord.y)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x + 0.75 * sampleDist, texCoord.y)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x + 1.0 * sampleDist, texCoord.y)) * 0.04;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag new file mode 100644 index 0000000..73b3472 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag @@ -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>.
+ */
+
+uniform sampler2D inputTex;
+uniform float sampleDist;
+
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 1.0 * sampleDist)) * 0.04;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 0.75 * sampleDist)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 0.5 * sampleDist)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 0.25 * sampleDist)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y )) * 0.20;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 0.25 * sampleDist)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 0.5 * sampleDist)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 0.75 * sampleDist)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 1.0 * sampleDist)) * 0.04;
+
+ gl_FragColor = sum;
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag new file mode 100644 index 0000000..1d6916a --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag @@ -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>. + */ + +uniform sampler2D inputTex; + +varying vec2 texCoord; + +void main(void) { + vec4 color = texture2D(inputTex, texCoord); + vec3 convert = vec3(0.3086, 0.6094, 0.0820); + + float luminance = dot(convert, color.rgb); + + gl_FragColor = vec4(luminance, luminance, luminance, 1.0); +}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.png b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.png Binary files differnew file mode 100644 index 0000000..14751d3 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.png diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag new file mode 100644 index 0000000..a69fbcc --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag @@ -0,0 +1,90 @@ +/** + * 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>. + */ + +uniform sampler3D texture; +uniform float levels; +uniform vec2 sliceOffset[16]; +//uniform float maxLevel; +uniform float validLevels; +uniform float textureSize; +uniform float texelSize; +uniform float showDebug; + +varying vec2 vVertex; + + +vec4 texture3DBilinear( const in sampler3D textureSampler, const in vec3 uv, const in vec2 offset ) +{ + vec4 tl = texture3D(textureSampler, uv); + vec4 tr = texture3D(textureSampler, uv + vec3(texelSize, 0, 0)); + vec4 bl = texture3D(textureSampler, uv + vec3(0, texelSize, 0)); + vec4 br = texture3D(textureSampler, uv + vec3(texelSize , texelSize, 0)); + + vec2 f = fract( uv.xy * textureSize ); // get the decimal part + vec4 tA = mix( tl, tr, f.x ); // will interpolate the red dot in the image + vec4 tB = mix( bl, br, f.x ); // will interpolate the blue dot in the image + return mix( tA, tB, f.y ); // will interpolate the green dot in the image +} + + +void main() +{ + float unit = (max(abs(vVertex.x), abs(vVertex.y))); + + unit = floor(unit); + unit = log2(unit); + unit = floor(unit); + +// unit = max(unit, maxLevel); + if (unit > validLevels) { + discard; + } +// unit = min(unit, validLevels); + + unit = max(unit, 0.0); + + vec2 offset = sliceOffset[int(unit)]; + float frac = unit; + frac = exp2(frac); + frac *= 4.0; //Magic number + vec2 texCoord = vVertex/vec2(frac); + vec2 fadeCoord = texCoord; + texCoord += vec2(0.5); + texCoord *= vec2(1.0 - texelSize); + texCoord += offset; + + float unit2 = unit + 1.0; + unit2 = min(unit2, validLevels); + vec2 offset2 = sliceOffset[int(unit2)]; + float frac2 = unit2; + frac2 = exp2(frac2); + frac2 *= 4.0; //Magic number + vec2 texCoord2 = vVertex/vec2(frac2); + texCoord2 += vec2(0.5); + texCoord2 *= vec2(1.0 - texelSize); + texCoord2 += offset2; + + unit /= levels; + unit = clamp(unit, 0.0, 0.99); + + unit2 /= levels; + unit2 = clamp(unit2, 0.0, 0.99); + + //vec4 tex = texture3D(texture, vec3(texCoord.x, texCoord.y, unit)); + vec4 tex = texture3DBilinear(texture, vec3(texCoord.x, texCoord.y, unit), offset); + vec4 tex2 = texture3DBilinear(texture, vec3(texCoord2.x, texCoord2.y, unit2), offset2); + + float fadeVal1 = abs(fadeCoord.x)*2.05; + float fadeVal2 = abs(fadeCoord.y)*2.05; + float fadeVal = max(fadeVal1, fadeVal2); + fadeVal = max(0.0, fadeVal-0.8)*5.0; + fadeVal = min(1.0, fadeVal); + gl_FragColor = mix(tex, tex2, fadeVal)+vec4(fadeVal*showDebug); +} diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert new file mode 100644 index 0000000..9f2cd70 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert @@ -0,0 +1,22 @@ +/** + * 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>. + */ + +uniform float scale; +uniform vec3 eyePosition; + +varying vec2 vVertex; + +void main(void){ + gl_TexCoord[0] = gl_MultiTexCoord0; + + vVertex = (gl_Vertex.xz - eyePosition.xz) * vec2(scale); + + gl_Position = ftransform(); +} diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag new file mode 100644 index 0000000..9e268fb --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag @@ -0,0 +1,77 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ if ( abovewater == false ) {
+ fresnel = -fresnel;
+ }
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 endColor = mix(waterColorNew,reflectionColor,fresnelTerm);
+
+ if( useFadeToFogColor == false) {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist);
+ } else {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert new file mode 100644 index 0000000..62d4f91 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert @@ -0,0 +1,49 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+//uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+
+void main()
+{
+ // Because we have a flat plane for water we already know the vectors for tangent space
+// vec3 normal = gl_Normal;
+ vec3 normal = gl_NormalMatrix * gl_Normal;
+ normal = normalize(normal);
+ vec3 tangent2 = gl_NormalMatrix * tangent;
+ tangent2 = normalize(tangent2);
+ vec3 binormal2 = gl_NormalMatrix * binormal;
+ binormal2 = normalize(binormal2);
+
+ // Calculate the vector coming from the vertex to the camera
+// vec3 viewDir = cameraPos - gl_Vertex.xyz;
+ vec4 v = gl_ModelViewMatrix * gl_Vertex;
+ vec3 viewDir = -(v.xyz/v.w);
+ viewDir = normalize(viewDir);
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent2);
+ viewTangetSpace.y = dot(viewDir, binormal2);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+
+ // This calculates our current projection coordinates
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ gl_Position = viewCoords;
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag new file mode 100644 index 0000000..039a30d --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag @@ -0,0 +1,89 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+uniform sampler2D refraction;
+uniform sampler2D depthMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ if ( abovewater == false ) {
+ fresnel = -fresnel;
+ }
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+ fresnel = 1.0 - fresnelTerm;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ vec2 projCoordDepth = projCoord;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ projCoordDepth += (dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoordDepth = clamp(projCoordDepth, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 refractionColor = texture2D(refraction, projCoordDepth);
+ float depth = texture2D(depthMap, projCoordDepth).r;
+ depth = pow(depth,15.0);
+ float invDepth = 1.0-depth;
+
+ vec4 endColor = refractionColor*vec4(invDepth*fresnel) + waterColorNew*vec4(depth*fresnel);
+
+ if( useFadeToFogColor == false) {
+ gl_FragColor = endColor + reflectionColor * vec4(fresnelTerm);
+ } else {
+ gl_FragColor = (endColor + reflectionColor * vec4(fresnelTerm)) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert new file mode 100644 index 0000000..62d4f91 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert @@ -0,0 +1,49 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+//uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+
+void main()
+{
+ // Because we have a flat plane for water we already know the vectors for tangent space
+// vec3 normal = gl_Normal;
+ vec3 normal = gl_NormalMatrix * gl_Normal;
+ normal = normalize(normal);
+ vec3 tangent2 = gl_NormalMatrix * tangent;
+ tangent2 = normalize(tangent2);
+ vec3 binormal2 = gl_NormalMatrix * binormal;
+ binormal2 = normalize(binormal2);
+
+ // Calculate the vector coming from the vertex to the camera
+// vec3 viewDir = cameraPos - gl_Vertex.xyz;
+ vec4 v = gl_ModelViewMatrix * gl_Vertex;
+ vec3 viewDir = -(v.xyz/v.w);
+ viewDir = normalize(viewDir);
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent2);
+ viewTangetSpace.y = dot(viewDir, binormal2);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+
+ // This calculates our current projection coordinates
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ gl_Position = viewCoords;
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag new file mode 100644 index 0000000..3975051 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag @@ -0,0 +1,88 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+uniform sampler2D foamMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+uniform float amplitude;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (vnormal + dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 endColor = mix(waterColorNew,reflectionColor,fresnelTerm);
+
+ float foamVal = (vVertex.y-vVertex.w) / (amplitude * 2.0);
+ foamVal = clamp(foamVal,0.0,1.0);
+ vec4 foamTex = texture2D(foamMap, foamCoords + vnormal * 0.6 + normalVector.xy * 0.05);
+ float normLength = length(vnormal*5.0);
+ foamVal *= 1.0-normLength;
+ foamVal *= foamTex.a;
+ endColor = mix(endColor,foamTex,clamp(foamVal,0.0,0.95));
+
+ if( useFadeToFogColor == false) {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist);
+ } else {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert new file mode 100644 index 0000000..1de2252 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert @@ -0,0 +1,53 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+uniform float waterHeight;
+uniform float heightFalloffStart;
+uniform float heightFalloffSpeed;
+
+void main()
+{
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ vVertex = gl_Vertex;
+ float heightAdjust = 1.0 - clamp((viewCoords.z-heightFalloffStart)/heightFalloffSpeed,0.0,1.0);
+ vVertex.y = mix(waterHeight,vVertex.y,heightAdjust);
+ viewCoords = gl_ModelViewProjectionMatrix * vVertex;
+ gl_Position = viewCoords;
+ vVertex.w = waterHeight;
+
+ // Because we have a flat plane for water we already know the vectors for tangent space
+ vec3 normal = vec3(gl_Normal.x*heightAdjust,gl_Normal.y,gl_Normal.z*heightAdjust);
+ vnormal = normal.xz * 0.15;
+
+ // Calculate the vector coming from the vertex to the camera
+ vec3 viewDir = cameraPos - gl_Vertex.xyz;
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent);
+ viewTangetSpace.y = dot(viewDir, binormal);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ //todo test 0.8
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+ foamCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation*0.4);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag new file mode 100644 index 0000000..16f2513 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag @@ -0,0 +1,101 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+uniform sampler2D refraction;
+uniform sampler2D depthMap;
+uniform sampler2D foamMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+uniform float amplitude;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+ fresnel = 1.0 - fresnelTerm;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ vec2 projCoordDepth = projCoord;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (vnormal + dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ projCoordDepth += (vnormal + dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoordDepth = clamp(projCoordDepth, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 endColor = mix(waterColorNew,reflectionColor,fresnelTerm);
+
+ float foamVal = (vVertex.y-vVertex.w) / (amplitude * 2.0);
+ foamVal = clamp(foamVal,0.0,1.0);
+ vec4 foamTex = texture2D(foamMap, foamCoords + vnormal * 0.6 + normalVector.xy * 0.05);
+ float normLength = length(vnormal*5.0);
+ foamVal *= 1.0-normLength;
+ foamVal *= foamTex.a;
+ endColor = mix(endColor,foamTex,clamp(foamVal,0.0,0.95));
+
+ vec4 refractionColor = texture2D(refraction, projCoordDepth);
+ float depth = texture2D(depthMap, projCoordDepth).r;
+ depth = pow(depth,15.0);
+ float invDepth = 1.0-depth;
+
+ endColor = refractionColor*vec4(invDepth*fresnel) + endColor*vec4(depth*fresnel);
+ if( useFadeToFogColor == false) {
+ gl_FragColor = endColor + reflectionColor * vec4(fresnelTerm);
+ } else {
+ gl_FragColor = (endColor + reflectionColor * vec4(fresnelTerm)) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+}
\ No newline at end of file diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert new file mode 100644 index 0000000..1de2252 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert @@ -0,0 +1,53 @@ +/**
+ * 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>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+uniform float waterHeight;
+uniform float heightFalloffStart;
+uniform float heightFalloffSpeed;
+
+void main()
+{
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ vVertex = gl_Vertex;
+ float heightAdjust = 1.0 - clamp((viewCoords.z-heightFalloffStart)/heightFalloffSpeed,0.0,1.0);
+ vVertex.y = mix(waterHeight,vVertex.y,heightAdjust);
+ viewCoords = gl_ModelViewProjectionMatrix * vVertex;
+ gl_Position = viewCoords;
+ vVertex.w = waterHeight;
+
+ // Because we have a flat plane for water we already know the vectors for tangent space
+ vec3 normal = vec3(gl_Normal.x*heightAdjust,gl_Normal.y,gl_Normal.z*heightAdjust);
+ vnormal = normal.xz * 0.15;
+
+ // Calculate the vector coming from the vertex to the camera
+ vec3 viewDir = cameraPos - gl_Vertex.xyz;
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent);
+ viewTangetSpace.y = dot(viewDir, binormal);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ //todo test 0.8
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+ foamCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation*0.4);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag new file mode 100644 index 0000000..34323d2 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2008-2011 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +varying float zDist; +uniform vec4 sampleDist; +uniform vec4 shadowColor; + +void main() +{ + float shade = 0.0; + if (zDist < sampleDist.x) { + shade = shadow2DProj(shadowMap0, gl_TexCoord[0]).x; + } else if (zDist < sampleDist.y) { + shade = shadow2DProj(shadowMap1, gl_TexCoord[1]).x; + } else if (zDist < sampleDist.z) { + shade = shadow2DProj(shadowMap2, gl_TexCoord[2]).x; + } else if (zDist < sampleDist.w) { + shade = shadow2DProj(shadowMap3, gl_TexCoord[3]).x; + } + + gl_FragColor = vec4(shadowColor.rgb, shadowColor.a * shade); +} diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert new file mode 100644 index 0000000..077f216 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert @@ -0,0 +1,24 @@ +/** + * 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>. + */ + +varying float zDist; + +void main(void){ + vec4 ePos = gl_ModelViewMatrix * gl_Vertex; + + gl_TexCoord[0] = gl_TextureMatrix[0] * ePos; + gl_TexCoord[1] = gl_TextureMatrix[1] * ePos; + gl_TexCoord[2] = gl_TextureMatrix[2] * ePos; + gl_TexCoord[3] = gl_TextureMatrix[3] * ePos; + + zDist = -ePos.z; + + gl_Position = ftransform(); +} diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag new file mode 100644 index 0000000..4a02b80 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag @@ -0,0 +1,39 @@ +/** + * 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>. + */ + +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +varying float zDist; +uniform vec4 sampleDist; + +void main() +{ + float shade = 0.0; + vec3 col = vec3(0.0); + if (zDist < sampleDist.x) { + shade = shadow2DProj(shadowMap0, gl_TexCoord[0]).x; + col.r = 0.5; + } else if (zDist < sampleDist.y) { + shade = shadow2DProj(shadowMap1, gl_TexCoord[1]).x; + col.g = 0.5; + } else if (zDist < sampleDist.z) { + shade = shadow2DProj(shadowMap2, gl_TexCoord[2]).x; + col.b = 0.5; + } else if (zDist < sampleDist.w) { + shade = shadow2DProj(shadowMap3, gl_TexCoord[3]).x; + col.r = 0.5; + col.b = 0.5; + } + + gl_FragColor = vec4(col.rgb, 0.5 * shade); +} diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert new file mode 100644 index 0000000..077f216 --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert @@ -0,0 +1,24 @@ +/** + * 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>. + */ + +varying float zDist; + +void main(void){ + vec4 ePos = gl_ModelViewMatrix * gl_Vertex; + + gl_TexCoord[0] = gl_TextureMatrix[0] * ePos; + gl_TexCoord[1] = gl_TextureMatrix[1] * ePos; + gl_TexCoord[2] = gl_TextureMatrix[2] * ePos; + gl_TexCoord[3] = gl_TextureMatrix[3] * ePos; + + zDist = -ePos.z; + + gl_Position = ftransform(); +} diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag new file mode 100644 index 0000000..ecbac9a --- /dev/null +++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag @@ -0,0 +1,76 @@ +/** + * 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>. + */ + +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +varying float zDist; +uniform vec4 sampleDist; +uniform vec4 shadowColor; +//uniform float _shadowSize; + +float offset_lookup(const in sampler2DShadow map, + const in vec4 loc, + const in vec2 offset, + const in float shadowSize) +{ + return shadow2DProj(map, vec4(loc.xy + offset * shadowSize * loc.w, loc.z, loc.w)).x; +} + +float shadowLookup(const in sampler2DShadow shadowmap, const in vec4 sCoord, const in float shadowSize) { + vec2 offset = mod(sCoord.xy, 0.5); + offset.y += offset.x; // y ^= x in floating point + + if (offset.y > 1.1) { + offset.y = 0.0; + } + offset = vec2(0.0); + + return (offset_lookup(shadowmap, sCoord, offset + + vec2(-1.5, 0.5), shadowSize) + + offset_lookup(shadowmap, sCoord, offset + + vec2(0.5, 0.5), shadowSize) + + offset_lookup(shadowmap, sCoord, offset + + vec2(-1.5, -1.5), shadowSize) + + offset_lookup(shadowmap, sCoord, offset + + vec2(0.5, -1.5), shadowSize) ) * 0.25; +} + +float shadowLookup33(const in sampler2DShadow shadowmap, const in vec4 sCoord, const in float shadowSize) { + float x,y; + float shadow = 0.0; + for (y = -1.5; y <= 1.5; y += 1.0) { + for (x = -1.5; x <= 1.5; x += 1.0) { + shadow += offset_lookup(shadowmap, sCoord, vec2(x,y), shadowSize); + } + } + + shadow /= 16.0; + + return shadow; +} + +void main() +{ + float shade = 0.0; + if (zDist < sampleDist.x) { + shade = shadowLookup33(shadowMap0, gl_TexCoord[0], 1.0/1024.0); + } else if (zDist < sampleDist.y) { + shade = shadowLookup33(shadowMap1, gl_TexCoord[1], 1.0/1024.0); + } else if (zDist < sampleDist.z) { + shade = shadowLookup(shadowMap2, gl_TexCoord[2], 1.0/1024.0); + } else if (zDist < sampleDist.w) { + shade = shadow2DProj(shadowMap3, gl_TexCoord[3]).x; + } + + gl_FragColor = vec4(shadowColor.rgb, shadowColor.a * shade); +} diff --git a/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.java b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.java new file mode 100644 index 0000000..70dfa5e --- /dev/null +++ b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.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.extension.shadow.map;
+
+import com.ardor3d.math.Vector3;
+
+public class MockPSSMCamera extends PSSMCamera {
+ public Vector3 getExtents() {
+ return _extents;
+ }
+}
diff --git a/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.java b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.java new file mode 100644 index 0000000..fb8bdc0 --- /dev/null +++ b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.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.extension.shadow.map;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.math.Vector3;
+
+public class TestPSSMCamera {
+ @Test
+ public void testBoxSphereCameraPack() {
+ MockPSSMCamera camera = new MockPSSMCamera();
+ camera.setLocation(0, 0, -10);
+ camera.setFrustumPerspective(50, 1, 1, 100);
+
+ final BoundingBox boundingBox = new BoundingBox();
+ boundingBox.setCenter(new Vector3(0, 0, 10));
+ boundingBox.setXExtent(2);
+ boundingBox.setYExtent(2);
+ boundingBox.setZExtent(2);
+
+ camera.pack(boundingBox);
+
+ final double boxNear1 = camera.getFrustumNear();
+ final double boxFar1 = camera.getFrustumFar();
+
+ Assert.assertEquals(new Vector3(2, 2, 2), camera.getExtents());
+
+ camera = new MockPSSMCamera();
+ camera.setLocation(0, 0, -10);
+ camera.setFrustumPerspective(50, 1, 1, 100);
+
+ final BoundingSphere boundingSphere = new BoundingSphere();
+ boundingSphere.setCenter(new Vector3(0, 0, 10));
+ boundingSphere.setRadius(2);
+
+ camera.pack(boundingSphere);
+
+ final double boxNear2 = camera.getFrustumNear();
+ final double boxFar2 = camera.getFrustumFar();
+
+ Assert.assertEquals(new Vector3(2, 2, 2), camera.getExtents());
+
+ Assert.assertEquals(boxNear1, boxNear2);
+ Assert.assertEquals(boxFar1, boxFar2);
+ }
+}
|