aboutsummaryrefslogtreecommitdiffstats
path: root/ardor3d-terrain
diff options
context:
space:
mode:
Diffstat (limited to 'ardor3d-terrain')
-rw-r--r--ardor3d-terrain/.classpath10
-rw-r--r--ardor3d-terrain/.project17
-rw-r--r--ardor3d-terrain/.settings/org.eclipse.jdt.core.prefs278
-rw-r--r--ardor3d-terrain/.settings/org.eclipse.jdt.ui.prefs118
-rw-r--r--ardor3d-terrain/ardorSettings.properties9
-rw-r--r--ardor3d-terrain/pom.xml37
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/ClipmapLevel.java637
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/Terrain.java712
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainBuilder.java279
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainCache.java58
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainConfiguration.java73
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainDataProvider.java50
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainGridCache.java644
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TerrainSource.java66
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureCache.java41
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureClipmap.java654
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureConfiguration.java79
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureGridCache.java606
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/TextureSource.java69
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/UrlInputSupplier.java20
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/CacheFunctionUtil.java50
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance12ToRGBFunction.java50
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8Alpha8ToRGBAFunction.java47
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/Luminance8ToRGBFunction.java46
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBAFunction.java46
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGB8ToRGBFunction.java45
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBAFunction.java45
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/RGBA8ToRGBFunction.java48
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/client/functions/SourceCacheFunction.java20
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/ImageHeightMap.java61
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/MidPointHeightMapGenerator.java307
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/heightmap/RawHeightMap.java246
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainDataProvider.java135
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTerrainSource.java111
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/array/ArrayTextureSource.java113
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AbstractAwtElement.java126
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtElementProvider.java62
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtImageElement.java76
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtShapeElement.java179
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/AwtTextureSource.java231
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/awt/ElementUpdateListener.java17
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/image/ImageTextureSource.java140
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainDataProvider.java89
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTerrainSource.java133
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/InMemoryTextureSource.java142
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/inmemory/data/InMemoryTerrainData.java250
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralNormalMapSource.java111
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainDataProvider.java74
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTerrainSource.java97
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/procedural/ProceduralTextureSource.java120
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainDataProvider.java91
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTerrainSource.java83
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/providers/simplearray/SimpleArrayTextureSource.java111
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/AbstractBresenhamTracer.java136
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamYUpGridTracer.java145
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/BresenhamZUpGridTracer.java145
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/ClipmapTerrainPicker.java328
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/DoubleBufferedList.java40
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/IntColorUtils.java48
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/LevelData.java19
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/NormalMapUtil.java93
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Region.java249
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TerrainGridCachePanel.java92
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TextureGridCachePanel.java92
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/Tile.java48
-rw-r--r--ardor3d-terrain/src/main/java/com/ardor3d/extension/terrain/util/TileLocator.java81
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader.frag112
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShaderPCF.frag164
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/shadowedGeometryClipmapShader_normalMap.frag121
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.frag85
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/textureClipmapShader.vert22
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.frag92
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader.vert52
-rw-r--r--ardor3d-terrain/src/main/resources/com/ardor3d/extension/terrain/texturedGeometryClipmapShader_normalMap.frag108
-rw-r--r--ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRGB.java45
-rw-r--r--ardor3d-terrain/src/test/java/com/ardor3d/extension/terrain/util/TestRegion.java33
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 &lt;http\://www.ardor3d.com/LICENSE&gt;.\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));
+ }
+}