diff options
Diffstat (limited to 'ardor3d-terrain')
76 files changed, 10309 insertions, 0 deletions
diff --git a/ardor3d-terrain/.classpath b/ardor3d-terrain/.classpath new file mode 100644 index 0000000..f1e81f6 --- /dev/null +++ b/ardor3d-terrain/.classpath @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src/main/java"/> + <classpathentry kind="src" path="src/main/resources"/> + <classpathentry kind="src" path="src/test/java"/> + <classpathentry combineaccessrules="false" kind="src" path="/ardor3d-core"/> + <classpathentry combineaccessrules="false" kind="src" path="/ardor3d-awt"/> + <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-terrain/.project b/ardor3d-terrain/.project new file mode 100644 index 0000000..aea5a30 --- /dev/null +++ b/ardor3d-terrain/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>ardor3d-terrain</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-terrain/.settings/org.eclipse.jdt.core.prefs b/ardor3d-terrain/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..fc5c1fb --- /dev/null +++ b/ardor3d-terrain/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,278 @@ +#Tue Aug 10 12:54:48 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.codeComplete.argumentPrefixes= +org.eclipse.jdt.core.codeComplete.argumentSuffixes= +org.eclipse.jdt.core.codeComplete.fieldPrefixes= +org.eclipse.jdt.core.codeComplete.fieldSuffixes= +org.eclipse.jdt.core.codeComplete.localPrefixes= +org.eclipse.jdt.core.codeComplete.localSuffixes= +org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=1 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=120 +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/ardor3d-terrain/.settings/org.eclipse.jdt.ui.prefs b/ardor3d-terrain/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..4df4a7a --- /dev/null +++ b/ardor3d-terrain/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,118 @@ +#Tue Aug 10 12:55:09 CDT 2010 +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=e +org.eclipse.jdt.ui.gettersetter.use.is=true +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.javadoc=false +org.eclipse.jdt.ui.keywordthis=false +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.overrideannotation=true +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\n * \n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment"/><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="false" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">/**\n * Copyright (c) 2008-2010 Ardor Labs, Inc.\n *\n * This file is part of Ardor3D.\n *\n * Ardor3D is free software\: you can redistribute it and/or modify it \n * under the terms of its license which may be found in the accompanying\n * LICENSE file or at <http\://www.ardor3d.com/LICENSE>.\n */\n\n${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created JavaScript files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\n * \n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * @author ${user}\n *\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for vars" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Jsdoc)\n * ${see_to_overridden}\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created function stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated function stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates> +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=true +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=true +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=true +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=true +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=true +sp_cleanup.use_this_for_non_static_field_access=true +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=true +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/ardor3d-terrain/ardorSettings.properties b/ardor3d-terrain/ardorSettings.properties new file mode 100644 index 0000000..d455af7 --- /dev/null +++ b/ardor3d-terrain/ardorSettings.properties @@ -0,0 +1,9 @@ +#Game Settings written by com.ardor3d.extension.terrain.example.base.PropertiesGameSettings at Thu Sep 23 03:50:28 CDT 2010 +#Thu Sep 23 03:50:28 CDT 2010 +SAMPLES=0 +FREQ=-1 +WIDTH=800 +RENDERER=LWJGL +HEIGHT=600 +DEPTH=24 +FULLSCREEN=false diff --git a/ardor3d-terrain/pom.xml b/ardor3d-terrain/pom.xml new file mode 100644 index 0000000..df3bfe6 --- /dev/null +++ b/ardor3d-terrain/pom.xml @@ -0,0 +1,37 @@ +<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-terrain</artifactId>
+ <packaging>bundle</packaging>
+ <name>Ardor 3D Terrain</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>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-awt</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/ClipmapLevel.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/ClipmapLevel.java new file mode 100644 index 0000000..a642655 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/ClipmapLevel.java @@ -0,0 +1,637 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.client;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Set;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.extension.terrain.util.Region;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Camera.FrustumIntersect;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * ClipmapLevel is the visual representation of one lod level of height data.
+ */
+public class ClipmapLevel extends Mesh {
+
+ /**
+ * Precalculated useful variables.
+ */
+ private final int doubleVertexDistance; // vertexDistance * 2;
+ private final int frameDistance; // (frameSize - 1) * vertexDistance
+
+ /**
+ * Gets the distance between two horizontal/vertical vertices. The finest level L = 0 has a distance of
+ * vertexDistance = 1. Then every level it doubles so vertexDistance is always 2^L
+ */
+ private final int vertexDistance;
+
+ /**
+ * Gets framesize in number of vertices between outer border of current clip and outer border of next finer (inner)
+ * clip.
+ */
+ private final int frameSize;
+
+ /**
+ * Gets the width of a clip in number of vertices. This is always one less than power of two (2^x - 1)
+ */
+ private final int clipSideSize;
+
+ /**
+ * Gets the region of the current clip.
+ */
+ private final Region clipRegion;
+ private final Region intersectionRegion;
+
+ /**
+ * Value that indicates the height scaling. It is also represents the maximum terrain height.
+ */
+ private final float heightScale;
+
+ private float heightRangeMin = 0.0f;
+ private float heightRangeMax = 1.0f;
+
+ /**
+ * Index to indicate how much vertices are added to the triangle strip.
+ */
+ private int stripIndex = 0;
+
+ /**
+ * The used terrain heightfield. This must be set per reference so all clip levels share the same memory for that
+ * variable. The values are between 0.0f and 1.0f
+ */
+ private final TerrainCache cache;
+
+ /**
+ * Number of components for vertices
+ */
+ public static final int VERT_SIZE = 4;
+
+ /**
+ * Camera frustum to test clipmap tiles against for culling
+ */
+ private final Camera clipmapTestFrustum;
+
+ /**
+ * Used to handle transformations of the terrain
+ */
+ private final Vector3 transformedFrustumPos = new Vector3();
+
+ /**
+ * Should the clipmap generate per vertex normals
+ */
+ private final boolean generateNormals = false;
+
+ /**
+ * Bounding box used for culling
+ */
+ private final BoundingBox frustumCheckBounds = new BoundingBox();
+
+ /**
+ * Possible nio speedup when storing indices
+ */
+ private int[] tmpIndices;
+
+ /**
+ * Should cull blocks outside camera frustum
+ */
+ private boolean cullingEnabled = true;
+
+ /**
+ * Creates a new clipmaplevel.
+ *
+ * @param levelIndex
+ * Levelindex of the clipmap. If is 0 this will be the finest level
+ * @param clipSideSize
+ * Number of vertices per clipside. Must be one less than power of two.
+ * @param heightScale
+ * Maximum terrainheight and heightscale
+ * @param fieldsize
+ * Width and heightvalue of the heightfield
+ * @param heightfield
+ * Heightvalues with a range of 0.0f - 1.0f
+ * @exception Exception
+ */
+ public ClipmapLevel(final int levelIndex, final Camera clipmapTestFrustum, final int clipSideSize,
+ final float heightScale, final TerrainCache cache) throws Exception {
+ super("Clipmap Level " + levelIndex);
+
+ // Check some exception cases
+ if (levelIndex < 0) {
+ throw new Exception("levelIndex must be positive");
+ }
+ if (!MathUtils.isPowerOfTwo(clipSideSize + 1)) {
+ throw new Exception("clipSideSize must be one less than power of two");
+ }
+
+ // Apply the values
+ this.clipmapTestFrustum = clipmapTestFrustum;
+ this.cache = cache;
+
+ this.heightScale = heightScale;
+ this.clipSideSize = clipSideSize;
+
+ // Calculate common variables
+ vertexDistance = (int) Math.pow(2, levelIndex);
+ frameSize = (clipSideSize + 1) / 4;
+ doubleVertexDistance = vertexDistance * 2;
+ frameDistance = (frameSize - 1) * vertexDistance;
+ clipRegion = new Region(0, 0, (clipSideSize - 1) * vertexDistance, (clipSideSize - 1) * vertexDistance);
+ intersectionRegion = new Region(0, 0, clipSideSize * vertexDistance, clipSideSize * vertexDistance);
+
+ // Initialize the vertices
+ initialize();
+ }
+
+ /**
+ * Initializes the vertices and indices.
+ */
+ private void initialize() {
+ getMeshData().setIndexMode(IndexMode.TriangleStrip);
+
+ // clipSideSize is the number of vertices per clipmapside, so number of all vertices is clipSideSize *
+ // clipSideSize
+ final FloatBuffer vertices = BufferUtils.createVector4Buffer(clipSideSize * clipSideSize);
+ getMeshData().setVertexCoords(new FloatBufferData(vertices, 4));
+
+ if (generateNormals) {
+ final FloatBuffer normals = BufferUtils.createVector3Buffer(clipSideSize * clipSideSize);
+ getMeshData().setNormalCoords(new FloatBufferData(normals, 3));
+ }
+
+ // final FloatBuffer textureCoords = BufferUtils.createVector2Buffer(N * N);
+ // getMeshData().setTextureBuffer(textureCoords, 0);
+
+ final int indicesSize = 4 * (3 * frameSize * frameSize + clipSideSize * clipSideSize / 2 + 4 * frameSize - 10);
+ final IntBuffer indices = BufferUtils.createIntBuffer(indicesSize);
+ tmpIndices = new int[indicesSize];
+ getMeshData().setIndexBuffer(indices);
+
+ // Go through all rows and fill them with vertexindices.
+ for (int z = 0; z < clipSideSize - 1; z++) {
+ fillRow(0, clipSideSize - 1, z, z + 1);
+ }
+ }
+
+ public void updateCache() {
+ getWorldTransform().applyInverse(clipmapTestFrustum.getLocation(), transformedFrustumPos);
+ final int cx = (int) transformedFrustumPos.getX();
+ final int cz = (int) transformedFrustumPos.getZ();
+
+ // Calculate the new position
+ int clipX = cx - (clipSideSize + 1) * vertexDistance / 2;
+ int clipY = cz - (clipSideSize + 1) * vertexDistance / 2;
+
+ // Calculate the modulo to doubleVertexDistance of the new position.
+ // This makes sure that the current level always fits in the hole of the
+ // coarser level. The gridspacing of the coarser level is vertexDistance * 2, so here doubleVertexDistance.
+ final int modX = MathUtils.moduloPositive(clipX, doubleVertexDistance);
+ final int modY = MathUtils.moduloPositive(clipY, doubleVertexDistance);
+ clipX = clipX + doubleVertexDistance - modX;
+ clipY = clipY + doubleVertexDistance - modY;
+
+ cache.setCurrentPosition(clipX / vertexDistance, clipY / vertexDistance);
+
+ // TODO
+ cache.handleUpdateRequests();
+ }
+
+ /**
+ * Update clipmap vertices
+ *
+ * @param center
+ */
+ public void updateVertices() {
+ getWorldTransform().applyInverse(clipmapTestFrustum.getLocation(), transformedFrustumPos);
+ final int cx = (int) transformedFrustumPos.getX();
+ final int cz = (int) transformedFrustumPos.getZ();
+
+ // Store the old position to be able to recover it if needed
+ final int oldX = clipRegion.getX();
+ final int oldZ = clipRegion.getY();
+
+ // Calculate the new position
+ clipRegion.setX(cx - (clipSideSize + 1) * vertexDistance / 2);
+ clipRegion.setY(cz - (clipSideSize + 1) * vertexDistance / 2);
+
+ // Calculate the modulo to doubleVertexDistance of the new position.
+ // This makes sure that the current level always fits in the hole of the
+ // coarser level. The gridspacing of the coarser level is vertexDistance * 2, so here doubleVertexDistance.
+ final int modX = MathUtils.moduloPositive(clipRegion.getX(), doubleVertexDistance);
+ final int modY = MathUtils.moduloPositive(clipRegion.getY(), doubleVertexDistance);
+ clipRegion.setX(clipRegion.getX() + doubleVertexDistance - modX);
+ clipRegion.setY(clipRegion.getY() + doubleVertexDistance - modY);
+
+ // Calculate the moving distance
+ final int dx = clipRegion.getX() - oldX;
+ final int dz = clipRegion.getY() - oldZ;
+
+ intersectionRegion.setX(clipRegion.getX());
+ intersectionRegion.setY(clipRegion.getY());
+
+ cache.setCurrentPosition(clipRegion.getX() / vertexDistance, clipRegion.getY() / vertexDistance);
+
+ updateVertices(dx, dz);
+
+ final Set<Tile> updatedTiles = cache.handleUpdateRequests();
+ if (updatedTiles != null) {
+ // TODO: only update what's changed
+ regenerate();
+ }
+ }
+
+ public void regenerate() {
+ updateVertices(clipRegion.getWidth(), clipRegion.getHeight());
+ }
+
+ /**
+ *
+ * @param cx
+ * @param cz
+ */
+ private void updateVertices(int dx, int dz) {
+ if (dx == 0 && dz == 0) {
+ return;
+ }
+
+ dx = MathUtils.clamp(dx, -clipSideSize + 1, clipSideSize - 1);
+ dz = MathUtils.clamp(dz, -clipSideSize + 1, clipSideSize - 1);
+
+ // Create some better readable variables.
+ // This are just the bounds of the current level (the new region).
+ final int xmin = clipRegion.getLeft() / vertexDistance;
+ final int xmax = clipRegion.getRight() / vertexDistance;
+ final int zmin = clipRegion.getTop() / vertexDistance;
+ final int zmax = clipRegion.getBottom() / vertexDistance;
+
+ final FloatBuffer vertices = getMeshData().getVertexBuffer();
+
+ // Update the L shaped region.
+ // This replaces the old data with the new one.
+ if (dz > 0) {
+ if (dx > 0) {
+ cache.updateRegion(vertices, xmax - dx, zmin, dx + 1, zmax - zmin - dz + 1);
+ } else if (dx < 0) {
+ cache.updateRegion(vertices, xmin, zmin, -dx + 1, zmax - zmin - dz + 1);
+ }
+
+ cache.updateRegion(vertices, xmin, zmax - dz, xmax - xmin + 1, dz + 1);
+ } else {
+ if (dx > 0) {
+ cache.updateRegion(vertices, xmax - dx, zmin - dz, dx + 1, zmax - zmin + dz + 1);
+ } else if (dx < 0) {
+ cache.updateRegion(vertices, xmin, zmin - dz, -dx + 1, zmax - zmin + dz + 1);
+ }
+
+ if (dz < 0) {
+ cache.updateRegion(vertices, xmin, zmin, xmax - xmin + 1, -dz + 1);
+ }
+ }
+ markDirty(DirtyType.Bounding);
+ }
+
+ /**
+ * Updates the whole indexarray.
+ *
+ * @param nextFinerLevel
+ * @param frustum
+ */
+ public void updateIndices(final ClipmapLevel nextFinerLevel) {
+ // set the stripindex to zero. We start count vertices from here.
+ // The stripindex will tell us how much of the array is used.
+ stripIndex = 0;
+
+ // MxM Block 1
+ fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getTop(), clipRegion.getTop()
+ + frameDistance);
+
+ // MxM Block 2
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + 2 * frameDistance, clipRegion.getTop(),
+ clipRegion.getTop() + frameDistance);
+
+ // MxM Block 3
+ fillBlock(clipRegion.getRight() - 2 * frameDistance, clipRegion.getRight() - frameDistance,
+ clipRegion.getTop(), clipRegion.getTop() + frameDistance);
+
+ // MxM Block 4
+ fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getTop(), clipRegion
+ .getTop()
+ + frameDistance);
+
+ // MxM Block 5
+ fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getTop() + frameDistance,
+ clipRegion.getTop() + 2 * frameDistance);
+
+ // MxM Block 6
+ fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getTop() + frameDistance,
+ clipRegion.getTop() + 2 * frameDistance);
+
+ // MxM Block 7
+ fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getBottom() - 2
+ * frameDistance, clipRegion.getBottom() - frameDistance);
+
+ // MxM Block 8
+ fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getBottom() - 2
+ * frameDistance, clipRegion.getBottom() - frameDistance);
+
+ // MxM Block 9
+ fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getBottom() - frameDistance,
+ clipRegion.getBottom());
+
+ // MxM Block 10
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + 2 * frameDistance, clipRegion
+ .getBottom()
+ - frameDistance, clipRegion.getBottom());
+
+ // MxM Block 11
+ fillBlock(clipRegion.getRight() - 2 * frameDistance, clipRegion.getRight() - frameDistance, clipRegion
+ .getBottom()
+ - frameDistance, clipRegion.getBottom());
+
+ // MxM Block 12
+ fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(), clipRegion.getBottom() - frameDistance,
+ clipRegion.getBottom());
+
+ // Fixup Top
+ fillBlock(clipRegion.getLeft() + 2 * frameDistance, clipRegion.getLeft() + 2 * frameDistance
+ + doubleVertexDistance, clipRegion.getTop(), clipRegion.getTop() + frameDistance);
+
+ // Fixup Left
+ fillBlock(clipRegion.getLeft(), clipRegion.getLeft() + frameDistance, clipRegion.getTop() + 2 * frameDistance,
+ clipRegion.getTop() + 2 * frameDistance + doubleVertexDistance);
+
+ // Fixup Right
+ fillBlock(clipRegion.getRight() - frameDistance, clipRegion.getRight(),
+ clipRegion.getTop() + 2 * frameDistance, clipRegion.getTop() + 2 * frameDistance + doubleVertexDistance);
+
+ // Fixup Bottom
+ fillBlock(clipRegion.getLeft() + 2 * frameDistance, clipRegion.getLeft() + 2 * frameDistance
+ + doubleVertexDistance, clipRegion.getBottom() - frameDistance, clipRegion.getBottom());
+
+ if (nextFinerLevel != null) {
+ if ((nextFinerLevel.clipRegion.getX() - clipRegion.getX()) / vertexDistance == frameSize) {
+ if ((nextFinerLevel.clipRegion.getY() - clipRegion.getY()) / vertexDistance == frameSize) {
+ // Upper Left L Shape
+
+ // Up
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance, clipRegion
+ .getTop()
+ + frameDistance, clipRegion.getTop() + frameDistance + vertexDistance);
+ // Left
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance
+ + vertexDistance, clipRegion.getTop() + frameDistance + vertexDistance, clipRegion
+ .getBottom()
+ - frameDistance);
+ } else {
+ // Lower Left L Shape
+
+ // Left
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance
+ + vertexDistance, clipRegion.getTop() + frameDistance, clipRegion.getBottom()
+ - frameDistance - vertexDistance);
+
+ // Bottom
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance, clipRegion
+ .getBottom()
+ - frameDistance - vertexDistance, clipRegion.getBottom() - frameDistance);
+ }
+ } else {
+ if ((nextFinerLevel.clipRegion.getY() - clipRegion.getY()) / vertexDistance == frameSize) {
+ // Upper Right L Shape
+
+ // Up
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance, clipRegion
+ .getTop()
+ + frameDistance, clipRegion.getTop() + frameDistance + vertexDistance);
+ // Right
+ fillBlock(clipRegion.getRight() - frameDistance - vertexDistance, clipRegion.getRight()
+ - frameDistance, clipRegion.getTop() + frameDistance + vertexDistance, clipRegion
+ .getBottom()
+ - frameDistance);
+ } else {
+ // Lower Right L Shape
+
+ // Right
+ fillBlock(clipRegion.getRight() - frameDistance - vertexDistance, clipRegion.getRight()
+ - frameDistance, clipRegion.getTop() + frameDistance, clipRegion.getBottom()
+ - frameDistance - vertexDistance);
+
+ // Bottom
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getRight() - frameDistance, clipRegion
+ .getBottom()
+ - frameDistance - vertexDistance, clipRegion.getBottom() - frameDistance);
+ }
+ }
+ }
+
+ // Fill in the middle patch if most detailed layer
+ if (nextFinerLevel == null) {
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance + clipSideSize / 2,
+ clipRegion.getTop() + frameDistance, clipRegion.getTop() + frameDistance + clipSideSize / 2);
+
+ fillBlock(clipRegion.getLeft() + frameDistance + clipSideSize / 2, clipRegion.getRight() - frameDistance,
+ clipRegion.getTop() + frameDistance, clipRegion.getTop() + frameDistance + clipSideSize / 2);
+
+ fillBlock(clipRegion.getLeft() + frameDistance, clipRegion.getLeft() + frameDistance + clipSideSize / 2,
+ clipRegion.getTop() + frameDistance + clipSideSize / 2, clipRegion.getBottom() - frameDistance);
+
+ fillBlock(clipRegion.getLeft() + frameDistance + clipSideSize / 2, clipRegion.getRight() - frameDistance,
+ clipRegion.getTop() + frameDistance + clipSideSize / 2, clipRegion.getBottom() - frameDistance);
+ }
+
+ final IntBuffer indices = (IntBuffer) getMeshData().getIndexBuffer();
+ indices.clear();
+ indices.put(tmpIndices, 0, getStripIndex());
+ indices.flip();
+ }
+
+ /**
+ * Fills a specified area to indexarray. This will be added only after a bounding test pass.
+ *
+ * @param left
+ * @param right
+ * @param top
+ * @param bottom
+ */
+ private void fillBlock(int left, int right, int top, int bottom) {
+ if (cullingEnabled) {
+ // Setup the boundingbox of the block to fill.
+ // The lowest value is zero, the highest is the scalesize.
+ frustumCheckBounds.setCenter((left + right) * 0.5, (heightRangeMax + heightRangeMin) * heightScale * 0.5,
+ (top + bottom) * 0.5);
+ frustumCheckBounds.setXExtent((left - right) * 0.5);
+ frustumCheckBounds.setYExtent((heightRangeMax - heightRangeMin) * heightScale * 0.5);
+ frustumCheckBounds.setZExtent((top - bottom) * 0.5);
+
+ frustumCheckBounds.transform(getWorldTransform(), frustumCheckBounds);
+
+ final int state = clipmapTestFrustum.getPlaneState();
+
+ final boolean isVisible = clipmapTestFrustum.contains(frustumCheckBounds) != FrustumIntersect.Outside;
+ clipmapTestFrustum.setPlaneState(state);
+
+ if (!isVisible) {
+ return;
+ }
+ }
+
+ // Same moduloprocedure as when we updated the vertices.
+ // Maps the terrainposition to arrayposition.
+ left = left / vertexDistance % clipSideSize;
+ right = right / vertexDistance % clipSideSize;
+ top = top / vertexDistance % clipSideSize;
+ bottom = bottom / vertexDistance % clipSideSize;
+ left += left < 0 ? clipSideSize : 0;
+ right += right < 0 ? clipSideSize : 0;
+ top += top < 0 ? clipSideSize : 0;
+ bottom += bottom < 0 ? clipSideSize : 0;
+
+ // Now fill the block.
+ if (bottom < top) {
+ // Bottom border is positioned somwhere over the top border,
+ // we have a wrapover so we must split up the update in two parts.
+
+ // Go from top border to the end of the array and update every row
+ for (int z = top; z <= clipSideSize - 2; z++) {
+ fillRow(left, right, z, z + 1);
+ }
+
+ // Update the wrapover row
+ fillRow(left, right, clipSideSize - 1, 0);
+
+ // Go from arraystart to the bottom border and update every row.
+ for (int z = 0; z <= bottom - 1; z++) {
+ fillRow(left, right, z, z + 1);
+ }
+ } else {
+ // Top border is over the bottom border. Update from top to bottom.
+ for (int z = top; z <= bottom - 1; z++) {
+ fillRow(left, right, z, z + 1);
+ }
+ }
+ }
+
+ /**
+ * Fills a strip of triangles that can be build between vertices row Zn and Zn1.
+ *
+ * @param startX
+ * Start x-coordinate
+ * @param endX
+ * End x-coordinate
+ * @param rowZ
+ * Row n
+ * @param rowZPlus1
+ * Row n + 1
+ */
+ private void fillRow(final int startX, final int endX, final int rowZ, final int rowZPlus1) {
+ addIndex(startX, rowZPlus1);
+ if (startX <= endX) {
+ for (int x = startX; x <= endX; x++) {
+ addIndex(x, rowZPlus1);
+ addIndex(x, rowZ);
+ }
+ } else {
+ for (int x = startX; x <= clipSideSize - 1; x++) {
+ addIndex(x, rowZPlus1);
+ addIndex(x, rowZ);
+ }
+ for (int x = 0; x <= endX; x++) {
+ addIndex(x, rowZPlus1);
+ addIndex(x, rowZ);
+ }
+ }
+ addIndex(endX, rowZ);
+ }
+
+ /**
+ * Adds a specific index to indexarray.
+ *
+ * @param x
+ * @param z
+ */
+ private void addIndex(final int x, final int z) {
+ // add the index and increment counter.
+ tmpIndices[stripIndex++] = x + z * clipSideSize;
+ }
+
+ /**
+ * Gets the number of triangles that are visible in current frame. This changes every frame.
+ */
+ public int getStripIndex() {
+ return stripIndex;
+ }
+
+ /**
+ * @return the vertexDistance
+ */
+ public int getVertexDistance() {
+ return vertexDistance;
+ }
+
+ public boolean isReady() {
+ return cache.isValid();
+ }
+
+ public TerrainCache getCache() {
+ return cache;
+ }
+
+ public void setHeightRange(final float heightRangeMin, final float heightRangeMax) {
+ this.heightRangeMin = heightRangeMin;
+ this.heightRangeMax = heightRangeMax;
+ }
+
+ public float getHeightRangeMax() {
+ return heightRangeMax;
+ }
+
+ public float getHeightRangeMin() {
+ return heightRangeMin;
+ }
+
+ public int getClipSideSize() {
+ return clipSideSize;
+ }
+
+ public Region getClipRegion() {
+ return clipRegion;
+ }
+
+ public Region getIntersectionRegion() {
+ return intersectionRegion;
+ }
+
+ public float getHeightScale() {
+ return heightScale;
+ }
+
+ public boolean isCullingEnabled() {
+ return cullingEnabled;
+ }
+
+ public void setCullingEnabled(final boolean cullingEnabled) {
+ this.cullingEnabled = cullingEnabled;
+ }
+
+ public void shutdown() {
+ cache.shutdown();
+ }
+}
\ No newline at end of file diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/Terrain.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/Terrain.java new file mode 100644 index 0000000..3b0afea --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/Terrain.java @@ -0,0 +1,712 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.extension.terrain.util.AbstractBresenhamTracer;
+import com.ardor3d.extension.terrain.util.ClipmapTerrainPicker;
+import com.ardor3d.extension.terrain.util.DoubleBufferedList;
+import com.ardor3d.extension.terrain.util.Region;
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.intersection.Pickable;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.GLSLShaderDataLogic;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.MaterialState;
+import com.ardor3d.renderer.state.MaterialState.MaterialFace;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.scenegraph.hint.DataMode;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.google.common.collect.Lists;
+import com.google.common.io.InputSupplier;
+
+/**
+ * An implementation of geometry clipmapping
+ */
+public class Terrain extends Node implements Pickable {
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(Terrain.class.getName());
+
+ /** Our picker. */
+ private ClipmapTerrainPicker _picker = null;
+
+ private List<ClipmapLevel> _clips;
+ private int _visibleLevels = 0;
+ private int _minVisibleLevel = 0;
+ private final Camera _terrainCamera;
+ private final int _clipSideSize;
+
+ private final BlendState blendState;
+
+ private boolean _initialized = false;
+
+ /** Shader for rendering clipmap geometry with morphing. */
+ private GLSLShaderObjectsState _geometryClipmapShader;
+
+ /** Reference to the texture clipmap */
+ private final List<TextureClipmap> _textureClipmaps = Lists.newArrayList();
+
+ /** Reference to normal map */
+ private TextureClipmap _normalClipmap;
+ private int _normalUnit = 5;
+
+ private final Vector3 transformedFrustumPos = new Vector3();
+
+ private final DoubleBufferedList<Region> mailBox = new DoubleBufferedList<Region>();
+
+ private InputSupplier<? extends InputStream> vertexShader;
+ private InputSupplier<? extends InputStream> pixelShader;
+
+ /** Timers for mailbox updates */
+ private long oldTime = 0;
+ private long updateTimer = 0;
+ private final long updateThreashold = 300;
+
+ final TextureState clipTextureState = new TextureState();
+
+ private final Comparator<Region> regionSorter = new Comparator<Region>() {
+ @Override
+ public int compare(final Region r1, final Region r2) {
+ return r1.getLevel() - r2.getLevel();
+ }
+ };
+
+ public Terrain(final Camera camera, final List<TerrainCache> cacheList, final int clipSideSize,
+ final TerrainConfiguration terrainConfiguration) {
+ _terrainCamera = camera;
+ _clipSideSize = clipSideSize;
+
+ _worldBound = new BoundingBox(Vector3.ZERO, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
+ Double.POSITIVE_INFINITY);
+
+ getSceneHints().setRenderBucketType(RenderBucketType.Opaque);
+ final CullState cs = new CullState();
+ cs.setEnabled(true);
+ cs.setCullFace(CullState.Face.Back);
+ setRenderState(cs);
+
+ final MaterialState materialState = new MaterialState();
+ materialState.setAmbient(MaterialFace.FrontAndBack, new ColorRGBA(1, 1, 1, 1));
+ materialState.setDiffuse(MaterialFace.FrontAndBack, new ColorRGBA(1, 1, 1, 1));
+ materialState.setSpecular(MaterialFace.FrontAndBack, new ColorRGBA(1, 1, 1, 1));
+ materialState.setShininess(MaterialFace.FrontAndBack, 64.0f);
+ setRenderState(materialState);
+
+ blendState = new BlendState();
+ blendState.setBlendEnabled(true);
+ blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ setRenderState(blendState);
+
+ // getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ try {
+ _clips = new ArrayList<ClipmapLevel>();
+
+ final float heightScale = terrainConfiguration.getScale().getYf();
+
+ for (int i = 0; i < cacheList.size(); i++) {
+ final TerrainCache cache = cacheList.get(i);
+ cache.setMailBox(mailBox);
+ final ClipmapLevel clipmap = new ClipmapLevel(i, camera, clipSideSize, heightScale, cache);
+ _clips.add(clipmap);
+ attachChild(clipmap);
+
+ clipmap.getSceneHints().setDataMode(DataMode.Arrays);
+
+ // clipmap.getSceneHints().setDataMode(DataMode.VBOInterleaved);
+ // final FloatBufferData interleavedData = new FloatBufferData();
+ // interleavedData.setVboAccessMode(VBOAccessMode.DynamicDraw);
+ // clipmap.getMeshData().setInterleavedData(interleavedData);
+
+ // clipmap.getSceneHints().setDataMode(DataMode.VBO);
+ // clipmap.getMeshData().getVertexCoords().setVboAccessMode(VBOAccessMode.DynamicDraw);
+ // clipmap.getMeshData().getIndices().setVboAccessMode(VBOAccessMode.DynamicDraw);
+ }
+ } catch (final Exception ex) {
+ ex.printStackTrace();
+ }
+
+ vertexShader = new UrlInputSupplier(ResourceLocatorTool.getClassPathResource(Terrain.class,
+ "com/ardor3d/extension/terrain/texturedGeometryClipmapShader.vert"));
+ pixelShader = new UrlInputSupplier(ResourceLocatorTool.getClassPathResource(Terrain.class,
+ "com/ardor3d/extension/terrain/texturedGeometryClipmapShader.frag"));
+
+ // setScale(terrainConfiguration.getScale());
+ // TODO: hack. unify scale handling over cache etc
+ setScale(terrainConfiguration.getScale().getX(), 1, terrainConfiguration.getScale().getZ());
+ setHeightRange(terrainConfiguration.getHeightRangeMin(), terrainConfiguration.getHeightRangeMax());
+ }
+
+ private final List<Long> timers = Lists.newArrayList();
+
+ @Override
+ protected void updateChildren(final double time) {
+ super.updateChildren(time);
+
+ for (int i = _minVisibleLevel; i < _clips.size(); i++) {
+ if (_clips.get(i).isReady()) {
+ _visibleLevels = i;
+ break;
+ }
+ }
+
+ // TODO: improve calcs for removing levels based on height above terrain
+ // getWorldTransform().applyInverse(_terrainCamera.getLocation(), transformedFrustumPos);
+ // final float heightRangeMax = 1f;
+ // if (transformedFrustumPos.getYf() > heightRangeMax) {
+ // final float diff = transformedFrustumPos.getYf() - heightRangeMax;
+ // final float x = (float) (diff * Math.tan(Math.toRadians(30)));
+ // for (int unit = _visibleLevels; unit < _clips.size(); unit++) {
+ // final float heightTest = _clipSideSize * MathUtils.pow2(unit) / x;
+ // if (heightTest > 1) {
+ // _visibleLevels = unit;
+ // break;
+ // }
+ // }
+ // }
+
+ if (timers.size() < _visibleLevels) {
+ for (int unit = 0; unit < _visibleLevels; unit++) {
+ timers.add(System.currentTimeMillis());
+ }
+ }
+ for (int unit = 0; unit < _visibleLevels; unit++) {
+ final long t = System.currentTimeMillis() - timers.get(unit);
+ if (t > 500) {
+ timers.set(unit, System.currentTimeMillis());
+ _clips.get(unit).updateCache();
+ }
+ }
+
+ // Update vertices.
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ _clips.get(i).updateVertices();
+ }
+
+ // Update from mailbox
+ updateFromMailbox();
+
+ // Update indices.
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ if (i == _visibleLevels) {
+ // Level 0 has no nested level, so pass null as parameter.
+ _clips.get(i).updateIndices(null);
+ } else {
+ // All other levels i have the level i-1 nested in.
+ _clips.get(i).updateIndices(_clips.get(i - 1));
+ }
+ }
+
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ _clips.get(i).getMeshData().getVertexCoords().setNeedsRefresh(true);
+ _clips.get(i).getMeshData().getIndices().setNeedsRefresh(true);
+ }
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ updateShader(r);
+
+ boolean first = true;
+ if (_normalClipmap != null) {
+ clipTextureState.setTexture(_normalClipmap.getTexture(), _normalUnit);
+ }
+ for (final TextureClipmap textureClipmap : _textureClipmaps) {
+ clipTextureState.setTexture(textureClipmap.getTexture());
+ if (first) {
+ blendState.setEnabled(false);
+ first = false;
+ } else {
+ blendState.setEnabled(true);
+ }
+
+ if (_textureClipmaps.size() > 1) {
+ r.getQueue().pushBuckets();
+ }
+
+ for (int i = _clips.size() - 1; i >= 0; i--) {
+ final ClipmapLevel clip = _clips.get(i);
+ clip.setRenderState(clipTextureState);
+ }
+
+ if (_textureClipmaps.size() > 1) {
+ _geometryClipmapShader.setUniform("scale", 1f / textureClipmap.getScale());
+ _geometryClipmapShader.setUniform("textureSize", (float) textureClipmap.getTextureSize());
+ _geometryClipmapShader.setUniform("texelSize", 1f / textureClipmap.getTextureSize());
+ _geometryClipmapShader.setUniform("levels", (float) textureClipmap.getTextureLevels());
+ _geometryClipmapShader.setUniform("validLevels", (float) textureClipmap.getValidLevels() - 1);
+ _geometryClipmapShader.setUniform("showDebug", textureClipmap.isShowDebug() ? 1.0f : 0.0f);
+ _geometryClipmapShader.setNeedsRefresh(true);
+ }
+
+ blendState.setNeedsRefresh(true);
+ this.updateWorldRenderStates(true);
+
+ if (!_initialized) {
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ final ClipmapLevel clip = _clips.get(i);
+
+ clip.getMeshData().getIndexBuffer().limit(clip.getMeshData().getIndexBuffer().capacity());
+ }
+
+ _initialized = true;
+ }
+
+ // draw levels from coarse to fine.
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ final ClipmapLevel clip = _clips.get(i);
+
+ if (clip.getStripIndex() > 0) {
+ clip.draw(r);
+ }
+ }
+
+ if (_textureClipmaps.size() > 1) {
+ r.renderBuckets();
+ r.getQueue().popBuckets();
+ }
+ }
+ }
+
+ private void updateFromMailbox() {
+ if (updateTimer > updateThreashold) {
+ final List<Region> regionList = mailBox.switchAndGet();
+ if (!regionList.isEmpty()) {
+ for (int i = regionList.size() - 1; i >= 0; i--) {
+ final Region region = regionList.get(i);
+
+ final ClipmapLevel clip = _clips.get(region.getLevel());
+ final Region clipRegion = clip.getIntersectionRegion();
+
+ if (clipRegion.intersects(region)) {
+ clipRegion.intersection(region);
+ } else {
+ regionList.remove(i);
+ }
+ }
+
+ Collections.sort(regionList, regionSorter);
+
+ final int start = regionList.size() - 1;
+ for (int i = start; i >= 0; i--) {
+ final Region region = regionList.get(i);
+
+ recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(),
+ region.getHeight());
+ }
+
+ for (int i = regionList.size() - 1; i >= 0; i--) {
+ final Region region = regionList.get(i);
+
+ final ClipmapLevel clip = _clips.get(region.getLevel());
+ final Region clipRegion = clip.getIntersectionRegion();
+
+ if (clipRegion.intersects(region)) {
+ clipRegion.intersection(region);
+ } else {
+ regionList.remove(i);
+ }
+ }
+
+ Collections.sort(regionList, regionSorter);
+
+ for (int i = regionList.size() - 1; i >= 0; i--) {
+ final Region region = regionList.get(i);
+ final ClipmapLevel clip = _clips.get(region.getLevel());
+ final FloatBuffer vertices = clip.getMeshData().getVertexBuffer();
+ final int vertexDistance = clip.getVertexDistance();
+
+ clip.getCache().updateRegion(vertices, region.getX() / vertexDistance,
+ region.getY() / vertexDistance, region.getWidth() / vertexDistance,
+ region.getHeight() / vertexDistance);
+ }
+ }
+ updateTimer %= updateThreashold;
+ }
+ final long time = System.currentTimeMillis();
+ updateTimer += time - oldTime;
+ oldTime = time;
+ }
+
+ private void recursiveAddUpdates(final List<Region> regionList, final int level, final int x, final int y,
+ final int width, final int height) {
+ if (level == 0) {
+ return;
+ }
+
+ final Region region = new Region(level - 1, x, y, width, height);
+ if (!regionList.contains(region)) {
+ regionList.add(region);
+ recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(),
+ region.getHeight());
+ }
+ }
+
+ private final Vector3 _boundsCenter = new Vector3();
+ private final Vector3 _boundsExtents = new Vector3();
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ final BoundingBox worldBound = (BoundingBox) _worldBound;
+ final Vector3 center = _boundsCenter.set(_terrainCamera.getLocation());
+ final double distanceToEdge = _clipSideSize * MathUtils.pow2(_clips.size() - 1) * 0.5;
+ final double heightScale = _clips.get(0).getHeightScale();
+ final double heightMin = _clips.get(0).getHeightRangeMin() * heightScale;
+ final double heightMax = _clips.get(0).getHeightRangeMax() * heightScale;
+
+ final Vector3 extents = _boundsExtents.set(distanceToEdge, (heightMax - heightMin) * 0.5, distanceToEdge);
+ worldToLocal(center, center);
+ worldBound.setXExtent(extents.getX());
+ worldBound.setYExtent(extents.getY());
+ worldBound.setZExtent(extents.getZ());
+ worldBound.setCenter(center.getX(), (heightMax + heightMin) * 0.5, center.getZ());
+ worldBound.transform(_worldTransform, worldBound);
+ clearDirty(DirtyType.Bounding);
+ }
+
+ /**
+ * Initialize/Update shaders
+ */
+ public void updateShader(final Renderer r) {
+ if (_geometryClipmapShader != null) {
+ getWorldTransform().applyInverse(_terrainCamera.getLocation(), transformedFrustumPos);
+ _geometryClipmapShader.setUniform("eyePosition", transformedFrustumPos);
+ for (final TextureClipmap textureClipmap : _textureClipmaps) {
+ textureClipmap.update(r, transformedFrustumPos);
+ }
+ if (_normalClipmap != null) {
+ _normalClipmap.update(r, transformedFrustumPos);
+ }
+
+ return;
+ }
+
+ reloadShader();
+ }
+
+ public void reloadShader() {
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+ if (caps.isGLSLSupported()) {
+ _geometryClipmapShader = new GLSLShaderObjectsState();
+ try {
+ _geometryClipmapShader.setVertexShader(vertexShader.getInput());
+ _geometryClipmapShader.setFragmentShader(pixelShader.getInput());
+ } catch (final IOException ex) {
+ Terrain.logger
+ .logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+
+ _geometryClipmapShader.setUniform("texture", 0);
+ _geometryClipmapShader.setUniform("clipSideSize", (float) _clipSideSize);
+
+ if (!_textureClipmaps.isEmpty()) {
+ final TextureClipmap textureClipmap = _textureClipmaps.get(0);
+ _geometryClipmapShader.setUniform("scale", 1f / textureClipmap.getScale());
+ _geometryClipmapShader.setUniform("textureSize", (float) textureClipmap.getTextureSize());
+ _geometryClipmapShader.setUniform("texelSize", 1f / textureClipmap.getTextureSize());
+
+ _geometryClipmapShader.setUniform("levels", (float) textureClipmap.getTextureLevels());
+ _geometryClipmapShader.setUniform("validLevels", (float) textureClipmap.getValidLevels() - 1);
+ _geometryClipmapShader.setUniform("minLevel", 0f);
+
+ _geometryClipmapShader.setUniform("showDebug", textureClipmap.isShowDebug() ? 1.0f : 0.0f);
+ }
+
+ _geometryClipmapShader.setShaderDataLogic(new GLSLShaderDataLogic() {
+ public void applyData(final GLSLShaderObjectsState shader, final Mesh mesh, final Renderer renderer) {
+ if (mesh instanceof ClipmapLevel) {
+ shader.setUniform("vertexDistance", (float) ((ClipmapLevel) mesh).getVertexDistance());
+ }
+ }
+ });
+
+ applyToClips();
+
+ for (final TextureClipmap textureClipmap : _textureClipmaps) {
+ textureClipmap.setShaderState(_geometryClipmapShader);
+ }
+
+ if (_normalClipmap != null) {
+ _normalClipmap.setShaderState(_geometryClipmapShader);
+ }
+
+ updateWorldRenderStates(false);
+ }
+ }
+
+ protected void applyToClips() {
+ for (int i = _clips.size() - 1; i >= 0; i--) {
+ final ClipmapLevel clip = _clips.get(i);
+ clip.setRenderState(_geometryClipmapShader);
+ }
+ }
+
+ public void regenerate(final Renderer renderer) {
+ for (int i = _clips.size() - 1; i >= 0; i--) {
+ if (!_clips.get(i).isReady()) {
+ _visibleLevels = i + 1;
+ break;
+ }
+ }
+
+ // Update vertices.
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ _clips.get(i).regenerate();
+ }
+
+ // Update indices.
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ if (i == _visibleLevels) {
+ // Level 0 has no nested level, so pass null as parameter.
+ _clips.get(i).updateIndices(null);
+ } else {
+ // All other levels i have the level i-1 nested in.
+ _clips.get(i).updateIndices(_clips.get(i - 1));
+ }
+ }
+
+ for (int i = _clips.size() - 1; i >= _visibleLevels; i--) {
+ _clips.get(i).getMeshData().getVertexCoords().setNeedsRefresh(true);
+ _clips.get(i).getMeshData().getIndices().setNeedsRefresh(true);
+ }
+
+ for (final TextureClipmap textureClipmap : _textureClipmaps) {
+ textureClipmap.regenerate(renderer);
+ }
+
+ if (_normalClipmap != null) {
+ _normalClipmap.regenerate(renderer);
+ }
+ }
+
+ /**
+ * @return the visibleLevels
+ */
+ public int getVisibleLevels() {
+ return _visibleLevels;
+ }
+
+ /**
+ * @param visibleLevels
+ * the visibleLevels to set
+ */
+ public void setVisibleLevels(final int visibleLevels) {
+ _visibleLevels = visibleLevels;
+ }
+
+ public void setHeightRange(final float heightRangeMin, final float heightRangeMax) {
+ for (int i = _clips.size() - 1; i >= 0; i--) {
+ final ClipmapLevel clip = _clips.get(i);
+ clip.setHeightRange(heightRangeMin, heightRangeMax);
+ }
+ }
+
+ public void setCullingEnabled(final boolean cullingEnabled) {
+ for (int i = _clips.size() - 1; i >= 0; i--) {
+ final ClipmapLevel clip = _clips.get(i);
+ clip.setCullingEnabled(cullingEnabled);
+ }
+ }
+
+ public void makePickable(final Class<? extends AbstractBresenhamTracer> tracerClass, final int maxChecks,
+ final Vector3 initialSpacing) throws InstantiationException, IllegalAccessException {
+ // init the terrain picker
+ _picker = new ClipmapTerrainPicker(_clips, tracerClass, maxChecks, initialSpacing);
+ }
+
+ public TextureClipmap getTextureClipmap() {
+ return _textureClipmaps.get(0);
+ }
+
+ public List<TextureClipmap> getTextureClipmaps() {
+ return _textureClipmaps;
+ }
+
+ public GLSLShaderObjectsState getGeometryClipmapShader() {
+ return _geometryClipmapShader;
+ }
+
+ public void setGeometryClipmapShader(final GLSLShaderObjectsState shaderState) {
+ _geometryClipmapShader = shaderState;
+
+ applyToClips();
+
+ for (final TextureClipmap textureClipmap : _textureClipmaps) {
+ textureClipmap.setShaderState(_geometryClipmapShader);
+ }
+
+ if (_normalClipmap != null) {
+ _normalClipmap.setShaderState(_geometryClipmapShader);
+ }
+ }
+
+ public ClipmapTerrainPicker getPicker() {
+ return _picker;
+ }
+
+ @Override
+ public boolean supportsBoundsIntersectionRecord() {
+ // for now we are not compatible with bounding volume picks
+ return false;
+ }
+
+ @Override
+ public boolean supportsPrimitivesIntersectionRecord() {
+ return true;
+ }
+
+ @Override
+ public boolean intersectsWorldBound(final Ray3 ray) {
+ // XXX: could optimize this by grabbing edges of terrain and checking if we are outside of that...
+ // for now we just return true.
+ return true;
+ }
+
+ @Override
+ public IntersectionRecord intersectsWorldBoundsWhere(final Ray3 ray) {
+ // for now we are not compatible with bounding volume picks
+ return null;
+ }
+
+ @Override
+ public IntersectionRecord intersectsPrimitivesWhere(final Ray3 ray) {
+ if (_picker != null) {
+ final Vector3 normalStore = new Vector3();
+ final Vector3 intersect = _picker.getTerrainIntersection(getWorldTransform(), _terrainCamera.getLocation(),
+ ray, null, normalStore);
+ if (intersect != null) {
+ final double distance = intersect.distance(ray.getOrigin());
+ final IntersectionRecord record = new IntersectionRecord(new double[] { distance },
+ new Vector3[] { intersect }, new Vector3[] { normalStore }, null);
+ return record;
+ }
+ }
+ return null;
+ }
+
+ public List<ClipmapLevel> getClipmaps() {
+ return _clips;
+ }
+
+ public void setVertexShader(final InputSupplier<? extends InputStream> vertexShader) {
+ this.vertexShader = vertexShader;
+ }
+
+ public void setPixelShader(final InputSupplier<? extends InputStream> pixelShader) {
+ this.pixelShader = pixelShader;
+ }
+
+ public void addTextureClipmap(final TextureClipmap textureClipmap) {
+ _textureClipmaps.add(textureClipmap);
+ }
+
+ /**
+ * set the minimum (highest resolution) clipmap level visible
+ *
+ * @param level
+ * clamped to valid range
+ */
+ public void setMinVisibleLevel(final int level) {
+ if (level < 0) {
+ _minVisibleLevel = 0;
+ } else if (level >= _clips.size()) {
+ _minVisibleLevel = _clips.size() - 1;
+ } else {
+ _minVisibleLevel = level;
+ }
+ }
+
+ public int getMinVisibleLevel() {
+ return _minVisibleLevel;
+ }
+
+ /**
+ * convenience function to set minimum (highest resolution) texture clipmap level on all TextureClipmaps held by
+ * this terrain
+ */
+ public void setTextureMinVisibleLevel(final int level) {
+ for (final TextureClipmap tc : _textureClipmaps) {
+ tc.setMinVisibleLevel(level);
+ }
+ }
+
+ public int getTextureMinVisibleLevel() {
+ if (!_textureClipmaps.isEmpty()) {
+ return _textureClipmaps.get(0).getMinVisibleLevel();
+ }
+ return 0;
+ }
+
+ public float getHeightAt(final double x, final double z) {
+ final Vector3 heightCalc = new Vector3(x, 0, z);
+ worldToLocal(heightCalc, heightCalc);
+ final float height = getClipmaps().get(0).getCache().getSubHeight(heightCalc.getXf(), heightCalc.getZf());
+ heightCalc.set(x, height, z);
+ localToWorld(heightCalc, heightCalc);
+ return heightCalc.getYf();
+ }
+
+ public void shutdown() {
+ for (final TextureClipmap textureClipmap : _textureClipmaps) {
+ textureClipmap.shutdown();
+ }
+ for (final ClipmapLevel terrainClipmap : _clips) {
+ terrainClipmap.shutdown();
+ }
+ if (_normalClipmap != null) {
+ _normalClipmap.shutdown();
+ }
+ }
+
+ public TextureState getClipTextureState() {
+ return clipTextureState;
+ }
+
+ public void setNormalClipmap(final TextureClipmap normalClipmap) {
+ _normalClipmap = normalClipmap;
+ }
+
+ public TextureClipmap getNormalClipmap() {
+ return _normalClipmap;
+ }
+
+ public int getNormalUnit() {
+ return _normalUnit;
+ }
+
+ public void setNormalUnit(final int unit) {
+ _normalUnit = unit;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainBuilder.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainBuilder.java new file mode 100644 index 0000000..0a132d5 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainBuilder.java @@ -0,0 +1,279 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import javax.swing.JFrame; + +import com.ardor3d.extension.terrain.util.BresenhamYUpGridTracer; +import com.ardor3d.extension.terrain.util.TerrainGridCachePanel; +import com.ardor3d.extension.terrain.util.TextureGridCachePanel; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Camera; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +public class TerrainBuilder { + private int cacheBufferSize = 4; + + /** The Constant logger. */ + private static final Logger logger = Logger.getLogger(TerrainBuilder.class.getName()); + + public static int MAX_PICK_CHECKS = 500; + + private final TerrainDataProvider terrainDataProvider; + private final Camera camera; + + private int clipmapTerrainCount = 20; + private int clipmapTerrainSize = 127; // pow2 - 1 + private int clipmapTextureCount = 20; + private int clipmapTextureSize = 128; + + private int gridCacheThreadCount = 10; + + private int mapId = -1; + + private boolean showDebugPanels = false; + + private final List<TextureSource> extraTextureSources = Lists.newArrayList(); + + public TerrainBuilder(final TerrainDataProvider terrainDataProvider, final Camera camera) { + this.terrainDataProvider = terrainDataProvider; + this.camera = camera; + } + + public void addTextureConnection(final TextureSource textureSource) { + extraTextureSources.add(textureSource); + } + + public Terrain build() throws Exception { + final Map<Integer, String> availableMaps = terrainDataProvider.getAvailableMaps(); + if (availableMaps.isEmpty()) { + throw new Exception("No available maps found on this terrain provider."); + } + + int selectedMapId = 0; + if (mapId < 0) { + selectedMapId = availableMaps.keySet().iterator().next(); + } else { + if (!availableMaps.containsKey(mapId)) { + throw new IllegalArgumentException(mapId + " is not a valid terrain ID on this terrain provider."); + } + selectedMapId = mapId; + } + + final TerrainSource terrainSource = terrainDataProvider.getTerrainSource(selectedMapId); + final Terrain terrain = buildTerrainSystem(terrainSource); + + final TextureSource textureSource = terrainDataProvider.getTextureSource(selectedMapId); + if (textureSource != null) { + terrain.addTextureClipmap(buildTextureSystem(textureSource)); + + for (final TextureSource extraSource : extraTextureSources) { + terrain.addTextureClipmap(buildTextureSystem(extraSource)); + } + } + + final TextureSource normalSource = terrainDataProvider.getNormalMapSource(selectedMapId); + if (normalSource != null) { + terrain.setNormalClipmap(buildTextureSystem(normalSource)); + } + + return terrain; + } + + private Terrain buildTerrainSystem(final TerrainSource terrainSource) throws Exception { + final TerrainConfiguration terrainConfiguration = terrainSource.getConfiguration(); + logger.info(terrainConfiguration.toString()); + + final int clipmapLevels = terrainConfiguration.getTotalNrClipmapLevels(); + final int clipLevelCount = Math.min(clipmapLevels, clipmapTerrainCount); + + final int tileSize = terrainConfiguration.getCacheGridSize(); + + int cacheSize = (clipmapTerrainSize + 1) / tileSize + cacheBufferSize; + cacheSize += cacheSize & 1 ^ 1; + + logger.info("server clipmapLevels: " + clipmapLevels); + + final List<TerrainCache> cacheList = Lists.newArrayList(); + TerrainCache parentCache = null; + + final int baseLevel = Math.max(clipmapLevels - clipLevelCount, 0); + int level = clipLevelCount - 1; + + logger.info("baseLevel: " + baseLevel); + logger.info("level: " + level); + + final ThreadPoolExecutor tileThreadService = new ThreadPoolExecutor(gridCacheThreadCount, gridCacheThreadCount, + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder() + .setThreadFactory(Executors.defaultThreadFactory()).setDaemon(true) + .setNameFormat("TerrainTileThread-%s").setPriority(Thread.MIN_PRIORITY).build()); + + for (int i = baseLevel; i < clipmapLevels; i++) { + final TerrainCache gridCache = new TerrainGridCache(parentCache, cacheSize, terrainSource, tileSize, + clipmapTerrainSize, terrainConfiguration, level--, i, tileThreadService); + + parentCache = gridCache; + cacheList.add(gridCache); + } + Collections.reverse(cacheList); + + final Terrain terrain = new Terrain(camera, cacheList, clipmapTerrainSize, terrainConfiguration); + + terrain.makePickable(BresenhamYUpGridTracer.class, MAX_PICK_CHECKS, new Vector3(1, 0, 1)); + + logger.info("client clipmapLevels: " + cacheList.size()); + + if (showDebugPanels) { + final TerrainGridCachePanel panel = new TerrainGridCachePanel(cacheList, cacheSize); + final JFrame frame = new JFrame("Terrain Cache Debug"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().add(panel); + frame.setBounds(10, 10, panel.getSize().width, panel.getSize().height); + frame.setVisible(true); + } + + return terrain; + } + + private TextureClipmap buildTextureSystem(final TextureSource textureSource) throws Exception { + final TextureConfiguration textureConfiguration = textureSource.getConfiguration(); + logger.info(textureConfiguration.toString()); + + final int clipmapLevels = textureConfiguration.getTotalNrClipmapLevels(); + final int textureClipLevelCount = Math.min(clipmapLevels, clipmapTextureCount); + + final int tileSize = textureConfiguration.getCacheGridSize(); + + int cacheSize = (clipmapTextureSize + 1) / tileSize + cacheBufferSize; + cacheSize += cacheSize & 1 ^ 1; + + logger.info("server clipmapLevels: " + clipmapLevels); + + final List<TextureCache> cacheList = Lists.newArrayList(); + TextureCache parentCache = null; + final int baseLevel = Math.max(clipmapLevels - textureClipLevelCount, 0); + int level = textureClipLevelCount - 1; + + logger.info("baseLevel: " + baseLevel); + logger.info("level: " + level); + + final ThreadPoolExecutor tileThreadService = new ThreadPoolExecutor(gridCacheThreadCount, gridCacheThreadCount, + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder() + .setThreadFactory(Executors.defaultThreadFactory()).setDaemon(true) + .setNameFormat("TextureTileThread-%s").setPriority(Thread.MIN_PRIORITY).build()); + + for (int i = baseLevel; i < clipmapLevels; i++) { + final TextureCache gridCache = new TextureGridCache(parentCache, cacheSize, textureSource, tileSize, + clipmapTextureSize, textureConfiguration, level--, i, tileThreadService); + + parentCache = gridCache; + cacheList.add(gridCache); + } + Collections.reverse(cacheList); + + logger.info("client clipmapLevels: " + cacheList.size()); + + final TextureClipmap textureClipmap = new TextureClipmap(cacheList, clipmapTextureSize, textureConfiguration); + + if (showDebugPanels) { + final TextureGridCachePanel panel = new TextureGridCachePanel(cacheList, cacheSize); + final JFrame frame = new JFrame("Texture Cache Debug"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().add(panel); + frame.setBounds(10, 120, panel.getSize().width, panel.getSize().height); + frame.setVisible(true); + } + + return textureClipmap; + } + + public TerrainBuilder setCacheBufferSize(final int size) { + cacheBufferSize = size; + return this; + } + + public int getCacheBufferSize() { + return cacheBufferSize; + } + + public TerrainBuilder setClipmapTerrainCount(final int clipmapTerrainCount) { + this.clipmapTerrainCount = clipmapTerrainCount; + return this; + } + + public int getClipmapTerrainCount() { + return clipmapTerrainCount; + } + + public TerrainBuilder setClipmapTerrainSize(final int clipmapTerrainSize) { + this.clipmapTerrainSize = clipmapTerrainSize; + return this; + } + + public int getClipmapTerrainSize() { + return clipmapTerrainSize; + } + + public TerrainBuilder setClipmapTextureCount(final int clipmapTextureCount) { + this.clipmapTextureCount = clipmapTextureCount; + return this; + } + + public int getClipmapTextureCount() { + return clipmapTextureCount; + } + + public TerrainBuilder setClipmapTextureSize(final int clipmapTextureSize) { + this.clipmapTextureSize = clipmapTextureSize; + return this; + } + + public int getClipmapTextureSize() { + return clipmapTextureSize; + } + + public TerrainBuilder setShowDebugPanels(final boolean showDebugPanels) { + this.showDebugPanels = showDebugPanels; + return this; + } + + public boolean isShowDebugPanels() { + return showDebugPanels; + } + + public TerrainBuilder setMapId(final int mapId) { + this.mapId = mapId; + return this; + } + + public int getMapId() { + return mapId; + } + + public TerrainBuilder setGridCacheThreadCount(final int gridCacheThreadCount) { + this.gridCacheThreadCount = gridCacheThreadCount; + return this; + } + + public int getGridCacheThreadCount() { + return gridCacheThreadCount; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainCache.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainCache.java new file mode 100644 index 0000000..d889e69 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainCache.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client; + +import java.nio.FloatBuffer; +import java.util.Set; + +import com.ardor3d.extension.terrain.util.DoubleBufferedList; +import com.ardor3d.extension.terrain.util.Region; +import com.ardor3d.extension.terrain.util.Tile; +import com.ardor3d.math.type.ReadOnlyVector3; + +/** + * Fetches data from a source to the clipmap destination data through updateRegion. + * + */ +public interface TerrainCache { + /** + * Tell the cache the current position so that it can start loading affected tiles + * + * @param x + * @param y + */ + void setCurrentPosition(int x, int y); + + float getHeight(int x, int z); + + float getSubHeight(float x, float z); + + /** + * Update destinationData from cache in specified region + * + * @param destinationData + * @param sourceX + * @param sourceY + * @param width + * @param height + */ + void updateRegion(FloatBuffer destinationData, int sourceX, int sourceY, int width, int height); + + void getEyeCoords(float[] destinationData, int sourceX, int sourceY, ReadOnlyVector3 eyePos); + + boolean isValid(); + + void setMailBox(final DoubleBufferedList<Region> mailBox); + + Set<Tile> handleUpdateRequests(); + + void shutdown(); +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainConfiguration.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainConfiguration.java new file mode 100644 index 0000000..e8362ca --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainConfiguration.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +/** + * Terrain Configuration data for a specific map. + */ +public class TerrainConfiguration { + /** Total number of clipmap levels in this map */ + private final int totalNrClipmapLevels; + /** "Tile size" for each tile in the cache */ + private final int cacheGridSize; + /** Scale of one unit of terrain in meters */ + private final ReadOnlyVector3 scale; + /** Minimum height value in the map */ + private final float heightRangeMin; + /** Maximum height value in the map */ + private final float heightRangeMax; + /** True if tiles are only valid in positive coordinates */ + private final boolean onlyPositiveQuadrant; + + public TerrainConfiguration(final int totalNrClipmapLevels, final int cacheGridSize, final ReadOnlyVector3 scale, + final float heightRangeMin, final float heightRangeMax, final boolean onlyPositiveQuadrant) { + this.totalNrClipmapLevels = totalNrClipmapLevels; + this.cacheGridSize = cacheGridSize; + this.scale = new Vector3(scale); + this.heightRangeMin = heightRangeMin; + this.heightRangeMax = heightRangeMax; + this.onlyPositiveQuadrant = onlyPositiveQuadrant; + } + + public int getCacheGridSize() { + return cacheGridSize; + } + + public float getHeightRangeMin() { + return heightRangeMin; + } + + public float getHeightRangeMax() { + return heightRangeMax; + } + + public ReadOnlyVector3 getScale() { + return scale; + } + + public int getTotalNrClipmapLevels() { + return totalNrClipmapLevels; + } + + public boolean isOnlyPositiveQuadrant() { + return onlyPositiveQuadrant; + } + + @Override + public String toString() { + return "TerrainConfiguration [cacheGridSize=" + cacheGridSize + ", heightRangeMax=" + heightRangeMax + + ", heightRangeMin=" + heightRangeMin + ", onlyPositiveQuadrant=" + onlyPositiveQuadrant + ", scale=" + + scale + ", totalNrClipmapLevels=" + totalNrClipmapLevels + "]"; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainDataProvider.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainDataProvider.java new file mode 100644 index 0000000..10e3e8b --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainDataProvider.java @@ -0,0 +1,50 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.client;
+
+import java.util.Map;
+
+/**
+ * The TerrainDataProvider is the connection between the terrain core and external data.
+ */
+public interface TerrainDataProvider {
+ /**
+ * Request for all available maps. Returns a Map with mapIDs and map names.
+ *
+ * @return Available maps
+ * @throws Exception
+ */
+ Map<Integer, String> getAvailableMaps() throws Exception;
+
+ /**
+ * Request for a TerrainSource of valid type for this Provider.
+ *
+ * @param mapId
+ * @return
+ */
+ TerrainSource getTerrainSource(int mapId);
+
+ /**
+ * Request for a TextureSource of valid type for this Provider.
+ *
+ * @param mapId
+ * @return
+ */
+ TextureSource getTextureSource(int mapId);
+
+ /**
+ * Request for a normalmap TextureSource of valid type for this Provider.
+ *
+ * @param mapId
+ * @return
+ */
+ TextureSource getNormalMapSource(int mapId);
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainGridCache.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainGridCache.java new file mode 100644 index 0000000..8c03bdd --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainGridCache.java @@ -0,0 +1,644 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.client; + +import java.nio.FloatBuffer; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.extension.terrain.util.DoubleBufferedList; +import com.ardor3d.extension.terrain.util.Region; +import com.ardor3d.extension.terrain.util.Tile; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.google.common.collect.Sets; + +/** + * Special tile/grid based cache for terrain data + */ +public class TerrainGridCache implements TerrainCache, Runnable { + /** The Constant logger. */ + private static final Logger logger = Logger.getLogger(TerrainGridCache.class.getName()); + + private final TerrainSource source; + + private final TerrainCache parentCache; + + private final int cacheSize; + private final int tileSize; + private final int dataSize; + + private final float[] data; + private final CacheData[][] cache; + private final int destinationSize; + + private final int clipmapLevel; + private final int requestedLevel; + + private final Set<TileLoadingData> currentTiles = Sets.newHashSet(); + private Set<TileLoadingData> newThreadTiles = Sets.newHashSet(); + private Set<TileLoadingData> backThreadTiles = Sets.newHashSet(); + private final Object SWAP_LOCK = new Object(); + private int backCurrentTileX = Integer.MAX_VALUE; + private int backCurrentTileY = Integer.MAX_VALUE; + private boolean updated = false; + + private final TerrainConfiguration terrainConfiguration; + + private final int vertexDistance; + private final float heightScale; + + private final int TILELOCATOR_SLEEP = 100; + + private boolean exit = false; + + private final boolean enableDebug = true; + private final Set<TileLoadingData> debugTiles = Sets.newHashSet(); + + public Set<TileLoadingData> getDebugTiles() { + Set<TileLoadingData> copyTiles = null; + synchronized (debugTiles) { + copyTiles = Sets.newHashSet(debugTiles); + } + return copyTiles; + } + + private final ThreadPoolExecutor tileThreadService; + + private DoubleBufferedList<Region> mailBox; + + private Set<Tile> validTiles; + + public TerrainGridCache(final TerrainCache parentCache, final int cacheSize, final TerrainSource source, + final int tileSize, final int destinationSize, final TerrainConfiguration terrainConfiguration, + final int clipmapLevel, final int requestedLevel, final ThreadPoolExecutor tileThreadService) { + this.parentCache = parentCache; + this.cacheSize = cacheSize; + this.source = source; + this.tileSize = tileSize; + dataSize = tileSize * cacheSize; + this.destinationSize = destinationSize; + heightScale = terrainConfiguration.getScale().getYf(); + this.terrainConfiguration = terrainConfiguration; + this.clipmapLevel = clipmapLevel; + this.requestedLevel = requestedLevel; + + this.tileThreadService = tileThreadService; + // tileThreadService = new ThreadPoolExecutor(nrThreads, nrThreads, 0L, TimeUnit.MILLISECONDS, + // new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder() + // .setThreadFactory(Executors.defaultThreadFactory()).setDaemon(true) + // .setNameFormat("TerrainTileThread-%s").setPriority(Thread.MIN_PRIORITY).build()); + + data = new float[dataSize * dataSize]; + + cache = new CacheData[cacheSize][cacheSize]; + for (int i = 0; i < cacheSize; i++) { + for (int j = 0; j < cacheSize; j++) { + cache[i][j] = new CacheData(); + } + } + + vertexDistance = (int) Math.pow(2, clipmapLevel); + } + + private boolean started = false; + private final int locatorSize = 20; + private Tile locatorTile = new Tile(Integer.MAX_VALUE, Integer.MAX_VALUE); + + @Override + public Set<Tile> handleUpdateRequests() { + Set<Tile> updateTiles; + try { + updateTiles = source.getInvalidTiles(requestedLevel, backCurrentTileX - cacheSize / 2, backCurrentTileY + - cacheSize / 2, cacheSize, cacheSize); + if (updateTiles == null || updateTiles.isEmpty()) { + return null; + } + } catch (final Exception e) { + logger.log(Level.WARNING, "Exception processing updates", e); + return null; + } + + for (final Tile tile : updateTiles) { + float[] sourceData; + try { + sourceData = source.getTile(requestedLevel, tile); + } catch (final InterruptedException e) { + // XXX: Loading can be interrupted + return null; + } catch (final Exception e) { + logger.log(Level.WARNING, "Exception getting tile", e); + return null; + } + + if (sourceData == null) { + continue; + } + + final int destX = MathUtils.moduloPositive(tile.getX(), cacheSize); + final int destY = MathUtils.moduloPositive(tile.getY(), cacheSize); + + final int offset = destY * tileSize * dataSize + destX * tileSize; + for (int y = 0; y < tileSize; y++) { + System.arraycopy(sourceData, y * tileSize, data, offset + y * dataSize, tileSize); + } + } + + return updateTiles; + } + + public void setCurrentPosition(final int x, final int y) { + final int tileX = MathUtils.floor((float) x / tileSize); + final int tileY = MathUtils.floor((float) y / tileSize); + + final int diffX = tileX - backCurrentTileX; + final int diffY = tileY - backCurrentTileY; + + if (diffX == 0 && diffY == 0) { + return; + } + + synchronized (SWAP_LOCK) { + backCurrentTileX = tileX; + backCurrentTileY = tileY; + + final Set<TileLoadingData> newTiles = Sets.newHashSet(); + for (int i = 0; i < cacheSize; i++) { + for (int j = 0; j < cacheSize; j++) { + final int sourceX = tileX + j - cacheSize / 2; + final int sourceY = tileY + i - cacheSize / 2; + + final int destX = MathUtils.moduloPositive(sourceX, cacheSize); + final int destY = MathUtils.moduloPositive(sourceY, cacheSize); + + newTiles.add(new TileLoadingData(mailBox, new Tile(sourceX, sourceY), new Tile(destX, destY), + source, cache, data, tileSize, dataSize, clipmapLevel, requestedLevel)); + } + } + + final Iterator<TileLoadingData> tileIterator = currentTiles.iterator(); + while (tileIterator.hasNext()) { + final TileLoadingData data = tileIterator.next(); + + if (!newTiles.contains(data)) { + cache[data.destTile.getX()][data.destTile.getY()].isValid = false; + + data.isCancelled = true; + final Future<Boolean> future = data.future; + if (future != null && !future.isDone()) { + future.cancel(true); + } + tileIterator.remove(); + if (backThreadTiles.contains(data)) { + backThreadTiles.remove(data); + } + } else { + newTiles.remove(data); + } + } + + backThreadTiles.addAll(newTiles); + updated = true; + + currentTiles.addAll(newTiles); + } + + tileThreadService.purge(); + + if (enableDebug) { + synchronized (debugTiles) { + debugTiles.clear(); + debugTiles.addAll(currentTiles); + } + } + + if (!started) { + final Thread terrainCacheThread = new Thread(this, "TerrainGridCache-" + clipmapLevel); + terrainCacheThread.setDaemon(true); + terrainCacheThread.start(); + started = true; + } + } + + @Override + public void run() { + while (!exit) { + try { + Thread.sleep(TILELOCATOR_SLEEP); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + + int tileX; + int tileY; + boolean needsUpdate = false; + synchronized (SWAP_LOCK) { + final Set<TileLoadingData> tmp = newThreadTiles; + newThreadTiles = backThreadTiles; + backThreadTiles = tmp; + backThreadTiles.clear(); + + tileX = backCurrentTileX; + tileY = backCurrentTileY; + + needsUpdate = updated; + updated = false; + } + + if (needsUpdate) { + if (tileX <= locatorTile.getX() - locatorSize / 2 + 1 + || tileX >= locatorTile.getX() + locatorSize / 2 - 2 + || tileY <= locatorTile.getY() - locatorSize / 2 + 1 + || tileY >= locatorTile.getY() + locatorSize / 2 - 2) { + try { + validTiles = source.getValidTiles(requestedLevel, tileX - locatorSize / 2, tileY - locatorSize + / 2, locatorSize, locatorSize); + } catch (final Exception e) { + logger.log(Level.WARNING, "Exception getting source info", e); + } + locatorTile = new Tile(tileX, tileY); + } + + threadedUpdateTiles(); + } + } + } + + private void threadedUpdateTiles() { + final Iterator<TileLoadingData> tileIterator = newThreadTiles.iterator(); + while (tileIterator.hasNext()) { + final TileLoadingData data = tileIterator.next(); + if (validTiles == null || validTiles.contains(data.sourceTile)) { + cache[data.destTile.getX()][data.destTile.getY()].isValid = false; + + final Future<Boolean> future = tileThreadService.submit(data); + data.future = future; + } + tileIterator.remove(); + } + } + + @Override + public float getHeight(final int x, final int z) { + int tileX = MathUtils.floor((float) x / tileSize); + int tileY = MathUtils.floor((float) z / tileSize); + final CacheData tileData; + final int cacheMin = -cacheSize / 2, cacheMax = cacheSize / 2 - (cacheSize % 2 == 0 ? 1 : 0); + if (tileX < cacheMin || tileX > cacheMax || tileY < cacheMin || tileY > cacheMax) { + tileData = null; + } else { + tileX = MathUtils.moduloPositive(tileX, cacheSize); + tileY = MathUtils.moduloPositive(tileY, cacheSize); + tileData = cache[tileX][tileY]; + } + + if (tileData == null || !tileData.isValid) { + if (parentCache != null) { + if (x % 2 == 0 && z % 2 == 0) { + return parentCache.getHeight(x / 2, z / 2); + } else { + return parentCache.getSubHeight(x / 2f, z / 2f); + } + } else { + return terrainConfiguration.getHeightRangeMin(); + } + } else { + final int dataX = MathUtils.moduloPositive(x, dataSize); + final int dataY = MathUtils.moduloPositive(z, dataSize); + + final float min = terrainConfiguration.getHeightRangeMin(); + final float max = terrainConfiguration.getHeightRangeMax(); + final float height = data[dataY * dataSize + dataX]; + if (height < min || height > max) { + return min; + } + + return height * heightScale; + } + } + + @Override + public float getSubHeight(final float x, final float z) { + int tileX = MathUtils.floor(x / tileSize); + int tileY = MathUtils.floor(z / tileSize); + final CacheData tileData; + final int min = -cacheSize / 2, max = cacheSize / 2 - (cacheSize % 2 == 0 ? 1 : 0); + if (tileX < min || tileX > max || tileY < min || tileY > max) { + tileData = null; + } else { + tileX = MathUtils.moduloPositive(tileX, cacheSize); + tileY = MathUtils.moduloPositive(tileY, cacheSize); + tileData = cache[tileX][tileY]; + } + + if (tileData == null || !tileData.isValid) { + if (parentCache != null) { + return parentCache.getSubHeight(x / 2f, z / 2f); + } else { + return terrainConfiguration.getHeightRangeMin(); + } + } else { + final double col = MathUtils.floor(x); + final double row = MathUtils.floor(z); + + final double intOnX = x - col, intOnZ = z - row; + + final double col1 = col + 1; + final double row1 = row + 1; + + final double topLeft = getHeight((int) col, (int) row); + final double topRight = getHeight((int) col1, (int) row); + final double bottomLeft = getHeight((int) col, (int) row1); + final double bottomRight = getHeight((int) col1, (int) row1); + + return (float) MathUtils.lerp(intOnZ, MathUtils.lerp(intOnX, topLeft, topRight), + MathUtils.lerp(intOnX, bottomLeft, bottomRight)); + } + } + + @Override + public void updateRegion(final FloatBuffer destinationData, final int sourceX, final int sourceY, final int width, + final int height) { + for (int z = 0; z < height; z++) { + final int currentZ = sourceY + z; + for (int x = 0; x < width; x++) { + final int currentX = sourceX + x; + + final float cacheHeight = getHeight(currentX, currentZ); + + final int destX = MathUtils.moduloPositive(currentX, destinationSize); + final int destY = MathUtils.moduloPositive(currentZ, destinationSize); + final int indexDest = (destY * destinationSize + destX) * ClipmapLevel.VERT_SIZE; + + destinationData.put(indexDest + 0, currentX * vertexDistance); // x + destinationData.put(indexDest + 1, cacheHeight); // y + destinationData.put(indexDest + 2, currentZ * vertexDistance); // z + + if (parentCache == null) { + destinationData.put(indexDest + 3, cacheHeight); // w + } else { + final int coarseX1 = (currentX < 0 ? currentX - 1 : currentX) / 2; + int coarseZ1 = (currentZ < 0 ? currentZ - 1 : currentZ) / 2; + + final boolean onGridX = currentX % 2 == 0; + final boolean onGridZ = currentZ % 2 == 0; + + if (onGridX && onGridZ) { + final float coarseHeight = parentCache.getHeight(coarseX1, coarseZ1); + destinationData.put(indexDest + 3, coarseHeight); // w + } else { + int coarseX2 = coarseX1; + int coarseZ2 = coarseZ1; + if (!onGridX && onGridZ) { + coarseX2++; + } else if (onGridX && !onGridZ) { + coarseZ2++; + } else if (!onGridX && !onGridZ) { + coarseX2++; + coarseZ1++; + } + + final float coarser1 = parentCache.getHeight(coarseX1, coarseZ1); + final float coarser2 = parentCache.getHeight(coarseX2, coarseZ2); + + // Apply the median of the coarser heightvalues to the W + // value + destinationData.put(indexDest + 3, (coarser1 + coarser2) * 0.5f); // w + } + } + } + } + } + + @Override + public void getEyeCoords(final float[] destinationData, final int sourceX, final int sourceY, + final ReadOnlyVector3 eyePos) { + for (int z = 0; z < 2; z++) { + final int currentZ = sourceY + z; + for (int x = 0; x < 2; x++) { + final int currentX = sourceX + x; + + final float cacheHeight = getHeight(currentX, currentZ); + + final int indexDest = z * 8 + x * 4; + destinationData[indexDest + 0] = currentX * vertexDistance - eyePos.getXf(); // x + destinationData[indexDest + 1] = currentZ * vertexDistance - eyePos.getZf(); // z + destinationData[indexDest + 2] = cacheHeight; // h + + if (parentCache == null) { + destinationData[indexDest + 3] = cacheHeight; // w + } else { + final int coarseX1 = (currentX < 0 ? currentX - 1 : currentX) / 2; + int coarseZ1 = (currentZ < 0 ? currentZ - 1 : currentZ) / 2; + + final boolean onGridX = currentX % 2 == 0; + final boolean onGridZ = currentZ % 2 == 0; + + if (onGridX && onGridZ) { + final float coarseHeight = parentCache.getHeight(coarseX1, coarseZ1); + destinationData[indexDest + 3] = coarseHeight; // w + } else { + int coarseX2 = coarseX1; + int coarseZ2 = coarseZ1; + if (!onGridX && onGridZ) { + coarseX2++; + } else if (onGridX && !onGridZ) { + coarseZ2++; + } else if (!onGridX && !onGridZ) { + coarseX2++; + coarseZ1++; + } + + final float coarser1 = parentCache.getHeight(coarseX1, coarseZ1); + final float coarser2 = parentCache.getHeight(coarseX2, coarseZ2); + + // Apply the median of the coarser heightvalues to the W + // value + destinationData[indexDest + 3] = (coarser1 + coarser2) * 0.5f; // w + } + } + } + } + } + + public static class TileLoadingData implements Callable<Boolean> { + private final TerrainSource source; + private final float[] data; + private final CacheData[][] cache; + private final int tileSize; + private final int dataSize; + + private final int clipmapLevel; + private final int requestedLevel; + + private final DoubleBufferedList<Region> mailBox; + + public final Tile sourceTile; + public final Tile destTile; + + public boolean isCancelled = false; + public Future<Boolean> future; + + public enum State { + init, loading, finished + } + + public State state = State.init; + + public TileLoadingData(final DoubleBufferedList<Region> mailBox, final Tile sourceTile, final Tile destTile, + final TerrainSource source, final CacheData[][] cache, final float[] data, final int tileSize, + final int dataSize, final int clipmapLevel, final int requestedLevel) { + this.mailBox = mailBox; + + this.sourceTile = sourceTile; + this.destTile = destTile; + + this.source = source; + this.cache = cache; + this.data = data; + this.tileSize = tileSize; + this.dataSize = dataSize; + + this.clipmapLevel = clipmapLevel; + this.requestedLevel = requestedLevel; + } + + @Override + public Boolean call() throws Exception { + state = State.loading; + + if (isCancelled()) { + return false; + } + + float[] sourceData = null; + try { + sourceData = source.getTile(requestedLevel, sourceTile); + } catch (final InterruptedException e) { + // XXX: Loading can be interrupted + return false; + } catch (final Throwable t) { + t.printStackTrace(); + } + + if (sourceData == null || isCancelled()) { + return false; + } + + final int offset = destTile.getY() * tileSize * dataSize + destTile.getX() * tileSize; + for (int y = 0; y < tileSize; y++) { + System.arraycopy(sourceData, y * tileSize, data, offset + y * dataSize, tileSize); + } + + if (isCancelled()) { + return false; + } + + state = State.finished; + cache[destTile.getX()][destTile.getY()].isValid = true; + + final int vertexDistance = MathUtils.pow2(clipmapLevel); + final Region region = new Region(clipmapLevel, sourceTile.getX() * tileSize * vertexDistance, + sourceTile.getY() * tileSize * vertexDistance, tileSize * vertexDistance, tileSize * vertexDistance); + if (mailBox != null) { + mailBox.add(region); + } + + return true; + } + + private boolean isCancelled() { + if (future != null && future.isCancelled()) { + return true; + } else if (isCancelled) { + return true; + } else if (Thread.interrupted()) { + return true; + } + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (destTile == null ? 0 : destTile.hashCode()); + result = prime * result + (sourceTile == null ? 0 : sourceTile.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof TileLoadingData)) { + return false; + } + final TileLoadingData other = (TileLoadingData) obj; + if (destTile == null) { + if (other.destTile != null) { + return false; + } + } else if (!destTile.equals(other.destTile)) { + return false; + } + if (sourceTile == null) { + if (other.sourceTile != null) { + return false; + } + } else if (!sourceTile.equals(other.sourceTile)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "TileLoadingData [destTile=" + destTile + ", sourceTile=" + sourceTile + "]"; + } + } + + public static class CacheData { + public boolean isValid; + + public CacheData() { + isValid = false; + } + } + + public boolean isValid() { + int nrValid = 0; + for (final TileLoadingData data : currentTiles) { + if (cache[data.destTile.getX()][data.destTile.getY()].isValid) { + nrValid++; + } + } + return nrValid != 0; + } + + public void setMailBox(final DoubleBufferedList<Region> mailBox) { + this.mailBox = mailBox; + } + + public void shutdown() { + exit = true; + started = false; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainSource.java new file mode 100644 index 0000000..a7ca923 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainSource.java @@ -0,0 +1,66 @@ + +package com.ardor3d.extension.terrain.client; + +import java.util.Set; + +import com.ardor3d.extension.terrain.util.Tile; + +/** + * Feeds terrain data to a TerrainCache + */ +public interface TerrainSource { + /** + * Called to initialize and setup the geometry clipmap terrain. + * + * @return TerrainConfiguration + * @throws Exception + */ + TerrainConfiguration getConfiguration() throws Exception; + + /** + * Returns which tiles that contain data in the requested region. + * + * @param clipmapLevel + * @param tileX + * @param tileY + * @param numTilesX + * @param numTilesY + * @return + * @throws Exception + */ + Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, int numTilesX, int numTilesY) + throws Exception; + + /** + * Returns which tiles that should be marked as invalid and updated in the requested region. + * + * @param clipmapLevel + * @param tileX + * @param tileY + * @param numTilesX + * @param numTilesY + * @return + * @throws Exception + */ + Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, int numTilesX, int numTilesY) + throws Exception; + + /** + * Returns the contributing source id for the requested tile. + * + * @param clipmapLevel + * @param tile + * @return + */ + int getContributorId(int clipmapLevel, Tile tile); + + /** + * Request for height data for a tile. + * + * @param clipmapLevel + * @param tile + * @return + * @throws Exception + */ + float[] getTile(int clipmapLevel, final Tile tile) throws Exception; +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureCache.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureCache.java new file mode 100644 index 0000000..f6d9aaa --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureCache.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client; + +import java.nio.ByteBuffer; +import java.util.Set; + +import com.ardor3d.extension.terrain.util.DoubleBufferedList; +import com.ardor3d.extension.terrain.util.Region; +import com.ardor3d.extension.terrain.util.Tile; + +/** + * Fetches data from a source to the texture clipmap destination data through updateRegion. + * + */ +public interface TextureCache { + void setCurrentPosition(final int x, final int y); + + int getColor(final int x, final int z); + + int getSubColor(final float x, final float z); + + void updateRegion(ByteBuffer destinationData, final int sourceX, final int sourceY, final int destX, + final int destY, final int width, final int height); + + boolean isValid(); + + void setMailBox(final DoubleBufferedList<Region> mailBox); + + Set<Tile> handleUpdateRequests(); + + void shutdown(); +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureClipmap.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureClipmap.java new file mode 100644 index 0000000..be2461f --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureClipmap.java @@ -0,0 +1,654 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.client; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.extension.terrain.util.DoubleBufferedList; +import com.ardor3d.extension.terrain.util.LevelData; +import com.ardor3d.extension.terrain.util.Region; +import com.ardor3d.extension.terrain.util.Tile; +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture3D; +import com.ardor3d.image.Texture.MagnificationFilter; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.GLSLShaderObjectsState; +import com.ardor3d.util.TextureKey; +import com.ardor3d.util.geom.BufferUtils; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * An implementation of texture clipmapping + */ +public class TextureClipmap { + /** The Constant logger. */ + private static final Logger logger = Logger.getLogger(TextureClipmap.class.getName()); + + private final int textureSize; + private final int textureLevels; + private final int validLevels; + private int currentShownLevels; + private int minVisibleLevel = 0; + + private float scale = 1f; + + private Texture3D textureClipmap; + private GLSLShaderObjectsState textureClipmapShader; + + private final List<LevelData> levelDataList = Lists.newArrayList(); + + private final FloatBuffer sliceDataBuffer; + + private final Vector3 eyePosition = new Vector3(); + + private boolean showDebug = false; + + private final List<TextureCache> cacheList; + + private final DoubleBufferedList<Region> mailBox = new DoubleBufferedList<Region>(); + + private final boolean useAlpha; + private final int colorBits; + + /** Timers for mailbox updates */ + private long oldTime = 0; + private long updateTimer = 0; + private final long updateThreashold = 300; + + private final Comparator<Region> regionSorter = new Comparator<Region>() { + @Override + public int compare(final Region r1, final Region r2) { + return r1.getLevel() - r2.getLevel(); + } + }; + + public TextureClipmap(final List<TextureCache> cacheList, final int textureSize, + final TextureConfiguration textureConfiguration) { + this.cacheList = cacheList; + this.textureSize = textureSize; + validLevels = cacheList.size(); + useAlpha = textureConfiguration.isUseAlpha(); + colorBits = useAlpha ? 4 : 3; + + for (final TextureCache cache : cacheList) { + cache.setMailBox(mailBox); + } + + textureLevels = roundUpPowerTwo(validLevels); + scale = textureConfiguration.getTextureDensity() * textureSize / 128; + + TextureClipmap.logger.info("Texture size: " + textureSize); + TextureClipmap.logger.info("ValidLevels: " + validLevels); + TextureClipmap.logger.info("3D Texture depth: " + textureLevels); + + sliceDataBuffer = BufferUtils.createFloatBuffer(textureLevels * 2); + + for (int i = 0; i < validLevels; i++) { + levelDataList.add(new LevelData(i, textureSize)); + } + + createTexture(); + } + + private final List<Long> timers = Lists.newArrayList(); + + public void update(final Renderer renderer, final ReadOnlyVector3 position) { + eyePosition.set(position); + textureClipmapShader.setUniform("eyePosition", eyePosition); + eyePosition.multiplyLocal(textureSize / (scale * 4f * 32f)); + + for (int unit = minVisibleLevel; unit < validLevels; unit++) { + if (cacheList.get(unit).isValid()) { + currentShownLevels = unit; + break; + } + } + + // TODO: improve calcs for removing levels based on height above terrain + // if (eyePosition.getYf() > heightRangeMax) { + // final float diff = eyePosition.getYf() - heightRangeMax; + // final float x = (float) (diff * Math.tan(Math.toRadians(80))); + // for (int unit = currentShownLevels; unit < validLevels; unit++) { + // final float heightTest = scale * textureSize * MathUtils.pow2(unit) / x; + // if (heightTest > 1) { + // currentShownLevels = unit; + // break; + // } + // } + // } + + textureClipmapShader.setUniform("minLevel", (float) currentShownLevels); + + if (timers.size() < currentShownLevels) { + for (int unit = 0; unit < currentShownLevels; unit++) { + timers.add(System.currentTimeMillis()); + } + } + for (int unit = 0; unit < currentShownLevels; unit++) { + final long t = System.currentTimeMillis() - timers.get(unit); + if (t > 500) { + timers.set(unit, System.currentTimeMillis()); + + float x = eyePosition.getXf(); + float y = eyePosition.getZf(); + + final int exp2 = MathUtils.pow2(unit); + x /= exp2; + y /= exp2; + + final int offX = MathUtils.floor(x); + final int offY = MathUtils.floor(y); + + final LevelData levelData = levelDataList.get(unit); + + if (levelData.x != offX || levelData.y != offY) { + final TextureCache cache = cacheList.get(unit); + cache.setCurrentPosition(offX, offY); + } + } + } + + // final long t = System.nanoTime(); + for (int unit = validLevels - 1; unit >= currentShownLevels; unit--) { + float x = eyePosition.getXf(); + float y = eyePosition.getZf(); + + final int exp2 = MathUtils.pow2(unit); + x /= exp2; + y /= exp2; + + final int offX = MathUtils.floor(x); + final int offY = MathUtils.floor(y); + + final LevelData levelData = levelDataList.get(unit); + + final TextureCache cache = cacheList.get(unit); + if (levelData.x != offX || levelData.y != offY) { + cache.setCurrentPosition(offX, offY); + updateLevel(renderer, levelData, offX, offY); + } + + final Set<Tile> updatedTiles = cache.handleUpdateRequests(); + if (updatedTiles != null) { + final int sX = offX - textureSize / 2; + final int sY = offY - textureSize / 2; + + // System.out.println(unit + "[" + sX + "," + sY + "]: " + updatedTiles); + + // TODO: this should update only what was changed + updateQuick(renderer, levelData, textureSize + 1, textureSize + 1, sX, sY, levelData.offsetX, + levelData.offsetY, textureSize, textureSize); + } + + x = MathUtils.moduloPositive(x, 2); + y = MathUtils.moduloPositive(y, 2); + + int shiftX = levelData.x; + int shiftY = levelData.y; + shiftX = MathUtils.moduloPositive(shiftX, 2); + shiftY = MathUtils.moduloPositive(shiftY, 2); + + x -= shiftX; + y -= shiftY; + + x += levelData.offsetX; + y += levelData.offsetY; + + x /= textureSize; + y /= textureSize; + + sliceDataBuffer.put(unit * 2, x); + sliceDataBuffer.put(unit * 2 + 1, y); + } + + sliceDataBuffer.rewind(); + textureClipmapShader.setUniform("sliceOffset", sliceDataBuffer, 2); + + updateFromMailbox(renderer); + } + + private void updateFromMailbox(final Renderer renderer) { + if (updateTimer > updateThreashold) { + final List<Region> regionList = mailBox.switchAndGet(); + if (!regionList.isEmpty()) { + for (int unit = validLevels - 1; unit >= 0; unit--) { + final LevelData levelData = levelDataList.get(unit); + // final int pow = (int) Math.pow(2, unit); + final int sX = levelData.x - textureSize / 2; + final int sY = levelData.y - textureSize / 2; + levelData.clipRegion.setX(sX); + levelData.clipRegion.setY(sY); + } + + for (int i = regionList.size() - 1; i >= 0; i--) { + final Region region = regionList.get(i); + final Region clipRegion = levelDataList.get(region.getLevel()).clipRegion; + + if (clipRegion.intersects(region)) { + clipRegion.intersection(region); + } else { + regionList.remove(i); + } + } + + Collections.sort(regionList, regionSorter); + + final int start = regionList.size() - 1; + for (int i = start; i >= 0; i--) { + final Region region = regionList.get(i); + + recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(), + region.getHeight()); + } + + for (int i = regionList.size() - 1; i >= 0; i--) { + final Region region = regionList.get(i); + final Region clipRegion = levelDataList.get(region.getLevel()).clipRegion; + + if (clipRegion.intersects(region)) { + clipRegion.intersection(region); + } else { + regionList.remove(i); + } + } + + Collections.sort(regionList, regionSorter); + + final Set<Integer> affectedUnits = Sets.newHashSet(); + for (int i = regionList.size() - 1; i >= 0; i--) { + final Region region = regionList.get(i); + + final int unit = region.getLevel(); + affectedUnits.add(unit); + + final LevelData levelData = levelDataList.get(unit); + final TextureCache cache = cacheList.get(unit); + final ByteBuffer imageDestination = levelData.sliceData; + + final int sX = region.getX(); + final int sY = region.getY(); + int dX = region.getX() + textureSize / 2; + int dY = region.getY() + textureSize / 2; + dX = MathUtils.moduloPositive(dX, textureSize); + dY = MathUtils.moduloPositive(dY, textureSize); + + cache.updateRegion(imageDestination, sX, sY, dX + 1, dY + 1, region.getWidth(), region.getHeight()); + } + + for (final int unit : affectedUnits) { + final LevelData levelData = levelDataList.get(unit); + final ByteBuffer imageDestination = levelData.sliceData; + + // TODO: only update subpart + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, 0, unit, textureSize, textureSize, 1, + imageDestination, 0, 0, 0, textureSize, textureSize); + } + } + updateTimer %= updateThreashold; + } + final long time = System.currentTimeMillis(); + updateTimer += time - oldTime; + oldTime = time; + } + + private void recursiveAddUpdates(final List<Region> regionList, final int level, final int x, final int y, + final int width, final int height) { + if (level == 0) { + return; + } + + final Region region = new Region(level - 1, x * 2, y * 2, width * 2, height * 2); + if (!regionList.contains(region)) { + regionList.add(region); + recursiveAddUpdates(regionList, region.getLevel(), region.getX(), region.getY(), region.getWidth(), region + .getHeight()); + } + } + + private void updateLevel(final Renderer renderer, final LevelData levelData, final int x, final int y) { + final int diffX = x - levelData.x; + final int diffY = y - levelData.y; + levelData.x = x; + levelData.y = y; + + final int sX = x - textureSize / 2; + final int sY = y - textureSize / 2; + + levelData.offsetX += diffX; + levelData.offsetY += diffY; + levelData.offsetX = MathUtils.moduloPositive(levelData.offsetX, textureSize); + levelData.offsetY = MathUtils.moduloPositive(levelData.offsetY, textureSize); + + updateQuick(renderer, levelData, diffX, diffY, sX, sY, levelData.offsetX, levelData.offsetY, textureSize, + textureSize); + } + + public void regenerate(final Renderer renderer) { + for (int unit = validLevels - 1; unit >= 0; unit--) { + float x = eyePosition.getXf(); + float y = eyePosition.getZf(); + + final int exp2 = (int) Math.pow(2, unit); + x /= exp2; + y /= exp2; + + final int offX = MathUtils.floor(x); + final int offY = MathUtils.floor(y); + + final LevelData levelData = levelDataList.get(unit); + + final int sX = offX - textureSize / 2; + final int sY = offY - textureSize / 2; + + updateQuick(renderer, levelData, textureSize + 1, textureSize + 1, sX, sY, levelData.offsetX, + levelData.offsetY, textureSize, textureSize); + } + } + + private void updateQuick(final Renderer renderer, final LevelData levelData, final int diffX, final int diffY, + int sX, int sY, int dX, int dY, int width, int height) { + final int unit = levelData.unit; + final ByteBuffer imageDestination = levelData.sliceData; + + final TextureCache cache = cacheList.get(unit); + + if (Math.abs(diffX) > textureSize || Math.abs(diffY) > textureSize) { + // Copy the whole slice + cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height); + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, 0, unit, textureSize, textureSize, 1, imageDestination, + 0, 0, 0, textureSize, textureSize); + } else if (diffX != 0 && diffY != 0) { + // Copy three rectangles. Horizontal, vertical and corner + + final int tmpSX = sX; + final int tmpDX = dX; + final int tmpWidth = width; + + // Vertical + if (diffX > 0) { + sX = sX + textureSize - diffX; + dX = dX - diffX; + } + width = Math.abs(diffX); + + cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height); + + dX = MathUtils.moduloPositive(dX, textureSize); + if (dX + width > textureSize) { + int dX1 = dX; + int width1 = textureSize - dX; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1, + imageDestination, dX1, 0, 0, textureSize, textureSize); + + dX1 = 0; + width1 = width - width1; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1, + imageDestination, dX1, 0, 0, textureSize, textureSize); + } else { + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, dX, 0, unit, width, textureSize, 1, imageDestination, + dX, 0, 0, textureSize, textureSize); + } + + sX = tmpSX; + dX = tmpDX; + width = tmpWidth; + + // Horizontal + if (diffY > 0) { + sY = sY + textureSize - diffY; + dY = dY - diffY; + } + height = Math.abs(diffY); + + cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height); + + dY = MathUtils.moduloPositive(dY, textureSize); + if (dY + height > textureSize) { + int dY1 = dY; + int height1 = textureSize - dY; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1, + imageDestination, 0, dY1, 0, textureSize, textureSize); + + dY1 = 0; + height1 = height - height1; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1, + imageDestination, 0, dY1, 0, textureSize, textureSize); + } else { + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, dY, unit, textureSize, height, 1, imageDestination, + 0, dY, 0, textureSize, textureSize); + } + } else if (diffX != 0) { + // Copy vertical only + if (diffX > 0) { + sX = sX + textureSize - diffX; + dX = dX - diffX; + } + width = Math.abs(diffX); + + cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height); + + dX = MathUtils.moduloPositive(dX, textureSize); + if (dX + width > textureSize) { + int dX1 = dX; + int width1 = textureSize - dX; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1, + imageDestination, dX1, 0, 0, textureSize, textureSize); + + dX1 = 0; + width1 = width - width1; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, dX1, 0, unit, width1, textureSize, 1, + imageDestination, dX1, 0, 0, textureSize, textureSize); + } else { + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, dX, 0, unit, width, textureSize, 1, imageDestination, + dX, 0, 0, textureSize, textureSize); + } + } else if (diffY != 0) { + // Copy horizontal only + if (diffY > 0) { + sY = sY + textureSize - diffY; + dY = dY - diffY; + } + height = Math.abs(diffY); + + cache.updateRegion(imageDestination, sX, sY, dX, dY, width, height); + + dY = MathUtils.moduloPositive(dY, textureSize); + if (dY + height > textureSize) { + int dY1 = dY; + int height1 = textureSize - dY; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1, + imageDestination, 0, dY1, 0, textureSize, textureSize); + + dY1 = 0; + height1 = height - height1; + + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, dY1, unit, textureSize, height1, 1, + imageDestination, 0, dY1, 0, textureSize, textureSize); + } else { + imageDestination.rewind(); + renderer.updateTexture3DSubImage(textureClipmap, 0, dY, unit, textureSize, height, 1, imageDestination, + 0, dY, 0, textureSize, textureSize); + } + } + } + + public Texture getTexture() { + return textureClipmap; + } + + public void reloadShader() { + textureClipmapShader = new GLSLShaderObjectsState(); + try { + textureClipmapShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(TextureClipmap.class, + "com/ardor3d/extension/terrain/textureClipmapShader.vert")); + textureClipmapShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream( + TextureClipmap.class, "com/ardor3d/extension/terrain/textureClipmapShader.frag")); + } catch (final IOException ex) { + TextureClipmap.logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", + ex); + } + textureClipmapShader.setUniform("texture", 0); + + textureClipmapShader.setUniform("scale", 1f / scale); + textureClipmapShader.setUniform("textureSize", (float) textureSize); + textureClipmapShader.setUniform("texelSize", 1f / textureSize); + + textureClipmapShader.setUniform("levels", (float) textureLevels); + textureClipmapShader.setUniform("validLevels", (float) validLevels - 1); + textureClipmapShader.setUniform("minLevel", 0f); + + textureClipmapShader.setUniform("showDebug", showDebug ? 1.0f : 0.0f); + } + + public GLSLShaderObjectsState getShaderState() { + if (textureClipmapShader == null) { + reloadShader(); + } + return textureClipmapShader; + } + + public void setShaderState(final GLSLShaderObjectsState textureClipmapShader) { + this.textureClipmapShader = textureClipmapShader; + } + + public static int clamp(final int x, final int low, final int high) { + return x < low ? low : x > high ? high : x; + } + + private Texture createTexture() { + textureClipmap = new Texture3D(); + textureClipmap.setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps); + textureClipmap.setMagnificationFilter(MagnificationFilter.NearestNeighbor); + // textureClipmap.setMinificationFilter(MinificationFilter.BilinearNoMipMaps); + // textureClipmap.setMagnificationFilter(MagnificationFilter.Bilinear); + final Image img = new Image(); + img.setWidth(textureSize); + img.setHeight(textureSize); + img.setDepth(textureLevels); + img.setDataFormat(useAlpha ? ImageDataFormat.RGBA : ImageDataFormat.RGB); + img.setDataType(PixelDataType.UnsignedByte); + textureClipmap.setTextureKey(TextureKey.getRTTKey(textureClipmap.getMinificationFilter())); + + for (int l = 0; l < textureLevels; l++) { + final ByteBuffer sliceData = BufferUtils.createByteBuffer(textureSize * textureSize * colorBits); + img.setData(l, sliceData); + if (l < validLevels) { + levelDataList.get(l).sliceData = sliceData; + } + } + textureClipmap.setImage(img); + + return textureClipmap; + } + + public float getScale() { + return scale; + } + + public void setScale(final float scale) { + this.scale = scale; + } + + private int roundUpPowerTwo(int v) { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; + } + + public boolean isShowDebug() { + return showDebug; + } + + public void setShowDebug(final boolean showDebug) { + this.showDebug = showDebug; + } + + public int getTextureSize() { + return textureSize; + } + + public int getTextureLevels() { + return textureLevels; + } + + public int getValidLevels() { + return validLevels; + } + + /** + * set the minimum (highest resolution) clipmap level visible + * + * @param level + * clamped to valid range + */ + public void setMinVisibleLevel(final int level) { + if (level < 0) { + minVisibleLevel = 0; + } else if (level >= validLevels) { + minVisibleLevel = validLevels - 1; + } else { + minVisibleLevel = level; + } + } + + public int getMinVisibleLevel() { + return minVisibleLevel; + } + + public void shutdown() { + for (final TextureCache cache : cacheList) { + cache.shutdown(); + } + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureConfiguration.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureConfiguration.java new file mode 100644 index 0000000..6ad448d --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureConfiguration.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client; + +import java.util.Map; + +import com.ardor3d.image.TextureStoreFormat; + +/** + * Terrain Configuration data for a specific map. + */ +public class TextureConfiguration { + /** Total number of clipmap levels in this map */ + private final int totalNrClipmapLevels; + /** Mapping of sourceIDs to texture format */ + private final Map<Integer, TextureStoreFormat> textureDataTypes; + /** "Tile size" for each tile in the cache */ + private final int cacheGridSize; + /** Texture density in relation to the terrain */ + private final float textureDensity; + /** True if tiles are only valid in positive coordinates */ + private final boolean onlyPositiveQuadrant; + /** True if destination data should render alpha */ + private final boolean useAlpha; + + public TextureConfiguration(final int totalNrClipmapLevels, + final Map<Integer, TextureStoreFormat> textureDataTypes, final int cacheGridSize, + final float textureDensity, final boolean onlyPositiveQuadrant, final boolean useAlpha) { + this.totalNrClipmapLevels = totalNrClipmapLevels; + this.textureDataTypes = textureDataTypes; + this.cacheGridSize = cacheGridSize; + this.textureDensity = textureDensity; + this.onlyPositiveQuadrant = onlyPositiveQuadrant; + this.useAlpha = useAlpha; + } + + public int getTotalNrClipmapLevels() { + return totalNrClipmapLevels; + } + + public Map<Integer, TextureStoreFormat> getTextureDataTypes() { + return textureDataTypes; + } + + public int getCacheGridSize() { + return cacheGridSize; + } + + public float getTextureDensity() { + return textureDensity; + } + + public boolean isOnlyPositiveQuadrant() { + return onlyPositiveQuadrant; + } + + public TextureStoreFormat getTextureDataType(final int sourceId) { + return textureDataTypes.get(sourceId); + } + + public boolean isUseAlpha() { + return useAlpha; + } + + @Override + public String toString() { + return "TextureConfiguration [cacheGridSize=" + cacheGridSize + ", onlyPositiveQuadrant=" + + onlyPositiveQuadrant + ", textureDataTypes=" + textureDataTypes + ", textureDensity=" + + textureDensity + ", totalNrClipmapLevels=" + totalNrClipmapLevels + ", useAlpha=" + useAlpha + "]"; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureGridCache.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureGridCache.java new file mode 100644 index 0000000..27df40d --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureGridCache.java @@ -0,0 +1,606 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.client; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ardor3d.extension.terrain.client.functions.CacheFunctionUtil; +import com.ardor3d.extension.terrain.client.functions.SourceCacheFunction; +import com.ardor3d.extension.terrain.util.DoubleBufferedList; +import com.ardor3d.extension.terrain.util.IntColorUtils; +import com.ardor3d.extension.terrain.util.Region; +import com.ardor3d.extension.terrain.util.Tile; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.math.MathUtils; +import com.google.common.collect.Sets; + +/** + * Special tile/grid based cache for texture data + */ +public class TextureGridCache implements TextureCache, Runnable { + /** The Constant logger. */ + private static final Logger logger = Logger.getLogger(TextureGridCache.class.getName()); + + private final TextureSource source; + + private final TextureCache parentCache; + + private final int cacheSize; + private final int tileSize; + private final int dataSize; + + private final byte[] data; + private final CacheData[][] cache; + private final int destinationSize; + + private final int clipmapLevel; + private final int requestedLevel; + + private final Set<TileLoadingData> currentTiles = Sets.newHashSet(); + private Set<TileLoadingData> newThreadTiles = Sets.newHashSet(); + private Set<TileLoadingData> backThreadTiles = Sets.newHashSet(); + private final Object SWAP_LOCK = new Object(); + private int backCurrentTileX = Integer.MAX_VALUE; + private int backCurrentTileY = Integer.MAX_VALUE; + private boolean updated = false; + + private final TextureConfiguration textureConfiguration; + private SourceCacheFunction function; + + private final int TILELOCATOR_SLEEP = 100; + + private final boolean useAlpha; + private final int colorBits; + + // Debug + private final boolean enableDebug = true; + private final Set<TileLoadingData> debugTiles = Sets.newHashSet(); + + public Set<TileLoadingData> getDebugTiles() { + Set<TileLoadingData> copyTiles = null; + synchronized (debugTiles) { + copyTiles = Sets.newHashSet(debugTiles); + } + return copyTiles; + } + + private final ThreadPoolExecutor tileThreadService; + + private DoubleBufferedList<Region> mailBox; + + private Set<Tile> validTiles; + + public TextureGridCache(final TextureCache parentCache, final int cacheSize, final TextureSource source, + final int tileSize, final int destinationSize, final TextureConfiguration textureConfiguration, + final int clipmapLevel, final int requestedLevel, final ThreadPoolExecutor tileThreadService) { + this.parentCache = parentCache; + this.cacheSize = cacheSize; + this.source = source; + this.tileSize = tileSize; + dataSize = tileSize * cacheSize; + this.destinationSize = destinationSize; + this.textureConfiguration = textureConfiguration; + useAlpha = textureConfiguration.isUseAlpha(); + colorBits = useAlpha ? 4 : 3; + this.clipmapLevel = clipmapLevel; + this.requestedLevel = requestedLevel; + + this.tileThreadService = tileThreadService; + // tileThreadService = new ThreadPoolExecutor(nrThreads, nrThreads, 0L, TimeUnit.MILLISECONDS, + // new LinkedBlockingQueue<Runnable>(), new ThreadFactoryBuilder() + // .setThreadFactory(Executors.defaultThreadFactory()).setDaemon(true) + // .setNameFormat("TextureTileThread-%s").setPriority(Thread.MIN_PRIORITY).build()); + + data = new byte[dataSize * dataSize * colorBits]; + for (int i = 0; i < dataSize * dataSize * colorBits; i++) { + data[i] = (byte) 1; + } + + cache = new CacheData[cacheSize][cacheSize]; + for (int i = 0; i < cacheSize; i++) { + for (int j = 0; j < cacheSize; j++) { + cache[i][j] = new CacheData(); + } + } + } + + private boolean started = false; + private final int locatorSize = 20; + private Tile locatorTile = new Tile(Integer.MAX_VALUE, Integer.MAX_VALUE); + + private boolean exit = false; + + @Override + public Set<Tile> handleUpdateRequests() { + Set<Tile> updateTiles; + try { + updateTiles = source.getInvalidTiles(requestedLevel, backCurrentTileX - cacheSize / 2, backCurrentTileY + - cacheSize / 2, cacheSize, cacheSize); + if (updateTiles == null || updateTiles.isEmpty()) { + return null; + } + } catch (final Exception e) { + logger.log(Level.WARNING, "Exception processing updates", e); + return null; + } + + for (final Tile tile : updateTiles) { + ByteBuffer sourceData; + try { + sourceData = source.getTile(requestedLevel, tile); + } catch (final InterruptedException e) { + // XXX: Loading can be interrupted + return null; + } catch (final Exception e) { + logger.log(Level.WARNING, "Exception getting tile", e); + return null; + } + + if (sourceData == null) { + continue; + } + + final int destX = MathUtils.moduloPositive(tile.getX(), cacheSize); + final int destY = MathUtils.moduloPositive(tile.getY(), cacheSize); + + final TextureStoreFormat format = textureConfiguration.getTextureDataType(source.getContributorId( + requestedLevel, tile)); + CacheFunctionUtil.applyFunction(useAlpha, function, sourceData, data, destX, destY, format, tileSize, + dataSize); + } + + return updateTiles; + } + + public void setCurrentPosition(final int x, final int y) { + final int tileX = MathUtils.floor((float) x / tileSize); + final int tileY = MathUtils.floor((float) y / tileSize); + + final int diffX = tileX - backCurrentTileX; + final int diffY = tileY - backCurrentTileY; + + if (diffX == 0 && diffY == 0) { + return; + } + + synchronized (SWAP_LOCK) { + backCurrentTileX = tileX; + backCurrentTileY = tileY; + + final Set<TileLoadingData> newTiles = Sets.newHashSet(); + for (int i = 0; i < cacheSize; i++) { + for (int j = 0; j < cacheSize; j++) { + final int sourceX = tileX + j - cacheSize / 2; + final int sourceY = tileY + i - cacheSize / 2; + + final int destX = MathUtils.moduloPositive(sourceX, cacheSize); + final int destY = MathUtils.moduloPositive(sourceY, cacheSize); + + newTiles.add(new TileLoadingData(mailBox, new Tile(sourceX, sourceY), new Tile(destX, destY), + source, cache, data, tileSize, dataSize, function, textureConfiguration, useAlpha, + clipmapLevel, requestedLevel)); + } + } + + final Iterator<TileLoadingData> tileIterator = currentTiles.iterator(); + while (tileIterator.hasNext()) { + final TileLoadingData data = tileIterator.next(); + + if (!newTiles.contains(data)) { + cache[data.destTile.getX()][data.destTile.getY()].isValid = false; + + data.isCancelled = true; + final Future<Boolean> future = data.future; + if (future != null && !future.isDone()) { + future.cancel(true); + } + tileIterator.remove(); + if (backThreadTiles.contains(data)) { + backThreadTiles.remove(data); + } + } else { + newTiles.remove(data); + } + } + + backThreadTiles.addAll(newTiles); + updated = true; + + currentTiles.addAll(newTiles); + } + + tileThreadService.purge(); + + if (enableDebug) { + synchronized (debugTiles) { + debugTiles.clear(); + debugTiles.addAll(currentTiles); + } + } + + if (!started) { + final Thread textureCacheThread = new Thread(this, "TextureGridCache-" + clipmapLevel); + textureCacheThread.setDaemon(true); + textureCacheThread.start(); + started = true; + } + } + + @Override + public void run() { + while (!exit) { + try { + Thread.sleep(TILELOCATOR_SLEEP); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + + int tileX; + int tileY; + boolean needsUpdate = false; + synchronized (SWAP_LOCK) { + final Set<TileLoadingData> tmp = newThreadTiles; + newThreadTiles = backThreadTiles; + backThreadTiles = tmp; + backThreadTiles.clear(); + + tileX = backCurrentTileX; + tileY = backCurrentTileY; + + needsUpdate = updated; + updated = false; + } + + if (needsUpdate) { + if (tileX <= locatorTile.getX() - locatorSize / 2 + 1 + || tileX >= locatorTile.getX() + locatorSize / 2 - 2 + || tileY <= locatorTile.getY() - locatorSize / 2 + 1 + || tileY >= locatorTile.getY() + locatorSize / 2 - 2) { + try { + validTiles = source.getValidTiles(requestedLevel, tileX - locatorSize / 2, tileY - locatorSize + / 2, locatorSize, locatorSize); + } catch (final Exception e) { + logger.log(Level.WARNING, "Exception getting source info", e); + } + locatorTile = new Tile(tileX, tileY); + } + + threadedUpdateTiles(); + } + } + } + + private void threadedUpdateTiles() { + final Iterator<TileLoadingData> tileIterator = newThreadTiles.iterator(); + while (tileIterator.hasNext()) { + final TileLoadingData data = tileIterator.next(); + if (validTiles == null || validTiles.contains(data.sourceTile)) { + cache[data.destTile.getX()][data.destTile.getY()].isValid = false; + + final Future<Boolean> future = tileThreadService.submit(data); + data.future = future; + } + tileIterator.remove(); + } + } + + @Override + public int getColor(final int x, final int z) { + int tileX = MathUtils.floor((float) x / tileSize); + int tileY = MathUtils.floor((float) z / tileSize); + tileX = MathUtils.moduloPositive(tileX, cacheSize); + tileY = MathUtils.moduloPositive(tileY, cacheSize); + final CacheData tileData = cache[tileX][tileY]; + + if (!tileData.isValid) { + if (parentCache != null) { + if (x % 2 == 0 && z % 2 == 0) { + return parentCache.getColor(x / 2, z / 2); + } else { + return parentCache.getSubColor(x / 2f, z / 2f); + } + } else { + return 0; + } + } else { + final int dataX = MathUtils.moduloPositive(x, dataSize); + final int dataY = MathUtils.moduloPositive(z, dataSize); + final int sourceIndex = (dataY * dataSize + dataX) * colorBits; + + int color = 0; + if (useAlpha) { + color = IntColorUtils.getColor(data[sourceIndex + 0], data[sourceIndex + 1], data[sourceIndex + 2], + data[sourceIndex + 3]); + } else { + color = IntColorUtils.getColor(data[sourceIndex + 0], data[sourceIndex + 1], data[sourceIndex + 2], + (byte) 0); + } + + // if (rgbStore.r == 0 && rgbStore.g == 0 && rgbStore.b == 0) { + // if (parentCache != null) { + // if (x % 2 == 0 && z % 2 == 0) { + // return parentCache.getRGB(x / 2, z / 2, rgbStore); + // } else { + // return parentCache.getSubRGB(x / 2f, z / 2f, rgbStore); + // } + // } else { + // return minRGB; + // } + // } + + return color; + } + } + + @Override + public int getSubColor(final float x, final float z) { + int tileX = MathUtils.floor(x / tileSize); + int tileY = MathUtils.floor(z / tileSize); + tileX = MathUtils.moduloPositive(tileX, cacheSize); + tileY = MathUtils.moduloPositive(tileY, cacheSize); + final CacheData tileData = cache[tileX][tileY]; + + if (!tileData.isValid) { + if (parentCache != null) { + return parentCache.getSubColor(x / 2f, z / 2f); + } else { + return 0; + } + } else { + final int col = MathUtils.floor(x); + final int row = MathUtils.floor(z); + final double intOnX = x - col; + final double intOnZ = z - row; + + final int topLeft = getColor(col, row); + final int topRight = getColor(col + 1, row); + final int top = IntColorUtils.lerp(intOnX, topLeft, topRight); + + final int bottomLeft = getColor(col, row + 1); + final int bottomRight = getColor(col + 1, row + 1); + final int bottom = IntColorUtils.lerp(intOnX, bottomLeft, bottomRight); + + return IntColorUtils.lerp(intOnZ, top, bottom); + } + } + + @Override + public void updateRegion(final ByteBuffer destinationData, final int sourceX, final int sourceY, final int destX, + final int destY, final int width, final int height) { + final byte[] rgbArray = new byte[width * colorBits]; + for (int z = 0; z < height; z++) { + final int currentSourceZ = sourceY + z; + final int currentDestZ = destY + z; + final int dataY = MathUtils.moduloPositive(currentDestZ, destinationSize); + + for (int x = 0; x < width; x++) { + final int currentSourceX = sourceX + x; + final int color = getColor(currentSourceX, currentSourceZ); + + final int index = x * colorBits; + rgbArray[index + 0] = (byte) (color >> 24 & 0xFF); + rgbArray[index + 1] = (byte) (color >> 16 & 0xFF); + rgbArray[index + 2] = (byte) (color >> 8 & 0xFF); + if (useAlpha) { + rgbArray[index + 3] = (byte) (color & 0xFF); + } + } + + final int dataX = MathUtils.moduloPositive(destX, destinationSize); + if (dataX + width > destinationSize) { + final int destIndex = dataY * destinationSize * colorBits; + + destinationData.position(destIndex + dataX * colorBits); + destinationData.put(rgbArray, 0, (destinationSize - dataX) * colorBits); + + destinationData.position(destIndex); + destinationData.put(rgbArray, (destinationSize - dataX) * colorBits, (dataX + width - destinationSize) + * colorBits); + } else { + final int destIndex = (dataY * destinationSize + dataX) * colorBits; + destinationData.position(destIndex); + destinationData.put(rgbArray); + } + } + } + + public static class TileLoadingData implements Callable<Boolean> { + private final TextureSource source; + private final byte[] data; + private final CacheData[][] cache; + private final int tileSize; + private final int dataSize; + + private final SourceCacheFunction function; + private final TextureConfiguration textureConfiguration; + private final boolean useAlpha; + + private final int clipmapLevel; + private final int requestedLevel; + + private final DoubleBufferedList<Region> mailBox; + + public final Tile sourceTile; + public final Tile destTile; + + public boolean isCancelled = false; + public Future<Boolean> future; + + public enum State { + init, loading, finished + } + + public State state = State.init; + + public TileLoadingData(final DoubleBufferedList<Region> mailBox, final Tile sourceTile, final Tile destTile, + final TextureSource source, final CacheData[][] cache, final byte[] data, final int tileSize, + final int dataSize, final SourceCacheFunction function, + final TextureConfiguration textureConfiguration, final boolean useAlpha, final int clipmapLevel, + final int requestedLevel) { + this.mailBox = mailBox; + + this.sourceTile = sourceTile; + this.destTile = destTile; + + this.source = source; + this.cache = cache; + this.data = data; + this.tileSize = tileSize; + this.dataSize = dataSize; + + this.function = function; + this.textureConfiguration = textureConfiguration; + this.useAlpha = useAlpha; + + this.clipmapLevel = clipmapLevel; + this.requestedLevel = requestedLevel; + } + + @Override + public Boolean call() throws Exception { + state = State.loading; + + if (isCancelled()) { + return false; + } + + ByteBuffer sourceData = null; + try { + sourceData = source.getTile(requestedLevel, sourceTile); + } catch (final InterruptedException e) { + // XXX: Loading can be interrupted + return false; + } catch (final Throwable t) { + t.printStackTrace(); + } + + if (sourceData == null || isCancelled()) { + return false; + } + + final TextureStoreFormat format = textureConfiguration.getTextureDataType(source.getContributorId( + requestedLevel, sourceTile)); + CacheFunctionUtil.applyFunction(useAlpha, function, sourceData, data, destTile.getX(), destTile.getY(), + format, tileSize, dataSize); + + if (isCancelled()) { + return false; + } + + state = State.finished; + cache[destTile.getX()][destTile.getY()].isValid = true; + + final Region region = new Region(clipmapLevel, sourceTile.getX() * tileSize, sourceTile.getY() * tileSize, + tileSize, tileSize); + if (mailBox != null) { + mailBox.add(region); + } + + return true; + } + + private boolean isCancelled() { + if (future != null && future.isCancelled()) { + return true; + } else if (isCancelled) { + return true; + } else if (Thread.interrupted()) { + return true; + } + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (destTile == null ? 0 : destTile.hashCode()); + result = prime * result + (sourceTile == null ? 0 : sourceTile.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof TileLoadingData)) { + return false; + } + final TileLoadingData other = (TileLoadingData) obj; + if (destTile == null) { + if (other.destTile != null) { + return false; + } + } else if (!destTile.equals(other.destTile)) { + return false; + } + if (sourceTile == null) { + if (other.sourceTile != null) { + return false; + } + } else if (!sourceTile.equals(other.sourceTile)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "TileLoadingData [destTile=" + destTile + ", sourceTile=" + sourceTile + "]"; + } + } + + public static class CacheData { + public boolean isValid; + + public CacheData() { + isValid = false; + } + } + + public boolean isValid() { + int nrValid = 0; + for (final TileLoadingData data : currentTiles) { + if (cache[data.destTile.getX()][data.destTile.getY()].isValid) { + nrValid++; + } + } + return nrValid != 0; + } + + public void setMailBox(final DoubleBufferedList<Region> mailBox) { + this.mailBox = mailBox; + } + + public SourceCacheFunction getFunction() { + return function; + } + + public void setFunction(final SourceCacheFunction function) { + this.function = function; + } + + public void shutdown() { + exit = true; + started = false; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureSource.java new file mode 100644 index 0000000..3c9608e --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureSource.java @@ -0,0 +1,69 @@ + +package com.ardor3d.extension.terrain.client; + +import java.nio.ByteBuffer; +import java.util.Set; + +import com.ardor3d.extension.terrain.util.Tile; + +/** + * Feeds texture data to a TextureCache + */ +public interface TextureSource { + /** + * Called to initialize and setup the texture clipmap. + * + * @param mapID + * Map to get configuration for. + * @return + * @throws Exception + */ + TextureConfiguration getConfiguration() throws Exception; + + /** + * Returns which tiles that contain data in the requested region. + * + * @param clipmapLevel + * @param tileX + * @param tileY + * @param numTilesX + * @param numTilesY + * @return + * @throws Exception + */ + Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, int numTilesX, int numTilesY) + throws Exception; + + /** + * Returns which tiles that should be marked as invalid and updated in the requested region. + * + * @param clipmapLevel + * @param tileX + * @param tileY + * @param numTilesX + * @param numTilesY + * @return + * @throws Exception + */ + Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, int numTilesX, int numTilesY) + throws Exception; + + /** + * Returns the contributing source id for the requested tile. + * + * @param clipmapLevel + * @param tile + * @return + */ + int getContributorId(int clipmapLevel, Tile tile); + + /** + * Request for texture data for a tile. + * + * @param clipmapLevel + * @param tile + * @return + * @throws Exception + */ + ByteBuffer getTile(int clipmapLevel, final Tile tile) throws Exception; +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/UrlInputSupplier.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/UrlInputSupplier.java new file mode 100644 index 0000000..99bec4d --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/UrlInputSupplier.java @@ -0,0 +1,20 @@ +package com.ardor3d.extension.terrain.client; + +import com.google.common.io.InputSupplier; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class UrlInputSupplier implements InputSupplier<InputStream> { + private final URL url; + + public UrlInputSupplier(URL url) { + this.url = url; + } + + @Override + public InputStream getInput() throws IOException { + return url.openStream(); + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/CacheFunctionUtil.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/CacheFunctionUtil.java new file mode 100644 index 0000000..6421ac9 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/CacheFunctionUtil.java @@ -0,0 +1,50 @@ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; + +import com.ardor3d.image.TextureStoreFormat; + +public class CacheFunctionUtil { + public static void applyFunction(final boolean targetAlpha, final SourceCacheFunction function, + final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final TextureStoreFormat format, final int tileSize, final int dataSize) { + if (function == null) { + switch (format) { + case Luminance8: + Luminance8ToRGBFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, + tileSize); + return; + case Luminance8Alpha8: + Luminance8Alpha8ToRGBAFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, tileSize); + return; + case Luminance12: + Luminance12ToRGBFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, + tileSize); + return; + case RGB8: + if (!targetAlpha) { + RGB8ToRGBFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, + tileSize); + return; + } else { + RGB8ToRGBAFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, + tileSize); + return; + } + case RGBA8: + if (!targetAlpha) { + RGBA8ToRGBFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, + tileSize); + return; + } else { + RGBA8ToRGBAFunction.getInstance().doConversion(sourceData, store, destX, destY, dataSize, + tileSize); + return; + } + } + } else { + function.doConversion(sourceData, store, destX, destY, dataSize, tileSize); + } + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance12ToRGBFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance12ToRGBFunction.java new file mode 100644 index 0000000..a2d5102 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance12ToRGBFunction.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; + +public class Luminance12ToRGBFunction implements SourceCacheFunction { + + private static Luminance12ToRGBFunction INSTANCE; + + public static Luminance12ToRGBFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new Luminance12ToRGBFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + final int offset = (destY * tileSize * dataSize + destX * tileSize) * 3; + final ShortBuffer source = sourceData.asShortBuffer(); + try { + int destIndex = offset; + int sourceIndex = 0; + for (int y = 0; y < tileSize; y++) { + for (int x = 0; x < tileSize; x++) { + final short sourceShort = source.get(sourceIndex++); + // Mark wants us to shift off bottom 4 bits and cast. + final byte sourceValue = (byte) (sourceShort >> 4 & 0xFF); + store[destIndex++] = sourceValue; + store[destIndex++] = sourceValue; + store[destIndex++] = sourceValue; + } + destIndex += (dataSize - tileSize) * 3; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8Alpha8ToRGBAFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8Alpha8ToRGBAFunction.java new file mode 100644 index 0000000..7cdc316 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8Alpha8ToRGBAFunction.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.terrain.client.functions; + +import java.nio.ByteBuffer; + +public class Luminance8Alpha8ToRGBAFunction { + + private static Luminance8Alpha8ToRGBAFunction INSTANCE; + + public static Luminance8Alpha8ToRGBAFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new Luminance8Alpha8ToRGBAFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + final int offset = (destY * tileSize * dataSize + destX * tileSize) * 4; + try { + int destIndex = offset; + int sourceIndex = 0; + for (int y = 0; y < tileSize; y++) { + for (int x = 0; x < tileSize; x++) { + final byte sourceValue = sourceData.get(sourceIndex++); + final byte sourceAlpha = sourceData.get(sourceIndex++); + store[destIndex++] = sourceValue; + store[destIndex++] = sourceValue; + store[destIndex++] = sourceValue; + store[destIndex++] = sourceAlpha; + } + destIndex += (dataSize - tileSize) * 4; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8ToRGBFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8ToRGBFunction.java new file mode 100644 index 0000000..983851a --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8ToRGBFunction.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; + +public class Luminance8ToRGBFunction implements SourceCacheFunction { + + private static Luminance8ToRGBFunction INSTANCE; + + public static Luminance8ToRGBFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new Luminance8ToRGBFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + final int offset = (destY * tileSize * dataSize + destX * tileSize) * 3; + try { + int destIndex = offset; + int sourceIndex = 0; + for (int y = 0; y < tileSize; y++) { + for (int x = 0; x < tileSize; x++) { + final byte sourceValue = sourceData.get(sourceIndex++); + store[destIndex++] = sourceValue; + store[destIndex++] = sourceValue; + store[destIndex++] = sourceValue; + } + destIndex += (dataSize - tileSize) * 3; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBAFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBAFunction.java new file mode 100644 index 0000000..836ae39 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBAFunction.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; + +public class RGB8ToRGBAFunction implements SourceCacheFunction { + + private static RGB8ToRGBAFunction INSTANCE; + + public static RGB8ToRGBAFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new RGB8ToRGBAFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + try { + int destIndex = (destY * tileSize * dataSize + destX * tileSize) * 4; + int sourceIndex = 0; + final int width = tileSize * 3; + + for (int y = 0; y < tileSize; y++) { + sourceData.position(sourceIndex); + sourceData.get(store, destIndex, width); + store[destIndex + 3] = (byte) 255; + + sourceIndex += width; + destIndex += dataSize * 4; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBFunction.java new file mode 100644 index 0000000..6a161f6 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBFunction.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; + +public class RGB8ToRGBFunction implements SourceCacheFunction { + + private static RGB8ToRGBFunction INSTANCE; + + public static RGB8ToRGBFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new RGB8ToRGBFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + try { + int destIndex = (destY * tileSize * dataSize + destX * tileSize) * 3; + int sourceIndex = 0; + final int width = tileSize * 3; + + for (int y = 0; y < tileSize; y++) { + sourceData.position(sourceIndex); + sourceData.get(store, destIndex, width); + + sourceIndex += width; + destIndex += dataSize * 3; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBAFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBAFunction.java new file mode 100644 index 0000000..88ca47a --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBAFunction.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; + +public class RGBA8ToRGBAFunction implements SourceCacheFunction { + + private static RGBA8ToRGBAFunction INSTANCE; + + public static RGBA8ToRGBAFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new RGBA8ToRGBAFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + try { + int destIndex = (destY * tileSize * dataSize + destX * tileSize) * 4; + int sourceIndex = 0; + final int width = tileSize * 4; + + for (int y = 0; y < tileSize; y++) { + sourceData.position(sourceIndex); + sourceData.get(store, destIndex, width); + + sourceIndex += width; + destIndex += dataSize * 4; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBFunction.java new file mode 100644 index 0000000..8c0c4df --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBFunction.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.client.functions; + +import java.nio.ByteBuffer; + +public class RGBA8ToRGBFunction implements SourceCacheFunction { + + private static RGBA8ToRGBFunction INSTANCE; + + public static RGBA8ToRGBFunction getInstance() { + if (INSTANCE == null) { + INSTANCE = new RGBA8ToRGBFunction(); + } + return INSTANCE; + } + + public void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize) { + try { + int destIndex = (destY * tileSize * dataSize + destX * tileSize) * 3; + int sourceIndex = 0; + + for (int y = 0; y < tileSize; y++) { + for (int x = 0; x < tileSize; x++) { + sourceData.position(sourceIndex); + sourceData.get(store, destIndex, 3); + + sourceIndex += 4; + destIndex += 3; + } + + destIndex += (dataSize - tileSize) * 3; + } + } catch (final Throwable t) { + t.printStackTrace(); + } + } + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/SourceCacheFunction.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/SourceCacheFunction.java new file mode 100644 index 0000000..c999a64 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/SourceCacheFunction.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.terrain.client.functions; + +import java.nio.ByteBuffer; + +public interface SourceCacheFunction { + + void doConversion(final ByteBuffer sourceData, final byte[] store, final int destX, final int destY, + final int dataSize, final int tileSize); + +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/ImageHeightMap.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/ImageHeightMap.java new file mode 100644 index 0000000..9984173 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/ImageHeightMap.java @@ -0,0 +1,61 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.heightmap;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.math.MathUtils;
+
+public class ImageHeightMap {
+
+ public static float[] generateHeightMap(final Image ardorImage, final float min, final float max) {
+ if (max <= min) {
+ throw new IllegalArgumentException("max must be greater than min");
+ }
+
+ final ImageDataFormat format = ardorImage.getDataFormat();
+ if (format != ImageDataFormat.RGB && format != ImageDataFormat.RGBA && format != ImageDataFormat.Luminance) {
+ throw new IllegalArgumentException("Unhandled format (must be Luminance, RGB or RGBA): " + format);
+ }
+
+ if (ardorImage.getWidth() != ardorImage.getHeight() || !MathUtils.isPowerOfTwo(ardorImage.getWidth())) {
+ throw new IllegalArgumentException("Only pow2, square images are supported.");
+ }
+
+ final int size = ardorImage.getWidth(), comps = format.getComponents();
+
+ // initialize the height data attributes
+ final float[] heightData = new float[ardorImage.getWidth() * ardorImage.getHeight()];
+ final byte[] data = new byte[heightData.length * comps];
+ ardorImage.getData(0).get(data);
+
+ int index = 0, dataIndex, gray;
+ final int rowsize = size * comps;
+ final float byteScale = (max - min) / 255f;
+ for (int h = 0; h < size; h++) {
+ for (int w = 0; w < size; w++) {
+ dataIndex = h * rowsize + w * comps;
+ if (comps == 1) {
+ gray = data[dataIndex] & 0xFF;
+ } else {
+ final int red = data[dataIndex] & 0xFF;
+ final int green = data[dataIndex + 1] & 0xFF;
+ final int blue = data[dataIndex + 2] & 0xFF;
+
+ gray = (int) (0.30 * red + 0.59 * green + 0.11 * blue);
+ }
+
+ heightData[index++] = gray * byteScale + min;
+ }
+ }
+ return heightData;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/MidPointHeightMapGenerator.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/MidPointHeightMapGenerator.java new file mode 100644 index 0000000..d633fd2 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/MidPointHeightMapGenerator.java @@ -0,0 +1,307 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.heightmap;
+
+import java.util.Random;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.util.Ardor3dException;
+
+public class MidPointHeightMapGenerator {
+ private static final Logger logger = Logger.getLogger(MidPointHeightMapGenerator.class.getName());
+
+ private float roughness;
+
+ /** Height data information. */
+ protected float[] heightData = null;
+
+ /** The size of the height map's width. */
+ protected int size = 0;
+
+ /** Allows scaling the Y height of the map. */
+ protected float heightScale = 1.0f;
+
+ /** The filter is used to erode the terrain. */
+ protected float filter = 0.5f;
+
+ /** The range used to normalize terrain */
+ private float heightRange = 1f;
+
+ private final Random random = new Random();
+ private boolean loaded = false;
+
+ /**
+ * Constructor builds a new heightmap using the midpoint displacement algorithm. Roughness determines how chaotic
+ * the terrain will be. Where 1 is perfectly self-similar, > 1 early iterations have a disproportionately large
+ * effect creating smooth terrain, and < 1 late iteraions have a disproportionately large effect creating chaotic
+ * terrain.
+ *
+ * @param size
+ * the size of the terrain, must be a power of 2.
+ * @param roughness
+ * how chaotic to make the terrain.
+ *
+ * @throws Ardor3dException
+ * if size is less than or equal to zero or roughtness is less than 0.
+ */
+ public MidPointHeightMapGenerator(final int size, final float roughness) {
+ if (!MathUtils.isPowerOfTwo(size)) {
+ throw new Ardor3dException("Size must be (2^N) sized.");
+ }
+ if (roughness < 0 || size <= 0) {
+ throw new Ardor3dException("size and roughness must be " + "greater than 0");
+ }
+ this.roughness = roughness;
+ this.size = size;
+ }
+
+ /**
+ * Constructor builds a new heightmap using the midpoint displacement algorithm. Roughness determines how chaotic
+ * the terrain will be. Where 1 is perfectly self-similar, > 1 early iterations have a disproportionately large
+ * effect creating smooth terrain, and < 1 late iteraions have a disproportionately large effect creating chaotic
+ * terrain.
+ *
+ * @param size
+ * the size of the terrain, must be a power of 2.
+ * @param roughness
+ * how chaotic to make the terrain.
+ *
+ * @throws Ardor3dException
+ * if size is less than or equal to zero or roughtness is less than 0.
+ */
+ public MidPointHeightMapGenerator(final int size, final float roughness, final long seed) {
+ this(size, roughness);
+ random.setSeed(seed);
+ }
+
+ /**
+ * @return the heightData
+ */
+ public float[] getHeightData() {
+ if (!loaded) {
+ generateHeightData();
+ }
+ return heightData;
+ }
+
+ /**
+ * <code>load</code> generates the heightfield using the Midpoint Displacement algorithm. <code>load</code> uses the
+ * latest attributes, so a call to <code>load</code> is recommended if attributes have changed using the set
+ * methods.
+ */
+ public boolean generateHeightData() {
+ float height;
+ double heightReducer;
+ float[][] tempBuffer;
+
+ // holds the points of the square.
+ int ni, nj;
+ int mi, mj;
+ int pmi, pmj;
+
+ height = 1f;
+ heightReducer = Math.pow(2, -1 * roughness);
+
+ heightData = new float[size * size];
+ tempBuffer = new float[size][size];
+
+ int counter = size;
+ while (counter > 0) {
+ // displace the center of the square.
+ for (int i = 0; i < size; i += counter) {
+ for (int j = 0; j < size; j += counter) {
+ // (0,0) point of the local square
+ ni = (i + counter) % size;
+ nj = (j + counter) % size;
+ // middle point of the local square
+ mi = i + counter / 2;
+ mj = j + counter / 2;
+
+ // displace the middle point by the average of the
+ // corners, and a random value.
+ tempBuffer[mi][mj] = (tempBuffer[i][j] + tempBuffer[ni][j] + tempBuffer[i][nj] + tempBuffer[ni][nj])
+ / 4 + random.nextFloat() * height - height / 2;
+ }
+ }
+
+ // next calculate the new midpoints of the line segments.
+ for (int i = 0; i < size; i += counter) {
+ for (int j = 0; j < size; j += counter) {
+ // (0,0) of the local square
+ ni = (i + counter) % size;
+ nj = (j + counter) % size;
+
+ // middle point of the local square.
+ mi = i + counter / 2;
+ mj = j + counter / 2;
+
+ // middle point on the line in the x-axis direction.
+ pmi = (i - counter / 2 + size) % size;
+ // middle point on the line in the y-axis direction.
+ pmj = (j - counter / 2 + size) % size;
+
+ // Calculate the square value for the top side of the rectangle
+ tempBuffer[mi][j] = (tempBuffer[i][j] + tempBuffer[ni][j] + tempBuffer[mi][pmj] + tempBuffer[mi][mj])
+ / 4 + random.nextFloat() * height - height / 2;
+
+ // Calculate the square value for the left side of the rectangle
+ tempBuffer[i][mj] = (tempBuffer[i][j] + tempBuffer[i][nj] + tempBuffer[pmi][mj] + tempBuffer[mi][mj])
+ / 4 + random.nextFloat() * height - height / 2;
+
+ }
+ }
+
+ counter /= 2;
+ height *= heightReducer;
+ }
+
+ normalizeTerrain(tempBuffer);
+
+ // transfer the new terrain into the height map.
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ setHeightAtPoint(tempBuffer[i][j], i, j);
+ }
+ }
+
+ MidPointHeightMapGenerator.logger.info("Created Heightmap using Mid Point");
+
+ loaded = true;
+
+ return true;
+ }
+
+ /**
+ * <code>setRoughness</code> sets the new roughness value of the heightmap. Roughness determines how chaotic the
+ * terrain will be. Where 1 is perfectly self-similar, > 1 early iterations have a disproportionately large effect
+ * creating smooth terrain, and < 1 late iteraions have a disproportionately large effect creating chaotic terrain.
+ *
+ * @param roughness
+ * how chaotic will the heightmap be.
+ */
+ public void setRoughness(final float roughness) {
+ if (roughness < 0) {
+ throw new Ardor3dException("roughness must be greater than 0");
+ }
+ this.roughness = roughness;
+ }
+
+ /**
+ * <code>setHeightAtPoint</code> sets the height value for a given coordinate.
+ *
+ * @param height
+ * the new height for the coordinate.
+ * @param x
+ * the x (east/west) coordinate.
+ * @param z
+ * the z (north/south) coordinate.
+ */
+ protected void setHeightAtPoint(final float height, final int x, final int z) {
+ heightData[x + z * size] = height;
+ }
+
+ /**
+ * <code>normalizeTerrain</code> takes the current terrain data and converts it to values between 0 and
+ * NORMALIZE_RANGE.
+ *
+ * @param tempBuffer
+ * the terrain to normalize.
+ */
+ protected void normalizeTerrain(final float[][] tempBuffer) {
+ float currentMin, currentMax;
+ float height;
+
+ currentMin = tempBuffer[0][0];
+ currentMax = tempBuffer[0][0];
+
+ // find the min/max values of the height fTemptemptempBuffer
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ if (tempBuffer[i][j] > currentMax) {
+ currentMax = tempBuffer[i][j];
+ } else if (tempBuffer[i][j] < currentMin) {
+ currentMin = tempBuffer[i][j];
+ }
+ }
+ }
+
+ // find the range of the altitude
+ if (currentMax <= currentMin) {
+ return;
+ }
+
+ height = currentMax - currentMin;
+
+ // scale the values to a range of 0-255
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ tempBuffer[i][j] = (tempBuffer[i][j] - currentMin) / height * heightRange;
+ }
+ }
+ }
+
+ /**
+ * <code>erodeTerrain</code> is a convenience method that applies the FIR filter to a given height map. This
+ * simulates water errosion.
+ *
+ * @param tempBuffer
+ * the terrain to filter.
+ */
+ protected void erodeTerrain(final float[][] tempBuffer) {
+ // erode left to right
+ float v;
+
+ for (int i = 0; i < size; i++) {
+ v = tempBuffer[i][0];
+ for (int j = 1; j < size; j++) {
+ tempBuffer[i][j] = filter * v + (1 - filter) * tempBuffer[i][j];
+ v = tempBuffer[i][j];
+ }
+ }
+
+ // erode right to left
+ for (int i = size - 1; i >= 0; i--) {
+ v = tempBuffer[i][0];
+ for (int j = 0; j < size; j++) {
+ tempBuffer[i][j] = filter * v + (1 - filter) * tempBuffer[i][j];
+ v = tempBuffer[i][j];
+ // erodeBand(tempBuffer[size * i + size - 1], -1);
+ }
+ }
+
+ // erode top to bottom
+ for (int i = 0; i < size; i++) {
+ v = tempBuffer[0][i];
+ for (int j = 0; j < size; j++) {
+ tempBuffer[j][i] = filter * v + (1 - filter) * tempBuffer[j][i];
+ v = tempBuffer[j][i];
+ }
+ }
+
+ // erode from bottom to top
+ for (int i = size - 1; i >= 0; i--) {
+ v = tempBuffer[0][i];
+ for (int j = 0; j < size; j++) {
+ tempBuffer[j][i] = filter * v + (1 - filter) * tempBuffer[j][i];
+ v = tempBuffer[j][i];
+ }
+ }
+ }
+
+ public float getHeightRange() {
+ return heightRange;
+ }
+
+ public void setHeightRange(final float heightRange) {
+ this.heightRange = heightRange;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/RawHeightMap.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/RawHeightMap.java new file mode 100644 index 0000000..b87bdb8 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/RawHeightMap.java @@ -0,0 +1,246 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.heightmap;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.LittleEndianDataInput;
+
+public class RawHeightMap {
+ private static final Logger logger = Logger.getLogger(RawHeightMap.class.getName());
+
+ public enum HeightMapFormat {
+ Byte, Short, UnsignedByte, UnsignedShort, Integer, Float
+ }
+
+ private HeightMapFormat format;
+ private boolean isLittleEndian;
+
+ private int size;
+ private InputStream stream;
+ private float heightData[];
+
+ private boolean swapXY;
+ private boolean flipX;
+ private boolean flipY;
+
+ private boolean loaded;
+
+ public RawHeightMap(final String filename, final int size) throws Exception {
+ // varify that filename and size are valid.
+ if (null == filename || size <= 0) {
+ throw new Exception("Must supply valid filename and " + "size (> 0)");
+ }
+ try {
+ setup(new FileInputStream(filename), size);
+ } catch (final FileNotFoundException e) {
+ throw new Exception("height file not found: " + filename);
+ }
+ }
+
+ public RawHeightMap(final InputStream stream, final int size) throws Exception {
+ setup(stream, size);
+ }
+
+ public RawHeightMap(final URL resource, final int size) throws Exception {
+ // varify that resource and size are valid.
+ if (null == resource || size <= 0) {
+ throw new Exception("Must supply valid resource and " + "size (> 0)");
+ }
+
+ try {
+ setup(resource.openStream(), size);
+ } catch (final IOException e) {
+ throw new Exception("Unable to open height url: " + resource);
+ }
+ }
+
+ private void setup(final InputStream stream, final int size) throws Exception {
+ // varify that filename and size are valid.
+ if (null == stream || size <= 0) {
+ throw new Exception("Must supply valid stream and " + "size (> 0)");
+ }
+
+ this.stream = stream;
+ this.size = size;
+ }
+
+ /**
+ * <code>load</code> fills the height data array with the appropriate data from the set RAW image stream or file.
+ *
+ * @return true if the load is successful, false otherwise.
+ */
+ public boolean loadHeightmap() {
+ // initialize the height data attributes
+ heightData = new float[size * size];
+
+ // attempt to connect to the supplied file.
+ BufferedInputStream bis = null;
+
+ try {
+ bis = new BufferedInputStream(stream);
+ final DataInputStream dis = new DataInputStream(bis);
+ DataInput di = dis;
+ if (isLittleEndian) {
+ di = new LittleEndianDataInput(dis);
+ }
+
+ // read the raw file
+ for (int y = 0; y < size; y++) {
+ for (int x = 0; x < size; x++) {
+ int index;
+ int xIndex = x;
+ int yIndex = y;
+
+ if (flipX) {
+ xIndex = size - x - 1;
+ }
+ if (flipY) {
+ yIndex = size - y - 1;
+ }
+
+ if (swapXY) {
+ index = xIndex * size + yIndex;
+ } else {
+ index = yIndex * size + xIndex;
+ }
+
+ switch (format) {
+ case Byte:
+ heightData[index] = di.readByte() * 0.5f / Byte.MAX_VALUE + 0.5f;
+ break;
+ case Short:
+ heightData[index] = di.readShort() * 0.5f / Short.MAX_VALUE + 0.5f;
+ break;
+ case UnsignedByte:
+ heightData[index] = di.readUnsignedByte() * 0.5f / Byte.MAX_VALUE;
+ break;
+ case UnsignedShort:
+ heightData[index] = di.readUnsignedShort() * 0.5f / Short.MAX_VALUE;
+ break;
+ case Integer:
+ heightData[index] = di.readInt() * 0.5f / Integer.MAX_VALUE + 0.5f;
+ break;
+ case Float:
+ heightData[index] = di.readFloat() / Float.MAX_VALUE;
+ break;
+ }
+ }
+ }
+ dis.close();
+ } catch (final IOException e1) {
+ logger.warning("Error reading height data from stream.");
+ return false;
+ }
+ loaded = true;
+ return true;
+ }
+
+ /**
+ * @return the heightData
+ */
+ public float[] getHeightData() {
+ if (!loaded) {
+ loadHeightmap();
+ }
+ return heightData;
+ }
+
+ /**
+ * @return the format
+ */
+ public HeightMapFormat getFormat() {
+ return format;
+ }
+
+ /**
+ * @param format
+ * the format to set
+ */
+ public void setFormat(final HeightMapFormat format) {
+ this.format = format;
+ }
+
+ /**
+ * @return the isLittleEndian
+ */
+ public boolean isLittleEndian() {
+ return isLittleEndian;
+ }
+
+ /**
+ * @param isLittleEndian
+ * the isLittleEndian to set
+ */
+ public void setLittleEndian(final boolean isLittleEndian) {
+ this.isLittleEndian = isLittleEndian;
+ }
+
+ /**
+ * @return the swapXY
+ */
+ public boolean isSwapXY() {
+ return swapXY;
+ }
+
+ /**
+ * @param swapXY
+ * the swapXY to set
+ */
+ public void setSwapXY(final boolean swapXY) {
+ this.swapXY = swapXY;
+ }
+
+ /**
+ * @return the flipX
+ */
+ public boolean isFlipX() {
+ return flipX;
+ }
+
+ /**
+ * @param flipX
+ * the flipX to set
+ */
+ public void setFlipX(final boolean flipX) {
+ this.flipX = flipX;
+ }
+
+ /**
+ * @return the flipY
+ */
+ public boolean isFlipY() {
+ return flipY;
+ }
+
+ /**
+ * @param flipY
+ * the flipY to set
+ */
+ public void setFlipY(final boolean flipY) {
+ this.flipY = flipY;
+ }
+
+ /**
+ * @return the loaded
+ */
+ public boolean isLoaded() {
+ return loaded;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java new file mode 100644 index 0000000..3e3b6ae --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java @@ -0,0 +1,135 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.array;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.extension.terrain.client.TerrainDataProvider;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.providers.image.ImageTextureSource;
+import com.ardor3d.extension.terrain.util.NormalMapUtil;
+import com.ardor3d.image.Image;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class ArrayTerrainDataProvider implements TerrainDataProvider {
+ private static final int tileSize = 128;
+
+ private final List<float[]> heightMaps;
+ private final List<Integer> heightMapSizes;
+ private final ReadOnlyVector3 scale;
+
+ private float heightMax = 1.0f;
+ private float heightMin = 0.0f;
+
+ private boolean generateNormalMap;
+
+ public ArrayTerrainDataProvider(final float[] data, final int size, final ReadOnlyVector3 scale) {
+ this(data, size, scale, false);
+ }
+
+ public ArrayTerrainDataProvider(final float[] data, final int size, final ReadOnlyVector3 scale,
+ final boolean generateNormalMap) {
+ this.scale = scale;
+ this.generateNormalMap = generateNormalMap;
+
+ // TODO: calculate clipLevelCount through size and tileSize
+ final int clipLevelCount = 6;
+
+ int currentSize = size;
+ heightMaps = Lists.newArrayList();
+ heightMapSizes = Lists.newArrayList();
+ heightMaps.add(data);
+ heightMapSizes.add(currentSize);
+ float[] parentHeightMap = data;
+ for (int i = 0; i < clipLevelCount; i++) {
+ currentSize /= 2;
+ final float[] heightMapMip = new float[currentSize * currentSize];
+ heightMaps.add(heightMapMip);
+ heightMapSizes.add(currentSize);
+ for (int x = 0; x < currentSize; x++) {
+ for (int z = 0; z < currentSize; z++) {
+ heightMapMip[z * currentSize + x] = parentHeightMap[z * currentSize * 4 + x * 2];
+ }
+ }
+ parentHeightMap = heightMapMip;
+ }
+
+ Collections.reverse(heightMaps);
+ Collections.reverse(heightMapSizes);
+ }
+
+ @Override
+ public Map<Integer, String> getAvailableMaps() throws Exception {
+ final Map<Integer, String> maps = Maps.newHashMap();
+ maps.put(0, "ArrayBasedMap");
+
+ return maps;
+ }
+
+ @Override
+ public TerrainSource getTerrainSource(final int mapId) {
+ return new ArrayTerrainSource(tileSize, heightMaps, heightMapSizes, scale, heightMin, heightMax);
+ }
+
+ @Override
+ public TextureSource getTextureSource(final int mapId) {
+ return new ArrayTextureSource(tileSize, heightMaps, heightMapSizes);
+ }
+
+ @Override
+ public TextureSource getNormalMapSource(final int mapId) {
+ if (generateNormalMap) {
+ try {
+ final float[] data = heightMaps.get(heightMaps.size() - 1);
+ final int size = heightMapSizes.get(heightMapSizes.size() - 1);
+ final Image normalImage = NormalMapUtil.constructNormalMap(data, size, scale.getY() / heightMax,
+ scale.getX(), scale.getZ());
+ return new ImageTextureSource(tileSize, normalImage, heightMapSizes);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ public float getHeightMin() {
+ return heightMin;
+ }
+
+ public void setHeightMin(final float heightMin) {
+ this.heightMin = heightMin;
+ }
+
+ public float getHeightMax() {
+ return heightMax;
+ }
+
+ public void setHeightMax(final float heightMax) {
+ this.heightMax = heightMax;
+ }
+
+ public List<Integer> getHeightMapSizes() {
+ return heightMapSizes;
+ }
+
+ public boolean isGenerateNormalMap() {
+ return generateNormalMap;
+ }
+
+ public void setGenerateNormalMap(final boolean generateNormalMap) {
+ this.generateNormalMap = generateNormalMap;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainSource.java new file mode 100644 index 0000000..71b6e04 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainSource.java @@ -0,0 +1,111 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.providers.array;
+
+import java.util.List;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TerrainConfiguration;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.google.common.collect.Sets;
+
+public class ArrayTerrainSource implements TerrainSource {
+ private final int tileSize;
+ private final List<float[]> heightMaps;
+ private final List<Integer> heightMapSizes;
+ private final ReadOnlyVector3 scale;
+ private final float heightMin;
+ private final float heightMax;
+
+ private final ThreadLocal<float[]> tileDataPool = new ThreadLocal<float[]>() {
+ @Override
+ protected float[] initialValue() {
+ return new float[tileSize * tileSize];
+ }
+ };
+
+ public ArrayTerrainSource(final int tileSize, final List<float[]> heightMaps, final List<Integer> heightMapSizes,
+ final ReadOnlyVector3 scale, final float heightMin, final float heightMax) {
+ this.tileSize = tileSize;
+ this.heightMaps = heightMaps;
+ this.heightMapSizes = heightMapSizes;
+ this.scale = scale;
+ this.heightMin = heightMin;
+ this.heightMax = heightMax;
+ }
+
+ @Override
+ public TerrainConfiguration getConfiguration() throws Exception {
+ return new TerrainConfiguration(heightMaps.size(), tileSize, scale, heightMin, heightMax, true);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final Set<Tile> validTiles = Sets.newHashSet();
+
+ final int heightMapSize = heightMapSizes.get(clipmapLevel);
+ for (int y = 0; y < numTilesY; y++) {
+ for (int x = 0; x < numTilesX; x++) {
+ final int xx = tileX + x;
+ final int yy = tileY + y;
+ if (xx >= 0 && xx * tileSize <= heightMapSize && yy >= 0 && yy * tileSize <= heightMapSize) {
+ final Tile tile = new Tile(xx, yy);
+ validTiles.add(tile);
+ }
+ }
+ }
+
+ return validTiles;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public float[] getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final float[] heightMap = heightMaps.get(clipmapLevel);
+ final int heightMapSize = heightMapSizes.get(clipmapLevel);
+
+ final float[] data = tileDataPool.get();
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int index = x + y * tileSize;
+
+ final int heightX = tileX * tileSize + x;
+ final int heightY = tileY * tileSize + y;
+ data[index] = getHeight(heightMap, heightMapSize, heightX, heightY);
+ }
+ }
+ return data;
+ }
+
+ private float getHeight(final float[] heightMap, final int heightMapSize, final int x, final int y) {
+ if (x < 0 || x >= heightMapSize || y < 0 || y >= heightMapSize) {
+ return 0;
+ }
+
+ return heightMap[y * heightMapSize + x];
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTextureSource.java new file mode 100644 index 0000000..b97855f --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTextureSource.java @@ -0,0 +1,113 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.array;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class ArrayTextureSource implements TextureSource {
+ private final int tileSize;
+ private final List<float[]> heightMaps;
+ private final List<Integer> heightMapSizes;
+
+ private final ThreadLocal<ByteBuffer> tileDataPool = new ThreadLocal<ByteBuffer>() {
+ @Override
+ protected ByteBuffer initialValue() {
+ return BufferUtils.createByteBufferOnHeap(tileSize * tileSize);
+ }
+ };
+
+ public ArrayTextureSource(final int tileSize, final List<float[]> heightMaps, final List<Integer> heightMapSizes) {
+ this.tileSize = tileSize;
+ this.heightMaps = heightMaps;
+ this.heightMapSizes = heightMapSizes;
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, TextureStoreFormat.Luminance8);
+
+ return new TextureConfiguration(heightMaps.size(), textureStoreFormat, tileSize, 1f, true, false);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final Set<Tile> validTiles = Sets.newHashSet();
+
+ final int heightMapSize = heightMapSizes.get(clipmapLevel);
+ for (int y = 0; y < numTilesY; y++) {
+ for (int x = 0; x < numTilesX; x++) {
+ final int xx = tileX + x;
+ final int yy = tileY + y;
+ if (xx >= 0 && xx * tileSize <= heightMapSize && yy >= 0 && yy * tileSize <= heightMapSize) {
+ final Tile tile = new Tile(xx, yy);
+ validTiles.add(tile);
+ }
+ }
+ }
+
+ return validTiles;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final float[] heightMap = heightMaps.get(clipmapLevel);
+ final int heightMapSize = heightMapSizes.get(clipmapLevel);
+
+ final ByteBuffer data = tileDataPool.get();
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int index = (x + y * tileSize) * 1;
+
+ final int heightX = tileX * tileSize + x;
+ final int heightY = tileY * tileSize + y;
+ final float height = getHeight(heightMap, heightMapSize, heightX, heightY);
+ final byte byteHeight = (byte) (height * 255 * 3);
+ data.put(index, byteHeight);
+ }
+ }
+ return data;
+ }
+
+ private float getHeight(final float[] heightMap, final int heightMapSize, final int x, final int y) {
+ if (x < 0 || x >= heightMapSize || y < 0 || y >= heightMapSize) {
+ return 0;
+ }
+
+ return heightMap[y * heightMapSize + x];
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AbstractAwtElement.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AbstractAwtElement.java new file mode 100644 index 0000000..a56eacb --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AbstractAwtElement.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.providers.awt; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.util.Map; + +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.Vector4; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector4; +import com.google.common.collect.Maps; + +public abstract class AbstractAwtElement { + + protected final Transform _transform = new Transform(); + protected Composite _compositeOverride; + protected ElementUpdateListener _listener; + protected Map<RenderingHints.Key, Object> hints = Maps.newHashMap(); + + protected Vector4 _awtBounds = new Vector4(); + + public AbstractAwtElement(final ReadOnlyTransform transform, final Composite compositeOverride) { + _transform.set(transform); + _compositeOverride = compositeOverride; + + hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + } + + public ReadOnlyTransform getTransform() { + return _transform; + } + + public void setTransform(final ReadOnlyTransform transform) { + _transform.set(transform); + updateBounds(); + } + + public Composite getCompositeOverride() { + return _compositeOverride; + } + + public void setCompositeOverride(final Composite override) { + _compositeOverride = override; + updateBounds(); + } + + public abstract void drawTo(BufferedImage image, ReadOnlyTransform localTransform, int clipmapLevel); + + public abstract void updateBoundsFromElement(); + + public void updateBounds() { + final Vector4 oldBounds = new Vector4(_awtBounds); + // update using size of element + updateBoundsFromElement(); + + // So apply transform + final double x = _awtBounds.getX(), y = _awtBounds.getY(), width = _awtBounds.getZ(), height = _awtBounds + .getW(); + final Vector3[] vects = new Vector3[] { // + // + new Vector3(x, y, 0), // + new Vector3(x + width, y, 0), // + new Vector3(x + width, y + height, 0), // + new Vector3(x, y + height, 0) // + }; + + // update final bounds info. + double minX, minY, maxX, maxY; + minX = minY = Double.POSITIVE_INFINITY; + maxX = maxY = Double.NEGATIVE_INFINITY; + + for (final Vector3 vect : vects) { + _transform.applyForward(vect); + if (vect.getX() < minX) { + minX = vect.getX(); + } + if (vect.getX() > maxX) { + maxX = vect.getX(); + } + if (vect.getY() < minY) { + minY = vect.getY(); + } + if (vect.getY() > maxY) { + maxY = vect.getY(); + } + } + + _awtBounds.set(minX, minY, maxX - minX, maxY - minY); + + if (_listener != null) { + _listener.elementUpdated(oldBounds, _awtBounds); + } + } + + public ReadOnlyVector4 getBounds() { + return _awtBounds; + } + + public void setUpdateListener(final ElementUpdateListener listener) { + _listener = listener; + } + + public static AlphaComposite makeAlphaComposite(final float alpha) { + final int type = AlphaComposite.SRC_OVER; + return AlphaComposite.getInstance(type, alpha); + } + + public Map<RenderingHints.Key, Object> getHints() { + return hints; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtElementProvider.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtElementProvider.java new file mode 100644 index 0000000..c8b5ae4 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtElementProvider.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.providers.awt; + +import java.util.List; + +import com.ardor3d.math.type.ReadOnlyVector4; +import com.google.common.collect.Lists; + +public class AwtElementProvider implements ElementUpdateListener { + private final List<AbstractAwtElement> _elements = Lists.newLinkedList(); + private final List<ElementUpdateListener> _updateListeners = Lists.newLinkedList(); + + public List<AbstractAwtElement> getElements() { + return _elements; + } + + public void addElement(final AbstractAwtElement element) { + _elements.add(element); + element.setUpdateListener(this); + elementUpdated(element.getBounds(), element.getBounds()); + } + + public void clear() { + for (final AbstractAwtElement element : _elements) { + element.setUpdateListener(null); + } + _elements.clear(); + } + + public void removeElement(final AbstractAwtElement element) { + element.setUpdateListener(null); + _elements.remove(element); + } + + public void addElementUpdateListener(final ElementUpdateListener listener) { + _updateListeners.add(listener); + } + + public void removeElementUpdateListener(final ElementUpdateListener listener) { + _updateListeners.remove(listener); + } + + public void cleanElementUpdateListeners() { + _updateListeners.clear(); + } + + @Override + public void elementUpdated(final ReadOnlyVector4 oldBounds, final ReadOnlyVector4 newBounds) { + for (final ElementUpdateListener listener : _updateListeners) { + listener.elementUpdated(oldBounds, newBounds); + } + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtImageElement.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtImageElement.java new file mode 100644 index 0000000..a16a090 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtImageElement.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.terrain.providers.awt; + +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +import com.ardor3d.math.Transform; +import com.ardor3d.math.type.ReadOnlyTransform; + +public class AwtImageElement extends AbstractAwtElement { + + private final Image _image; + + public AwtImageElement(final Image image) { + this(image, Transform.IDENTITY, null); + updateBounds(); + } + + public AwtImageElement(final Image image, final ReadOnlyTransform transform) { + this(image, transform, null); + } + + public AwtImageElement(final Image image, final ReadOnlyTransform transform, final Composite compositeOverride) { + super(transform, compositeOverride); + _image = image; + } + + public Image getImage() { + return _image; + } + + @Override + public void updateBoundsFromElement() { + _awtBounds.set(0, 0, _image.getWidth(null), _image.getHeight(null)); + } + + @Override + public void drawTo(final BufferedImage image, final ReadOnlyTransform localTransform, final int clipmapLevel) { + // apply the two transforms together and then use result to scale/translate and rotate image + final Transform trans = new Transform(); + localTransform.multiply(getTransform(), trans); + + // grab a copy of the graphics so we don't bleed state to next image + final Graphics2D g2d = (Graphics2D) image.getGraphics().create(); + + // apply hints + for (final RenderingHints.Key key : hints.keySet()) { + g2d.setRenderingHint(key, hints.get(key)); + } + + // set transform + g2d.translate(trans.getTranslation().getX(), trans.getTranslation().getY()); + g2d.rotate(trans.getMatrix().toAngles(null)[2]); // rotation about z + g2d.scale(trans.getScale().getX(), trans.getScale().getY()); + + // set composite + if (_compositeOverride != null) { + g2d.setComposite(_compositeOverride); + } + + // draw the image + g2d.drawImage(_image, 0, 0, null); + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtShapeElement.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtShapeElement.java new file mode 100644 index 0000000..bbfcdc8 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtShapeElement.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.providers.awt; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.Map; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector4; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.google.common.collect.Maps; + +public class AwtShapeElement extends AbstractAwtElement { + + protected Shape _shape; + + protected boolean _filled = true; + protected Color _fillColor = Color.WHITE; + + protected boolean _outlined = false; + protected Color _outlineColor = Color.BLACK; + + protected float _strokeWidth = 1.0f; + + protected Vector4 _margin = new Vector4(1, 1, 1, 1); + + protected final Map<Integer, BasicStroke> _strokes = Maps.newHashMap(); + + public AwtShapeElement(final Shape shape) { + this(shape, Transform.IDENTITY, null); + } + + public AwtShapeElement(final Shape shape, final ReadOnlyTransform transform) { + this(shape, transform, null); + } + + public AwtShapeElement(final Shape shape, final ReadOnlyTransform transform, final Composite compositeOverride) { + super(transform, compositeOverride); + _shape = shape; + updateBounds(); + } + + public Shape getShape() { + return _shape; + } + + public void setShape(final Shape shape) { + _shape = shape; + updateBounds(); + } + + public boolean isFilled() { + return _filled; + } + + public void setFilled(final boolean filled) { + _filled = filled; + } + + public Color getFillColor() { + return _fillColor; + } + + public void setFillColor(final Color color) { + _fillColor = color; + } + + public boolean isOutlined() { + return _outlined; + } + + public void setOutlined(final boolean outlined) { + _outlined = outlined; + } + + public Color getOutlineColor() { + return _outlineColor; + } + + public void setOutlineColor(final Color color) { + _outlineColor = color; + } + + @Override + public void updateBoundsFromElement() { + final Rectangle2D rect = _shape.getBounds2D(); + _awtBounds.set(rect.getMinX() - _margin.getX(), rect.getMinY() - _margin.getY(), + rect.getWidth() + _margin.getX() + _margin.getZ(), rect.getHeight() + _margin.getY() + _margin.getW()); + } + + @Override + public void drawTo(final BufferedImage image, final ReadOnlyTransform localTransform, final int clipmapLevel) { + // apply the two transforms together and then use result to scale/translate and rotate image + final Transform trans = new Transform(); + localTransform.multiply(getTransform(), trans); + + // grab a copy of the graphics so we don't bleed state to next image + final Graphics2D g2d = (Graphics2D) image.getGraphics().create(); + + // apply hints + for (final RenderingHints.Key key : hints.keySet()) { + g2d.setRenderingHint(key, hints.get(key)); + } + + // set transform + g2d.translate(trans.getTranslation().getX(), trans.getTranslation().getY()); + g2d.rotate(trans.getMatrix().toAngles(null)[2]); // rotation about z + g2d.scale(trans.getScale().getX(), trans.getScale().getY()); + + // set composite + if (_compositeOverride != null) { + g2d.setComposite(_compositeOverride); + } + + // draw outline and/or fill + if (_filled) { + g2d.setColor(_fillColor); + g2d.fill(_shape); + } + + if (_outlined) { + // set stroke + BasicStroke stroke = _strokes.get(clipmapLevel); + if (stroke == null) { + stroke = new BasicStroke(_strokeWidth / MathUtils.pow2(clipmapLevel)); + _strokes.put(clipmapLevel, stroke); + } + g2d.setStroke(stroke); + + g2d.setColor(_outlineColor); + g2d.draw(_shape); + } + } + + public Vector4 getMargin() { + return _margin; + } + + public void setMargin(final Vector4 margin) { + _margin.set(margin); + } + + public void setMargin(final double left, final double right, final double top, final double bottom) { + _margin.set(left, top, right, bottom); + } + + public void setMargin(final double outline) { + _margin.set(outline, outline, outline, outline); + } + + public void setStrokeWidth(final float width) { + _strokeWidth = width; + clearStrokes(); + } + + public float getStrokeWidth() { + return _strokeWidth; + } + + protected void clearStrokes() { + _strokes.clear(); + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtTextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtTextureSource.java new file mode 100644 index 0000000..ed6b903 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtTextureSource.java @@ -0,0 +1,231 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.awt;
+
+import java.awt.AlphaComposite;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.type.ReadOnlyVector4;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class AwtTextureSource implements TextureSource, ElementUpdateListener {
+ private static final int tileSize = 128;
+ private final int availableClipmapLevels;
+
+ private final AwtElementProvider provider;
+ private final TextureStoreFormat format;
+ private final boolean hasAlpha;
+
+ private final BufferedImage _image[];
+ private final Set<Tile> _updatedTiles[];
+
+ private final ThreadLocal<byte[]> tileDataPool = new ThreadLocal<byte[]>() {
+ @Override
+ protected byte[] initialValue() {
+ return new byte[tileSize * tileSize * (hasAlpha ? 4 : 3)];
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ public AwtTextureSource(final int availableClipmapLevels, final TextureStoreFormat format) {
+ if (format != TextureStoreFormat.RGB8 && format != TextureStoreFormat.RGBA8) {
+ throw new IllegalArgumentException("Only RGB8 and RGBA8 currently supported.");
+ }
+
+ this.availableClipmapLevels = availableClipmapLevels;
+ this.format = format;
+ hasAlpha = format == TextureStoreFormat.RGBA8;
+
+ _image = new BufferedImage[availableClipmapLevels];
+ _updatedTiles = new Set[availableClipmapLevels];
+
+ provider = new AwtElementProvider();
+ provider.addElementUpdateListener(this);
+
+ for (int i = 0; i < availableClipmapLevels; i++) {
+ _image[i] = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB);
+ _updatedTiles[i] = Sets.newHashSet();
+ }
+ }
+
+ public AwtElementProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, format);
+
+ return new TextureConfiguration(availableClipmapLevels, textureStoreFormat, tileSize, 1f, false, true);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ if (_updatedTiles[baseClipmapLevel].isEmpty()) {
+ return null;
+ }
+
+ final Set<Tile> tiles = Sets.newHashSet();
+
+ int checkX, checkY;
+ for (final Iterator<Tile> it = _updatedTiles[baseClipmapLevel].iterator(); it.hasNext();) {
+ final Tile tile = it.next();
+ checkX = tile.getX();
+ checkY = tile.getY();
+ if (checkX >= tileX && checkX < tileX + numTilesX && checkY >= tileY && checkY < tileY + numTilesY) {
+ tiles.add(tile);
+ it.remove();
+ }
+ }
+
+ return tiles;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile sourceTile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ // build a transform that would take us to the local space of the tile.
+ final Transform localTransform = new Transform();
+ localTransform.setTranslation(-tileX * tileSize, -tileY * tileSize, 0);
+ final double scale = 1.0 / MathUtils.pow2(baseClipmapLevel);
+ localTransform.setScale(scale);
+
+ final double tileInScale = MathUtils.pow2(baseClipmapLevel) * tileSize;
+ final double minX = tileInScale * tileX;
+ final double minY = tileInScale * tileY;
+ final double maxX = minX + tileInScale - 1;
+ final double maxY = minY + tileInScale - 1;
+
+ // Clear image
+ final Graphics2D graphics = (Graphics2D) _image[baseClipmapLevel].getGraphics();
+ final Composite composite = graphics.getComposite();
+ // TODO: Add: do regular clear with black if no alpha channel right?
+ graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
+ graphics.fillRect(0, 0, tileSize, tileSize);
+ graphics.setComposite(composite);
+
+ // get list of elements that intersect the given region
+ final List<AbstractAwtElement> elements = Lists.newArrayList(provider.getElements());
+ for (final Iterator<AbstractAwtElement> it = elements.iterator(); it.hasNext();) {
+ final AbstractAwtElement element = it.next();
+
+ // check bounds to toss it or keep it.
+ final ReadOnlyVector4 bounds = element.getBounds();
+
+ if (bounds.getX() > maxX || bounds.getX() + bounds.getZ() <= minX || bounds.getY() > maxY
+ || bounds.getY() + bounds.getW() <= minY) {
+ // toss it
+ it.remove();
+ }
+ }
+
+ // make our buffer - init to all 0's
+ final ByteBuffer data = BufferUtils.createByteBufferOnHeap(tileSize * tileSize * (hasAlpha ? 4 : 3));
+
+ // shortcut - no data, return buffer.
+ if (elements.isEmpty()) {
+ // graphics.setBackground((tileX + tileY) % 2 == 0 ? Color.GREEN : Color.GRAY);
+ // graphics.clearRect(0, 0, tileSize, tileSize);
+ return data;
+ }
+
+ // otherwise draw... for each element, apply to our image.
+ for (final AbstractAwtElement element : elements) {
+ element.drawTo(_image[baseClipmapLevel], localTransform, clipmapLevel);
+ }
+
+ // if (clipmapLevel == 0) {
+ // graphics.setBackground((tileX + tileY) % 2 == 0 ? Color.GREEN : Color.GRAY);
+ // graphics.clearRect(0, 0, tileSize, tileSize);
+ // }
+
+ // grab image contents to buffer
+ final byte[] byteArray = tileDataPool.get();
+ final DataBufferInt dataBuffer = (DataBufferInt) _image[baseClipmapLevel].getData().getDataBuffer();
+ final int[] tmpData = dataBuffer.getData();
+ int index = 0;
+ for (int i = 0; i < tileSize * tileSize; i++) {
+ final int argb = tmpData[i];
+ byteArray[index++] = (byte) (argb >> 16 & 0xFF);
+ byteArray[index++] = (byte) (argb >> 8 & 0xFF);
+ byteArray[index++] = (byte) (argb & 0xFF);
+ if (hasAlpha) {
+ byteArray[index++] = (byte) (argb >> 24 & 0xFF);
+ }
+ }
+
+ data.put(byteArray);
+ data.flip();
+
+ // return buffer
+ return data;
+ }
+
+ @Override
+ public void elementUpdated(final ReadOnlyVector4 oldBounds, final ReadOnlyVector4 newBounds) {
+ addTiles(oldBounds);
+ addTiles(newBounds);
+ }
+
+ protected void addTiles(final ReadOnlyVector4 bounds) {
+ for (int i = 0; i < availableClipmapLevels; i++) {
+ final double scale = 1.0 / (tileSize * MathUtils.pow2(i));
+ final int minX = (int) MathUtils.floor(bounds.getX() * scale);
+ final int minY = (int) MathUtils.floor(bounds.getY() * scale);
+ final int maxX = (int) MathUtils.floor((bounds.getX() + bounds.getZ() - 1) * scale);
+ final int maxY = (int) MathUtils.floor((bounds.getY() + bounds.getW() - 1) * scale);
+
+ Tile tile;
+ for (int y = minY; y <= maxY; y++) {
+ for (int x = minX; x <= maxX; x++) {
+ tile = new Tile(x, y);
+ _updatedTiles[i].add(tile);
+ }
+ }
+ }
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/ElementUpdateListener.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/ElementUpdateListener.java new file mode 100644 index 0000000..06078d4 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/ElementUpdateListener.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.providers.awt; + +import com.ardor3d.math.type.ReadOnlyVector4; + +public interface ElementUpdateListener { + void elementUpdated(ReadOnlyVector4 oldBounds, ReadOnlyVector4 newBounds); +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/image/ImageTextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/image/ImageTextureSource.java new file mode 100644 index 0000000..9deb52c --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/image/ImageTextureSource.java @@ -0,0 +1,140 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.image;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.Image;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class ImageTextureSource implements TextureSource {
+ private final int tileSize;
+ private final List<byte[]> maps;
+ private final List<Integer> heightMapSizes;
+
+ private final ThreadLocal<ByteBuffer> tileDataPool = new ThreadLocal<ByteBuffer>() {
+ @Override
+ protected ByteBuffer initialValue() {
+ return BufferUtils.createByteBufferOnHeap(tileSize * tileSize * 3);
+ }
+ };
+
+ public ImageTextureSource(final int tileSize, final Image map, final List<Integer> heightMapSizes) {
+ this.tileSize = tileSize;
+ maps = Lists.newArrayListWithExpectedSize(heightMapSizes.size());
+ this.heightMapSizes = Lists.newArrayList(heightMapSizes);
+ buildMips(map);
+ }
+
+ private void buildMips(final Image map) {
+ final int max = heightMapSizes.size();
+ int currentSize = heightMapSizes.get(max - 1);
+ byte[] parentHeightMap = new byte[currentSize * currentSize * 3];
+ // populate parentHeightMap from image
+ map.getData(0).get(parentHeightMap);
+
+ maps.add(parentHeightMap);
+ // populate mips
+ for (int i = 1; i < max; i++) {
+ currentSize = heightMapSizes.get(max - i - 1);
+ final byte[] heightMapMip = new byte[currentSize * currentSize * 3];
+ for (int x = 0; x < currentSize; x++) {
+ for (int z = 0; z < currentSize; z++) {
+ heightMapMip[3 * (z * currentSize + x) + 0] = parentHeightMap[3 * (z * currentSize * 4 + x * 2) + 0];
+ heightMapMip[3 * (z * currentSize + x) + 1] = parentHeightMap[3 * (z * currentSize * 4 + x * 2) + 1];
+ heightMapMip[3 * (z * currentSize + x) + 2] = parentHeightMap[3 * (z * currentSize * 4 + x * 2) + 2];
+ }
+ }
+ parentHeightMap = heightMapMip;
+ maps.add(parentHeightMap);
+ }
+ Collections.reverse(maps);
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, TextureStoreFormat.RGB8);
+
+ return new TextureConfiguration(maps.size(), textureStoreFormat, tileSize, 1f, true, false);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final Set<Tile> validTiles = Sets.newHashSet();
+
+ final int heightMapSize = heightMapSizes.get(clipmapLevel);
+ for (int y = 0; y < numTilesY; y++) {
+ for (int x = 0; x < numTilesX; x++) {
+ final int xx = tileX + x;
+ final int yy = tileY + y;
+ if (xx >= 0 && xx * tileSize <= heightMapSize && yy >= 0 && yy * tileSize <= heightMapSize) {
+ final Tile tile = new Tile(xx, yy);
+ validTiles.add(tile);
+ }
+ }
+ }
+
+ return validTiles;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final byte[] heightMap = maps.get(clipmapLevel);
+ final int heightMapSize = heightMapSizes.get(clipmapLevel);
+
+ final ByteBuffer data = tileDataPool.get();
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int index = x + y * tileSize;
+
+ final int heightX = tileX * tileSize + x;
+ final int heightY = tileY * tileSize + y;
+ if (heightX < 0 || heightX >= heightMapSize || heightY < 0 || heightY >= heightMapSize) {
+ data.put(index * 3 + 0, (byte) 0);
+ data.put(index * 3 + 1, (byte) 0);
+ data.put(index * 3 + 2, (byte) 0);
+ } else {
+ data.put(index * 3 + 0, heightMap[3 * (heightY * heightMapSize + heightX) + 0]);
+ data.put(index * 3 + 1, heightMap[3 * (heightY * heightMapSize + heightX) + 1]);
+ data.put(index * 3 + 2, heightMap[3 * (heightY * heightMapSize + heightX) + 2]);
+ }
+ }
+ }
+ return data;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java new file mode 100644 index 0000000..e737243 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java @@ -0,0 +1,89 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.inmemory;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.extension.terrain.client.TerrainDataProvider;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.providers.image.ImageTextureSource;
+import com.ardor3d.extension.terrain.providers.inmemory.data.InMemoryTerrainData;
+import com.ardor3d.extension.terrain.util.NormalMapUtil;
+import com.ardor3d.image.Image;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class InMemoryTerrainDataProvider implements TerrainDataProvider {
+ private static final int tileSize = 128;
+ private final InMemoryTerrainData inMemoryTerrainData;
+
+ private boolean generateNormalMap;
+
+ public InMemoryTerrainDataProvider(final InMemoryTerrainData inMemoryTerrainData) {
+ this(inMemoryTerrainData, false);
+ }
+
+ public InMemoryTerrainDataProvider(final InMemoryTerrainData inMemoryTerrainData, final boolean generateNormalMap) {
+ this.inMemoryTerrainData = inMemoryTerrainData;
+ this.generateNormalMap = generateNormalMap;
+ }
+
+ @Override
+ public Map<Integer, String> getAvailableMaps() throws Exception {
+ final Map<Integer, String> maps = Maps.newHashMap();
+ maps.put(0, "InMemoryData");
+
+ return maps;
+ }
+
+ @Override
+ public TerrainSource getTerrainSource(final int mapId) {
+ return new InMemoryTerrainSource(tileSize, inMemoryTerrainData);
+ }
+
+ @Override
+ public TextureSource getTextureSource(final int mapId) {
+ return new InMemoryTextureSource(tileSize, inMemoryTerrainData);
+ }
+
+ @Override
+ public TextureSource getNormalMapSource(final int mapId) {
+ if (generateNormalMap) {
+ try {
+ final Image normalImage = NormalMapUtil.constructNormalMap(inMemoryTerrainData.getHeightData(),
+ inMemoryTerrainData.getSide(), inMemoryTerrainData.getMaxHeight(), inMemoryTerrainData
+ .getScale().getX(), inMemoryTerrainData.getScale().getY());
+
+ final List<Integer> heightMapSizes = Lists.newArrayList();
+ int currentSize = inMemoryTerrainData.getSide();
+ heightMapSizes.add(currentSize);
+ for (int i = 0; i < inMemoryTerrainData.getClipmapLevels(); i++) {
+ currentSize /= 2;
+ heightMapSizes.add(currentSize);
+ }
+ return new ImageTextureSource(tileSize, normalImage, heightMapSizes);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ public boolean isGenerateNormalMap() {
+ return generateNormalMap;
+ }
+
+ public void setGenerateNormalMap(final boolean generateNormalMap) {
+ this.generateNormalMap = generateNormalMap;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainSource.java new file mode 100644 index 0000000..dc5633a --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainSource.java @@ -0,0 +1,133 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.providers.inmemory;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TerrainConfiguration;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.providers.inmemory.data.InMemoryTerrainData;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.google.common.collect.Sets;
+
+public class InMemoryTerrainSource implements TerrainSource {
+ private final int tileSize;
+ private final InMemoryTerrainData inMemoryTerrainData;
+ private final int availableClipmapLevels;
+
+ public InMemoryTerrainSource(final int tileSize, final InMemoryTerrainData inMemoryTerrainData) {
+ this.tileSize = tileSize;
+ this.inMemoryTerrainData = inMemoryTerrainData;
+ availableClipmapLevels = inMemoryTerrainData.getClipmapLevels();
+ }
+
+ @Override
+ public TerrainConfiguration getConfiguration() throws Exception {
+ return new TerrainConfiguration(availableClipmapLevels, tileSize, inMemoryTerrainData.getScale(),
+ inMemoryTerrainData.getMinHeight(), inMemoryTerrainData.getMaxHeight(), true);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final Set<Tile> validTiles = Sets.newHashSet();
+
+ final int levelSize = 1 << baseClipmapLevel;
+ final int size = inMemoryTerrainData.getSide();
+
+ for (int y = 0; y < numTilesY; y++) {
+ for (int x = 0; x < numTilesX; x++) {
+ final int xx = tileX + x;
+ final int yy = tileY + y;
+ if (xx >= 0 && xx * tileSize * levelSize <= size && yy >= 0 && yy * tileSize * levelSize <= size) {
+ final Tile tile = new Tile(xx, yy);
+ validTiles.add(tile);
+ }
+ }
+ }
+
+ return validTiles;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final Set<Tile> updatedTiles[] = inMemoryTerrainData.getUpdatedTerrainTiles();
+ if (updatedTiles == null) {
+ return null;
+ }
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final Set<Tile> tiles = Sets.newHashSet();
+
+ synchronized (updatedTiles[baseClipmapLevel]) {
+ if (updatedTiles[baseClipmapLevel].isEmpty()) {
+ return null;
+ }
+
+ int checkX, checkY;
+ for (final Iterator<Tile> it = updatedTiles[baseClipmapLevel].iterator(); it.hasNext();) {
+ final Tile tile = it.next();
+ checkX = tile.getX();
+ checkY = tile.getY();
+ if (checkX >= tileX && checkX < tileX + numTilesX && checkY >= tileY && checkY < tileY + numTilesY) {
+ tiles.add(tile);
+ it.remove();
+ }
+ }
+ }
+
+ return tiles;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public float[] getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final int levelSize = 1 << baseClipmapLevel;
+
+ final int size = inMemoryTerrainData.getSide();
+
+ final float[] heightData = inMemoryTerrainData.getHeightData();
+
+ final float[] data = new float[tileSize * tileSize];
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int index = x + y * tileSize;
+
+ final int heightX = (tileX * tileSize + x) * levelSize;
+ final int heightY = (tileY * tileSize + y) * levelSize;
+ data[index] = getHeight(heightData, size, heightX, heightY);
+ }
+ }
+ return data;
+ }
+
+ private float getHeight(final float[] heightMap, final int heightMapSize, final int x, final int y) {
+ if (x < 0 || x >= heightMapSize || y < 0 || y >= heightMapSize) {
+ return 0;
+ }
+
+ return heightMap[y * heightMapSize + x];
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTextureSource.java new file mode 100644 index 0000000..32a046d --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTextureSource.java @@ -0,0 +1,142 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.inmemory;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.providers.inmemory.data.InMemoryTerrainData;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class InMemoryTextureSource implements TextureSource {
+ private final int tileSize;
+ private final InMemoryTerrainData inMemoryTerrainData;
+ private final int availableClipmapLevels;
+
+ public InMemoryTextureSource(final int tileSize, final InMemoryTerrainData inMemoryTerrainData) {
+ this.tileSize = tileSize;
+ this.inMemoryTerrainData = inMemoryTerrainData;
+ availableClipmapLevels = inMemoryTerrainData.getClipmapLevels();
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, TextureStoreFormat.RGBA8);
+
+ return new TextureConfiguration(availableClipmapLevels, textureStoreFormat, tileSize, 1f, true, true);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final Set<Tile> validTiles = Sets.newHashSet();
+
+ final int levelSize = 1 << baseClipmapLevel;
+ final int size = inMemoryTerrainData.getSide();
+
+ for (int y = 0; y < numTilesY; y++) {
+ for (int x = 0; x < numTilesX; x++) {
+ final int xx = tileX + x;
+ final int yy = tileY + y;
+ if (xx >= 0 && xx * tileSize * levelSize <= size && yy >= 0 && yy * tileSize * levelSize <= size) {
+ final Tile tile = new Tile(xx, yy);
+ validTiles.add(tile);
+ }
+ }
+ }
+
+ return validTiles;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final Set<Tile> updatedTiles[] = inMemoryTerrainData.getUpdatedTextureTiles();
+ if (updatedTiles == null) {
+ return null;
+ }
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final Set<Tile> tiles = Sets.newHashSet();
+
+ synchronized (updatedTiles[baseClipmapLevel]) {
+ if (updatedTiles[baseClipmapLevel].isEmpty()) {
+ return null;
+ }
+
+ int checkX, checkY;
+ for (final Iterator<Tile> it = updatedTiles[baseClipmapLevel].iterator(); it.hasNext();) {
+ final Tile tile = it.next();
+ checkX = tile.getX();
+ checkY = tile.getY();
+ if (checkX >= tileX && checkX < tileX + numTilesX && checkY >= tileY && checkY < tileY + numTilesY) {
+ tiles.add(tile);
+ it.remove();
+ }
+ }
+ }
+
+ return tiles;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int levelSize = 1 << availableClipmapLevels - clipmapLevel - 1;
+
+ final int size = inMemoryTerrainData.getSide();
+ final byte[] colorData = inMemoryTerrainData.getColorData();
+
+ final ByteBuffer data = BufferUtils.createByteBufferOnHeap(tileSize * tileSize * 4);
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int heightX = (tileX * tileSize + x) * levelSize;
+ final int heightY = (tileY * tileSize + y) * levelSize;
+
+ final int indexTile = (y * tileSize + x) * 4;
+ final int index = heightY * size + heightX;
+
+ if (heightX < 0 || heightX >= size || heightY < 0 || heightY >= size) {
+ data.put(indexTile + 0, (byte) 0);
+ data.put(indexTile + 1, (byte) 0);
+ data.put(indexTile + 2, (byte) 0);
+ data.put(indexTile + 3, (byte) 255);
+ } else {
+ data.put(indexTile + 0, colorData[index * 4 + 0]);
+ data.put(indexTile + 1, colorData[index * 4 + 1]);
+ data.put(indexTile + 2, colorData[index * 4 + 2]);
+ data.put(indexTile + 3, colorData[index * 4 + 3]);
+ }
+ }
+ }
+
+ return data;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/data/InMemoryTerrainData.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/data/InMemoryTerrainData.java new file mode 100644 index 0000000..5418266 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/data/InMemoryTerrainData.java @@ -0,0 +1,250 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.providers.inmemory.data; + +import java.util.Set; + +import com.ardor3d.extension.terrain.util.Tile; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Rectangle2; +import com.ardor3d.math.functions.FbmFunction3D; +import com.ardor3d.math.functions.Function3D; +import com.ardor3d.math.functions.Functions; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.google.common.collect.Sets; + +public class InMemoryTerrainData { + + protected final float[] heightData; + protected final byte[] colorData; + protected final int side; + + protected boolean running = false; + protected float minHeight = 0.0f; + protected float maxHeight = 10.0f; + protected float updateDelta = .5f; + + protected final int tileSize; + protected final int clipmapLevels; + + protected final Set<Tile>[] updatedTerrainTiles; + protected final Set<Tile>[] updatedTextureTiles; + + protected ReadOnlyVector3 scale; + + /** + * + * @param totalSide + * must be greater than 10. + */ + @SuppressWarnings("unchecked") + public InMemoryTerrainData(final int totalSide, final int clipmapLevels, final int tileSize, + final ReadOnlyVector3 scale) { + if (totalSide < 10) { + throw new IllegalArgumentException("totalSide must be at least 10."); + } + side = totalSide; + heightData = new float[side * side]; + colorData = new byte[side * side * 4]; // rgba + this.tileSize = tileSize; + this.clipmapLevels = clipmapLevels; + this.scale = scale; + + updatedTerrainTiles = new Set[clipmapLevels]; + updatedTextureTiles = new Set[clipmapLevels]; + for (int i = 0; i < clipmapLevels; i++) { + updatedTerrainTiles[i] = Sets.newHashSet(); + updatedTextureTiles[i] = Sets.newHashSet(); + } + + final double procScale = 1.0 / 4000.0; + final Function3D functionTmp = new FbmFunction3D(Functions.simplexNoise(), 9, 0.5, 0.5, 3.14); + final Function3D function = Functions.scaleInput(functionTmp, procScale, procScale, 1); + + for (int y = 0; y < side; y++) { + for (int x = 0; x < side; x++) { + final int index = y * side + x; + + final float h = (float) function.eval(x, y, 0); + heightData[index] = h; + + int col = (int) (h * h * 255 * 0.6); + col = MathUtils.clamp(col, 0, 255); + colorData[index * 4 + 0] = (byte) (col * 1.0); + colorData[index * 4 + 1] = (byte) (col * 0.8); + colorData[index * 4 + 2] = (byte) (col * 0.7); + colorData[index * 4 + 3] = (byte) 1; + } + } + } + + public void startUpdates() { + if (running) { + return; + } + + running = true; + final Thread t = new Thread() { + @Override + public void run() { + while (running) { + // sleep some random amount of time (0 - 2 secs) + try { + Thread.sleep(MathUtils.nextRandomInt(0, 2000)); + } catch (final InterruptedException e) { + running = false; + continue; + } + + // pick a place to modify the terrain + final int x = MathUtils.nextRandomInt(0, side - 1); + final int y = MathUtils.nextRandomInt(0, side - 1); + + // pick a color + final ColorRGBA paint = ColorRGBA.randomColor(null); + + // pick a random radius 0 - tenth side + final int radius = MathUtils.nextRandomInt(0, side / 10); + + // pick an offset amount + final float offset = MathUtils.nextRandomFloat() * updateDelta + * (MathUtils.nextRandomInt(0, 2) != 0 ? 1 : -1); + + // modify the terrain! + updateTerrain(x, y, radius, paint, offset); + + // queue up an update alert for the rectangle updated + final int minY = Math.max(0, y - radius), maxY = Math.min(y + radius, side - 1); + final int minX = Math.max(0, x - radius), maxX = Math.min(x + radius, side - 1); + final Rectangle2 region = new Rectangle2(minX, minY, maxX - minX, maxY - minY); + + // break up by clipmaplevel + // add to two queues since these updates are called in different threads potentially + for (int i = 0; i < clipmapLevels; i++) { + addTiles(region, updatedTerrainTiles[i]); + addTiles(region, updatedTextureTiles[i]); + } + } + } + }; + t.setDaemon(true); + t.start(); + } + + protected void addTiles(final Rectangle2 bounds, final Set<Tile> store) { + for (int i = 0; i <= clipmapLevels; i++) { + final double scale = 1.0 / (tileSize * MathUtils.pow2(i)); + final int minX = (int) MathUtils.floor(bounds.getX() * scale); + final int minY = (int) MathUtils.floor(bounds.getY() * scale); + final int maxX = (int) MathUtils.floor((bounds.getX() + bounds.getWidth() - 1) * scale); + final int maxY = (int) MathUtils.floor((bounds.getY() + bounds.getHeight() - 1) * scale); + + Tile tile; + for (int y = minY; y <= maxY; y++) { + for (int x = minX; x <= maxX; x++) { + tile = new Tile(x, y); + synchronized (store) { + store.add(tile); + } + } + } + } + } + + protected void updateTerrain(final int x, final int y, final int radius, final ColorRGBA paint, final float offset) { + float r, dr; + int dx, dy, index; + byte red, green, blue, alpha; + final int minY = Math.max(0, y - radius), maxY = Math.min(y + radius, side - 1); + final int minX = Math.max(0, x - radius), maxX = Math.min(x + radius, side - 1); + for (int i = minY; i <= maxY; i++) { + dy = Math.abs(y - i); + for (int j = minX; j <= maxX; j++) { + dx = Math.abs(x - j); + r = (float) MathUtils.sqrt(dx * dx + dy * dy); + if (r <= radius) { + dr = (radius - r) / radius; + index = i * side + j; + heightData[index] = Math.max(minHeight, Math.min(heightData[index] + dr * offset, maxHeight)); + red = (byte) ((int) MathUtils.lerp(dr, colorData[index * 4 + 0] & 0xff, (paint.getRed() * 255)) & 0xff); + green = (byte) ((int) MathUtils.lerp(dr, colorData[index * 4 + 1] & 0xff, (paint.getGreen() * 255)) & 0xff); + blue = (byte) ((int) MathUtils.lerp(dr, colorData[index * 4 + 2] & 0xff, (paint.getBlue() * 255)) & 0xff); + alpha = (byte) ((int) MathUtils.lerp(dr, colorData[index * 4 + 3] & 0xff, (paint.getAlpha() * 255)) & 0xff); + colorData[index * 4 + 0] = red; + colorData[index * 4 + 1] = green; + colorData[index * 4 + 2] = blue; + colorData[index * 4 + 3] = alpha; + } + } + } + } + + public void stopUpdates() { + running = false; + } + + public float getMinHeight() { + return minHeight; + } + + public void setMinHeight(final float minHeight) { + this.minHeight = minHeight; + } + + public float getMaxHeight() { + return maxHeight; + } + + public void setMaxHeight(final float maxHeight) { + this.maxHeight = maxHeight; + } + + public float getUpdateDelta() { + return updateDelta; + } + + public void setUpdateDelta(final float updateDelta) { + this.updateDelta = updateDelta; + } + + public byte[] getColorData() { + return colorData; + } + + public float[] getHeightData() { + return heightData; + } + + public int getSide() { + return side; + } + + public Set<Tile>[] getUpdatedTerrainTiles() { + return updatedTerrainTiles; + } + + public Set<Tile>[] getUpdatedTextureTiles() { + return updatedTextureTiles; + } + + public boolean isRunning() { + return running; + } + + public ReadOnlyVector3 getScale() { + return scale; + } + + public int getClipmapLevels() { + return clipmapLevels; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralNormalMapSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralNormalMapSource.java new file mode 100644 index 0000000..df07262 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralNormalMapSource.java @@ -0,0 +1,111 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.providers.procedural;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.functions.Function3D;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Maps;
+
+public class ProceduralNormalMapSource implements TextureSource {
+ private final Function3D function;
+
+ private static final int tileSize = 128;
+ private static final int availableClipmapLevels = 8;
+
+ private final ReentrantLock textureLock = new ReentrantLock();
+ private final ThreadLocal<ByteBuffer> tileDataPool = new ThreadLocal<ByteBuffer>() {
+ @Override
+ protected ByteBuffer initialValue() {
+ return BufferUtils.createByteBufferOnHeap(tileSize * tileSize * 3);
+ }
+ };
+
+ public ProceduralNormalMapSource(final Function3D function) {
+ this.function = function;
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, TextureStoreFormat.RGB8);
+
+ return new TextureConfiguration(availableClipmapLevels, textureStoreFormat, tileSize, 1f, false, false);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final ByteBuffer data = tileDataPool.get();
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final Vector3 normal = new Vector3();
+ textureLock.lock();
+ try {
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ if (Thread.interrupted()) {
+ return null;
+ }
+
+ final int heightX = tileX * tileSize + x;
+ final int heightY = tileY * tileSize + y;
+
+ normal.setZ(1);
+ final double eval1 = function.eval(heightX - 1 << baseClipmapLevel, heightY << baseClipmapLevel, 0);
+ final double eval2 = function.eval(heightX + 1 << baseClipmapLevel, heightY << baseClipmapLevel, 0);
+ final double eval3 = function.eval(heightX << baseClipmapLevel, heightY - 1 << baseClipmapLevel, 0);
+ final double eval4 = function.eval(heightX << baseClipmapLevel, heightY + 1 << baseClipmapLevel, 0);
+
+ normal.setX((eval1 - eval2) / 2.);
+ normal.setY((eval3 - eval4) / 2.);
+ normal.normalizeLocal();
+
+ final int index = (x + y * tileSize) * 3;
+ data.put(index, (byte) (normal.getX() * 255));
+ data.put(index + 1, (byte) (normal.getY() * 255));
+ data.put(index + 2, (byte) (normal.getZ() * 255));
+ }
+ }
+ } finally {
+ textureLock.unlock();
+ }
+ return data;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainDataProvider.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainDataProvider.java new file mode 100644 index 0000000..575a514 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainDataProvider.java @@ -0,0 +1,74 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.providers.procedural;
+
+import java.util.Map;
+
+import com.ardor3d.extension.terrain.client.TerrainDataProvider;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.math.functions.Function3D;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.google.common.collect.Maps;
+
+public class ProceduralTerrainDataProvider implements TerrainDataProvider {
+ private final Function3D function;
+ private final ReadOnlyVector3 scale;
+ private final float minHeight;
+ private final float maxHeight;
+
+ private boolean generateNormalMap;
+
+ public ProceduralTerrainDataProvider(final Function3D function, final ReadOnlyVector3 scale, final float minHeight,
+ final float maxHeight) {
+ this(function, scale, minHeight, maxHeight, false);
+ }
+
+ public ProceduralTerrainDataProvider(final Function3D function, final ReadOnlyVector3 scale, final float minHeight,
+ final float maxHeight, final boolean generateNormalMap) {
+ this.function = function;
+ this.scale = scale;
+ this.minHeight = minHeight;
+ this.maxHeight = maxHeight;
+ this.generateNormalMap = generateNormalMap;
+ }
+
+ @Override
+ public Map<Integer, String> getAvailableMaps() throws Exception {
+ final Map<Integer, String> maps = Maps.newHashMap();
+ maps.put(0, "ProceduralMap");
+
+ return maps;
+ }
+
+ @Override
+ public TerrainSource getTerrainSource(final int mapId) {
+ return new ProceduralTerrainSource(function, scale, minHeight, maxHeight);
+ }
+
+ @Override
+ public TextureSource getTextureSource(final int mapId) {
+ return new ProceduralTextureSource(function);
+ }
+
+ @Override
+ public TextureSource getNormalMapSource(final int mapId) {
+ return new ProceduralNormalMapSource(function);
+ }
+
+ public boolean isGenerateNormalMap() {
+ return generateNormalMap;
+ }
+
+ public void setGenerateNormalMap(final boolean generateNormalMap) {
+ this.generateNormalMap = generateNormalMap;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainSource.java new file mode 100644 index 0000000..19515b7 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainSource.java @@ -0,0 +1,97 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.providers.procedural;
+
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.ardor3d.extension.terrain.client.TerrainConfiguration;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.math.functions.Function3D;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+public class ProceduralTerrainSource implements TerrainSource {
+ private final Function3D function;
+ private final ReadOnlyVector3 scale;
+ private final float minHeight;
+ private final float maxHeight;
+
+ private static final int tileSize = 128;
+ private static final int availableClipmapLevels = 8;
+
+ private final ReentrantLock terrainLock = new ReentrantLock();
+ private final ThreadLocal<float[]> tileDataPool = new ThreadLocal<float[]>() {
+ @Override
+ protected float[] initialValue() {
+ return new float[tileSize * tileSize];
+ }
+ };
+
+ public ProceduralTerrainSource(final Function3D function, final ReadOnlyVector3 scale, final float minHeight,
+ final float maxHeight) {
+ this.function = function;
+ this.scale = scale;
+ this.minHeight = minHeight;
+ this.maxHeight = maxHeight;
+ }
+
+ @Override
+ public TerrainConfiguration getConfiguration() throws Exception {
+ return new TerrainConfiguration(availableClipmapLevels, tileSize, scale, minHeight, maxHeight, false);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public float[] getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final float[] data = tileDataPool.get();
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ terrainLock.lock();
+ try {
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ if (Thread.interrupted()) {
+ return null;
+ }
+
+ final int heightX = tileX * tileSize + x;
+ final int heightY = tileY * tileSize + y;
+
+ final int index = x + y * tileSize;
+ data[index] = (float) function.eval(heightX << baseClipmapLevel, heightY << baseClipmapLevel, 0);
+ }
+ }
+ } finally {
+ terrainLock.unlock();
+ }
+ return data;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTextureSource.java new file mode 100644 index 0000000..0a48626 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTextureSource.java @@ -0,0 +1,120 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.providers.procedural;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.image.util.GeneratedImageFactory;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.functions.Function3D;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Maps;
+
+public class ProceduralTextureSource implements TextureSource {
+ private final Function3D function;
+
+ private static final int tileSize = 128;
+ private static final int availableClipmapLevels = 8;
+
+ private final ReadOnlyColorRGBA[] terrainColors;
+
+ private final ReentrantLock textureLock = new ReentrantLock();
+ private final ThreadLocal<ByteBuffer> tileDataPool = new ThreadLocal<ByteBuffer>() {
+ @Override
+ protected ByteBuffer initialValue() {
+ return BufferUtils.createByteBufferOnHeap(tileSize * tileSize * 3);
+ }
+ };
+
+ public ProceduralTextureSource(final Function3D function) {
+ this.function = function;
+
+ terrainColors = new ReadOnlyColorRGBA[256];
+ terrainColors[0] = new ColorRGBA(0, 0, .5f, 1);
+ terrainColors[95] = new ColorRGBA(0, 0, 1, 1);
+ terrainColors[127] = new ColorRGBA(0, .5f, 1, 1);
+ terrainColors[137] = new ColorRGBA(240 / 255f, 240 / 255f, 64 / 255f, 1);
+ terrainColors[143] = new ColorRGBA(32 / 255f, 160 / 255f, 0, 1);
+ terrainColors[175] = new ColorRGBA(224 / 255f, 224 / 255f, 0, 1);
+ terrainColors[223] = new ColorRGBA(128 / 255f, 128 / 255f, 128 / 255f, 1);
+ terrainColors[255] = ColorRGBA.WHITE;
+ GeneratedImageFactory.fillInColorTable(terrainColors);
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, TextureStoreFormat.RGB8);
+
+ return new TextureConfiguration(availableClipmapLevels, textureStoreFormat, tileSize, 1f, false, false);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final ByteBuffer data = tileDataPool.get();
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ textureLock.lock();
+ try {
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ if (Thread.interrupted()) {
+ return null;
+ }
+
+ final int heightX = tileX * tileSize + x;
+ final int heightY = tileY * tileSize + y;
+
+ final double eval = function.eval(heightX << baseClipmapLevel, heightY << baseClipmapLevel, 0) * 0.4167f + 0.5f;
+ final byte colIndex = (byte) (eval * 255);
+
+ final ReadOnlyColorRGBA c = terrainColors[colIndex & 0xFF];
+
+ final int index = (x + y * tileSize) * 3;
+ data.put(index, (byte) (c.getRed() * 255));
+ data.put(index + 1, (byte) (c.getGreen() * 255));
+ data.put(index + 2, (byte) (c.getBlue() * 255));
+ }
+ }
+ } finally {
+ textureLock.unlock();
+ }
+ return data;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java new file mode 100644 index 0000000..3d7878b --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.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.terrain.providers.simplearray;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.extension.terrain.client.TerrainDataProvider;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.providers.image.ImageTextureSource;
+import com.ardor3d.extension.terrain.util.NormalMapUtil;
+import com.ardor3d.image.Image;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class SimpleArrayTerrainDataProvider implements TerrainDataProvider {
+ private static final int tileSize = 128;
+
+ private final float[] heightData;
+ private final byte[] colorData;
+ private final int side;
+
+ private boolean generateNormalMap;
+
+ public SimpleArrayTerrainDataProvider(final float[] heightData, final byte[] colorData, final int side) {
+ this(heightData, colorData, side, false);
+ }
+
+ public SimpleArrayTerrainDataProvider(final float[] heightData, final byte[] colorData, final int side,
+ final boolean generateNormalMap) {
+ this.heightData = heightData;
+ this.colorData = colorData;
+ this.side = side;
+ this.generateNormalMap = generateNormalMap;
+ }
+
+ @Override
+ public Map<Integer, String> getAvailableMaps() throws Exception {
+ final Map<Integer, String> maps = Maps.newHashMap();
+ maps.put(0, "InMemoryData");
+
+ return maps;
+ }
+
+ @Override
+ public TerrainSource getTerrainSource(final int mapId) {
+ return new SimpleArrayTerrainSource(tileSize, heightData, side);
+ }
+
+ @Override
+ public TextureSource getTextureSource(final int mapId) {
+ return new SimpleArrayTextureSource(tileSize, colorData, side);
+ }
+
+ @Override
+ public TextureSource getNormalMapSource(final int mapId) {
+ if (generateNormalMap) {
+ try {
+ final Image normalImage = NormalMapUtil.constructNormalMap(heightData, side, 1, 1, 1);
+ final List<Integer> heightMapSizes = Lists.newArrayList();
+ int currentSize = side;
+ heightMapSizes.add(currentSize);
+ for (int i = 0; i < 8; i++) {
+ currentSize /= 2;
+ heightMapSizes.add(currentSize);
+ }
+ return new ImageTextureSource(tileSize, normalImage, heightMapSizes);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ public boolean isGenerateNormalMap() {
+ return generateNormalMap;
+ }
+
+ public void setGenerateNormalMap(final boolean generateNormalMap) {
+ this.generateNormalMap = generateNormalMap;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainSource.java new file mode 100644 index 0000000..05d1747 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainSource.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.terrain.providers.simplearray;
+
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TerrainConfiguration;
+import com.ardor3d.extension.terrain.client.TerrainSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.math.Vector3;
+
+public class SimpleArrayTerrainSource implements TerrainSource {
+ private final int tileSize;
+ private final float[] heightData;
+ private final int size;
+ private final int availableClipmapLevels = 8;
+
+ public SimpleArrayTerrainSource(final int tileSize, final float[] heightData, final int size) {
+ this.tileSize = tileSize;
+ this.heightData = heightData;
+ this.size = size;
+ }
+
+ @Override
+ public TerrainConfiguration getConfiguration() throws Exception {
+ return new TerrainConfiguration(availableClipmapLevels, tileSize, new Vector3(1, 1, 1), 0.0f, 1.0f, true);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public float[] getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final int levelSize = 1 << baseClipmapLevel;
+
+ final float[] data = new float[tileSize * tileSize];
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int index = x + y * tileSize;
+
+ final int heightX = (tileX * tileSize + x) * levelSize;
+ final int heightY = (tileY * tileSize + y) * levelSize;
+ data[index] = getHeight(heightData, size, heightX, heightY);
+ }
+ }
+ return data;
+ }
+
+ private float getHeight(final float[] heightMap, final int heightMapSize, final int x, final int y) {
+ if (x < 0 || x >= heightMapSize || y < 0 || y >= heightMapSize) {
+ return 0;
+ }
+
+ return heightMap[y * heightMapSize + x];
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTextureSource.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTextureSource.java new file mode 100644 index 0000000..02fa121 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTextureSource.java @@ -0,0 +1,111 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under 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.terrain.providers.simplearray;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Set;
+
+import com.ardor3d.extension.terrain.client.TextureConfiguration;
+import com.ardor3d.extension.terrain.client.TextureSource;
+import com.ardor3d.extension.terrain.util.Tile;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class SimpleArrayTextureSource implements TextureSource {
+ private final int tileSize;
+ private final byte[] colorData;
+ private final int size;
+ private final int availableClipmapLevels = 8;
+
+ public SimpleArrayTextureSource(final int tileSize, final byte[] colorData, final int size) {
+ this.tileSize = tileSize;
+ this.colorData = colorData;
+ this.size = size;
+ }
+
+ @Override
+ public TextureConfiguration getConfiguration() throws Exception {
+ final Map<Integer, TextureStoreFormat> textureStoreFormat = Maps.newHashMap();
+ textureStoreFormat.put(0, TextureStoreFormat.RGBA8);
+
+ return new TextureConfiguration(availableClipmapLevels, textureStoreFormat, tileSize, 1f, true, true);
+ }
+
+ @Override
+ public Set<Tile> getValidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ final Set<Tile> validTiles = Sets.newHashSet();
+
+ final int levelSize = 1 << availableClipmapLevels - clipmapLevel;
+
+ for (int y = 0; y < numTilesY; y++) {
+ for (int x = 0; x < numTilesX; x++) {
+ final int xx = tileX + x;
+ final int yy = tileY + y;
+ if (xx >= 0 && xx * tileSize * levelSize <= size && yy >= 0 && yy * tileSize * levelSize <= size) {
+ final Tile tile = new Tile(xx, yy);
+ validTiles.add(tile);
+ }
+ }
+ }
+
+ return validTiles;
+ }
+
+ @Override
+ public Set<Tile> getInvalidTiles(final int clipmapLevel, final int tileX, final int tileY, final int numTilesX,
+ final int numTilesY) throws Exception {
+ return null;
+ }
+
+ @Override
+ public int getContributorId(final int clipmapLevel, final Tile tile) {
+ return 0;
+ }
+
+ @Override
+ public ByteBuffer getTile(final int clipmapLevel, final Tile tile) throws Exception {
+ final int tileX = tile.getX();
+ final int tileY = tile.getY();
+
+ final int baseClipmapLevel = availableClipmapLevels - clipmapLevel - 1;
+
+ final int levelSize = 1 << baseClipmapLevel;
+
+ final ByteBuffer data = BufferUtils.createByteBufferOnHeap(tileSize * tileSize * 4);
+ for (int y = 0; y < tileSize; y++) {
+ for (int x = 0; x < tileSize; x++) {
+ final int heightX = (tileX * tileSize + x) * levelSize;
+ final int heightY = (tileY * tileSize + y) * levelSize;
+
+ final int indexTile = (y * tileSize + x) * 4;
+ final int index = heightY * size + heightX;
+
+ if (heightX < 0 || heightX >= size || heightY < 0 || heightY >= size) {
+ data.put(indexTile + 0, (byte) 0);
+ data.put(indexTile + 1, (byte) 0);
+ data.put(indexTile + 2, (byte) 0);
+ data.put(indexTile + 3, (byte) 0);
+ } else {
+ data.put(indexTile + 0, colorData[index * 4 + 0]);
+ data.put(indexTile + 1, colorData[index * 4 + 1]);
+ data.put(indexTile + 2, colorData[index * 4 + 2]);
+ data.put(indexTile + 3, colorData[index * 4 + 3]);
+ }
+ }
+ }
+
+ return data;
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/AbstractBresenhamTracer.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/AbstractBresenhamTracer.java new file mode 100644 index 0000000..b6cd470 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/AbstractBresenhamTracer.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.util; + +import com.ardor3d.math.Ray3; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +/** + * AbstractBresenhamTracer is a simple base class for using Bresenham's line equation. Bresenham's line equation is + * useful for doing various tasks that involve regularly spaced grids (such as picking against a height-map.) This class + * is not intended to do any picking, only traveling and reporting the grid squares traversed. + */ +public abstract class AbstractBresenhamTracer { + + protected final Vector3 _gridOrigin = new Vector3(); + protected final Vector3 _gridSpacing = new Vector3(1, 1, 1); + protected final int[] _gridLocation = new int[2]; + protected final Vector3 _rayLocation = new Vector3(); + protected final Ray3 _walkRay = new Ray3(); + + protected Direction _stepDirection = Direction.None; + protected double _totalTravel; + + public enum Direction { + None, PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ; + }; + + /** + * @return the direction of our last step on the grid. + */ + public Direction getLastStepDirection() { + return _stepDirection; + } + + /** + * @return the row and column we are currently in on the grid. + */ + public int[] getGridLocation() { + return _gridLocation; + } + + /** + * @return the total length we have traveled from the origin of our ray set in startWalk. + */ + public double getTotalTraveled() { + return _totalTravel; + } + + /** + * Set the world origin of our grid. This is useful to the tracer when doing conversion between world coordinates + * and grid locations. + * + * @param origin + * our new origin (copied into the tracer) + */ + public void setGridOrigin(final Vector3 origin) { + _gridOrigin.set(origin); + } + + /** + * @return the current grid origin + * @see #setGridOrigin(Vector3) + */ + public Vector3 getGridOrigin() { + return _gridOrigin; + } + + /** + * Set the world spacing (scale) of our grid. Also useful for converting between world coordinates and grid + * location. + * + * @param spacing + * our new spacing (copied into the tracer) + */ + public void setGridSpacing(final Vector3 spacing) { + _gridSpacing.set(spacing); + } + + /** + * @return the current grid spacing + * @see #setGridSpacing(Vector3) + */ + public Vector3 getGridSpacing() { + return _gridSpacing; + } + + /** + * Set up our position on the grid and initialize the tracer using the provided ray. + * + * @param walkRay + * the world ray along which we we walk the grid. + */ + public abstract void startWalk(Ray3 walkRay); + + /** + * Move us along our walkRay to the next grid location. + */ + public abstract void next(); + + /** + * @return true if our walkRay, specified in startWalk, ended up being perpendicular to the grid (and therefore can + * not move to a new grid location on calls to next(). You should test this after calling startWalk and + * before calling next(). + */ + public abstract boolean isRayPerpendicularToGrid(); + + /** + * Turns a point on a 2D grid and a height into a 3D point based on the world up of this tracer. + * + * @param row + * @param col + * @param height + * @param store + * the vector to store our result in. if null a new vector is created and returned. + * @return + */ + public abstract Vector3 get3DPoint(double gridX, double gridY, double height, Vector3 store); + + /** + * Casts a world location to the local plane of this tracer. + * + * @param worldLocation + * @return the point on the plane used by this tracer. + */ + public abstract Vector2 get2DPoint(ReadOnlyVector3 worldLocation, Vector2 store); +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamYUpGridTracer.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamYUpGridTracer.java new file mode 100644 index 0000000..53e8c2c --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamYUpGridTracer.java @@ -0,0 +1,145 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.util;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+/**
+ * An implementation of AbstractBresenhamTracer that works on the XZ plane, with positive Y as up.
+ */
+public class BresenhamYUpGridTracer extends AbstractBresenhamTracer {
+
+ // a "near zero" value we will use to determine if the walkRay is
+ // perpendicular to the grid.
+ protected static double TOLERANCE = 0.0000001;
+
+ private int _stepXDirection;
+ private int _stepZDirection;
+
+ // from current position along ray
+ private double _distToNextXIntersection, _distToNextZIntersection;
+ private double _distBetweenXIntersections, _distBetweenZIntersections;
+
+ @Override
+ public void startWalk(final Ray3 walkRay) {
+ // store ray
+ _walkRay.set(walkRay);
+
+ // simplify access to direction
+ final ReadOnlyVector3 direction = _walkRay.getDirection();
+
+ // Move start point to grid space
+ final Vector3 start = _walkRay.getOrigin().subtract(_gridOrigin, null);
+
+ _gridLocation[0] = (int) MathUtils.floor(start.getX() / _gridSpacing.getX());
+ _gridLocation[1] = (int) MathUtils.floor(start.getZ() / _gridSpacing.getZ());
+
+ final double invDirX = 1.0 / direction.getX();
+ final double invDirZ = 1.0 / direction.getZ();
+
+ // Check which direction on the X world axis we are moving.
+ if (direction.getX() > BresenhamYUpGridTracer.TOLERANCE) {
+ _distToNextXIntersection = ((_gridLocation[0] + 1) * _gridSpacing.getX() - start.getX()) * invDirX;
+ _distBetweenXIntersections = _gridSpacing.getX() * invDirX;
+ _stepXDirection = 1;
+ } else if (direction.getX() < -BresenhamYUpGridTracer.TOLERANCE) {
+ _distToNextXIntersection = (start.getX() - _gridLocation[0] * _gridSpacing.getX()) * -direction.getX();
+ _distBetweenXIntersections = -_gridSpacing.getX() * invDirX;
+ _stepXDirection = -1;
+ } else {
+ _distToNextXIntersection = Double.MAX_VALUE;
+ _distBetweenXIntersections = Double.MAX_VALUE;
+ _stepXDirection = 0;
+ }
+
+ // Check which direction on the Z world axis we are moving.
+ if (direction.getZ() > BresenhamYUpGridTracer.TOLERANCE) {
+ _distToNextZIntersection = ((_gridLocation[1] + 1) * _gridSpacing.getZ() - start.getZ()) * invDirZ;
+ _distBetweenZIntersections = _gridSpacing.getZ() * invDirZ;
+ _stepZDirection = 1;
+ } else if (direction.getZ() < -BresenhamYUpGridTracer.TOLERANCE) {
+ _distToNextZIntersection = (start.getZ() - _gridLocation[1] * _gridSpacing.getZ()) * -direction.getZ();
+ _distBetweenZIntersections = -_gridSpacing.getZ() * invDirZ;
+ _stepZDirection = -1;
+ } else {
+ _distToNextZIntersection = Double.MAX_VALUE;
+ _distBetweenZIntersections = Double.MAX_VALUE;
+ _stepZDirection = 0;
+ }
+
+ // Reset some variables
+ _rayLocation.set(start);
+ _totalTravel = 0.0;
+ _stepDirection = Direction.None;
+ }
+
+ @Override
+ public void next() {
+ // Walk us to our next location based on distances to next X or Z grid
+ // line.
+ if (_distToNextXIntersection < _distToNextZIntersection) {
+ _totalTravel = _distToNextXIntersection;
+ _gridLocation[0] += _stepXDirection;
+ _distToNextXIntersection += _distBetweenXIntersections;
+ switch (_stepXDirection) {
+ case -1:
+ _stepDirection = Direction.NegativeX;
+ break;
+ case 0:
+ _stepDirection = Direction.None;
+ break;
+ case 1:
+ _stepDirection = Direction.PositiveX;
+ break;
+ }
+ } else {
+ _totalTravel = _distToNextZIntersection;
+ _gridLocation[1] += _stepZDirection;
+ _distToNextZIntersection += _distBetweenZIntersections;
+ switch (_stepZDirection) {
+ case -1:
+ _stepDirection = Direction.NegativeZ;
+ break;
+ case 0:
+ _stepDirection = Direction.None;
+ break;
+ case 1:
+ _stepDirection = Direction.PositiveZ;
+ break;
+ }
+ }
+
+ _rayLocation.set(_walkRay.getDirection()).multiplyLocal(_totalTravel).addLocal(_walkRay.getOrigin());
+ }
+
+ @Override
+ public boolean isRayPerpendicularToGrid() {
+ return _stepXDirection == 0 && _stepZDirection == 0;
+ }
+
+ @Override
+ public Vector3 get3DPoint(final double gridX, final double gridY, final double height, final Vector3 store) {
+ final Vector3 rVal = store != null ? store : new Vector3();
+
+ return rVal.set(gridX, height, gridY);
+ }
+
+ @Override
+ public Vector2 get2DPoint(final ReadOnlyVector3 worldLocation, final Vector2 store) {
+ final Vector2 rVal = store != null ? store : new Vector2();
+
+ return rVal.set(worldLocation.getX(), worldLocation.getZ());
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamZUpGridTracer.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamZUpGridTracer.java new file mode 100644 index 0000000..9e39102 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamZUpGridTracer.java @@ -0,0 +1,145 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.util;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+/**
+ * An implementation of AbstractBresenhamTracer that works on the XY plane, with positive Z as up.
+ */
+public class BresenhamZUpGridTracer extends AbstractBresenhamTracer {
+
+ // a "near zero" value we will use to determine if the walkRay is
+ // perpendicular to the grid.
+ protected static double TOLERANCE = 0.0000001;
+
+ private int _stepXDirection;
+ private int _stepYDirection;
+
+ // from current position along ray
+ private double _distToNextXIntersection, _distToNextYIntersection;
+ private double _distBetweenXIntersections, _distBetweenYIntersections;
+
+ @Override
+ public void startWalk(final Ray3 walkRay) {
+ // store ray
+ _walkRay.set(walkRay);
+
+ // simplify access to direction
+ final ReadOnlyVector3 direction = _walkRay.getDirection();
+
+ // Move start point to grid space
+ final Vector3 start = _walkRay.getOrigin().subtract(_gridOrigin, null);
+
+ _gridLocation[0] = (int) MathUtils.floor(start.getX() / _gridSpacing.getX());
+ _gridLocation[1] = (int) MathUtils.floor(start.getY() / _gridSpacing.getY());
+
+ final double invDirX = 1.0 / direction.getX();
+ final double invDirY = 1.0 / direction.getY();
+
+ // Check which direction on the X world axis we are moving.
+ if (direction.getX() > BresenhamZUpGridTracer.TOLERANCE) {
+ _distToNextXIntersection = ((_gridLocation[0] + 1) * _gridSpacing.getX() - start.getX()) * invDirX;
+ _distBetweenXIntersections = _gridSpacing.getX() * invDirX;
+ _stepXDirection = 1;
+ } else if (direction.getX() < -BresenhamZUpGridTracer.TOLERANCE) {
+ _distToNextXIntersection = (start.getX() - _gridLocation[0] * _gridSpacing.getX()) * -direction.getX();
+ _distBetweenXIntersections = -_gridSpacing.getX() * invDirX;
+ _stepXDirection = -1;
+ } else {
+ _distToNextXIntersection = Double.MAX_VALUE;
+ _distBetweenXIntersections = Double.MAX_VALUE;
+ _stepXDirection = 0;
+ }
+
+ // Check which direction on the Y world axis we are moving.
+ if (direction.getY() > BresenhamZUpGridTracer.TOLERANCE) {
+ _distToNextYIntersection = ((_gridLocation[1] + 1) * _gridSpacing.getY() - start.getY()) * invDirY;
+ _distBetweenYIntersections = _gridSpacing.getY() * invDirY;
+ _stepYDirection = 1;
+ } else if (direction.getY() < -BresenhamZUpGridTracer.TOLERANCE) {
+ _distToNextYIntersection = (start.getY() - _gridLocation[1] * _gridSpacing.getY()) * -direction.getY();
+ _distBetweenYIntersections = -_gridSpacing.getY() * invDirY;
+ _stepYDirection = -1;
+ } else {
+ _distToNextYIntersection = Double.MAX_VALUE;
+ _distBetweenYIntersections = Double.MAX_VALUE;
+ _stepYDirection = 0;
+ }
+
+ // Reset some variables
+ _rayLocation.set(start);
+ _totalTravel = 0.0;
+ _stepDirection = Direction.None;
+ }
+
+ @Override
+ public void next() {
+ // Walk us to our next location based on distances to next X or Y grid
+ // line.
+ if (_distToNextXIntersection < _distToNextYIntersection) {
+ _totalTravel = _distToNextXIntersection;
+ _gridLocation[0] += _stepXDirection;
+ _distToNextXIntersection += _distBetweenXIntersections;
+ switch (_stepXDirection) {
+ case -1:
+ _stepDirection = Direction.NegativeX;
+ break;
+ case 0:
+ _stepDirection = Direction.None;
+ break;
+ case 1:
+ _stepDirection = Direction.PositiveX;
+ break;
+ }
+ } else {
+ _totalTravel = _distToNextYIntersection;
+ _gridLocation[1] += _stepYDirection;
+ _distToNextYIntersection += _distBetweenYIntersections;
+ switch (_stepYDirection) {
+ case -1:
+ _stepDirection = Direction.NegativeY;
+ break;
+ case 0:
+ _stepDirection = Direction.None;
+ break;
+ case 1:
+ _stepDirection = Direction.PositiveY;
+ break;
+ }
+ }
+
+ _rayLocation.set(_walkRay.getDirection()).multiplyLocal(_totalTravel).addLocal(_walkRay.getOrigin());
+ }
+
+ @Override
+ public boolean isRayPerpendicularToGrid() {
+ return _stepXDirection == 0 && _stepYDirection == 0;
+ }
+
+ @Override
+ public Vector3 get3DPoint(final double gridX, final double gridY, final double height, final Vector3 store) {
+ final Vector3 rVal = store != null ? store : new Vector3();
+
+ return rVal.set(gridX, gridY, height);
+ }
+
+ @Override
+ public Vector2 get2DPoint(final ReadOnlyVector3 worldLocation, final Vector2 store) {
+ final Vector2 rVal = store != null ? store : new Vector2();
+
+ return rVal.set(worldLocation.getX(), worldLocation.getY());
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/ClipmapTerrainPicker.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/ClipmapTerrainPicker.java new file mode 100644 index 0000000..7ced2ba --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/ClipmapTerrainPicker.java @@ -0,0 +1,328 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.util; + +import java.util.List; + +import com.ardor3d.extension.terrain.client.ClipmapLevel; +import com.ardor3d.extension.terrain.util.AbstractBresenhamTracer.Direction; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Ray3; +import com.ardor3d.math.Triangle; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyRay3; +import com.ardor3d.math.type.ReadOnlyTransform; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.google.common.collect.Lists; + +/** + * A picking assistant to be used with ClipmapLevel and an AbstractBresenhamTracer. + */ +public class ClipmapTerrainPicker { + + private final List<ClipmapLevel> _clipmapLevels; + private final List<AbstractBresenhamTracer> _tracers; + private int _maxChecks; + + private final Ray3 _workRay = new Ray3(); + private final Vector3 _workEyePos = new Vector3(); + private final Triangle _gridTriA = new Triangle(), _gridTriB = new Triangle(); + private int _minLevel, _maxLevel; + private final float[] tileStore = new float[16]; + + /** + * Construct a new picker using the supplied pyramid, tracer and arguments. + * + * @param levels + * the source for our height information.. + * @param tracerClass + * class type for our Bresenham tracer. + * @param maxChecks + * the maximum number of grid spaces we'll walk before giving up our search. + * @throws IllegalAccessException + * if we are unable to create an instance of our tracerClass + * @throws InstantiationException + * if we are unable to create an instance of our tracerClass + */ + public ClipmapTerrainPicker(final List<ClipmapLevel> levels, + final Class<? extends AbstractBresenhamTracer> tracerClass, final int maxChecks, + final Vector3 initialSpacing) throws InstantiationException, IllegalAccessException { + _clipmapLevels = levels; + _tracers = Lists.newArrayList(); + for (int i = 0, max = levels.size(); i < max; i++) { + final AbstractBresenhamTracer tracer = tracerClass.newInstance(); + final int space = 1 << i; + final Vector3 vec = new Vector3(initialSpacing).multiplyLocal(space); + if (vec.getX() == 0) { + vec.setX(1); + } + if (vec.getY() == 0) { + vec.setY(1); + } + if (vec.getZ() == 0) { + vec.setZ(1); + } + tracer.setGridSpacing(vec); + _tracers.add(tracer); + } + _maxChecks = maxChecks; + _minLevel = 0; + _maxLevel = levels.size() - 1; + } + + public Vector3 getTerrainIntersection(final ReadOnlyTransform terrainWorldTransform, final ReadOnlyVector3 eyePos, + final ReadOnlyRay3 pickRay, final Vector3 store, final Vector3 normalStore) { + _workRay.setOrigin(terrainWorldTransform.applyInverse(pickRay.getOrigin(), null)); + _workRay.setDirection(terrainWorldTransform.applyInverseVector(pickRay.getDirection(), null).normalizeLocal()); + terrainWorldTransform.applyInverse(eyePos, _workEyePos); + + // check which clipmap level we start in + int index = findClipIndex(_workRay.getOrigin().subtract(_workEyePos, null)); + // simple test to see if our level at least has SOME data. XXX: could look to the tile level. + while (index > 0 && !_clipmapLevels.get(index).isReady()) { + index--; + } + AbstractBresenhamTracer tracer = _tracers.get(index); + ClipmapLevel level = _clipmapLevels.get(index); + + // start our tracer + tracer.startWalk(_workRay); + + final Vector3 intersection = store != null ? store : new Vector3(); + + if (tracer.isRayPerpendicularToGrid()) { + // XXX: "HACK" for perpendicular ray + level.getCache().getEyeCoords(tileStore, tracer.getGridLocation()[0], tracer.getGridLocation()[1], + _workEyePos); + final float scaledClipSideSize = level.getClipSideSize() * level.getVertexDistance() * 0.5f; + + final float h1 = getWeightedHeight(tileStore[0], tileStore[1], tileStore[2], tileStore[3], + scaledClipSideSize); + final float h2 = getWeightedHeight(tileStore[4], tileStore[5], tileStore[6], tileStore[7], + scaledClipSideSize); + final float h3 = getWeightedHeight(tileStore[8], tileStore[9], tileStore[10], tileStore[11], + scaledClipSideSize); + final float h4 = getWeightedHeight(tileStore[12], tileStore[13], tileStore[14], tileStore[15], + scaledClipSideSize); + + final double x = _workEyePos.getX(); + final double z = _workEyePos.getZ(); + final double intOnX = x - Math.floor(x), intOnZ = z - Math.floor(z); + final double height = MathUtils + .lerp(intOnZ, MathUtils.lerp(intOnX, h1, h2), MathUtils.lerp(intOnX, h3, h4)); + + intersection.set(x, height, z); + terrainWorldTransform.applyForward(intersection, intersection); + return intersection; + } + + // walk our way along the ray, asking for intersections along the way + int iter = 0; + while (iter < _maxChecks) { + + // check the triangles of main square for intersection. + if (checkTriangles(tracer.getGridLocation()[0], tracer.getGridLocation()[1], intersection, normalStore, + tracer, level, _workEyePos)) { + // we found an intersection, so return that! + terrainWorldTransform.applyForward(intersection, intersection); + terrainWorldTransform.applyForward(normalStore, normalStore); + return intersection; + } + + // because of how we get our height coords, we will + // sometimes be off be a grid spot, so we check the next + // grid space up. + int dx = 0, dy = 0; + final Direction d = tracer.getLastStepDirection(); + switch (d) { + case PositiveX: + case NegativeX: + dx = 0; + dy = 1; + break; + case PositiveZ: + case NegativeZ: + dx = 1; + dy = 0; + break; + } + + if (checkTriangles(tracer.getGridLocation()[0] + dx, tracer.getGridLocation()[1] + dy, intersection, + normalStore, tracer, level, _workEyePos)) { + // we found an intersection, so return that! + terrainWorldTransform.applyForward(intersection, intersection); + terrainWorldTransform.applyForward(normalStore, normalStore); + return intersection; + } + + final double dist = tracer.getTotalTraveled(); + // look at where we are and switch to the next cliplevel if needed + final Vector3 loc = new Vector3(_workRay.getDirection()).multiplyLocal(dist).addLocal(_workRay.getOrigin()); + final int newIndex = findClipIndex(loc.subtract(_workEyePos, null)); + // simple test to see if our next level at least has SOME data. XXX: could look to the tile level. + if (newIndex != index && _clipmapLevels.get(index).isReady()) { + _workRay.setOrigin(loc); + index = newIndex; + tracer = _tracers.get(index); + level = _clipmapLevels.get(index); + tracer.startWalk(_workRay); + } else { + tracer.next(); + } + + iter++; + } + + return null; + } + + private int findClipIndex(final ReadOnlyVector3 pointInEyeSpace) { + final Vector2 gridPoint = _tracers.get(_minLevel).get2DPoint(pointInEyeSpace, null); + final int maxDist = Math.max(Math.abs((int) gridPoint.getX()), Math.abs((int) gridPoint.getY())) + / (_clipmapLevels.get(_minLevel).getClipSideSize() + 1 >> 1); + int index = (int) MathUtils.floor(Math.log(maxDist) / Math.log(2)) + 1; + index = MathUtils.clamp(index, _minLevel, _maxLevel); + return index; + } + + public int getMaxChecks() { + return _maxChecks; + } + + public void setMaxChecks(final int max) { + _maxChecks = max; + } + + public List<ClipmapLevel> getPyramid() { + return _clipmapLevels; + } + + /** + * Check the two triangles of a given grid space for intersection. + * + * @param gridX + * grid row + * @param gridY + * grid column + * @param store + * the store variable + * @param tracer + * @return true if a pick was found on these triangles. + */ + private boolean checkTriangles(final int gridX, final int gridY, final Vector3 store, final Vector3 normalStore, + final AbstractBresenhamTracer tracer, final ClipmapLevel level, final ReadOnlyVector3 eyePos) { + if (!getTriangles(gridX, gridY, tracer, level, eyePos)) { + return false; + } + + if (!_workRay.intersectsTriangle(_gridTriA.getA(), _gridTriA.getB(), _gridTriA.getC(), store)) { + final boolean intersects = _workRay.intersectsTriangle(_gridTriB.getA(), _gridTriB.getB(), + _gridTriB.getC(), store); + if (intersects && normalStore != null) { + final Vector3 edge1 = Vector3.fetchTempInstance().set(_gridTriB.getB()).subtractLocal(_gridTriB.getA()); + final Vector3 edge2 = Vector3.fetchTempInstance().set(_gridTriB.getC()).subtractLocal(_gridTriB.getA()); + normalStore.set(edge1).crossLocal(edge2).normalizeLocal(); + } + return intersects; + } else { + if (normalStore != null) { + final Vector3 edge1 = Vector3.fetchTempInstance().set(_gridTriA.getB()).subtractLocal(_gridTriA.getA()); + final Vector3 edge2 = Vector3.fetchTempInstance().set(_gridTriA.getC()).subtractLocal(_gridTriA.getA()); + normalStore.set(edge1).crossLocal(edge2).normalizeLocal(); + } + + return true; + } + } + + /** + * Calculate the triangles (in world coordinate space) of a Pyramid that correspond to the given grid location. The + * triangles are stored in the class fields _gridTriA and _gridTriB. + * + * @param gridX + * grid row + * @param gridY + * grid column + * @return true if the grid square was found, false otherwise. + */ + private boolean getTriangles(final int gridX, final int gridY, final AbstractBresenhamTracer tracer, + final ClipmapLevel level, final ReadOnlyVector3 eyePos) { + // TODO: pull this with updateRegion instead, then apply W + level.getCache().getEyeCoords(tileStore, gridX, gridY, eyePos); + // final float h1 = level.getCache().getHeight(gridX, gridY); + // final float h2 = level.getCache().getHeight(gridX + 1, gridY); + // final float h3 = level.getCache().getHeight(gridX, gridY + 1); + // final float h4 = level.getCache().getHeight(gridX + 1, gridY + 1); + + final float scaledClipSideSize = level.getClipSideSize() * level.getVertexDistance() * 0.5f; + + final float h1 = getWeightedHeight(tileStore[0], tileStore[1], tileStore[2], tileStore[3], scaledClipSideSize); + final float h2 = getWeightedHeight(tileStore[4], tileStore[5], tileStore[6], tileStore[7], scaledClipSideSize); + final float h3 = getWeightedHeight(tileStore[8], tileStore[9], tileStore[10], tileStore[11], scaledClipSideSize); + final float h4 = getWeightedHeight(tileStore[12], tileStore[13], tileStore[14], tileStore[15], + scaledClipSideSize); + + final Vector3 scaleVec = Vector3.fetchTempInstance(); + final Vector3 workVec = Vector3.fetchTempInstance(); + + scaleVec.set(tracer.getGridSpacing()); + + // First triangle (h1, h3, h2) + tracer.get3DPoint(gridX, gridY, h1, workVec); + workVec.multiplyLocal(scaleVec).addLocal(tracer.getGridOrigin()); + _gridTriA.setA(workVec); + + tracer.get3DPoint(gridX, gridY + 1, h3, workVec); + workVec.multiplyLocal(scaleVec).addLocal(tracer.getGridOrigin()); + _gridTriA.setB(workVec); + + tracer.get3DPoint(gridX + 1, gridY, h2, workVec); + workVec.multiplyLocal(scaleVec).addLocal(tracer.getGridOrigin()); + _gridTriA.setC(workVec); + + // Second triangle (h2, h3, h4) + _gridTriB.setA(_gridTriA.getC()); + _gridTriB.setB(_gridTriA.getB()); + + tracer.get3DPoint(gridX + 1, gridY + 1, h4, workVec); + workVec.multiplyLocal(scaleVec).addLocal(tracer.getGridOrigin()); + _gridTriB.setC(workVec); + + Vector3.releaseTempInstance(scaleVec); + Vector3.releaseTempInstance(workVec); + + return true; + } + + private float getWeightedHeight(final float viewX, final float viewY, final float h, final float w, + final float scaledClipSideSize) { + final float maxDistance = Math.max(viewX, viewY) / scaledClipSideSize; + final float blend = MathUtils.clamp((maxDistance - 0.51f) * 2.2f, 0.0f, 1.0f); + return MathUtils.lerp(blend, h, w); + } + + public void setMaxLevel(final int maxLevel) { + _maxLevel = maxLevel; + } + + public int getMaxLevel() { + return _maxLevel; + } + + public void setMinLevel(final int minLevel) { + _minLevel = minLevel; + } + + public int getMinLevel() { + return _minLevel; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/DoubleBufferedList.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/DoubleBufferedList.java new file mode 100644 index 0000000..f6c12da --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/DoubleBufferedList.java @@ -0,0 +1,40 @@ + +package com.ardor3d.extension.terrain.util; + +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * Utility class used by the mailbox update system. + * + * @param <T> + */ +public class DoubleBufferedList<T> { + private List<T> frontList = Lists.newArrayList(); + private List<T> backList = Lists.newArrayList(); + + /** + * The add method can be called at any point. + * + * @param t + */ + public synchronized void add(final T t) { + if (!backList.contains(t)) { + backList.add(t); + } + } + + /** + * The switchAndGet call and it's returned list has to be accessed sequencially. + * + * @return + */ + public synchronized List<T> switchAndGet() { + final List<T> tmp = backList; + backList = frontList; + frontList = tmp; + backList.clear(); + return frontList; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/IntColorUtils.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/IntColorUtils.java new file mode 100644 index 0000000..0ad23fa --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/IntColorUtils.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.util; + +public class IntColorUtils { + + public static int getColor(final int r, final int g, final int b, final int a) { + return r << 24 | g << 16 | b << 8 | a; + } + + public static int getColor(final byte r, final byte g, final byte b, final byte a) { + return getColor(r & 0xFF, g & 0xFF, b & 0xFF, a & 0xFF); + } + + public static int lerp(final double percent, final int startColor, final int endColor) { + if (startColor == endColor) { + return startColor; + } else if (percent <= 0.0) { + return startColor; + } else if (percent >= 1.0) { + return endColor; + } + + final int r = (int) ((1.0 - percent) * (startColor >> 24 & 0xFF) + percent * (endColor >> 24 & 0xFF)); + final int g = (int) ((1.0 - percent) * (startColor >> 16 & 0xFF) + percent * (endColor >> 16 & 0xFF)); + final int b = (int) ((1.0 - percent) * (startColor >> 8 & 0xFF) + percent * (endColor >> 8 & 0xFF)); + final int a = (int) ((1.0 - percent) * (startColor & 0xFF) + percent * (endColor & 0xFF)); + + return r << 24 | g << 16 | b << 8 | a; + } + + public static String toString(final int color) { + final int r = color >> 24 & 0xFF; + final int g = color >> 16 & 0xFF; + final int b = color >> 8 & 0xFF; + final int a = color & 0xFF; + + return "[r=" + r + ", g=" + g + ", b=" + b + ", a=" + a + "]"; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/LevelData.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/LevelData.java new file mode 100644 index 0000000..e2e3964 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/LevelData.java @@ -0,0 +1,19 @@ +
+package com.ardor3d.extension.terrain.util;
+
+import java.nio.ByteBuffer;
+
+public class LevelData {
+ public int unit;
+ public int x = Integer.MAX_VALUE, y = Integer.MAX_VALUE;
+ public int offsetX, offsetY;
+ public int textureOffsetX, textureOffsetY;
+ public Region clipRegion;
+
+ public ByteBuffer sliceData;
+
+ public LevelData(final int unit, final int textureSize) {
+ this.unit = unit;
+ clipRegion = new Region(unit, 0, 0, textureSize, textureSize);
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java new file mode 100644 index 0000000..89e012b --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.util; + +import java.nio.ByteBuffer; + +import com.ardor3d.image.Image; +import com.ardor3d.image.ImageDataFormat; +import com.ardor3d.image.PixelDataType; +import com.ardor3d.math.Vector3; + +public class NormalMapUtil { + + /** + * Generate an image from the given terrain height data to be used as a source for terrain normal maps. + * + * @param heightmap + * the base height data. Generally this is the most detailed height data available. It must be a square + * heightmap, with a side of "side" as passed below. + * @param side + * the number of samples on a side of the heightmap. This could be calculated by taking the squareroot of + * heightmap.length, but generally this number is well known by the caller. + * @param heightScale + * the scaling factor applied to the heightMap values to get real world height. + * @param xGridSpacing + * real world spacing between grid in the x direction + * @param zGridSpacing + * real world spacing between grid in the z direction + * @return the normal image. + */ + public static Image constructNormalMap(final float[] heightmap, final int side, final double heightScale, + final double xGridSpacing, final double zGridSpacing) { + int x, z; + final Vector3 n = new Vector3(); + final Vector3 n2 = new Vector3(); + final ByteBuffer data = ByteBuffer.allocateDirect(side * side * 3); + final Image normalMap = new Image(ImageDataFormat.RGB, PixelDataType.UnsignedByte, side, side, data, null); + for (z = 0; z < side; ++z) { + for (x = 0; x < side; ++x) { + if (x == 0 || z == 0 || x == side - 1 || z == side - 1) { + n.set(0, 0, 1); + } else { + // change across "x" from point to our "left" to point on our "right" + double dXh = heightmap[z * side + x - 1] - heightmap[z * side + x + 1]; + if (dXh != 0) { + // alter by our height scale + dXh *= heightScale; + // determine slope of perpendicular line + final double slopeX = 2.0 * xGridSpacing / dXh; + // now plug into cos(arctan(x)) to get unit length vector + n.setX(Math.copySign(1.0 / Math.sqrt(1 + slopeX * slopeX), dXh)); + n.setY(0); + n.setZ(Math.abs(slopeX * n.getX())); + } else { + n.set(0, 0, 1); + } + + // change across "z" from point "above" us to point "below" us + double dZh = heightmap[(z - 1) * side + x] - heightmap[(z + 1) * side + x]; + if (dZh != 0) { + // alter by our height scale + dZh *= heightScale; + // determine slope of perpendicular line + final double slopeZ = 2.0 * zGridSpacing / dZh; + // now plug into cos(arctan(x)) to get unit length vector + n2.setX(0); + n2.setY(Math.copySign(1.0 / Math.sqrt(1 + slopeZ * slopeZ), dZh)); + n2.setZ(Math.abs(slopeZ * n2.getY())); + } else { + n2.set(0, 0, 1); + } + + // add together the vectors across X and Z and normalize to get final normal + n.addLocal(n2).normalizeLocal(); + } + // System.err.println(n); + data.put(3 * (z * side + x) + 0, (byte) ((int) (127 * n.getX()) + 128)); + data.put(3 * (z * side + x) + 1, (byte) ((int) (127 * n.getY()) + 128)); + data.put(3 * (z * side + x) + 2, (byte) ((int) (127 * n.getZ()) + 128)); + } + } + + return normalMap; + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Region.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Region.java new file mode 100644 index 0000000..19a280a --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Region.java @@ -0,0 +1,249 @@ +/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.terrain.util;
+
+/**
+ * Used to calculate clipmap block boundaries etc
+ */
+public class Region {
+ private int x;
+ private int y;
+ private int width;
+ private int height;
+
+ private int left;
+ private int right;
+ private int top;
+ private int bottom;
+
+ private final int level;
+
+ public Region(final int x, final int y, final int width, final int height) {
+ this(0, x, y, width, height);
+ }
+
+ public Region(final int level, final int x, final int y, final int width, final int height) {
+ this.level = level;
+
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+
+ left = x;
+ right = x + width;
+ top = y;
+ bottom = y + height;
+ }
+
+ /**
+ * @return the x
+ */
+ public int getX() {
+ return x;
+ }
+
+ /**
+ * @return the y
+ */
+ public int getY() {
+ return y;
+ }
+
+ /**
+ * @param x
+ * the x to set
+ */
+ public void setX(final int x) {
+ this.x = x;
+ left = x;
+ right = x + width;
+ }
+
+ /**
+ * @param y
+ * the y to set
+ */
+ public void setY(final int y) {
+ this.y = y;
+ top = y;
+ bottom = y + height;
+ }
+
+ public void setWidth(final int width) {
+ this.width = width;
+ right = x + width;
+ }
+
+ public void setHeight(final int height) {
+ this.height = height;
+ bottom = y + height;
+ }
+
+ /**
+ * @return the left
+ */
+ public int getLeft() {
+ return left;
+ }
+
+ /**
+ * @return the right
+ */
+ public int getRight() {
+ return right;
+ }
+
+ /**
+ * @return the top
+ */
+ public int getTop() {
+ return top;
+ }
+
+ /**
+ * @return the bottom
+ */
+ public int getBottom() {
+ return bottom;
+ }
+
+ /**
+ * @return the width
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return the height
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ public boolean intersects(final Region r) {
+ int tw = width;
+ int th = height;
+ int rw = r.width;
+ int rh = r.height;
+ if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
+ return false;
+ }
+ final int tx = x;
+ final int ty = y;
+ final int rx = r.x;
+ final int ry = r.y;
+ rw += rx;
+ rh += ry;
+ tw += tx;
+ th += ty;
+ // overflow || intersect
+ return (rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry);
+ }
+
+ public Region intersection(final Region r) {
+ int tx1 = x;
+ int ty1 = y;
+ final int rx1 = r.x;
+ final int ry1 = r.y;
+ long tx2 = tx1;
+ tx2 += width;
+ long ty2 = ty1;
+ ty2 += height;
+ long rx2 = rx1;
+ rx2 += r.width;
+ long ry2 = ry1;
+ ry2 += r.height;
+ if (tx1 < rx1) {
+ tx1 = rx1;
+ }
+ if (ty1 < ry1) {
+ ty1 = ry1;
+ }
+ if (tx2 > rx2) {
+ tx2 = rx2;
+ }
+ if (ty2 > ry2) {
+ ty2 = ry2;
+ }
+ tx2 -= tx1;
+ ty2 -= ty1;
+ // tx2,ty2 will never overflow (they will never be
+ // larger than the smallest of the two source w,h)
+ // they might underflow, though...
+ if (tx2 < Integer.MIN_VALUE) {
+ tx2 = Integer.MIN_VALUE;
+ }
+ if (ty2 < Integer.MIN_VALUE) {
+ ty2 = Integer.MIN_VALUE;
+ }
+
+ r.setX(tx1);
+ r.setY(ty1);
+ r.setWidth((int) tx2);
+ r.setHeight((int) ty2);
+
+ return r;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + height;
+ result = prime * result + level;
+ result = prime * result + width;
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Region)) {
+ return false;
+ }
+ final Region other = (Region) obj;
+ if (height != other.height) {
+ return false;
+ }
+ if (level != other.level) {
+ return false;
+ }
+ if (width != other.width) {
+ return false;
+ }
+ if (x != other.x) {
+ return false;
+ }
+ if (y != other.y) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Region [level=" + level + ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
+ }
+
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TerrainGridCachePanel.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TerrainGridCachePanel.java new file mode 100644 index 0000000..5a6bb60 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TerrainGridCachePanel.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.util; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import com.ardor3d.extension.terrain.client.TerrainCache; +import com.ardor3d.extension.terrain.client.TerrainGridCache; +import com.ardor3d.extension.terrain.client.TerrainGridCache.TileLoadingData; +import com.ardor3d.extension.terrain.client.TerrainGridCache.TileLoadingData.State; +import com.ardor3d.math.MathUtils; + +public class TerrainGridCachePanel extends JPanel { + private static final long serialVersionUID = 1L; + + private final List<TerrainCache> cacheList; + private final int cacheSize; + + private final int size = 4; + + public TerrainGridCachePanel(final List<TerrainCache> cacheList, final int cacheSize) { + this.cacheList = cacheList; + this.cacheSize = cacheSize; + + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + repaint(); + } + }); + try { + Thread.sleep(100); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } + }, "repaintalot").start(); + + setSize(cacheList.size() * (size * cacheSize + 5) + 60, 100); + } + + @Override + protected void paintComponent(final Graphics g) { + super.paintComponent(g); + + final Graphics2D g2 = (Graphics2D) g; + + for (int i = 0; i < cacheList.size(); i++) { + final TerrainGridCache cache = (TerrainGridCache) cacheList.get(i); + for (final TileLoadingData data : cache.getDebugTiles()) { + if (data.state == State.init) { + g2.setColor(Color.lightGray); + } else if (data.state == State.loading) { + g2.setColor(Color.blue); + } else if (data.state == State.finished) { + g2.setColor(Color.green); + } else { + g2.setColor(Color.white); + } + final int x = MathUtils.moduloPositive(data.sourceTile.getX(), cacheSize); + final int y = MathUtils.moduloPositive(data.sourceTile.getY(), cacheSize); + final int xPos = x * size + 20 + (cacheList.size() - i - 1) * (size * cacheSize + 5); + final int yPos = y * size + 20; + g2.fillRect(xPos, yPos, size, size); + g2.setColor(Color.darkGray); + g2.drawRect(xPos, yPos, size, size); + } + g2 + .drawString("" + (cacheList.size() - i - 1), (cacheList.size() - i - 1) * (size * cacheSize + 5) + + 25, 15); + } + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TextureGridCachePanel.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TextureGridCachePanel.java new file mode 100644 index 0000000..8eb3a2d --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TextureGridCachePanel.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under 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.terrain.util; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import com.ardor3d.extension.terrain.client.TextureCache; +import com.ardor3d.extension.terrain.client.TextureGridCache; +import com.ardor3d.extension.terrain.client.TextureGridCache.TileLoadingData; +import com.ardor3d.extension.terrain.client.TextureGridCache.TileLoadingData.State; +import com.ardor3d.math.MathUtils; + +public class TextureGridCachePanel extends JPanel { + private static final long serialVersionUID = 1L; + + private final List<TextureCache> cacheList; + private final int cacheSize; + + private final int size = 4; + + public TextureGridCachePanel(final List<TextureCache> cacheList, final int cacheSize) { + this.cacheList = cacheList; + this.cacheSize = cacheSize; + + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + repaint(); + } + }); + try { + Thread.sleep(100); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } + }, "repaintalot").start(); + + setSize(cacheList.size() * (size * cacheSize + 5) + 60, 100); + } + + @Override + protected void paintComponent(final Graphics g) { + super.paintComponent(g); + + final Graphics2D g2 = (Graphics2D) g; + + for (int i = 0; i < cacheList.size(); i++) { + final TextureGridCache cache = (TextureGridCache) cacheList.get(i); + for (final TileLoadingData data : cache.getDebugTiles()) { + if (data.state == State.init) { + g2.setColor(Color.lightGray); + } else if (data.state == State.loading) { + g2.setColor(Color.blue); + } else if (data.state == State.finished) { + g2.setColor(Color.green); + } else { + g2.setColor(Color.white); + } + final int x = MathUtils.moduloPositive(data.sourceTile.getX(), cacheSize); + final int y = MathUtils.moduloPositive(data.sourceTile.getY(), cacheSize); + final int xPos = x * size + 20 + (cacheList.size() - i - 1) * (size * cacheSize + 5); + final int yPos = y * size + 20; + g2.fillRect(xPos, yPos, size, size); + g2.setColor(Color.darkGray); + g2.drawRect(xPos, yPos, size, size); + } + g2 + .drawString("" + (cacheList.size() - i - 1), (cacheList.size() - i - 1) * (size * cacheSize + 5) + + 25, 15); + } + } +} diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Tile.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Tile.java new file mode 100644 index 0000000..afefc37 --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Tile.java @@ -0,0 +1,48 @@ +
+package com.ardor3d.extension.terrain.util;
+
+import java.io.Serializable;
+
+public class Tile implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final int x, y;
+
+ public Tile(final int x, final int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result += 31 * result + x;
+ result += 31 * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Tile)) {
+ return false;
+ }
+ final Tile other = (Tile) obj;
+ return x == other.x && y == other.y;
+ }
+
+ @Override
+ public String toString() {
+ return "Tile [x=" + x + ", y=" + y + "]";
+ }
+}
diff --git a/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TileLocator.java b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TileLocator.java new file mode 100644 index 0000000..1d0a22d --- /dev/null +++ b/ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TileLocator.java @@ -0,0 +1,81 @@ + +package com.ardor3d.extension.terrain.util; + +import java.io.Serializable; +import java.net.URL; + +public class TileLocator implements Serializable { + private static final long serialVersionUID = 1L; + + private final Tile tile; + private final int sourceId; + private final int clipmapLevel; + private final URL url; + + public TileLocator(final Tile tile, final int sourceId, final int clipmapLevel, final URL url) { + this.tile = tile; + this.sourceId = sourceId; + this.clipmapLevel = clipmapLevel; + this.url = url; + } + + public Tile getTile() { + return tile; + } + + public int getSourceId() { + return sourceId; + } + + public int getClipmapLevel() { + return clipmapLevel; + } + + public URL getUrl() { + return url; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + clipmapLevel; + result = prime * result + sourceId; + result = prime * result + (tile == null ? 0 : tile.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof TileLocator)) { + return false; + } + final TileLocator other = (TileLocator) obj; + if (clipmapLevel != other.clipmapLevel) { + return false; + } + if (sourceId != other.sourceId) { + return false; + } + if (tile == null) { + if (other.tile != null) { + return false; + } + } else if (!tile.equals(other.tile)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "TileLocator [clipmapLevel=" + clipmapLevel + ", sourceId=" + sourceId + ", tile=" + tile + ", url=" + + url + "]"; + } +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader.frag b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader.frag new file mode 100644 index 0000000..cab2b17 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader.frag @@ -0,0 +1,112 @@ +/** + * 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 sampler3D texture; +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +uniform float levels; +uniform vec2 sliceOffset[16]; +uniform float minLevel; +uniform float validLevels; +uniform float textureSize; +uniform float texelSize; +uniform float showDebug; + +uniform vec4 sampleDist; +uniform vec4 shadowColor; + +varying vec2 vVertex; +varying vec3 eyeSpacePosition; + +varying vec4 diffuse,ambient; + +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 ); // +texelSize on ATI? + + vec4 tA = mix( tl, tr, f.x ); + vec4 tB = mix( bl, br, f.x ); + + return mix( tA, tB, f.y ); +} + +void main() +{ + float unit = (max(abs(vVertex.x), abs(vVertex.y))); + + unit = floor(unit); + unit = log2(unit); + unit = floor(unit); + + unit = min(unit, validLevels); + unit = max(unit, minLevel); + + 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 = 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); + vec4 texCol = mix(tex, tex2, fadeVal) + vec4(fadeVal*showDebug); + + float dist = length(eyeSpacePosition); + float fog = clamp((gl_Fog.end - dist) * gl_Fog.scale, 0.0, 1.0); + + float shade = 0.0; + float zDist = -eyeSpacePosition.z; + 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 = mix(gl_Fog.color, texCol * (ambient + diffuse) * vec4(1.0-shade*shadowColor.a), fog); +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShaderPCF.frag b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShaderPCF.frag new file mode 100644 index 0000000..c186002 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShaderPCF.frag @@ -0,0 +1,164 @@ +/** + * 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 sampler3D texture; +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +uniform float levels; +uniform vec2 sliceOffset[16]; +uniform float minLevel; +uniform float validLevels; +uniform float textureSize; +uniform float texelSize; +uniform float showDebug; +//uniform vec3 eyePosition; + +uniform vec4 sampleDist; +uniform vec4 shadowColor; +uniform float _shadowSize; + +varying vec2 vVertex; +varying vec2 texCoord; +varying vec3 eyeSpacePosition; + +varying vec4 diffuse,ambient; +varying vec3 lightDir; +varying vec3 normal; + +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 ); // +texelSize on ATI? + + vec4 tA = mix( tl, tr, f.x ); + vec4 tB = mix( bl, br, f.x ); + + return mix( tA, tB, f.y ); +} + +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 unit = (max(abs(vVertex.x), abs(vVertex.y))); + + unit = floor(unit); + unit = log2(unit); + unit = floor(unit); + + unit = min(unit, validLevels); + unit = max(unit, minLevel); + + 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 tex2 = texture3D(texture, vec3(texCoord2.x, texCoord2.y, unit2)); + 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); + vec4 texCol = mix(tex, tex2, fadeVal) + vec4(fadeVal*showDebug); + +// vec4 vDiffuse = diffuse * vec4(max( dot(lightDir, normalize(normal)), 0.0 )); +// texCol = (ambient + vDiffuse) * texCol; + + float dist = length(eyeSpacePosition); + float fog = clamp((gl_Fog.end - dist) * gl_Fog.scale, 0.0, 1.0); + + float shade = 0.0; + float zDist = -eyeSpacePosition.z; + 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 = mix(gl_Fog.color, texCol * vec4(1.0-shade*shadowColor.a), fog); +// gl_FragColor = mix(gl_Fog.color, texCol * vec4(1.0-shade*shadowColor.a)*shadowColor, fog); +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader_normalMap.frag b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader_normalMap.frag new file mode 100644 index 0000000..b1b6f49 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader_normalMap.frag @@ -0,0 +1,121 @@ +/** + * 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 sampler3D texture; +uniform sampler3D normalMap; +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +uniform float levels; +uniform vec2 sliceOffset[16]; +uniform float minLevel; +uniform float validLevels; +uniform float textureSize; +uniform float texelSize; +uniform float showDebug; + +uniform vec4 sampleDist; +uniform vec3 lightDir; + +varying vec2 vVertex; +varying vec3 eyeSpacePosition; + +varying vec4 diffuse,ambient; + +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 ); // +texelSize on ATI? + + vec4 tA = mix( tl, tr, f.x ); + vec4 tB = mix( bl, br, f.x ); + + return mix( tA, tB, f.y ); +} + +void main() +{ + float unit = (max(abs(vVertex.x), abs(vVertex.y))); + + unit = floor(unit); + unit = log2(unit); + unit = floor(unit); + + unit = min(unit, validLevels); + unit = max(unit, minLevel); + + 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 = texture3DBilinear(texture, vec3(texCoord.x, texCoord.y, unit), offset); + vec4 tex2 = texture3DBilinear(texture, vec3(texCoord2.x, texCoord2.y, unit2), offset2); + + vec4 norm = texture3DBilinear(normalMap, vec3(texCoord.x, texCoord.y, unit), offset); + vec4 norm2 = texture3DBilinear(normalMap, 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); + vec4 texCol = mix(tex, tex2, fadeVal) + vec4(fadeVal*showDebug); + vec4 normCol = (mix(norm, norm2, fadeVal)) * vec4(2.0) - vec4(1.0); + + vec3 n = normalize(normCol.xyz); + + float dist = length(eyeSpacePosition); + float fog = clamp((gl_Fog.end - dist) * gl_Fog.scale, 0.0, 1.0); + + float shade = 0.0; + float zDist = -eyeSpacePosition.z; + 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; + } + + float NdotL = max(dot(n,-lightDir.xzy),0.0) * (1.0-shade); + vec4 color = ambient + diffuse * NdotL; + gl_FragColor = mix(gl_Fog.color, color * texCol, fog); +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.frag b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.frag new file mode 100644 index 0000000..99335f4 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.frag @@ -0,0 +1,85 @@ +/** + * 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 sampler3D texture; +uniform float levels; +uniform vec2 sliceOffset[16]; +uniform float minLevel; +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 = min(unit, validLevels); + unit = max(unit, minLevel); + + 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-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.vert b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.vert new file mode 100644 index 0000000..deeb208 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.vert @@ -0,0 +1,22 @@ +/** + * 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 float scale; +uniform vec3 eyePosition; + +varying vec2 vVertex; + +void main(void){ + gl_TexCoord[0] = gl_MultiTexCoord0; + + vVertex = (gl_Vertex.xz - eyePosition.xz) * vec2(scale/32.0); + + gl_Position = ftransform(); +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.frag b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.frag new file mode 100644 index 0000000..0eb893c --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.frag @@ -0,0 +1,92 @@ +/** + * 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 sampler3D texture; +uniform float levels; +uniform vec2 sliceOffset[16]; +uniform float minLevel; +uniform float validLevels; +uniform float textureSize; +uniform float texelSize; +uniform float showDebug; +uniform vec3 eyePosition; + +varying vec2 vVertex; +varying vec3 eyeSpacePosition; + +varying vec4 diffuse,ambient; + +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 ); // +texelSize on ATI? + + vec4 tA = mix( tl, tr, f.x ); + vec4 tB = mix( bl, br, f.x ); + + return mix( tA, tB, f.y ); +} + +void main() +{ + float unit = (max(abs(vVertex.x), abs(vVertex.y))); + + unit = floor(unit); + unit = log2(unit); + unit = floor(unit); + + unit = min(unit, validLevels); + unit = max(unit, minLevel); + + 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 = 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); + vec4 texCol = mix(tex, tex2, fadeVal) + vec4(fadeVal*showDebug); + + float dist = length(eyeSpacePosition); + float fog = clamp((gl_Fog.end - dist) * gl_Fog.scale, 0.0, 1.0); + gl_FragColor = mix(gl_Fog.color, texCol * (ambient + diffuse), fog); +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.vert b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.vert new file mode 100644 index 0000000..af0f2d4 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.vert @@ -0,0 +1,52 @@ +/** + * 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 float scale; +uniform vec3 eyePosition; +uniform float vertexDistance; // Clip-Gridspacing +uniform float clipSideSize; // Clip-Size + +varying vec2 vVertex; +varying vec3 eyeSpacePosition; + +varying vec4 diffuse, ambient; + +void main(void){ + gl_TexCoord[0] = gl_MultiTexCoord0; + + vVertex = (gl_Vertex.xz - eyePosition.xz) * vec2(scale/32.0); + + vec4 position = gl_Vertex; + +//////////// terrain blending + float scaledClipSideSize = clipSideSize * vertexDistance * 0.5; + vec2 viewDistance = abs(position.xz - eyePosition.xz); + float maxDistance = max(viewDistance.x, viewDistance.y)/scaledClipSideSize; + float blend = clamp((maxDistance - 0.51) * 2.2, 0.0, 1.0); + + position.y = mix(position.y, position.w, blend); + position.w = 1.0; +////////////// + + gl_Position = gl_ModelViewProjectionMatrix * position; + + vec4 ePos = gl_ModelViewMatrix * position; + eyeSpacePosition = ePos.xyz; + + gl_TexCoord[0] = gl_TextureMatrix[1] * ePos; + gl_TexCoord[1] = gl_TextureMatrix[2] * ePos; + gl_TexCoord[2] = gl_TextureMatrix[3] * ePos; + gl_TexCoord[3] = gl_TextureMatrix[4] * ePos; + + // LIGHTING and MATERIALS + diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; + ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; + ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient; +} diff --git a/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader_normalMap.frag b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader_normalMap.frag new file mode 100644 index 0000000..779db28 --- /dev/null +++ b/ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader_normalMap.frag @@ -0,0 +1,108 @@ +/** + * 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 sampler3D texture; +uniform sampler3D normalMap; + + + + + +uniform float levels; +uniform vec2 sliceOffset[16]; +uniform float minLevel; +uniform float validLevels; +uniform float textureSize; +uniform float texelSize; +uniform float showDebug; + +uniform vec3 lightDir; + +varying vec2 vVertex; +varying vec3 eyeSpacePosition; + +varying vec4 diffuse,ambient; + +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 ); // +texelSize on ATI? + + vec4 tA = mix( tl, tr, f.x ); + vec4 tB = mix( bl, br, f.x ); + + return mix( tA, tB, f.y ); +} + +void main() +{ + float unit = (max(abs(vVertex.x), abs(vVertex.y))); + + unit = floor(unit); + unit = log2(unit); + unit = floor(unit); + + unit = min(unit, validLevels); + unit = max(unit, minLevel); + + 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 = texture3DBilinear(texture, vec3(texCoord.x, texCoord.y, unit), offset); + vec4 tex2 = texture3DBilinear(texture, vec3(texCoord2.x, texCoord2.y, unit2), offset2); + + vec4 norm = texture3DBilinear(normalMap, vec3(texCoord.x, texCoord.y, unit), offset); + vec4 norm2 = texture3DBilinear(normalMap, 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); + vec4 texCol = mix(tex, tex2, fadeVal) + vec4(fadeVal*showDebug); + vec4 normCol = (mix(norm, norm2, fadeVal)) * vec4(2.0) - vec4(1.0); + + vec3 n = normalize(normCol.xyz); + + float dist = length(eyeSpacePosition); + float fog = clamp((gl_Fog.end - dist) * gl_Fog.scale, 0.0, 1.0); + + float NdotL = max(dot(n,-lightDir.xzy),0.0); + vec4 color = ambient + diffuse * NdotL; + gl_FragColor = mix(gl_Fog.color, color * texCol, fog); +} diff --git a/ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRGB.java b/ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRGB.java new file mode 100644 index 0000000..13f530c --- /dev/null +++ b/ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRGB.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.util; + +import org.junit.Test; + +import com.ardor3d.extension.terrain.util.IntColorUtils; + +public class TestRGB { + @Test + public void testLerp() throws Exception { + final byte x1 = 0, y1 = 100, z1 = (byte) 200, a1 = 0; + final byte x2 = 100, y2 = (byte) 255, z2 = 50, a2 = 0; + + final int col1 = IntColorUtils.getColor(x1, y1, z1, a1); + final int col2 = IntColorUtils.getColor(x2, y2, z2, a2); + System.out.println(IntColorUtils.toString(col1)); + System.out.println(IntColorUtils.toString(col2)); + + int col = IntColorUtils.lerp(0.0, col1, col2); + System.out.println(IntColorUtils.toString(col)); + col = IntColorUtils.lerp(0.5, col1, col2); + System.out.println(IntColorUtils.toString(col)); + col = IntColorUtils.lerp(0.75, col1, col2); + System.out.println(IntColorUtils.toString(col)); + col = IntColorUtils.lerp(1.0, col1, col2); + System.out.println(IntColorUtils.toString(col)); + + System.out.println(); + + col = IntColorUtils.lerp(-2.0, col1, col2); + System.out.println(IntColorUtils.toString(col)); + col = IntColorUtils.lerp(2.0, col1, col2); + System.out.println(IntColorUtils.toString(col)); + + } +} diff --git a/ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRegion.java b/ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRegion.java new file mode 100644 index 0000000..4e1c700 --- /dev/null +++ b/ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRegion.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.terrain.util; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.ardor3d.extension.terrain.util.Region; + +public class TestRegion { + @Test + public void testIntersects() throws Exception { + final Region r1 = new Region(0, 0, 20, 20); + final Region r2 = new Region(5, 5, 10, 10); + + Assert.assertTrue(r1.intersects(r2)); + Assert.assertTrue(r2.intersects(r1)); + + Assert.assertEquals(new Region(5, 5, 10, 10), r2.intersection(r1)); + + final Region r3 = new Region(0, 0, 20, 20); + Assert.assertEquals(new Region(5, 5, 10, 10), r3.intersection(r2)); + } +} |