aboutsummaryrefslogtreecommitdiffstats
path: root/ardor3d-effects
diff options
context:
space:
mode:
authorneothemachine <[email protected]>2012-12-05 17:03:16 +0100
committerneothemachine <[email protected]>2012-12-05 17:03:16 +0100
commit9dd02f103042cb8a196f8a3ed2278da443e345bf (patch)
tree422449f0c62ff9518316ce5d4219bb2b12f0ed15 /ardor3d-effects
parent2b26b12fd794de0f03a064a10024a3d9f5583756 (diff)
move all files from trunk to root folder
Diffstat (limited to 'ardor3d-effects')
-rw-r--r--ardor3d-effects/.classpath9
-rw-r--r--ardor3d-effects/.project17
-rw-r--r--ardor3d-effects/.settings/org.eclipse.core.resources.prefs3
-rw-r--r--ardor3d-effects/.settings/org.eclipse.jdt.core.prefs276
-rw-r--r--ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs114
-rw-r--r--ardor3d-effects/pom.xml32
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.java107
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java186
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.java205
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java440
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.java98
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.java129
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java496
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java172
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java517
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.java18
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.java47
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.java83
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.java127
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java219
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.java162
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java1084
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java186
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java453
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java185
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.java91
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java112
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.java65
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.java82
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.java26
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.java40
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.java65
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.java65
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.java25
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java31
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.java76
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java662
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.java127
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java1085
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java285
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java1365
-rw-r--r--ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.java20
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag21
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag74
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert19
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag26
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag28
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag30
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag29
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag26
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag31
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag33
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag25
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert19
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag18
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert19
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag26
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag29
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert19
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag31
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag31
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag22
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.pngbin0 -> 167 bytes
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag90
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert22
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag77
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert49
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag89
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert49
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag88
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert53
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag101
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert53
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag34
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert24
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag39
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert24
-rw-r--r--ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag76
-rw-r--r--ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.java19
-rw-r--r--ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.java59
80 files changed, 11089 insertions, 0 deletions
diff --git a/ardor3d-effects/.classpath b/ardor3d-effects/.classpath
new file mode 100644
index 0000000..b2417a3
--- /dev/null
+++ b/ardor3d-effects/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/test/java"/>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/ardor3d-core"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/ardor3d-effects/.project b/ardor3d-effects/.project
new file mode 100644
index 0000000..a38130a
--- /dev/null
+++ b/ardor3d-effects/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>ardor3d-effects</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/ardor3d-effects/.settings/org.eclipse.core.resources.prefs b/ardor3d-effects/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..e21a61e
--- /dev/null
+++ b/ardor3d-effects/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Wed Jan 07 11:32:43 PST 2009
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/ardor3d-effects/.settings/org.eclipse.jdt.core.prefs b/ardor3d-effects/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..e914104
--- /dev/null
+++ b/ardor3d-effects/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,276 @@
+#Tue Apr 06 11:29:47 CDT 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+org.eclipse.jdt.core.codeComplete.fieldPrefixes=_
+org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+org.eclipse.jdt.core.codeComplete.localPrefixes=
+org.eclipse.jdt.core.codeComplete.localSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=1
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=120
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs b/ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..f479490
--- /dev/null
+++ b/ardor3d-effects/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,114 @@
+#Sun Jan 04 11:43:23 CST 2009
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=true
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_serial_version_id=true
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=true
+cleanup.always_use_this_for_non_static_field_access=false
+cleanup.always_use_this_for_non_static_method_access=false
+cleanup.convert_to_enhanced_for_loop=false
+cleanup.correct_indentation=true
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=false
+cleanup.organize_imports=true
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_parentheses_in_expressions=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+cleanup_profile=_ArdorLabs
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_ArdorLabs
+formatter_settings_version=11
+org.eclipse.jdt.ui.exception.name=ex
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.javadoc=false
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.overrideannotation=true
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param} the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="false" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment"/><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="false" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">/**\r\n * Copyright (c) 2008-2010 Ardor Labs, Inc.\r\n *\r\n * This file is part of Ardor3D.\r\n *\r\n * Ardor3D is free software\: you can redistribute it and/or modify it \r\n * under the terms of its license which may be found in the accompanying\r\n * LICENSE file or at &lt;http\://www.ardor3d.com/LICENSE&gt;.\r\n */\r\n\r\n${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\r\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=true
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=true
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/ardor3d-effects/pom.xml b/ardor3d-effects/pom.xml
new file mode 100644
index 0000000..123dbe3
--- /dev/null
+++ b/ardor3d-effects/pom.xml
@@ -0,0 +1,32 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ardor3d</groupId>
+ <artifactId>ardor3d</artifactId>
+ <version>0.9-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ardor3d-effects</artifactId>
+ <packaging>bundle</packaging>
+ <name>Ardor 3D Effects</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.java
new file mode 100644
index 0000000..dcd3b80
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/ColorReplaceEffect.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.WrapMode;
+import com.ardor3d.renderer.effect.EffectManager;
+import com.ardor3d.renderer.effect.EffectStep_RenderScreenOverlay;
+import com.ardor3d.renderer.effect.EffectStep_SetRenderTarget;
+import com.ardor3d.renderer.effect.RenderEffect;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+
+public class ColorReplaceEffect extends RenderEffect {
+
+ private String shaderDirectory = "com/ardor3d/extension/effect/";
+ private float _redWeight = 0.3086f;
+ private float _greenWeight = 0.6094f;
+ private float _blueWeight = 0.0820f;
+ private Texture _colorRampTexture;
+
+ public ColorReplaceEffect(final Texture colorRampTexture) {
+ _colorRampTexture = colorRampTexture;
+ _colorRampTexture.setWrap(WrapMode.EdgeClamp);
+ }
+
+ private GLSLShaderObjectsState getColorizeShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class,
+ shaderDirectory + "color_replace.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("inputTex", 0);
+ shader.setUniform("colorRampTex", 1);
+ shader.setUniform("redWeight", _redWeight);
+ shader.setUniform("greenWeight", _greenWeight);
+ shader.setUniform("blueWeight", _blueWeight);
+ return shader;
+ }
+
+ @Override
+ public void prepare(final EffectManager manager) {
+ _steps.clear();
+ _steps.add(new EffectStep_SetRenderTarget("*Next"));
+
+ final EffectStep_RenderScreenOverlay colorizeStep = new EffectStep_RenderScreenOverlay();
+ colorizeStep.getTextureState().setTexture(_colorRampTexture, 1);
+ colorizeStep.getTargetMap().put("*Previous", 0);
+ colorizeStep.getEnforcedStates().put(StateType.GLSLShader, getColorizeShader());
+ _steps.add(colorizeStep);
+
+ super.prepare(manager);
+ }
+
+ public float getRedWeight() {
+ return _redWeight;
+ }
+
+ public void setRedWeight(final float redWeight) {
+ _redWeight = redWeight;
+ }
+
+ public float getGreenWeight() {
+ return _greenWeight;
+ }
+
+ public void setGreenWeight(final float greenWeight) {
+ _greenWeight = greenWeight;
+ }
+
+ public float getBlueWeight() {
+ return _blueWeight;
+ }
+
+ public void setBlueWeight(final float blueWeight) {
+ _blueWeight = blueWeight;
+ }
+
+ public String getShaderDirectory() {
+ return shaderDirectory;
+ }
+
+ public void setShaderDirectory(final String shaderDirectory) {
+ this.shaderDirectory = shaderDirectory;
+ }
+
+ public Texture getColorRampTexture() {
+ return _colorRampTexture;
+ }
+
+ public void setColorRampTexture(final Texture colorRampTexture) {
+ _colorRampTexture = colorRampTexture;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java
new file mode 100644
index 0000000..27cb7db
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/HDREffect.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect;
+
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.image.Texture.MagnificationFilter;
+import com.ardor3d.image.Texture.MinificationFilter;
+import com.ardor3d.image.Texture.WrapMode;
+import com.ardor3d.renderer.effect.EffectManager;
+import com.ardor3d.renderer.effect.EffectStep_RenderScreenOverlay;
+import com.ardor3d.renderer.effect.EffectStep_SetRenderTarget;
+import com.ardor3d.renderer.effect.RenderEffect;
+import com.ardor3d.renderer.effect.RenderTarget;
+import com.ardor3d.renderer.effect.RenderTarget_Texture2D;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+
+public class HDREffect extends RenderEffect {
+ private String shaderDirectory = "com/ardor3d/extension/effect/";
+
+ protected static final String RT_DOWNSAMPLED = "HDREffect.DOWNSAMPLED";
+ protected static final String RT_LUM64x64 = "HDREffect.LUM64x64";
+ protected static final String RT_LUM16x16 = "HDREffect.LUM16x16";
+ protected static final String RT_LUM1x1 = "HDREffect.LUM1x1";
+ protected static final String RT_BRIGHTMAP = "HDREffect.BRIGHTMAP";
+ protected static final String RT_BLOOM_HORIZONTAL = "HDREffect.BLOOM_HORIZONTAL";
+ protected static final String RT_BLOOM = "HDREffect.BLOOM";
+
+ protected float _downsampleRatio = 0.25f;
+
+ @Override
+ public void prepare(final EffectManager manager) {
+ // init targets used in this effect
+ initTargets(manager);
+
+ _steps.clear();
+
+ // step 1: downsample previous rendering in chain
+ {
+ _steps.add(new EffectStep_SetRenderTarget(RT_DOWNSAMPLED));
+ final EffectStep_RenderScreenOverlay downsample = new EffectStep_RenderScreenOverlay();
+ downsample.getTargetMap().put("*Previous", 0);
+ _steps.add(downsample);
+ }
+
+ // step 2: extract our average luminance value
+ {
+ _steps.add(new EffectStep_SetRenderTarget(RT_LUM64x64));
+ final EffectStep_RenderScreenOverlay extract64 = new EffectStep_RenderScreenOverlay();
+ extract64.getEnforcedStates().put(StateType.GLSLShader, getLuminanceExtractionShader());
+ extract64.getTargetMap().put(RT_DOWNSAMPLED, 0);
+ _steps.add(extract64);
+
+ _steps.add(new EffectStep_SetRenderTarget(RT_LUM16x16));
+ final EffectStep_RenderScreenOverlay extract4 = new EffectStep_RenderScreenOverlay();
+ extract4.getTargetMap().put(RT_LUM64x64, 0);
+ _steps.add(extract4);
+
+ _steps.add(new EffectStep_SetRenderTarget(RT_LUM1x1));
+ final EffectStep_RenderScreenOverlay extract1 = new EffectStep_RenderScreenOverlay();
+ extract1.getTargetMap().put(RT_LUM16x16, 0);
+ _steps.add(extract1);
+ }
+
+ // step 3: apply bright pass, extracting the brighter than normal portions of the scene
+ {
+ _steps.add(new EffectStep_SetRenderTarget(RT_BRIGHTMAP));
+ final EffectStep_RenderScreenOverlay bright = new EffectStep_RenderScreenOverlay();
+ bright.getEnforcedStates().put(StateType.GLSLShader, getBrightMapShader());
+ bright.getTargetMap().put(RT_DOWNSAMPLED, 0);
+ bright.getTargetMap().put(RT_LUM1x1, 1);
+ _steps.add(bright);
+ }
+
+ // finally: draw bloom texture and previous texture on fsq, blended.
+ _steps.add(new EffectStep_SetRenderTarget("*Next"));
+
+ final EffectStep_RenderScreenOverlay blendOverlay = new EffectStep_RenderScreenOverlay();
+ blendOverlay.getTargetMap().put(RT_BRIGHTMAP, 0);
+ _steps.add(blendOverlay);
+
+ super.prepare(manager);
+ }
+
+ private RenderState getLuminanceExtractionShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class,
+ shaderDirectory + "luminance.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("inputTex", 0);
+ return shader;
+ }
+
+ private RenderState getBrightMapShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(ColorReplaceEffect.class,
+ shaderDirectory + "brightmap.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("inputTex", 0);
+ shader.setUniform("lum1x1Tex", 1);
+ shader.setUniform("exposurePow", 3.0f);
+ shader.setUniform("exposureCutoff", 0.0f);
+ return shader;
+ }
+
+ private void initTargets(final EffectManager manager) {
+ final DisplaySettings canvas = manager.getCanvasSettings();
+ final int downsampledHeight = Math.round(canvas.getHeight() * _downsampleRatio);
+ final int downsampledWidth = Math.round(canvas.getWidth() * _downsampleRatio);
+
+ final RenderTarget_Texture2D downsampled = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight,
+ TextureStoreFormat.RGBA16F);
+ downsampled.getTexture().setWrap(WrapMode.Clamp);
+ manager.getRenderTargetMap().put(RT_DOWNSAMPLED, downsampled);
+
+ manager.getRenderTargetMap().put(RT_LUM64x64, getLuminanceDownsampleTexture(64));
+ manager.getRenderTargetMap().put(RT_LUM16x16, getLuminanceDownsampleTexture(16));
+ manager.getRenderTargetMap().put(RT_LUM1x1, getLuminanceDownsampleTexture(1));
+
+ final RenderTarget_Texture2D brightmap = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight,
+ TextureStoreFormat.RGBA16F);
+ brightmap.getTexture().setWrap(WrapMode.Clamp);
+ manager.getRenderTargetMap().put(RT_BRIGHTMAP, brightmap);
+
+ final RenderTarget_Texture2D bloomHoriz = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight,
+ TextureStoreFormat.RGBA8);
+ bloomHoriz.getTexture().setWrap(WrapMode.Clamp);
+ manager.getRenderTargetMap().put(RT_BLOOM_HORIZONTAL, bloomHoriz);
+
+ final RenderTarget_Texture2D bloom = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight,
+ TextureStoreFormat.RGBA8);
+ bloom.getTexture().setWrap(WrapMode.Clamp);
+ manager.getRenderTargetMap().put(RT_BLOOM, bloom);
+ }
+
+ private RenderTarget getLuminanceDownsampleTexture(final int size) {
+ final RenderTarget_Texture2D target = new RenderTarget_Texture2D(size, size, TextureStoreFormat.RGBA16F);
+ if (size != 1) {
+ target.getTexture().setMinificationFilter(MinificationFilter.Trilinear);
+ target.getTexture().setMagnificationFilter(MagnificationFilter.Bilinear);
+ } else {
+ target.getTexture().setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps);
+ target.getTexture().setMagnificationFilter(MagnificationFilter.NearestNeighbor);
+ }
+
+ target.getTexture().setWrap(WrapMode.Clamp);
+ return target;
+ }
+
+ public float getDownsampleRatio() {
+ return _downsampleRatio;
+ }
+
+ public void setDownsampleRatio(final float downsampleRatio) {
+ _downsampleRatio = downsampleRatio;
+ }
+
+ public String getShaderDirectory() {
+ return shaderDirectory;
+ }
+
+ public void setShaderDirectory(final String shaderDirectory) {
+ this.shaderDirectory = shaderDirectory;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.java
new file mode 100644
index 0000000..bca0512
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/SimpleBloomEffect.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect;
+
+import java.util.List;
+
+import com.ardor3d.extension.effect.bloom.BloomRenderPass;
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.image.Texture.WrapMode;
+import com.ardor3d.renderer.effect.EffectManager;
+import com.ardor3d.renderer.effect.EffectStep_RenderScreenOverlay;
+import com.ardor3d.renderer.effect.EffectStep_RenderSpatials;
+import com.ardor3d.renderer.effect.EffectStep_SetRenderTarget;
+import com.ardor3d.renderer.effect.RenderEffect;
+import com.ardor3d.renderer.effect.RenderTarget_Texture2D;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.google.common.collect.Lists;
+
+public class SimpleBloomEffect extends RenderEffect {
+ protected static final String RT_MAIN = "LDRBloomEffect.MAIN";
+ protected static final String RT_SECONDARY = "LDRBloomEffect.SECONDARY";
+
+ protected String shaderDirectory = "com/ardor3d/extension/effect/";
+ protected final List<Spatial> _bloomItems = Lists.newArrayList();
+
+ protected float _downsampleRatio = .33f;
+ private final GLSLShaderObjectsState _extractionShader, _blurHorizShader, _blurVertShader;
+
+ public SimpleBloomEffect() {
+ _extractionShader = getExtractionShader();
+ _blurHorizShader = getBlurHorizShader();
+ _blurVertShader = getBlurVertShader();
+ setExposureIntensity(1.3f);
+ setExposureCutoff(0.15f);
+ setSampleDistance(0.02f);
+ }
+
+ @Override
+ public void prepare(final EffectManager manager) {
+ // init targets used in this effect
+ initTargets(manager);
+
+ final boolean useBloomItems = !_bloomItems.isEmpty();
+
+ _steps.clear();
+ // step 1: pick whether we are blooming the whole previous render buffer or just specific items
+ if (useBloomItems) {
+ // render these items to a texture
+ _steps.add(new EffectStep_SetRenderTarget(RT_MAIN));
+ _steps.add(new EffectStep_RenderSpatials(null));
+ }
+
+ // step 2: extract intensity
+ {
+ _steps.add(new EffectStep_SetRenderTarget(RT_SECONDARY));
+ final EffectStep_RenderScreenOverlay extract = new EffectStep_RenderScreenOverlay();
+ extract.getEnforcedStates().put(StateType.GLSLShader, _extractionShader);
+ extract.getTargetMap().put(useBloomItems ? RT_MAIN : "*Previous", 0);
+ _steps.add(extract);
+ }
+
+ // step 3: blur
+ {
+ _steps.add(new EffectStep_SetRenderTarget(RT_MAIN));
+ final EffectStep_RenderScreenOverlay blurHoriz = new EffectStep_RenderScreenOverlay();
+ blurHoriz.getEnforcedStates().put(StateType.GLSLShader, _blurHorizShader);
+ blurHoriz.getTargetMap().put(RT_SECONDARY, 0);
+ _steps.add(blurHoriz);
+
+ _steps.add(new EffectStep_SetRenderTarget(RT_SECONDARY));
+ final EffectStep_RenderScreenOverlay blurVert = new EffectStep_RenderScreenOverlay();
+ blurVert.getEnforcedStates().put(StateType.GLSLShader, _blurVertShader);
+ blurVert.getTargetMap().put(RT_MAIN, 0);
+ _steps.add(blurVert);
+ }
+
+ // finally: draw bloom texture and previous texture on fsq, blended.
+ _steps.add(new EffectStep_SetRenderTarget("*Next"));
+
+ final EffectStep_RenderScreenOverlay blendOverlay = new EffectStep_RenderScreenOverlay();
+ blendOverlay.getEnforcedStates().put(StateType.GLSLShader, getBlendShader());
+ blendOverlay.getTargetMap().put("*Previous", 0);
+ blendOverlay.getTargetMap().put(RT_SECONDARY, 1);
+ _steps.add(blendOverlay);
+
+ super.prepare(manager);
+ }
+
+ protected void initTargets(final EffectManager manager) {
+ final DisplaySettings canvas = manager.getCanvasSettings();
+ final int downsampledHeight = Math.round(canvas.getHeight() * _downsampleRatio);
+ final int downsampledWidth = Math.round(canvas.getWidth() * _downsampleRatio);
+
+ final RenderTarget_Texture2D main = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight, manager
+ .getOutputFormat());
+ main.getTexture().setWrap(WrapMode.Clamp);
+ manager.getRenderTargetMap().put(RT_MAIN, main);
+
+ final RenderTarget_Texture2D secondary = new RenderTarget_Texture2D(downsampledWidth, downsampledHeight,
+ manager.getOutputFormat());
+ secondary.getTexture().setWrap(WrapMode.Clamp);
+ manager.getRenderTargetMap().put(RT_SECONDARY, secondary);
+ }
+
+ protected GLSLShaderObjectsState getExtractionShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_extract.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("inputTex", 0);
+ return shader;
+ }
+
+ protected GLSLShaderObjectsState getBlurHorizShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "gausian_blur_horizontal9.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("inputTex", 0);
+ return shader;
+ }
+
+ protected GLSLShaderObjectsState getBlurVertShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "gausian_blur_vertical9.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("inputTex", 0);
+ return shader;
+ }
+
+ protected GLSLShaderObjectsState getBlendShader() {
+ final GLSLShaderObjectsState shader = new GLSLShaderObjectsState();
+ try {
+ shader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "fsq.vert"));
+ shader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "add2textures.frag"));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ shader.setUniform("tex1", 0);
+ shader.setUniform("tex2", 1);
+ return shader;
+ }
+
+ public void setExposureIntensity(final float value) {
+ _extractionShader.setUniform("exposureIntensity", value);
+ }
+
+ public void setExposureCutoff(final float value) {
+ _extractionShader.setUniform("exposureCutoff", value);
+ }
+
+ public void setSampleDistance(final float value) {
+ _blurHorizShader.setUniform("sampleDist", value);
+ _blurVertShader.setUniform("sampleDist", value);
+ }
+
+ public float getDownsampleRatio() {
+ return _downsampleRatio;
+ }
+
+ public void setDownsampleRatio(final float downsampleRatio) {
+ _downsampleRatio = downsampleRatio;
+ }
+
+ public String getShaderDirectory() {
+ return shaderDirectory;
+ }
+
+ public void setShaderDirectory(final String shaderDirectory) {
+ this.shaderDirectory = shaderDirectory;
+ }
+
+ public List<Spatial> getBloomItems() {
+ return _bloomItems;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java
new file mode 100644
index 0000000..1ef32bd
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/bloom/BloomRenderPass.java
@@ -0,0 +1,440 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.bloom;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.pass.Pass;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Renderable;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+
+/**
+ * GLSL bloom effect pass. - Render supplied source to a texture - Extract intensity - Blur intensity - Blend with first
+ * pass
+ */
+public class BloomRenderPass extends Pass {
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(BloomRenderPass.class.getName());
+
+ private static final long serialVersionUID = 1L;
+
+ private double throttle = 0;
+ private double sinceLast = 1;
+
+ private TextureRenderer tRenderer = null;
+ private TextureRenderer fullTRenderer = null;
+ private Texture2D mainTexture = null;
+ private Texture2D secondTexture = null;
+ private Texture2D screenTexture = null;
+
+ private Quad fullScreenQuad = null;
+
+ private GLSLShaderObjectsState extractionShader = null;
+ private GLSLShaderObjectsState blurShader = null;
+ private GLSLShaderObjectsState blurShaderHorizontal = null;
+ private GLSLShaderObjectsState blurShaderVertical = null;
+ private GLSLShaderObjectsState finalShader = null;
+
+ private final Camera cam;
+ private final int renderScale;
+
+ private int nrBlurPasses;
+ private float blurSize;
+ private float blurIntensityMultiplier;
+ private float exposurePow;
+ private float exposureCutoff;
+ private boolean supported = true;
+ private boolean useCurrentScene = false;
+
+ private boolean useSeparateConvolution = false;
+
+ public static String shaderDirectory = "com/ardor3d/extension/effect/bloom/";
+
+ private boolean initialized = false;
+
+ /**
+ * Reset bloom parameters to default
+ */
+ public void resetParameters() {
+ nrBlurPasses = 2;
+ blurSize = 0.02f;
+ blurIntensityMultiplier = 1.3f;
+ exposurePow = 3.0f;
+ exposureCutoff = 0.0f;
+ }
+
+ /**
+ * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
+ */
+ @Override
+ public void cleanUp() {
+ super.cleanUp();
+ if (tRenderer != null) {
+ tRenderer.cleanup();
+ }
+ if (fullTRenderer != null) {
+ fullTRenderer.cleanup();
+ }
+ }
+
+ public boolean isSupported() {
+ return supported;
+ }
+
+ /**
+ * Creates a new bloom renderpass
+ *
+ * @param cam
+ * Camera used for rendering the bloomsource
+ * @param renderScale
+ * Scale of bloom texture
+ */
+ public BloomRenderPass(final Camera cam, final int renderScale) {
+ this.cam = cam;
+ this.renderScale = renderScale;
+ resetParameters();
+ }
+
+ @Override
+ protected void doUpdate(final double tpf) {
+ super.doUpdate(tpf);
+ sinceLast += tpf;
+ }
+
+ @Override
+ public void doRender(final Renderer r) {
+ if (!initialized) {
+ doInit(r);
+ }
+
+ if (!isSupported() || !useCurrentScene && _spatials.size() == 0) {
+ return;
+ }
+
+ final BlendState blend = (BlendState) fullScreenQuad.getWorldRenderState(RenderState.StateType.Blend);
+
+ if (sinceLast > throttle) {
+ sinceLast = 0;
+
+ tRenderer.getCamera().setLocation(cam.getLocation());
+ tRenderer.getCamera().setDirection(cam.getDirection());
+ tRenderer.getCamera().setUp(cam.getUp());
+ tRenderer.getCamera().setLeft(cam.getLeft());
+
+ blend.setEnabled(false);
+ final TextureState ts = (TextureState) fullScreenQuad.getWorldRenderState(RenderState.StateType.Texture);
+
+ // see if we should use the current scene to bloom, or only things added to the pass.
+ if (useCurrentScene) {
+ // grab backbuffer to texture
+ if (screenTexture == null) {
+ final DisplaySettings settings = new DisplaySettings(cam.getWidth(), cam.getHeight(), 24, 0, 0, 8,
+ 0, 0, false, false);
+ fullTRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(settings, false, r,
+ ContextManager.getCurrentContext().getCapabilities());
+ screenTexture = new Texture2D();
+ screenTexture.setWrap(Texture.WrapMode.Clamp);
+ screenTexture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ fullTRenderer.setupTexture(screenTexture);
+ }
+ fullTRenderer.copyToTexture(screenTexture, 0, 0, cam.getWidth(), cam.getHeight(), 0, 0);
+ ts.setTexture(screenTexture, 0);
+ } else {
+ // Render scene to texture
+ tRenderer.render(_spatials, mainTexture, Renderer.BUFFER_COLOR_AND_DEPTH);
+ ts.setTexture(mainTexture, 0);
+ }
+
+ // Extract intensity
+ extractionShader.setUniform("exposurePow", getExposurePow());
+ extractionShader.setUniform("exposureCutoff", getExposureCutoff());
+
+ fullScreenQuad.setRenderState(extractionShader);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = extractionShader;
+ tRenderer.render(fullScreenQuad, secondTexture, Renderer.BUFFER_NONE);
+
+ if (!useSeparateConvolution) {
+ blurShader.setUniform("sampleDist", getBlurSize());
+ blurShader.setUniform("blurIntensityMultiplier", getBlurIntensityMultiplier());
+
+ ts.setTexture(secondTexture, 0);
+ fullScreenQuad.setRenderState(blurShader);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = blurShader;
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+
+ // Extra blur passes
+ for (int i = 1; i < getNrBlurPasses(); i++) {
+ blurShader.setUniform("sampleDist", getBlurSize() - i * getBlurSize() / getNrBlurPasses());
+ if (i % 2 == 1) {
+ ts.setTexture(mainTexture, 0);
+ tRenderer.render(fullScreenQuad, secondTexture, Renderer.BUFFER_NONE);
+ } else {
+ ts.setTexture(secondTexture, 0);
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+ }
+ }
+ if (getNrBlurPasses() % 2 == 1) {
+ ts.setTexture(mainTexture, 0);
+ } else {
+ ts.setTexture(secondTexture, 0);
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+ ts.setTexture(mainTexture, 0);
+ }
+ } else {
+ blurShaderVertical.setUniform("blurIntensityMultiplier", getBlurIntensityMultiplier());
+
+ for (int i = 0; i < getNrBlurPasses(); i++) {
+ blurShaderHorizontal
+ .setUniform("sampleDist", getBlurSize() - i * getBlurSize() / getNrBlurPasses());
+ blurShaderVertical.setUniform("sampleDist", getBlurSize() - i * getBlurSize() / getNrBlurPasses());
+
+ ts.setTexture(secondTexture, 0);
+ fullScreenQuad.setRenderState(blurShaderHorizontal);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = blurShaderHorizontal;
+ tRenderer.render(fullScreenQuad, mainTexture, Renderer.BUFFER_NONE);
+ ts.setTexture(mainTexture, 0);
+ fullScreenQuad.setRenderState(blurShaderVertical);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = blurShaderVertical;
+ tRenderer.render(fullScreenQuad, secondTexture, Renderer.BUFFER_NONE);
+ }
+ ts.setTexture(secondTexture, 0);
+ }
+ }
+
+ // Final blend
+ blend.setEnabled(true);
+
+ fullScreenQuad.setRenderState(finalShader);
+ fullScreenQuad.updateWorldRenderStates(false);
+ // fullScreenQuad.states[RenderState.StateType.GLSLShaderObjects.ordinal()] = finalShader;
+ r.draw((Renderable) fullScreenQuad);
+ }
+
+ private void doInit(final Renderer r) {
+ initialized = true;
+
+ cleanUp();
+
+ // Test for glsl support
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+ if (!caps.isGLSLSupported() || !(caps.isPbufferSupported() || caps.isFBOSupported())) {
+ supported = false;
+ return;
+ }
+
+ // Create texture renderers and rendertextures(alternating between two not to overwrite pbuffers)
+ final DisplaySettings settings = new DisplaySettings(cam.getWidth() / renderScale, cam.getHeight()
+ / renderScale, 24, 0, 0, 8, 0, 0, false, false);
+ tRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(settings, false, r, ContextManager
+ .getCurrentContext().getCapabilities());
+
+ if (tRenderer == null) {
+ supported = false;
+ return;
+ }
+ tRenderer.setMultipleTargets(true);
+ tRenderer.setBackgroundColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f));
+ tRenderer.getCamera().setFrustum(cam.getFrustumNear(), cam.getFrustumFar(), cam.getFrustumLeft(),
+ cam.getFrustumRight(), cam.getFrustumTop(), cam.getFrustumBottom());
+
+ mainTexture = new Texture2D();
+ mainTexture.setWrap(Texture.WrapMode.Clamp);
+ mainTexture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ tRenderer.setupTexture(mainTexture);
+
+ secondTexture = new Texture2D();
+ secondTexture.setWrap(Texture.WrapMode.Clamp);
+ secondTexture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ tRenderer.setupTexture(secondTexture);
+
+ extractionShader = new GLSLShaderObjectsState();
+ try {
+ extractionShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_extract.vert"));
+ extractionShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_extract.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ extractionShader.setUniform("RT", 0);
+
+ // Create blur shader
+ blurShader = new GLSLShaderObjectsState();
+ try {
+ blurShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_blur.vert"));
+ blurShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_blur.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShader.setUniform("RT", 0);
+
+ // Create blur shader horizontal
+ blurShaderHorizontal = new GLSLShaderObjectsState();
+ try {
+ blurShaderHorizontal.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ BloomRenderPass.class, shaderDirectory + "bloom_blur.vert"));
+ blurShaderHorizontal.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ BloomRenderPass.class, shaderDirectory + "bloom_blur_horizontal7.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShaderHorizontal.setUniform("RT", 0);
+
+ // Create blur shader vertical
+ blurShaderVertical = new GLSLShaderObjectsState();
+ try {
+ blurShaderVertical.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_blur.vert"));
+ blurShaderVertical.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ BloomRenderPass.class, shaderDirectory + "bloom_blur_vertical7.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShaderVertical.setUniform("RT", 0);
+
+ // Create final shader(basic texturing)
+ finalShader = new GLSLShaderObjectsState();
+ try {
+ finalShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_final.vert"));
+ finalShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(BloomRenderPass.class,
+ shaderDirectory + "bloom_final.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+
+ // Create fullscreen quad
+ fullScreenQuad = new Quad("FullScreenQuad", cam.getWidth() / 4, cam.getHeight() / 4);
+ fullScreenQuad.setTranslation(cam.getWidth() / 2, cam.getHeight() / 2, 0);
+ fullScreenQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ fullScreenQuad.getSceneHints().setCullHint(CullHint.Never);
+ fullScreenQuad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ fullScreenQuad.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ final TextureState ts = new TextureState();
+ ts.setEnabled(true);
+ fullScreenQuad.setRenderState(ts);
+
+ final BlendState as = new BlendState();
+ as.setBlendEnabled(true);
+ as.setSourceFunction(BlendState.SourceFunction.One);
+ as.setDestinationFunction(BlendState.DestinationFunction.One);
+ as.setEnabled(true);
+ fullScreenQuad.setRenderState(as);
+
+ fullScreenQuad.updateGeometricState(0.0f, true);
+ }
+
+ /**
+ * @return The throttle amount - or in other words, how much time in seconds must pass before the bloom effect is
+ * updated.
+ */
+ public double getThrottle() {
+ return throttle;
+ }
+
+ /**
+ * @param throttle
+ * The throttle amount - or in other words, how much time in seconds must pass before the bloom effect is
+ * updated.
+ */
+ public void setThrottle(final float throttle) {
+ this.throttle = throttle;
+ }
+
+ public float getBlurSize() {
+ return blurSize;
+ }
+
+ public void setBlurSize(final float blurSize) {
+ this.blurSize = blurSize;
+ }
+
+ public float getExposurePow() {
+ return exposurePow;
+ }
+
+ public void setExposurePow(final float exposurePow) {
+ this.exposurePow = exposurePow;
+ }
+
+ public float getExposureCutoff() {
+ return exposureCutoff;
+ }
+
+ public void setExposureCutoff(final float exposureCutoff) {
+ this.exposureCutoff = exposureCutoff;
+ }
+
+ public float getBlurIntensityMultiplier() {
+ return blurIntensityMultiplier;
+ }
+
+ public void setBlurIntensityMultiplier(final float blurIntensityMultiplier) {
+ this.blurIntensityMultiplier = blurIntensityMultiplier;
+ }
+
+ public int getNrBlurPasses() {
+ return nrBlurPasses;
+ }
+
+ public void setNrBlurPasses(final int nrBlurPasses) {
+ this.nrBlurPasses = nrBlurPasses;
+ }
+
+ public boolean useCurrentScene() {
+ return useCurrentScene;
+ }
+
+ public void setUseCurrentScene(final boolean useCurrentScene) {
+ this.useCurrentScene = useCurrentScene;
+ }
+
+ public void setUseSeparateConvolution(final boolean useSeparateConvolution) {
+ this.useSeparateConvolution = useSeparateConvolution;
+ }
+
+ public boolean isUseSeparateConvolution() {
+ return useSeparateConvolution;
+ }
+
+ public void markNeedsRefresh() {
+ initialized = false;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.java
new file mode 100644
index 0000000..56e5121
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/AnimationEntry.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public class AnimationEntry implements Savable {
+ protected double _offset = 0.05; // 5% of life from previous entry
+ protected double _rate = 0.2; // 5 fps
+ protected int[] _frames = new int[1];
+
+ public AnimationEntry() {}
+
+ public AnimationEntry(final double offset) {
+ _offset = offset;
+ }
+
+ public int[] getFrames() {
+ return _frames;
+ }
+
+ public void setFrames(final int[] frames) {
+ _frames = frames;
+ }
+
+ public double getOffset() {
+ return _offset;
+ }
+
+ public void setOffset(final double offset) {
+ _offset = offset;
+ }
+
+ public double getRate() {
+ return _rate;
+ }
+
+ public void setRate(final double rate) {
+ _rate = rate;
+ }
+
+ public Class<? extends AnimationEntry> getClassTag() {
+ return getClass();
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _offset = capsule.readDouble("offsetMS", 0.05);
+ _rate = capsule.readDouble("rate", 0.2);
+ _frames = capsule.readIntArray("frames", null);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_offset, "offsetMS", 0.05);
+ capsule.write(_rate, "rate", 0.2);
+ capsule.write(_frames, "frames", null);
+ }
+
+ private static String makeText(final int[] frames) {
+ if (frames == null || frames.length == 0) {
+ return "";
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ for (final int frame : frames) {
+ sb.append(frame);
+ sb.append(",");
+ }
+ return sb.substring(0, sb.length() - 1);
+ }
+
+ @Override
+ public String toString() {
+
+ final StringBuilder builder = new StringBuilder();
+
+ builder.append("prev+");
+ builder.append((int) (_offset * 100));
+ builder.append("% age...");
+
+ builder.append(" rate: " + _rate);
+
+ builder.append(" sequence: " + makeText(_frames));
+
+ return builder.toString();
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.java
new file mode 100644
index 0000000..5891b13
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/FloorInfluence.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyPlane;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class FloorInfluence extends ParticleInfluence {
+
+ /**
+ * Bounciness is the factor of multiplication when bouncing off the floor. A bounciness factor of 1 means the
+ * particle leaves the floor with the same velocity as it hit the floor.
+ */
+ private double _bounciness = 1;
+
+ /**
+ * Our imaginary floor
+ */
+ private final Plane _floor = new Plane();
+
+ public FloorInfluence() {}
+
+ /**
+ * @param plane
+ * The imaginary floor plane
+ * @param bounciness
+ * Bounciness is the factor of multiplication when bouncing off the floor. A bounciness factor of 1 means
+ * the ball leaves the floor with the same velocity as it hit the floor, much like a rubber ball.
+ */
+ public FloorInfluence(final ReadOnlyPlane plane, final double bounciness) {
+ _bounciness = bounciness;
+ _floor.set(plane);
+ }
+
+ @Override
+ public void apply(final double dt, final Particle particle, final int index) {
+ // Is particle alive, AND "under" our floor?
+ if (particle.getStatus() == Particle.Status.Alive && _floor.pseudoDistance(particle.getPosition()) <= 0) {
+ final Vector3 tempVect1 = Vector3.fetchTempInstance();
+ final double scale = particle.getVelocity().length();
+ tempVect1.set(particle.getVelocity()).divideLocal(scale); // normalize
+
+ // Is the particle moving further into the floor?
+ if (_floor.getNormal().smallestAngleBetween(tempVect1) > MathUtils.HALF_PI) {
+ // reflect our velocity vector across the floor plane
+ _floor.reflectVector(tempVect1, tempVect1);
+
+ // apply the "bounciness" factor
+ tempVect1.multiplyLocal(scale * _bounciness);
+
+ // write back to particle
+ particle.setVelocity(tempVect1);
+ }
+ Vector3.releaseTempInstance(tempVect1);
+ }
+ }
+
+ public double getBounciness() {
+ return _bounciness;
+ }
+
+ public void setBounciness(final double bounciness) {
+ _bounciness = bounciness;
+ }
+
+ public ReadOnlyPlane getFloor() {
+ return _floor;
+ }
+
+ public void setFloor(final ReadOnlyPlane floor) {
+
+ _floor.set(floor);
+ }
+
+ public ReadOnlyVector3 getNormal() {
+ return _floor.getNormal();
+ }
+
+ public void setNormal(final Vector3 normal) {
+ _floor.setNormal(normal.normalize(null));
+ }
+
+ public double getConstant() {
+ return _floor.getConstant();
+ }
+
+ public void setConstant(final double constant) {
+ _floor.setConstant(constant);
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<? extends FloorInfluence> getClassTag() {
+ return this.getClass();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_bounciness, "bounciness", 1.0);
+ capsule.write(_floor, "floor", new Plane());
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _bounciness = capsule.readDouble("bounciness", 1.0);
+ _floor.set((Plane) capsule.readSavable("floor", new Plane()));
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java
new file mode 100644
index 0000000..48ff5ff
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/Particle.java
@@ -0,0 +1,496 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.extension.effect.particle.ParticleSystem.ParticleType;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Triangle;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Particle</code> defines a single Particle of a Particle system. Generally, you would not interact with this
+ * class directly.
+ */
+public class Particle implements Savable {
+
+ public enum Status {
+ /** Particle is dead -- not in play. */
+ Dead,
+ /** Particle is currently active. */
+ Alive,
+ /** Particle is available for spawning. */
+ Available;
+ }
+
+ static final int VAL_CURRENT_SIZE = 0;
+ static final int VAL_CURRENT_SPIN = 1;
+ static final int VAL_CURRENT_MASS = 2;
+
+ private int startIndex;
+ private final Vector3 _position = new Vector3();
+ private final Vector3 _velocity = new Vector3();
+ private final ColorRGBA currColor = new ColorRGBA(ColorRGBA.BLACK);
+ private Status status = Status.Available;
+ private double lifeSpan;
+ private final double[] values = new double[3];
+ private int currentAge;
+ private int currentTexIndex = -1;
+ private ParticleSystem parent;
+ private final Vector3 bbX = new Vector3(), bbY = new Vector3();
+
+ // colors
+ private ParticleType type = ParticleSystem.ParticleType.Quad;
+
+ private Triangle triModel;
+
+ /**
+ * Empty constructor - mostly for use with Savable interface
+ */
+ public Particle() {}
+
+ /**
+ * Normal use constructor. Sets up the parent and particle type for this particle.
+ *
+ * @param parent
+ * the particle collection this particle belongs to
+ */
+ public Particle(final ParticleSystem parent) {
+ this.parent = parent;
+ type = parent.getParticleType();
+ }
+
+ /**
+ * Cause this particle to reset it's lifespan, velocity, color, age and size per the parent's settings. status is
+ * set to Status.Available and location is set to 0,0,0. Actual geometry data is not affected by this call, only
+ * particle params.
+ */
+ public void init() {
+ init(parent.getRandomVelocity(_velocity), Vector3.ZERO, parent.getRandomLifeSpan());
+ }
+
+ /**
+ * Cause this particle to reset it's color, age and size per the parent's settings. status is set to
+ * Status.Available. Location, velocity and lifespan are set as given. Actual geometry data is not affected by this
+ * call, only particle params.
+ *
+ * @param velocity
+ * new initial particle velocity
+ * @param position
+ * new initial particle position
+ * @param lifeSpan
+ * new particle lifespan in ms
+ */
+ public void init(final ReadOnlyVector3 velocity, final ReadOnlyVector3 position, final double lifeSpan) {
+ this.lifeSpan = lifeSpan;
+ _velocity.set(velocity);
+ _position.set(position);
+
+ currColor.set(parent.getStartColor());
+ currentAge = 0;
+ status = Status.Available;
+ values[VAL_CURRENT_SIZE] = parent.getStartSize();
+ }
+
+ /**
+ * Reset particle conditions. Besides the passed lifespan, we also reset color, size, and spin angle to their
+ * starting values (as given by parent.) Status is set to Status.Available.
+ *
+ * @param lifeSpan
+ * the recreated particle's new lifespan
+ */
+ public void recreateParticle(final double lifeSpan) {
+ this.lifeSpan = lifeSpan;
+
+ final int verts = ParticleSystem.getVertsForParticleType(type);
+ currColor.set(parent.getStartColor());
+ for (int x = 0; x < verts; x++) {
+ BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getMeshData().getColorBuffer(), startIndex
+ + x);
+ }
+ values[VAL_CURRENT_SIZE] = parent.getStartSize();
+ currentAge = 0;
+ values[VAL_CURRENT_MASS] = 1;
+ status = Status.Available;
+ }
+
+ /**
+ * Update the vertices for this particle, taking size, spin and viewer into consideration. In the case of particle
+ * type ParticleType.GeomMesh, the original triangle normal is maintained rather than rotating it to face the camera
+ * or parent vectors.
+ *
+ * @param cam
+ * Camera to use in determining viewer aspect. If null, or if parent is not set to camera facing,
+ * parent's left and up vectors are used.
+ */
+ public void updateVerts(final Camera cam) {
+ final double orient = parent.getParticleOrientation() + values[VAL_CURRENT_SPIN];
+ final double currSize = values[VAL_CURRENT_SIZE];
+
+ if (type == ParticleSystem.ParticleType.GeomMesh || type == ParticleSystem.ParticleType.Point) {
+ ; // nothing to do
+ } else if (cam != null && parent.isCameraFacing()) {
+ final ReadOnlyVector3 camUp = cam.getUp();
+ final ReadOnlyVector3 camLeft = cam.getLeft();
+ final ReadOnlyVector3 camDir = cam.getDirection();
+ if (parent.isVelocityAligned()) {
+ bbX.set(_velocity).normalizeLocal().multiplyLocal(currSize);
+ camDir.cross(bbX, bbY).normalizeLocal().multiplyLocal(currSize);
+ } else if (orient == 0) {
+ bbX.set(camLeft).multiplyLocal(currSize);
+ bbY.set(camUp).multiplyLocal(currSize);
+ } else {
+ final double cA = MathUtils.cos(orient) * currSize;
+ final double sA = MathUtils.sin(orient) * currSize;
+ bbX.set(camLeft).multiplyLocal(cA).addLocal(camUp.getX() * sA, camUp.getY() * sA, camUp.getZ() * sA);
+ bbY.set(camLeft).multiplyLocal(-sA).addLocal(camUp.getX() * cA, camUp.getY() * cA, camUp.getZ() * cA);
+ }
+ } else {
+ final ReadOnlyVector3 left = parent.getFacingLeftVector();
+ final ReadOnlyVector3 up = parent.getFacingUpVector();
+
+ if (parent.isVelocityAligned()) {
+ bbX.set(_velocity).normalizeLocal().multiplyLocal(currSize);
+ up.cross(bbX, bbY).normalizeLocal().multiplyLocal(currSize);
+ } else if (orient == 0) {
+ bbX.set(left).multiplyLocal(currSize);
+ bbY.set(up).multiplyLocal(currSize);
+ } else {
+ final double cA = MathUtils.cos(orient) * currSize;
+ final double sA = MathUtils.sin(orient) * currSize;
+ bbX.set(left).multiplyLocal(cA).addLocal(up.getX() * sA, up.getY() * sA, up.getZ() * sA);
+ bbY.set(left).multiplyLocal(-sA).addLocal(up.getX() * cA, up.getY() * cA, up.getZ() * cA);
+ }
+ }
+
+ final Vector3 tempVec3 = Vector3.fetchTempInstance();
+ final FloatBuffer vertexBuffer = parent.getParticleGeometry().getMeshData().getVertexBuffer();
+ switch (type) {
+ case Quad: {
+ _position.subtract(bbX, tempVec3).subtractLocal(bbY);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 0);
+
+ _position.subtract(bbX, tempVec3).addLocal(bbY);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1);
+
+ _position.add(bbX, tempVec3).addLocal(bbY);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 2);
+
+ _position.add(bbX, tempVec3).subtractLocal(bbY);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 3);
+ break;
+ }
+ case GeomMesh: {
+ final Quaternion tempQuat = Quaternion.fetchTempInstance();
+ final ReadOnlyVector3 norm = triModel.getNormal();
+ if (orient != 0) {
+ tempQuat.fromAngleNormalAxis(orient, norm);
+ }
+
+ for (int x = 0; x < 3; x++) {
+ if (orient != 0) {
+ tempQuat.apply(triModel.get(x), tempVec3);
+ } else {
+ tempVec3.set(triModel.get(x));
+ }
+ tempVec3.multiplyLocal(currSize).addLocal(_position);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + x);
+ }
+ Quaternion.releaseTempInstance(tempQuat);
+ break;
+ }
+ case Triangle: {
+ _position.subtract(3 * bbX.getX(), 3 * bbX.getY(), 3 * bbX.getZ(), tempVec3).subtractLocal(bbY);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 0);
+
+ _position.add(bbX, tempVec3).addLocal(3 * bbY.getX(), 3 * bbY.getY(), 3 * bbY.getZ());
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1);
+
+ _position.add(bbX, tempVec3).subtractLocal(bbY);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 2);
+ break;
+ }
+ case Line: {
+ _position.subtract(bbX, tempVec3);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex);
+
+ _position.add(bbX, tempVec3);
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1);
+ break;
+ }
+ case Point: {
+ BufferUtils.setInBuffer(_position, vertexBuffer, startIndex);
+ break;
+ }
+ }
+ Vector3.releaseTempInstance(tempVec3);
+ }
+
+ /**
+ * <p>
+ * update position (using current position and velocity), color (interpolating between start and end color), size
+ * (interpolating between start and end size), spin (using parent's spin speed) and current age of particle. If this
+ * particle's age is greater than its lifespan, it is set to status DEAD.
+ * </p>
+ * <p>
+ * Note that this only changes the parameters of the Particle, not the geometry the particle is associated with.
+ * </p>
+ *
+ * @param secondsPassed
+ * number of seconds passed since last update.
+ * @return true if this particle is not ALIVE (in other words, if it is ready to be reused.)
+ */
+ public boolean updateAndCheck(final double secondsPassed) {
+ if (status != Status.Alive) {
+ return true;
+ }
+ currentAge += secondsPassed * 1000; // add ms time to age
+ if (currentAge > lifeSpan) {
+ killParticle();
+ return true;
+ }
+
+ final Vector3 temp = Vector3.fetchTempInstance();
+ _position.addLocal(_velocity.multiply(secondsPassed * 1000f, temp));
+ Vector3.releaseTempInstance(temp);
+
+ // get interpolated values from appearance ramp:
+ parent.getRamp().getValuesAtAge(currentAge, lifeSpan, currColor, values, parent);
+
+ // interpolate colors
+ final int verts = ParticleSystem.getVertsForParticleType(type);
+ for (int x = 0; x < verts; x++) {
+ BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getMeshData().getColorBuffer(), startIndex
+ + x);
+ }
+
+ // check for tex animation
+ final int newTexIndex = parent.getTexAnimation().getTexIndexAtAge(currentAge, lifeSpan, parent);
+ // Update tex coords if applicable
+ if (currentTexIndex != newTexIndex) {
+ // Only supported in Quad type for now.
+ if (ParticleType.Quad.equals(parent.getParticleType())) {
+ // determine side
+ final float side = (float) Math.sqrt(parent.getTexQuantity());
+ int index = newTexIndex;
+ if (index >= parent.getTexQuantity()) {
+ index %= parent.getTexQuantity();
+ }
+ // figure row / col
+ final float row = side - (int) (index / side) - 1;
+ final float col = index % side;
+ // set texcoords
+ final float sU = col / side, eU = (col + 1) / side;
+ final float sV = row / side, eV = (row + 1) / side;
+ final FloatBuffer texs = parent.getParticleGeometry().getMeshData().getTextureCoords(0).getBuffer();
+ texs.position(startIndex * 2);
+ texs.put(eU).put(sV);
+ texs.put(eU).put(eV);
+ texs.put(sU).put(eV);
+ texs.put(sU).put(sV);
+ texs.clear();
+ }
+ currentTexIndex = newTexIndex;
+ }
+
+ return false;
+ }
+
+ public void killParticle() {
+ setStatus(Status.Dead);
+
+ final Vector3 tempVec3 = Vector3.fetchTempInstance();
+ final FloatBuffer vertexBuffer = parent.getParticleGeometry().getMeshData().getVertexBuffer();
+ BufferUtils.populateFromBuffer(tempVec3, vertexBuffer, startIndex);
+ final int verts = ParticleSystem.getVertsForParticleType(type);
+ for (int x = 1; x < verts; x++) {
+ BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + x);
+ }
+ Vector3.releaseTempInstance(tempVec3);
+
+ }
+
+ /**
+ * Resets current age to 0
+ */
+ public void resetAge() {
+ currentAge = 0;
+ }
+
+ /**
+ * @return the current age of the particle in ms
+ */
+ public int getCurrentAge() {
+ return currentAge;
+ }
+
+ /**
+ * @return the current position of the particle in space
+ */
+ public Vector3 getPosition() {
+ return _position;
+ }
+
+ /**
+ * Set the position of the particle in space.
+ *
+ * @param position
+ * the new position in world coordinates
+ */
+ public void setPosition(final Vector3 position) {
+ _position.set(position);
+ }
+
+ /**
+ * @return the current status of this particle.
+ * @see Status
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * Set the status of this particle.
+ *
+ * @param status
+ * new status of this particle
+ * @see Status
+ */
+ public void setStatus(final Status status) {
+ this.status = status;
+ }
+
+ /**
+ * @return the current velocity of this particle
+ */
+ public Vector3 getVelocity() {
+ return _velocity;
+ }
+
+ /**
+ * Set the current velocity of this particle
+ *
+ * @param velocity
+ * the new velocity
+ */
+ public void setVelocity(final Vector3 velocity) {
+ _velocity.set(velocity);
+ }
+
+ /**
+ * @return the current color applied to this particle
+ */
+ public ColorRGBA getCurrentColor() {
+ return currColor;
+ }
+
+ /**
+ * @return the start index of this particle in relation to where it exists in its parent's geometry data.
+ */
+ public int getStartIndex() {
+ return startIndex;
+ }
+
+ /**
+ * Set the starting index where this particle is represented in its parent's geometry data
+ *
+ * @param index
+ */
+ public void setStartIndex(final int index) {
+ startIndex = index;
+ }
+
+ /**
+ * @return the mass of this particle. Only used by ParticleInfluences such as drag.
+ */
+ public double getMass() {
+ return values[VAL_CURRENT_MASS];
+ }
+
+ /**
+ * @return the inverse mass of this particle. Often useful for skipping constant division by mass calculations. If
+ * the mass is 0, the inverse mass is considered to be positive infinity. Conversely, if the mass is
+ * positive infinity, the inverse is 0. The inverse of negative infinity is considered to be -0.
+ */
+ public double getInvMass() {
+ final double mass = values[VAL_CURRENT_MASS];
+ if (mass == 0) {
+ return Float.POSITIVE_INFINITY;
+ } else if (mass == Float.POSITIVE_INFINITY) {
+ return 0;
+ } else if (mass == Float.NEGATIVE_INFINITY) {
+ return -0;
+ } else {
+ return 1f / mass;
+ }
+ }
+
+ /**
+ * Sets a triangle model to use for particle calculations when using particle type ParticleType.GeomMesh. The
+ * particle will maintain the triangle's ratio and plane of orientation. It will spin (if applicable) around the
+ * triangle's normal axis. The triangle should already have its center and normal fields calculated before calling
+ * this method.
+ *
+ * @param t
+ * the triangle to model this particle after.
+ */
+ public void setTriangleModel(final Triangle t) {
+ triModel = t;
+ }
+
+ /**
+ * @return the triangle model used by this particle
+ * @see #setTriangleModel(Triangle)
+ */
+ public Triangle getTriangleModel() {
+ return triModel;
+ }
+
+ // /////
+ // Savable interface methods
+ // /////
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(startIndex, "startIndex", 0);
+ capsule.write(_position, "position", new Vector3(Vector3.ZERO));
+ capsule.write(status, "status", Status.Available);
+ capsule.write(lifeSpan, "lifeSpan", 0);
+ capsule.write(currentAge, "currentAge", 0);
+ capsule.write(parent, "parent", null);
+ capsule.write(_velocity, "velocity", new Vector3());
+ capsule.write(type, "type", ParticleSystem.ParticleType.Quad);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ startIndex = capsule.readInt("startIndex", 0);
+ _position.set((Vector3) capsule.readSavable("position", new Vector3(Vector3.ZERO)));
+ status = capsule.readEnum("status", Status.class, Status.Available);
+ lifeSpan = capsule.readDouble("lifeSpan", 0);
+ currentAge = capsule.readInt("currentAge", 0);
+ parent = (ParticleSystem) capsule.readSavable("parent", null);
+ _velocity.set((Vector3) capsule.readSavable("velocity", new Vector3()));
+ type = capsule.readEnum("type", ParticleSystem.ParticleType.class, ParticleSystem.ParticleType.Quad);
+ }
+
+ public Class<? extends Particle> getClassTag() {
+ return this.getClass();
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java
new file mode 100644
index 0000000..c55f504
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleAppearanceRamp.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public class ParticleAppearanceRamp implements Savable {
+
+ protected List<RampEntry> _entries = new ArrayList<RampEntry>();
+
+ public void addEntry(final RampEntry entry) {
+ _entries.add(entry);
+ }
+
+ public void addEntry(final int index, final RampEntry entry) {
+ _entries.add(index, entry);
+ }
+
+ public void clearEntries() {
+ _entries.clear();
+ }
+
+ public Iterator<RampEntry> getEntries() {
+ return _entries.iterator();
+ }
+
+ public void removeEntry(final RampEntry entry) {
+ _entries.remove(entry);
+ }
+
+ public void removeEntry(final int index) {
+ _entries.remove(index);
+ }
+
+ public void getValuesAtAge(final double age, final double maxAge, final ColorRGBA store, final double[] fStore,
+ final ParticleSystem particles) {
+ double prevCAge = 0, prevMAge = 0, prevSiAge = 0, prevSpAge = 0;
+ double nextCAge = maxAge, nextMAge = maxAge, nextSiAge = maxAge, nextSpAge = maxAge;
+ double trAge = 0;
+ RampEntry prevCEntry = null, prevMEntry = null, prevSiEntry = null, prevSpEntry = null;
+ RampEntry nextCEntry = null, nextMEntry = null, nextSiEntry = null, nextSpEntry = null;
+ for (int i = 0; i < _entries.size(); i++) {
+ final RampEntry entry = _entries.get(i);
+ trAge += entry.getOffset() * maxAge;
+ // Color
+ if (nextCEntry == null) {
+ if (trAge > age) {
+ if (entry.hasColorSet()) {
+ nextCAge = trAge;
+ nextCEntry = entry;
+ }
+ } else {
+ if (entry.hasColorSet()) {
+ prevCAge = trAge;
+ prevCEntry = entry;
+ }
+ }
+ }
+
+ // mass
+ if (nextMEntry == null) {
+ if (trAge > age) {
+ if (entry.hasMassSet()) {
+ nextMAge = trAge;
+ nextMEntry = entry;
+ }
+ } else {
+ if (entry.hasMassSet()) {
+ prevMAge = trAge;
+ prevMEntry = entry;
+ }
+ }
+ }
+
+ // size
+ if (nextSiEntry == null) {
+ if (trAge > age) {
+ if (entry.hasSizeSet()) {
+ nextSiAge = trAge;
+ nextSiEntry = entry;
+ }
+ } else {
+ if (entry.hasSizeSet()) {
+ prevSiAge = trAge;
+ prevSiEntry = entry;
+ }
+ }
+ }
+
+ // spin
+ if (nextSpEntry == null) {
+ if (trAge > age) {
+ if (entry.hasSpinSet()) {
+ nextSpAge = trAge;
+ nextSpEntry = entry;
+ }
+ } else {
+ if (entry.hasSpinSet()) {
+ prevSpAge = trAge;
+ prevSpEntry = entry;
+ }
+ }
+ }
+
+ }
+
+ // color
+ {
+ final float lifeCRatio = (float) ((age - prevCAge) / (nextCAge - prevCAge));
+ final ReadOnlyColorRGBA start = prevCEntry != null ? prevCEntry.getColor() : particles.getStartColor();
+ final ReadOnlyColorRGBA end = nextCEntry != null ? nextCEntry.getColor() : particles.getEndColor();
+ ColorRGBA.lerp(start, end, lifeCRatio, store);
+ }
+
+ // mass
+ {
+ final double lifeMRatio = (age - prevMAge) / (nextMAge - prevMAge);
+ final double start = prevMEntry != null ? prevMEntry.getMass() : particles.getStartMass();
+ final double end = nextMEntry != null ? nextMEntry.getMass() : particles.getEndMass();
+ fStore[Particle.VAL_CURRENT_MASS] = (1 - lifeMRatio) * start + lifeMRatio * end;
+ }
+
+ // Size
+ {
+ final double lifeSiRatio = (age - prevSiAge) / (nextSiAge - prevSiAge);
+ final double start = prevSiEntry != null ? prevSiEntry.getSize() : particles.getStartSize();
+ final double end = nextSiEntry != null ? nextSiEntry.getSize() : particles.getEndSize();
+ fStore[Particle.VAL_CURRENT_SIZE] = (1 - lifeSiRatio) * start + lifeSiRatio * end;
+ }
+
+ // Spin
+ {
+ final double lifeSpRatio = (age - prevSpAge) / (nextSpAge - prevSpAge);
+ final double start = prevSpEntry != null ? prevSpEntry.getSpin() : particles.getStartSpin();
+ final double end = nextSpEntry != null ? nextSpEntry.getSpin() : particles.getEndSpin();
+ fStore[Particle.VAL_CURRENT_SPIN] = (1 - lifeSpRatio) * start + lifeSpRatio * end;
+ }
+ }
+
+ public Class<? extends ParticleAppearanceRamp> getClassTag() {
+ return getClass();
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _entries = capsule.readSavableList("entries", null);
+ if (_entries == null) {
+ _entries = new ArrayList<RampEntry>();
+ }
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.writeSavableList(_entries, "entries", null);
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java
new file mode 100644
index 0000000..f91e9c0
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleController.java
@@ -0,0 +1,517 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Camera.FrustumIntersect;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.controller.ComplexSpatialController;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>ParticleController</code> controls and maintains the parameters of a particle system over time.
+ */
+public class ParticleController extends ComplexSpatialController<ParticleSystem> {
+
+ private static final long serialVersionUID = 1L;
+
+ private int _particlesToCreate = 0;
+ private double _releaseVariance;
+ private double _currentTime;
+ private double _prevTime;
+ private double _releaseParticles;
+ private double _timePassed;
+ private double _precision;
+ private boolean _controlFlow;
+ private boolean _updateOnlyInView;
+ private Camera _viewCamera;
+
+ private int iterations;
+ private List<ParticleInfluence> influences;
+ protected List<ParticleControllerListener> listeners;
+
+ /**
+ * ParticleController constructor
+ */
+ public ParticleController() {
+
+ setMinTime(0);
+ setMaxTime(Float.MAX_VALUE);
+ setRepeatType(RepeatType.WRAP);
+ setSpeed(1.0f);
+
+ _releaseVariance = 0;
+ _controlFlow = false;
+ _updateOnlyInView = false;
+ _precision = .01f; // 10ms
+ }
+
+ protected boolean _ignoreOneUpdate = false;
+
+ protected void ignoreNextUpdate() {
+ _ignoreOneUpdate = true;
+ }
+
+ /**
+ * Update the particles managed by this manager. If any particles are "dead" recreate them at the origin position
+ * (which may be a point, line or rectangle.)
+ *
+ * @param secondsPassed
+ * double precision time
+ * @param particles
+ * the particles we are updating
+ */
+ @Override
+ public void update(final double secondsPassed, final ParticleSystem particles) {
+
+ if (_ignoreOneUpdate) {
+ _ignoreOneUpdate = false;
+ return;
+ }
+
+ // If instructed, check to see if our last frustum check passed
+ if (isUpdateOnlyInView()) {
+ final Camera cam = _viewCamera != null ? _viewCamera : ContextManager.getCurrentContext()
+ .getCurrentCamera();
+ if (cam != null) {
+ final int state = cam.getPlaneState();
+ final boolean out = cam.contains(particles.getWorldBound()).equals(FrustumIntersect.Outside);
+ cam.setPlaneState(state);
+ if (out) {
+ return;
+ }
+ }
+ }
+
+ // Add time and unless we have more than precision time passed
+ // since last real update, do nothing
+ _currentTime += secondsPassed * getSpeed();
+
+ // Check precision passes
+ _timePassed = _currentTime - _prevTime;
+ if (_timePassed < _precision * getSpeed()) {
+ return;
+ }
+
+ // We are actually going to do a real update,
+ // so this is our new previous time
+ _prevTime = _currentTime;
+
+ // Update the current rotation matrix if needed.
+ particles.updateRotationMatrix();
+
+ // If we are in the time window where this controller is active
+ // (defaults to 0 to Float.MAX_VALUE for ParticleController)
+ if (_currentTime >= getMinTime() && _currentTime <= getMaxTime()) {
+
+ // If we are controlling the flow (ie the rate of particle spawning.)
+ if (_controlFlow) {
+ // Release a number of particles based on release rate,
+ // timePassed (already scaled for speed) and variance. This
+ // is added to any current value Note this is a double value,
+ // so we will keep adding up partial particles
+
+ _releaseParticles += (particles.getReleaseRate() * _timePassed * (1.0 + _releaseVariance
+ * (MathUtils.nextRandomFloat() - 0.5)));
+
+ // Try to create all "whole" particles we have added up
+ _particlesToCreate = (int) _releaseParticles;
+
+ // If we have any whole particles, then subtract them from
+ // releaseParticles
+ if (_particlesToCreate > 0) {
+ _releaseParticles -= _particlesToCreate;
+ } else {
+ _particlesToCreate = 0;
+ }
+ }
+
+ particles.updateInvScale();
+
+ // If we have any influences, prepare them all
+ if (influences != null) {
+ for (int x = 0; x < influences.size(); x++) {
+ final ParticleInfluence inf = influences.get(x);
+ inf.prepare(particles);
+ }
+ }
+
+ // Track particle index
+ int i = 0;
+
+ // Track whether the whole set of particles is "dead" - if any
+ // particles are still alive, this will be set to false
+ boolean dead = true;
+
+ // opposite of above boolean, but tracked separately
+ boolean anyAlive = false;
+
+ // i is index through all particles
+ while (i < particles.getNumParticles()) {
+ // Current particle
+ final Particle p = particles.getParticle(i);
+
+ // If we have influences and particle is alive
+ if (influences != null && p.getStatus() == Particle.Status.Alive) {
+ // Apply each enabled influence to the current particle
+ for (int x = 0; x < influences.size(); x++) {
+ final ParticleInfluence inf = influences.get(x);
+ if (inf.isEnabled()) {
+ inf.apply(_timePassed, p, i);
+ }
+ }
+ }
+
+ // Update and check the particle.
+ // If this returns true, indicating particle is ready to be
+ // reused, we may reuse it. Do so if we are not using
+ // control flow, OR we intend to create particles based on
+ // control flow count calculated above
+ final boolean reuse = p.updateAndCheck(_timePassed);
+ if (reuse && (!_controlFlow || _particlesToCreate > 0)) {
+
+ // Don't recreate the particle if it is dead, and we are clamped
+ if (p.getStatus() == Particle.Status.Dead && getRepeatType() == RepeatType.CLAMP) {
+ ;
+
+ // We plan to reuse the particle
+ } else {
+ // Not all particles are dead (this one will be reused)
+ dead = false;
+
+ // If we are doing flow control, decrement
+ // particlesToCreate, since we are about to create
+ // one
+ if (_controlFlow) {
+ _particlesToCreate--;
+ }
+
+ // Recreate the particle
+ p.recreateParticle(particles.getRandomLifeSpan());
+ p.setStatus(Particle.Status.Alive);
+ particles.initParticleLocation(i);
+ particles.resetParticleVelocity(i);
+ p.updateVerts(null);
+ }
+
+ } else if (!reuse || (_controlFlow && particles.getReleaseRate() > 0)) {
+ // The particle wasn't dead, or we expect more particles
+ // later, so we're not dead!
+ dead = false;
+ }
+
+ // Check for living particles so we know when to update our boundings.
+ if (p.getStatus() == Particle.Status.Alive) {
+ anyAlive = true;
+ }
+
+ // Next particle
+ i++;
+ }
+
+ // If we are dead, deactivate and tell our listeners
+ if (dead) {
+ setActive(false);
+ if (listeners != null && listeners.size() > 0) {
+ for (final ParticleControllerListener listener : listeners) {
+ listener.onDead(particles);
+ }
+ }
+ } else {
+ // if not dead make sure our particles refresh their vbos, etc.
+ final MeshData md = particles.getParticleGeometry().getMeshData();
+ md.getVertexCoords().setNeedsRefresh(true);
+ md.getColorCoords().setNeedsRefresh(true);
+ md.getTextureCoords(0).setNeedsRefresh(true);
+ }
+
+ // If we have any live particles and are offscreen, update it
+ if (anyAlive) {
+ boolean updateMB = true;
+ final Camera cam = _viewCamera != null ? _viewCamera
+ : (ContextManager.getCurrentContext() != null ? ContextManager.getCurrentContext()
+ .getCurrentCamera() : null);
+ if (cam != null) {
+ final int state = cam.getPlaneState();
+ updateMB = cam.contains(particles.getWorldBound()).equals(FrustumIntersect.Outside);
+ cam.setPlaneState(state);
+ }
+ if (updateMB) {
+ particles.getParticleGeometry().updateModelBound();
+ particles.updateWorldBoundManually();
+ }
+ }
+ }
+ }
+
+ /**
+ * Get how soon after the last update the manager will send updates to the particles.
+ *
+ * @return The precision.
+ */
+ public double getPrecision() {
+ return _precision;
+ }
+
+ /**
+ * Set how soon after the last update the manager will send updates to the particles. Defaults to .01f (10ms)<br>
+ * <br>
+ * This means that if an update is called every 2ms (e.g. running at 500 FPS) the particles position and stats will
+ * be updated every fifth frame with the elapsed time (in this case, 10ms) since previous update.
+ *
+ * @param precision
+ * in seconds
+ */
+ public void setPrecision(final double precision) {
+ _precision = precision;
+ }
+
+ /**
+ * Get the variance possible on the release rate. 0.0f = no variance 0.5f = between releaseRate / 2f and 1.5f *
+ * releaseRate
+ *
+ * @return release variance as a percent.
+ */
+ public double getReleaseVariance() {
+ return _releaseVariance;
+ }
+
+ /**
+ * Set the variance possible on the release rate.
+ *
+ * @param variance
+ * release rate +/- variance as a percent (eg. .5 = 50%)
+ */
+ public void setReleaseVariance(final double variance) {
+ _releaseVariance = variance;
+ }
+
+ /**
+ * Does this manager regulate the particle flow?
+ *
+ * @return true if this manager regulates how many particles per sec are emitted.
+ */
+ public boolean isControlFlow() {
+ return _controlFlow;
+ }
+
+ /**
+ * Set the regulate flow property on the manager.
+ *
+ * @param regulate
+ * regulate particle flow.
+ */
+ public void setControlFlow(final boolean regulate) {
+ _controlFlow = regulate;
+ }
+
+ /**
+ * Does this manager use the particle's bounding volume to limit updates?
+ *
+ * @return true if this manager only updates the particles when they are in view.
+ */
+ public boolean isUpdateOnlyInView() {
+ return _updateOnlyInView;
+ }
+
+ /**
+ * Set the updateOnlyInView property on the manager.
+ *
+ * @param updateOnlyInView
+ * use the particle's bounding volume to limit updates.
+ */
+ public void setUpdateOnlyInView(final boolean updateOnlyInView) {
+ _updateOnlyInView = updateOnlyInView;
+ }
+
+ /**
+ * @return the camera to be used in updateOnlyInView situations. If null, the current displaySystem's renderer
+ * camera is used.
+ */
+ public Camera getViewCamera() {
+ return _viewCamera;
+ }
+
+ /**
+ * @param viewCamera
+ * sets the camera to be used in updateOnlyInView situations. If null, the current displaySystem's
+ * renderer camera is used.
+ */
+ public void setViewCamera(final Camera viewCamera) {
+ _viewCamera = viewCamera;
+ }
+
+ /**
+ * Return the number this manager has warmed up
+ *
+ * @return int
+ */
+ public int getIterations() {
+ return iterations;
+ }
+
+ /**
+ * Sets the iterations for the warmup and calls warmUp with the number of iterations as the argument
+ *
+ * @param iterations
+ */
+ public void setIterations(final int iterations) {
+ this.iterations = iterations;
+ }
+
+ /**
+ * Add an external influence to this particle controller.
+ *
+ * @param influence
+ * ParticleInfluence
+ */
+ public void addInfluence(final ParticleInfluence influence) {
+ if (influences == null) {
+ influences = new ArrayList<ParticleInfluence>(1);
+ }
+ influences.add(influence);
+ }
+
+ /**
+ * Remove an influence from this particle controller.
+ *
+ * @param influence
+ * ParticleInfluence
+ * @return true if found and removed.
+ */
+ public boolean removeInfluence(final ParticleInfluence influence) {
+ if (influences == null) {
+ return false;
+ }
+ return influences.remove(influence);
+ }
+
+ /**
+ * Returns the list of influences acting on this particle controller.
+ *
+ * @return ArrayList
+ */
+ public List<ParticleInfluence> getInfluences() {
+ return influences;
+ }
+
+ public void clearInfluences() {
+ if (influences != null) {
+ influences.clear();
+ }
+ }
+
+ /**
+ * Subscribe a listener to receive mouse events. Enable event generation.
+ *
+ * @param listener
+ * to be subscribed
+ */
+ public void addListener(final ParticleControllerListener listener) {
+ if (listeners == null) {
+ listeners = new ArrayList<ParticleControllerListener>();
+ }
+
+ listeners.add(listener);
+ }
+
+ /**
+ * Unsubscribe a listener. Disable event generation if no more listeners.
+ *
+ * @param listener
+ * to be unsuscribed
+ * @see #addListener(ParticleControllerListener)
+ */
+ public void removeListener(final ParticleControllerListener listener) {
+ if (listeners != null) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Remove all listeners and disable event generation.
+ */
+ public void removeListeners() {
+ if (listeners != null) {
+ listeners.clear();
+ }
+ }
+
+ /**
+ * Check if a listener is allready added to this ParticleController
+ *
+ * @param listener
+ * listener to check for
+ * @return true if listener is contained in the listenerlist
+ */
+ public boolean containsListener(final ParticleControllerListener listener) {
+ if (listeners != null) {
+ return listeners.contains(listener);
+ }
+ return false;
+ }
+
+ /**
+ * Get all added ParticleController listeners
+ *
+ * @return ArrayList of listeners added to this ParticleController
+ */
+ public List<ParticleControllerListener> getListeners() {
+ return listeners;
+ }
+
+ /**
+ * Runs the update method of this particle manager X number of times passing .1 seconds for each call. This is used
+ * to "warm up" and get the particle manager going.
+ *
+ * @param iterations
+ * The number of iterations to warm up.
+ */
+ public void warmUp(int iterations, final ParticleSystem particles) {
+ // first set the initial positions of all the verts
+ particles.initAllParticlesLocation();
+
+ iterations *= 10;
+ for (int i = iterations; --i >= 0;) {
+ particles.updateGeometricState(0.1, false);
+ }
+ ignoreNextUpdate();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_releaseVariance, "releaseVariance", 0);
+ capsule.write(_precision, "precision", 0);
+ capsule.write(_controlFlow, "controlFlow", false);
+ capsule.write(_updateOnlyInView, "updateOnlyInView", false);
+ capsule.write(iterations, "iterations", 0);
+ capsule.writeSavableList(influences, "influences", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _releaseVariance = capsule.readDouble("releaseVariance", 0);
+ _precision = capsule.readDouble("precision", 0);
+ _controlFlow = capsule.readBoolean("controlFlow", false);
+ _updateOnlyInView = capsule.readBoolean("updateOnlyInView", false);
+ iterations = capsule.readInt("iterations", 0);
+ influences = capsule.readSavableList("influences", null);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.java
new file mode 100644
index 0000000..1b5b40b
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleControllerListener.java
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+/**
+ * ParticleControllerListener This interface is used to receive key events from ParticleController
+ */
+public interface ParticleControllerListener {
+ void onDead(ParticleSystem particles);
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.java
new file mode 100644
index 0000000..d212e6b
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleFactory.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import com.ardor3d.extension.effect.particle.ParticleSystem.ParticleType;
+import com.ardor3d.scenegraph.Mesh;
+
+public class ParticleFactory {
+
+ public static ParticleSystem buildParticles(final String name, final int number) {
+ return buildParticles(name, number, ParticleSystem.ParticleType.Quad);
+ }
+
+ public static ParticleSystem buildParticles(final String name, final int number,
+ final ParticleSystem.ParticleType particleType) {
+ if (particleType == ParticleSystem.ParticleType.GeomMesh) {
+ throw new IllegalArgumentException("particleType can not be GeomMesh");
+ }
+ ParticleSystem system;
+ if (particleType == ParticleType.Point) {
+ system = new ParticlePoints(name, number);
+ } else if (particleType == ParticleType.Line) {
+ system = new ParticleLines(name, number);
+ } else {
+ system = new ParticleMesh(name, number, particleType);
+ }
+ final ParticleController particleController = new ParticleController();
+ system.addController(particleController);
+ return system;
+ }
+
+ public static ParticleMesh buildMeshParticles(final String name, final Mesh mesh) {
+ final ParticleMesh particleMesh = new ParticleMesh(name, mesh);
+ final ParticleController particleController = new ParticleController();
+ particleMesh.addController(particleController);
+ return particleMesh;
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.java
new file mode 100644
index 0000000..3cd6906
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleInfluence.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * <code>ParticleInfluence</code> is an abstract class defining an external influence to be used with the ParticleMesh
+ * class.
+ */
+public abstract class ParticleInfluence implements Savable {
+
+ /**
+ * Is this influence enabled? ie, should it be used when updating particles.
+ */
+ private boolean _enabled = true;
+
+ /**
+ * Set this influence enabled or not.
+ *
+ * @param enabled
+ * boolean
+ */
+ public void setEnabled(final boolean enabled) {
+ _enabled = enabled;
+ }
+
+ /**
+ * Return whether or not this influence is enabled.
+ *
+ * @return boolean
+ */
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ /**
+ * Gives the influence a chance to perform any necessary initialization immediately before {@link #apply} is called
+ * on each particle for the current frame.
+ *
+ * @param particleGeom
+ * the particle system containing the influence
+ */
+ public void prepare(final ParticleSystem particleGeom) {}
+
+ /**
+ * Apply the influence defined by this class on a given particle. Should probably do this by making a call to
+ * <i>particle.getSpeed().addLocal(....);</i> etc.
+ *
+ * @param dt
+ * amount of time since last apply call in ms.
+ * @param particle
+ * the particle to apply the influence to.
+ * @param index
+ * the index of the particle we are working with. This is useful for adding small steady amounts of
+ * variation, or remembering information.
+ */
+ public abstract void apply(double dt, Particle particle, int index);
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_enabled, "enabled", true);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _enabled = capsule.readBoolean("enabled", true);
+ }
+
+ public Class<? extends ParticleInfluence> getClassTag() {
+ return this.getClass();
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.java
new file mode 100644
index 0000000..5bc25a5
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleLines.java
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * ParticleLines is a particle system that uses Line as its underlying geometric data.
+ */
+public class ParticleLines extends ParticleSystem {
+
+ public ParticleLines() {}
+
+ public ParticleLines(final String name, final int numParticles) {
+ super(name, numParticles);
+ }
+
+ @Override
+ protected void initializeParticles(final int numParticles) {
+
+ // setup texture coords
+ final Vector2[] sharedTextureData = new Vector2[] { new Vector2(0.0, 0.0), new Vector2(1.0, 1.0) };
+
+ final int verts = getVertsForParticleType(getParticleType());
+
+ _geometryCoordinates = BufferUtils.createVector3Buffer(numParticles * verts);
+
+ // setup indices for PT_LINES
+
+ _appearanceColors = BufferUtils.createColorBuffer(numParticles * verts);
+ _particles = new Particle[numParticles];
+
+ if (_particleMesh != null) {
+ detachChild(_particleMesh);
+ }
+ final Line line = new Line(getName() + "_lines") {
+
+ @Override
+ public void updateWorldTransform(final boolean recurse) {
+ ; // Do nothing.
+ }
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ super.updateWorldTransform(recurse);
+ super.updateWorldBound(recurse);
+ }
+ };
+ _particleMesh = line;
+ attachChild(line);
+ line.getMeshData().setVertexBuffer(_geometryCoordinates);
+ line.getMeshData().setColorBuffer(_appearanceColors);
+ line.getMeshData().setTextureBuffer(BufferUtils.createVector2Buffer(numParticles * 2), 0);
+ getSceneHints().setRenderBucketType(RenderBucketType.Opaque);
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ for (int k = 0; k < numParticles; k++) {
+ _particles[k] = new Particle(this);
+ _particles[k].init();
+ _particles[k].setStartIndex(k * verts);
+ for (int a = verts - 1; a >= 0; a--) {
+ final int ind = (k * verts) + a;
+ BufferUtils.setInBuffer(sharedTextureData[a], line.getMeshData().getTextureCoords(0).getBuffer(), ind);
+ BufferUtils.setInBuffer(_particles[k].getCurrentColor(), _appearanceColors, (ind));
+ }
+
+ }
+ updateWorldRenderStates(true);
+ _particleMesh.getSceneHints().setCastsShadows(false);
+ }
+
+ @Override
+ public ParticleType getParticleType() {
+ return ParticleSystem.ParticleType.Line;
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ final Camera camera = Camera.getCurrentCamera();
+ boolean anyAlive = false;
+ for (int i = 0; i < _particles.length; i++) {
+ final Particle particle = _particles[i];
+ if (particle.getStatus() == Particle.Status.Alive) {
+ particle.updateVerts(camera);
+ anyAlive = true;
+ }
+ }
+
+ // Since we've updated our verts, update the model boundary where applicable
+ if (getParticleGeometry().getWorldBound() != null && anyAlive) {
+ getParticleGeometry().updateModelBound();
+ }
+
+ if (!_particlesInWorldCoords) {
+ getParticleGeometry().setWorldTransform(getWorldTransform());
+ } else {
+ getParticleGeometry().setWorldTranslation(Vector3.ZERO);
+ getParticleGeometry().setWorldRotation(Matrix3.IDENTITY);
+ getParticleGeometry().setWorldScale(getWorldScale());
+ }
+
+ getParticleGeometry().draw(r);
+ }
+
+ @Override
+ public Line getParticleGeometry() {
+ return (Line) _particleMesh;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java
new file mode 100644
index 0000000..c700471
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleMesh.java
@@ -0,0 +1,219 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.extension.effect.particle.emitter.MeshEmitter;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * ParticleMesh is a particle system that uses Mesh as its underlying geometric data.
+ */
+public class ParticleMesh extends ParticleSystem {
+
+ private boolean _useMeshTexCoords = true;
+ private boolean _useTriangleNormalEmit = true;
+
+ public ParticleMesh() {}
+
+ public ParticleMesh(final String name, final int numParticles) {
+ super(name, numParticles);
+ getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ }
+
+ public ParticleMesh(final String name, final int numParticles, final ParticleSystem.ParticleType type) {
+ super(name, numParticles, type);
+ getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ }
+
+ public ParticleMesh(final String name, final Mesh sourceMesh) {
+ super(name, 0, ParticleSystem.ParticleType.GeomMesh);
+ _numParticles = sourceMesh.getMeshData().getTotalPrimitiveCount();
+ setParticleEmitter(new MeshEmitter(sourceMesh, false));
+ getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ initializeParticles(_numParticles);
+ }
+
+ @Override
+ protected void initializeParticles(final int numParticles) {
+
+ if (_particleMesh != null) {
+ detachChild(_particleMesh);
+ }
+ final Mesh mesh = new Mesh(getName() + "_mesh") {
+
+ @Override
+ public void updateWorldTransform(final boolean recurse) {
+ ; // Do nothing.
+ }
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ super.updateWorldTransform(recurse);
+ super.updateWorldBound(recurse);
+ }
+ };
+ _particleMesh = mesh;
+ attachChild(mesh);
+ _particles = new Particle[numParticles];
+ if (numParticles == 0) {
+ return;
+ }
+ Vector2 sharedTextureData[];
+
+ // setup texture coords and index mode
+ final MeshData meshData = mesh.getMeshData();
+ switch (getParticleType()) {
+ case GeomMesh:
+ case Triangle:
+ sharedTextureData = new Vector2[] { new Vector2(2.0, 0.0), new Vector2(0.0, 2.0), new Vector2(0.0, 0.0) };
+ meshData.setIndexMode(IndexMode.Triangles);
+ break;
+ case Quad:
+ sharedTextureData = new Vector2[] { new Vector2(1.0, 0.0), new Vector2(1.0, 1.0),
+ new Vector2(0.0, 1.0), new Vector2(0.0, 0.0) };
+ meshData.setIndexMode(IndexMode.Quads);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Particle Mesh may only have particle type of ParticleType.Quad, ParticleType.GeomMesh or ParticleType.Triangle");
+ }
+
+ final int verts = getVertsForParticleType(getParticleType());
+
+ _geometryCoordinates = BufferUtils.createVector3Buffer(numParticles * verts);
+ _appearanceColors = BufferUtils.createColorBuffer(numParticles * verts);
+
+ meshData.setVertexBuffer(_geometryCoordinates);
+ meshData.setColorBuffer(_appearanceColors);
+ meshData.setTextureBuffer(BufferUtils.createVector2Buffer(numParticles * verts), 0);
+
+ final Vector2 temp = Vector2.fetchTempInstance();
+ for (int k = 0; k < numParticles; k++) {
+ _particles[k] = new Particle(this);
+ _particles[k].init();
+ _particles[k].setStartIndex(k * verts);
+ for (int a = verts - 1; a >= 0; a--) {
+ final int ind = (k * verts) + a;
+ if (_particleType == ParticleSystem.ParticleType.GeomMesh && _useMeshTexCoords) {
+ final MeshEmitter source = (MeshEmitter) getParticleEmitter();
+ final Mesh sourceMesh = source.getSource();
+ final int index = sourceMesh.getMeshData().getIndexBuffer() != null ? sourceMesh.getMeshData()
+ .getIndices().get(ind) : ind;
+ BufferUtils.populateFromBuffer(temp, sourceMesh.getMeshData().getTextureCoords(0).getBuffer(),
+ index);
+ BufferUtils.setInBuffer(temp, meshData.getTextureCoords(0).getBuffer(), ind);
+ } else {
+ BufferUtils.setInBuffer(sharedTextureData[a], meshData.getTextureCoords(0).getBuffer(), ind);
+ }
+ BufferUtils.setInBuffer(_particles[k].getCurrentColor(), _appearanceColors, (ind));
+ }
+
+ }
+ Vector2.releaseTempInstance(temp);
+ updateWorldRenderStates(true);
+ _particleMesh.getSceneHints().setCastsShadows(false);
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ final Camera camera = Camera.getCurrentCamera();
+ boolean anyAlive = false;
+ for (int i = 0; i < _particles.length; i++) {
+ final Particle particle = _particles[i];
+ if (particle.getStatus() == Particle.Status.Alive) {
+ particle.updateVerts(camera);
+ anyAlive = true;
+ }
+ }
+
+ // Since we've updated our verts, update the model boundary where applicable
+ if (getParticleGeometry().getWorldBound() != null && anyAlive) {
+ getParticleGeometry().updateModelBound();
+ }
+
+ if (!_particlesInWorldCoords) {
+ getParticleGeometry().setWorldTransform(getWorldTransform());
+ } else {
+ getParticleGeometry().setWorldTranslation(Vector3.ZERO);
+ getParticleGeometry().setWorldRotation(Matrix3.IDENTITY);
+ getParticleGeometry().setWorldScale(getWorldScale());
+ }
+
+ getParticleGeometry().draw(r);
+ }
+
+ @Override
+ public void resetParticleVelocity(final int i) {
+ if (_particleType == ParticleSystem.ParticleType.GeomMesh && _useTriangleNormalEmit) {
+ _particles[i].getVelocity().set(_particles[i].getTriangleModel().getNormal());
+ _particles[i].getVelocity().multiplyLocal(_emissionDirection);
+ _particles[i].getVelocity().multiplyLocal(getInitialVelocity());
+ } else {
+ super.resetParticleVelocity(i);
+ }
+ }
+
+ public boolean isUseMeshTexCoords() {
+ return _useMeshTexCoords;
+ }
+
+ public void setUseMeshTexCoords(final boolean useMeshTexCoords) {
+ _useMeshTexCoords = useMeshTexCoords;
+ }
+
+ public boolean isUseTriangleNormalEmit() {
+ return _useTriangleNormalEmit;
+ }
+
+ public void setUseTriangleNormalEmit(final boolean useTriangleNormalEmit) {
+ _useTriangleNormalEmit = useTriangleNormalEmit;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_useMeshTexCoords, "useMeshTexCoords", true);
+ capsule.write(_useTriangleNormalEmit, "useTriangleNormalEmit", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _useMeshTexCoords = capsule.readBoolean("useMeshTexCoords", true);
+ _useTriangleNormalEmit = capsule.readBoolean("useTriangleNormalEmit", true);
+ }
+
+ @Override
+ public Mesh getParticleGeometry() {
+ return _particleMesh;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.java
new file mode 100644
index 0000000..c14ff07
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticlePoints.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.scenegraph.Point;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * ParticlePoints is a particle system that uses Point as its underlying geometric data.
+ */
+public class ParticlePoints extends ParticleSystem {
+
+ public ParticlePoints() {}
+
+ public ParticlePoints(final String name, final int numParticles) {
+ super(name, numParticles);
+ }
+
+ @Override
+ protected void initializeParticles(final int numParticles) {
+ Vector2 sharedTextureData[];
+
+ // setup texture coords
+ sharedTextureData = new Vector2[] { new Vector2(0.0, 0.0) };
+
+ final int verts = getVertsForParticleType(getParticleType());
+
+ _geometryCoordinates = BufferUtils.createVector3Buffer(numParticles * verts);
+
+ _appearanceColors = BufferUtils.createColorBuffer(numParticles * verts);
+ _particles = new Particle[numParticles];
+
+ if (_particleMesh != null) {
+ detachChild(_particleMesh);
+ }
+ _particleMesh = new Point(getName() + "_points", _geometryCoordinates, null, _appearanceColors, null) {
+
+ @Override
+ public void updateWorldTransform(final boolean recurse) {
+ ; // Do nothing.
+ }
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ super.updateWorldTransform(recurse);
+ super.updateWorldBound(recurse);
+ }
+ };
+ _particleMesh.getMeshData().setTextureBuffer(BufferUtils.createVector2Buffer(numParticles), 0);
+ attachChild(_particleMesh);
+ getSceneHints().setRenderBucketType(RenderBucketType.Opaque);
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ for (int k = 0; k < numParticles; k++) {
+ _particles[k] = new Particle(this);
+ _particles[k].init();
+ _particles[k].setStartIndex(k * verts);
+ for (int a = verts - 1; a >= 0; a--) {
+ final int ind = (k * verts) + a;
+ BufferUtils.setInBuffer(sharedTextureData[a], getParticleGeometry().getMeshData().getTextureCoords(0)
+ .getBuffer(), ind);
+ BufferUtils.setInBuffer(_particles[k].getCurrentColor(), _appearanceColors, (ind));
+ }
+
+ }
+ updateWorldRenderStates(true);
+ _particleMesh.getSceneHints().setCastsShadows(false);
+ }
+
+ @Override
+ public ParticleType getParticleType() {
+ return ParticleSystem.ParticleType.Point;
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ final Camera camera = Camera.getCurrentCamera();
+ boolean anyAlive = false;
+ for (int i = 0; i < _particles.length; i++) {
+ final Particle particle = _particles[i];
+ if (particle.getStatus() == Particle.Status.Alive) {
+ particle.updateVerts(camera);
+ anyAlive = true;
+ }
+ }
+
+ // Since we've updated our verts, update the model boundary where applicable
+ if (getParticleGeometry().getWorldBound() != null && anyAlive) {
+ getParticleGeometry().updateModelBound();
+ }
+
+ if (!_particlesInWorldCoords) {
+ getParticleGeometry().setWorldTransform(getWorldTransform());
+ } else {
+ getParticleGeometry().setWorldTranslation(Vector3.ZERO);
+ getParticleGeometry().setWorldRotation(Matrix3.IDENTITY);
+ getParticleGeometry().setWorldScale(getWorldScale());
+ }
+
+ getParticleGeometry().draw(r);
+ }
+
+ /**
+ * @return true if points are to be drawn antialiased
+ */
+ public boolean isAntialiased() {
+ return getParticleGeometry().isAntialiased();
+ }
+
+ /**
+ * Sets whether the points should be antialiased. May decrease performance. If you want to enabled antialiasing, you
+ * should also use an alphastate with a source of SourceFunction.SourceAlpha and a destination of
+ * DB_ONE_MINUS_SRC_ALPHA or DB_ONE.
+ *
+ * @param antialiased
+ * true if the line should be antialiased.
+ */
+ public void setAntialiased(final boolean antialiased) {
+ getParticleGeometry().setAntialiased(antialiased);
+ }
+
+ /**
+ * @return the pixel size of each point.
+ */
+ public float getPointSize() {
+ return getParticleGeometry().getPointSize();
+ }
+
+ /**
+ * Sets the pixel width of the points when drawn. Non anti-aliased point sizes are rounded to the nearest whole
+ * number by opengl.
+ *
+ * @param size
+ * The size to set.
+ */
+ public void setPointSize(final float size) {
+ getParticleGeometry().setPointSize(size);
+ }
+
+ @Override
+ public Point getParticleGeometry() {
+ return (Point) _particleMesh;
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java
new file mode 100644
index 0000000..351470e
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/ParticleSystem.java
@@ -0,0 +1,1084 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.ardor3d.extension.effect.particle.emitter.MeshEmitter;
+import com.ardor3d.extension.effect.particle.emitter.SavableParticleEmitter;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Triangle;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.ComplexSpatialController.RepeatType;
+import com.ardor3d.scenegraph.controller.SpatialController;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * ParticleSystem is an abstract class representing a particle system. A ParticleController must be attached for the
+ * effect to be complete.
+ */
+public abstract class ParticleSystem extends Node {
+ private static final Logger logger = Logger.getLogger(ParticleSystem.class.getName());
+
+ protected static final long serialVersionUID = 2L;
+
+ public enum ParticleType {
+ Quad, Triangle, Point, Line, GeomMesh;
+ }
+
+ protected static final double DEFAULT_END_SIZE = 4;
+ protected static final double DEFAULT_START_SIZE = 20;
+ protected static final double DEFAULT_MAX_ANGLE = 0.7853982;
+ protected static final double DEFAULT_MAX_LIFE = 3000;
+ protected static final double DEFAULT_MIN_LIFE = 2000;
+
+ protected static final ReadOnlyColorRGBA DEFAULT_START_COLOR = new ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f);
+ protected static final ReadOnlyColorRGBA DEFAULT_END_COLOR = new ColorRGBA(1.0f, 1.0f, 0.0f, 0.0f);
+
+ protected ParticleType _particleType;
+ protected SavableParticleEmitter _particleEmitter;
+ protected boolean _cameraFacing = true;
+ protected boolean _velocityAligned = false;
+ protected boolean _particlesInWorldCoords = true;
+
+ protected double _startSize, _endSize;
+ protected final ColorRGBA _startColor = new ColorRGBA(DEFAULT_START_COLOR);
+ protected final ColorRGBA _endColor = new ColorRGBA(DEFAULT_END_COLOR);
+ protected ParticleAppearanceRamp _ramp = new ParticleAppearanceRamp();
+ protected TexAnimation _texAnimation = new TexAnimation();
+ protected double _initialVelocity;
+ protected double _minimumLifeTime, _maximumLifeTime;
+ protected double _minimumAngle, _maximumAngle;
+ protected double _startSpin, _endSpin;
+ protected double _startMass, _endMass;
+ protected int _startTexIndex, _texQuantity;
+ protected final Vector3 _emissionDirection = new Vector3(Vector3.UNIT_Y);
+ protected final Transform _emitterTransform = new Transform();
+ protected final Vector3 _worldEmit = new Vector3();
+ protected int _numParticles;
+ protected boolean _rotateWithScene = false;
+ protected final Matrix3 _rotMatrix = new Matrix3();
+ protected double _particleOrientation;
+
+ protected FloatBuffer _geometryCoordinates;
+ protected FloatBuffer _appearanceColors;
+
+ // vectors to prevent repeated object creation:
+ protected final Vector3 _upXemit = new Vector3(), _absUpVector = new Vector3(), _abUpMinUp = new Vector3();
+ protected final Vector3 _upVector = new Vector3(Vector3.UNIT_Y);
+ protected final Vector3 _leftVector = new Vector3(-1, 0, 0);
+ protected final Vector3 _invScale = new Vector3();
+
+ // These vectors are used for determining particle orientation if you turn off camera facing
+ protected final Vector3 _facingUpVector = new Vector3(Vector3.UNIT_Y);
+ protected final Vector3 _facingLeftVector = new Vector3(-1, 0, 0);
+
+ protected Particle _particles[];
+
+ // protected Vector3 particleSpeed;
+ protected int _releaseRate; // particles per second
+ protected final Vector3 _originOffset = new Vector3();
+ protected final Vector3 _originCenter = new Vector3();
+
+ protected Mesh _particleMesh;
+ protected ParticleController _controller;
+
+ protected Vector3 _oldEmit = new Vector3(Float.NaN, Float.NaN, Float.NaN);
+ protected double _matData[] = new double[9];
+
+ public ParticleSystem() {}
+
+ public ParticleSystem(final String name, final int numParticles) {
+ this(name, numParticles, ParticleType.Quad);
+ }
+
+ public ParticleSystem(final String name, final int numParticles, final ParticleType particleType) {
+ super(name);
+ _numParticles = numParticles;
+ _particleType = particleType;
+ _minimumLifeTime = DEFAULT_MIN_LIFE;
+ _maximumLifeTime = DEFAULT_MAX_LIFE;
+ _maximumAngle = DEFAULT_MAX_ANGLE;
+ _startSize = DEFAULT_START_SIZE;
+ _endSize = DEFAULT_END_SIZE;
+ _startSpin = 0;
+ _endSpin = 0;
+ _startMass = 1;
+ _endMass = 1;
+ _startTexIndex = 0;
+ _texQuantity = 1;
+ _releaseRate = numParticles;
+ _initialVelocity = 1.0;
+
+ initializeParticles(numParticles);
+ }
+
+ protected abstract void initializeParticles(int numParticles);
+
+ public static int getVertsForParticleType(final ParticleType type) {
+ if (type == null) {
+ throw new NullPointerException("type is null");
+ }
+ switch (type) {
+ case Triangle:
+ case GeomMesh:
+ return 3;
+ case Point:
+ return 1;
+ case Line:
+ return 2;
+ case Quad:
+ return 4;
+ }
+ throw new IllegalArgumentException("Invalid ParticleType: " + type);
+ }
+
+ public void forceRespawn() {
+ for (final Particle p : _particles) {
+ p.recreateParticle(0);
+ p.setStatus(Particle.Status.Alive);
+ p.updateAndCheck(1);
+ p.setStatus(Particle.Status.Available);
+ }
+
+ if (_controller != null) {
+ _controller.setActive(true);
+ }
+ }
+
+ /**
+ * Setup the rotation matrix used to determine initial particle velocity based on emission angle and emission
+ * direction. called automatically by the set* methods for those parameters.
+ */
+ public void updateRotationMatrix() {
+
+ if (_oldEmit.equals(_worldEmit)) {
+ return;
+ }
+
+ final double upDotEmit = _upVector.dot(_worldEmit);
+ if (Math.abs(upDotEmit) > 1.0 - MathUtils.EPSILON) {
+ _absUpVector.setX(_upVector.getX() <= 0.0 ? -_upVector.getX() : _upVector.getX());
+ _absUpVector.setY(_upVector.getY() <= 0.0 ? -_upVector.getY() : _upVector.getY());
+ _absUpVector.setZ(_upVector.getZ() <= 0.0 ? -_upVector.getZ() : _upVector.getZ());
+ if (_absUpVector.getX() < _absUpVector.getY()) {
+ if (_absUpVector.getX() < _absUpVector.getZ()) {
+ _absUpVector.set(Vector3.UNIT_X);
+ } else {
+ _absUpVector.set(Vector3.UNIT_Z);
+ }
+ } else if (_absUpVector.getY() < _absUpVector.getZ()) {
+ _absUpVector.set(Vector3.UNIT_Y);
+ } else {
+ _absUpVector.set(Vector3.UNIT_Z);
+ }
+ _absUpVector.subtract(_upVector, _abUpMinUp);
+ _absUpVector.subtract(_worldEmit, _upXemit);
+ final double f4 = 2.0 / _abUpMinUp.dot(_abUpMinUp);
+ final double f6 = 2.0 / _upXemit.dot(_upXemit);
+ final double f8 = f4 * f6 * _abUpMinUp.dot(_upXemit);
+ final double af1[] = { _abUpMinUp.getX(), _abUpMinUp.getY(), _abUpMinUp.getZ() };
+ final double af2[] = { _upXemit.getX(), _upXemit.getY(), _upXemit.getZ() };
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ _matData[(i * 3) + j] = (-f4 * af1[i] * af1[j] - f6 * af2[i] * af2[j]) + f8 * af2[i] * af1[j];
+
+ }
+ _matData[(i * 3) + i]++;
+ }
+
+ } else {
+ _upVector.cross(_worldEmit, _upXemit);
+ final double f2 = 1.0 / (1.0 + upDotEmit);
+ final double f5 = f2 * _upXemit.getX();
+ final double f7 = f2 * _upXemit.getZ();
+ final double f9 = f5 * _upXemit.getY();
+ final double f10 = f5 * _upXemit.getZ();
+ final double f11 = f7 * _upXemit.getY();
+ _matData[0] = upDotEmit + f5 * _upXemit.getX();
+ _matData[1] = f9 - _upXemit.getZ();
+ _matData[2] = f10 + _upXemit.getY();
+ _matData[3] = f9 + _upXemit.getZ();
+ _matData[4] = upDotEmit + f2 * _upXemit.getY() * _upXemit.getY();
+ _matData[5] = f11 - _upXemit.getX();
+ _matData[6] = f10 - _upXemit.getY();
+ _matData[7] = f11 + _upXemit.getX();
+ _matData[8] = upDotEmit + f7 * _upXemit.getZ();
+ }
+ _rotMatrix.fromArray(_matData);
+ _oldEmit.set(_worldEmit);
+ }
+
+ public abstract Mesh getParticleGeometry();
+
+ public ParticleController getParticleController() {
+ return _controller;
+ }
+
+ @Override
+ public void addController(final SpatialController<?> c) {
+ super.addController(c);
+ if (c instanceof ParticleController) {
+ _controller = (ParticleController) c;
+ }
+ }
+
+ public Vector3 getEmissionDirection() {
+ return _emissionDirection;
+ }
+
+ public void setEmissionDirection(final ReadOnlyVector3 emissionDirection) {
+ _emissionDirection.set(emissionDirection);
+ _worldEmit.set(emissionDirection);
+ }
+
+ public double getEndSize() {
+ return _endSize;
+ }
+
+ public void setEndSize(final double size) {
+ _endSize = size >= 0.0 ? size : 0.0;
+ }
+
+ public double getStartSize() {
+ return _startSize;
+ }
+
+ public void setStartSize(final double size) {
+ _startSize = size >= 0.0 ? size : 0.0;
+ }
+
+ /**
+ * Set the start color for particles.
+ *
+ * @param color
+ * The new start color.
+ */
+ public void setStartColor(final ReadOnlyColorRGBA color) {
+ _startColor.set(color);
+ }
+
+ /**
+ * @return The start color.
+ */
+ public ReadOnlyColorRGBA getStartColor() {
+ return _startColor;
+ }
+
+ /**
+ * Set the end color for particles. The base color of the particle will linearly approach this color from the start
+ * color over the lifetime of the particle.
+ *
+ * @param color
+ * ColorRGBA The ending color.
+ */
+ public void setEndColor(final ReadOnlyColorRGBA color) {
+ _endColor.set(color);
+ }
+
+ /**
+ * getEndColor returns the ending color.
+ *
+ * @return The ending color
+ */
+ public ReadOnlyColorRGBA getEndColor() {
+ return _endColor;
+ }
+
+ /**
+ * Set the start and end spinSpeed of particles managed by this manager. Setting it to 0 means no spin.
+ *
+ * @param speed
+ * double
+ */
+ public void setParticleSpinSpeed(final double speed) {
+ _startSpin = speed;
+ _endSpin = speed;
+ }
+
+ public Vector3 getInvScale() {
+ return _invScale;
+ }
+
+ public void setInvScale(final ReadOnlyVector3 invScale) {
+ _invScale.set(invScale);
+ }
+
+ public void updateInvScale() {
+ _invScale.set(1.0 / getWorldScale().getX(), 1.0 / getWorldScale().getY(), 1.0 / getWorldScale().getZ());
+ }
+
+ /**
+ * Add an external influence to the particle controller for this mesh.
+ *
+ * @param influence
+ * ParticleInfluence
+ */
+ public void addInfluence(final ParticleInfluence influence) {
+ _controller.addInfluence(influence);
+ }
+
+ /**
+ * Remove an influence from the particle controller for this mesh.
+ *
+ * @param influence
+ * ParticleInfluence
+ * @return true if found and removed.
+ */
+ public boolean removeInfluence(final ParticleInfluence influence) {
+ return _controller.removeInfluence(influence);
+ }
+
+ /**
+ * Returns the list of influences acting on this particle controller.
+ *
+ * @return ArrayList
+ */
+ public List<ParticleInfluence> getInfluences() {
+ return _controller.getInfluences();
+ }
+
+ public void clearInfluences() {
+ _controller.clearInfluences();
+ }
+
+ public void setParticleMass(final double mass) {
+ _startMass = _endMass = mass;
+ }
+
+ /**
+ * Set the minimum angle (in radians) that particles can be emitted away from the emission direction. Any angle less
+ * than 0 is trimmed to 0.
+ *
+ * @param f
+ * The new emission minimum angle.
+ */
+ public void setMinimumAngle(final double f) {
+ _minimumAngle = f >= 0.0 ? f : 0.0;
+ }
+
+ /**
+ * getEmissionMinimumAngle returns the minimum emission angle.
+ *
+ * @return The minimum emission angle.
+ */
+ public double getMinimumAngle() {
+ return _minimumAngle;
+ }
+
+ /**
+ * Set the maximum angle (in radians) that particles can be emitted away from the emission direction. Any angle less
+ * than 0 is trimmed to 0.
+ *
+ * @param f
+ * The new emission maximum angle.
+ */
+ public void setMaximumAngle(final double f) {
+ _maximumAngle = f >= 0.0 ? f : 0.0;
+ }
+
+ /**
+ * getEmissionMaximumAngle returns the maximum emission angle.
+ *
+ * @return The maximum emission angle.
+ */
+ public double getMaximumAngle() {
+ return _maximumAngle;
+ }
+
+ /**
+ * Set the minimum lifespan of new particles (or recreated) managed by this manager. if a value less than zero is
+ * given, 1.0 is used.
+ *
+ * @param lifeSpan
+ * in ms
+ */
+ public void setMinimumLifeTime(final double lifeSpan) {
+ _minimumLifeTime = lifeSpan >= 0.0 ? lifeSpan : 1.0;
+ }
+
+ /**
+ * getParticlesMinimumLifeTime returns the minimum life time of a particle.
+ *
+ * @return The current minimum life time in ms.
+ */
+ public double getMinimumLifeTime() {
+ return _minimumLifeTime;
+ }
+
+ /**
+ * Set the maximum lifespan of new particles (or recreated) managed by this manager. if a value less than zero is
+ * given, 1.0 is used.
+ *
+ * @param lifeSpan
+ * in ms
+ */
+ public void setMaximumLifeTime(final double lifeSpan) {
+ _maximumLifeTime = lifeSpan >= 0.0 ? lifeSpan : 1.0;
+ }
+
+ /**
+ * getParticlesMaximumLifeTime returns the maximum life time of a particle.
+ *
+ * @return The current maximum life time in ms.
+ */
+ public double getMaximumLifeTime() {
+ return _maximumLifeTime;
+ }
+
+ public ReadOnlyMatrix3 getRotMatrix() {
+ return _rotMatrix;
+ }
+
+ public void setRotMatrix(final ReadOnlyMatrix3 rotMatrix) {
+ _rotMatrix.set(rotMatrix);
+ }
+
+ public ReadOnlyTransform getEmitterTransform() {
+ return _emitterTransform;
+ }
+
+ public void setEmitterTransform(final ReadOnlyTransform emitterTransform) {
+ _emitterTransform.set(emitterTransform);
+ }
+
+ public double getParticleOrientation() {
+ return _particleOrientation;
+ }
+
+ public void setParticleOrientation(final double orient) {
+ _particleOrientation = orient;
+ }
+
+ /**
+ * Set the acceleration for any new particles created (or recreated) by this manager.
+ *
+ * @param velocity
+ * particle v0
+ */
+ public void setInitialVelocity(final double velocity) {
+ _initialVelocity = velocity;
+ }
+
+ /**
+ * Get the acceleration set in this manager.
+ *
+ * @return The initialVelocity
+ */
+ public double getInitialVelocity() {
+ return _initialVelocity;
+ }
+
+ /**
+ * Set the offset for any new particles created (or recreated) by this manager. This is applicable only to managers
+ * generating from a point (not a line, rectangle, etc..)
+ *
+ * @param offset
+ * new offset position
+ */
+ public void setOriginOffset(final ReadOnlyVector3 offset) {
+ _originOffset.set(offset);
+ }
+
+ /**
+ * Get the offset point set in this manager.
+ *
+ * @return origin
+ */
+ public ReadOnlyVector3 getOriginOffset() {
+ return _originOffset;
+ }
+
+ public ReadOnlyVector3 getWorldEmit() {
+ return _worldEmit;
+ }
+
+ public void setWorldEmit(final ReadOnlyVector3 worldEmit) {
+ _worldEmit.set(worldEmit);
+ }
+
+ /**
+ * Get the number of particles the manager should release per second.
+ *
+ * @return The number of particles that should be released per second.
+ */
+ public int getReleaseRate() {
+ return _releaseRate;
+ }
+
+ /**
+ * Set the number of particles the manager should release per second.
+ *
+ * @param particlesPerSecond
+ * number of particles per second
+ */
+ public void setReleaseRate(final int particlesPerSecond) {
+ final int oldRate = _releaseRate;
+ _releaseRate = particlesPerSecond;
+ if (_controller != null && !_controller.isActive() && _controller.isControlFlow() && oldRate == 0) {
+ _controller.setActive(true);
+ }
+ }
+
+ public double getEndMass() {
+ return _endMass;
+ }
+
+ public void setEndMass(final double endMass) {
+ _endMass = endMass;
+ }
+
+ public double getEndSpin() {
+ return _endSpin;
+ }
+
+ public void setEndSpin(final double endSpin) {
+ _endSpin = endSpin;
+ }
+
+ public double getStartMass() {
+ return _startMass;
+ }
+
+ public void setStartMass(final double startMass) {
+ _startMass = startMass;
+ }
+
+ public double getStartSpin() {
+ return _startSpin;
+ }
+
+ public void setStartSpin(final double startSpin) {
+ _startSpin = startSpin;
+ }
+
+ public int getTexQuantity() {
+ return _texQuantity;
+ }
+
+ public void setTexQuantity(final int quantity) {
+ _texQuantity = quantity;
+ }
+
+ public int getStartTexIndex() {
+ return _startTexIndex;
+ }
+
+ public void setStartTexIndex(final int startTexIndex) {
+ _startTexIndex = startTexIndex;
+ }
+
+ /**
+ * Get which emittype method is being used by the underlying system. One of ParticleType.Quad,
+ * ParticleType.Triangle, ParticleType.Point, ParticleType.Line, ParticleType.GeomMesh
+ *
+ * @return An int representing the type of particle we are emitting.
+ */
+ public ParticleType getParticleType() {
+ return _particleType;
+ }
+
+ /**
+ * Set what type of particle to emit from this sytem. Does not have an effect unless recreate is called.
+ *
+ * @param type
+ * particle type to use, should be one of ParticleType.Quad, ParticleType.Triangle, ParticleType.Point,
+ * ParticleType.Line, ParticleType.GeomMesh
+ */
+ public void setParticleType(final ParticleType type) {
+ _particleType = type;
+ }
+
+ /**
+ * Set our particle emitter.
+ *
+ * @param emitter
+ * New emitter or null for default point emitter.
+ */
+ public void setParticleEmitter(final SavableParticleEmitter emitter) {
+ _particleEmitter = emitter;
+ }
+
+ /**
+ * @return the set particle emitter, or null if none is set.
+ */
+ public SavableParticleEmitter getParticleEmitter() {
+ return _particleEmitter;
+ }
+
+ public void initAllParticlesLocation() {
+ for (int i = _particles.length; --i >= 0;) {
+ initParticleLocation(i);
+ _particles[i].updateVerts(null);
+ }
+ getParticleGeometry().updateModelBound();
+ }
+
+ @Override
+ public void onDraw(final Renderer r) {
+ // make sure our particle world bound is correct
+ updateWorldBoundManually();
+
+ super.onDraw(r);
+ };
+
+ public void initParticleLocation(final int index) {
+ final Particle p = _particles[index];
+ if (getParticleType() == ParticleType.GeomMesh && getParticleEmitter() instanceof MeshEmitter) {
+ final MeshEmitter emitter = (MeshEmitter) getParticleEmitter();
+ final Mesh mesh = emitter.getSource();
+
+ // Update the triangle model on each new particle creation.
+ final Vector3[] vertices = new Vector3[3];
+ final MeshData mData = mesh.getMeshData();
+ for (int x = 0; x < 3; x++) {
+ vertices[x] = new Vector3();
+
+ final int vertIndex = mData.getVertexIndex(index, x, 0);
+ BufferUtils.populateFromBuffer(vertices[x], mData.getVertexBuffer(),
+ mData.getIndexBuffer() != null ? mData.getIndices().get(vertIndex) : vertIndex);
+ }
+ Triangle t = p.getTriangleModel();
+ if (t == null) {
+ t = new Triangle(vertices[0], vertices[1], vertices[2]);
+ } else {
+ t.setA(vertices[0]);
+ t.setB(vertices[1]);
+ t.setC(vertices[2]);
+ }
+ // turn the triangle corners into vector offsets from center
+ for (int x = 0; x < 3; x++) {
+ vertices[x].subtract(t.getCenter(), vertices[x]);
+ t.set(x, vertices[x]);
+ }
+ p.setTriangleModel(t);
+ mesh.localToWorld(t.getCenter(), p.getPosition());
+ p.getPosition().multiplyLocal(getInvScale());
+
+ } else if (getParticleEmitter() instanceof MeshEmitter) {
+ final MeshEmitter emitter = (MeshEmitter) getParticleEmitter();
+ final Mesh mesh = emitter.getSource();
+ mesh.getMeshData().randomPointOnPrimitives(p.getPosition());
+ mesh.localToWorld(p.getPosition(), p.getPosition());
+ p.getPosition().multiplyLocal(getInvScale());
+ } else {
+ if (getParticleEmitter() != null) {
+ getParticleEmitter().randomEmissionPoint(p.getPosition());
+ } else {
+ p.getPosition().set(_originOffset);
+ }
+
+ _emitterTransform.applyForward(p.getPosition());
+ p.getPosition().divideLocal(_emitterTransform.getScale());
+ }
+ }
+
+ public boolean isCameraFacing() {
+ return _cameraFacing;
+ }
+
+ public void setCameraFacing(final boolean cameraFacing) {
+ _cameraFacing = cameraFacing;
+ }
+
+ public boolean isVelocityAligned() {
+ return _velocityAligned;
+ }
+
+ public void setVelocityAligned(final boolean velocityAligned) {
+ _velocityAligned = velocityAligned;
+ }
+
+ public Particle getParticle(final int i) {
+ return _particles[i];
+ }
+
+ public boolean isActive() {
+ return _controller.isActive();
+ }
+
+ public void setSpeed(final double f) {
+ _controller.setSpeed(f);
+ }
+
+ public void setRepeatType(final RepeatType type) {
+ _controller.setRepeatType(type);
+ }
+
+ public void setControlFlow(final boolean b) {
+ _controller.setControlFlow(b);
+ }
+
+ public ReadOnlyVector3 getOriginCenter() {
+ return _originCenter;
+ }
+
+ public ReadOnlyVector3 getUpVector() {
+ return _upVector;
+ }
+
+ public void setUpVector(final ReadOnlyVector3 vector) {
+ _upVector.set(vector);
+ }
+
+ public ReadOnlyVector3 getLeftVector() {
+ return _leftVector;
+ }
+
+ public void setLeftVector(final ReadOnlyVector3 vector) {
+ _leftVector.set(vector);
+ }
+
+ public ReadOnlyVector3 getFacingUpVector() {
+ return _facingUpVector;
+ }
+
+ /**
+ * Used to determine particle orientation (only if cameraFacing is false.)
+ *
+ * @param vector
+ */
+ public void setFacingUpVector(final ReadOnlyVector3 vector) {
+ _facingUpVector.set(vector);
+ }
+
+ public ReadOnlyVector3 getFacingLeftVector() {
+ return _facingLeftVector;
+ }
+
+ /**
+ * Used to determine particle orientation (only if cameraFacing is false.)
+ *
+ * @param vector
+ */
+ public void setFacingLeftVector(final ReadOnlyVector3 vector) {
+ _facingLeftVector.set(vector);
+ }
+
+ public boolean isRotateWithScene() {
+ return _rotateWithScene;
+ }
+
+ public void setRotateWithScene(final boolean rotate) {
+ _rotateWithScene = rotate;
+ }
+
+ public void resetParticleVelocity(final int i) {
+ getRandomVelocity(_particles[i].getVelocity());
+ }
+
+ /**
+ * Returns a random angle between the min and max angles.
+ *
+ * @return the random angle.
+ */
+ public double getRandomAngle() {
+ return getMinimumAngle() + MathUtils.nextRandomFloat() * (getMaximumAngle() - getMinimumAngle());
+ }
+
+ /**
+ * generate a random lifespan between the min and max lifespan of the particle system.
+ *
+ * @return the generated lifespan value
+ */
+ public double getRandomLifeSpan() {
+ return getMinimumLifeTime() + ((getMaximumLifeTime() - getMinimumLifeTime()) * MathUtils.nextRandomFloat());
+ }
+
+ /**
+ * Generate a random velocity within the parameters of max angle and the rotation matrix.
+ *
+ * @param store
+ * a vector to store the results in.
+ */
+ protected Vector3 getRandomVelocity(final Vector3 store) {
+ final double randDir = MathUtils.TWO_PI * MathUtils.nextRandomFloat();
+ final double randAngle = getRandomAngle();
+ Vector3 result = store;
+ if (result == null) {
+ result = new Vector3();
+ }
+ result.setX(MathUtils.cos(randDir) * MathUtils.sin(randAngle));
+ result.setY(MathUtils.cos(randAngle));
+ result.setZ(MathUtils.sin(randDir) * MathUtils.sin(randAngle));
+ rotateVectorSpeed(result);
+ result.multiplyLocal(getInitialVelocity());
+ return result;
+ }
+
+ /**
+ * Apply the rotation matrix to a given vector representing a particle velocity.
+ *
+ * @param pSpeed
+ * the velocity vector to be modified.
+ */
+ protected void rotateVectorSpeed(final Vector3 pSpeed) {
+
+ final double x = pSpeed.getX(), y = pSpeed.getY(), z = pSpeed.getZ();
+
+ pSpeed.setX(-1 * ((_rotMatrix.getM00() * x) + (_rotMatrix.getM10() * y) + (_rotMatrix.getM20() * z)));
+ pSpeed.setY((_rotMatrix.getM01() * x) + (_rotMatrix.getM11() * y) + (_rotMatrix.getM21() * z));
+ pSpeed.setZ(-1 * ((_rotMatrix.getM02() * x) + (_rotMatrix.getM12() * y) + (_rotMatrix.getM22() * z)));
+ }
+
+ public void warmUp(final int iterations) {
+ if (_controller != null) {
+ _controller.warmUp(iterations, this);
+ }
+ }
+
+ public int getNumParticles() {
+ return _numParticles;
+ }
+
+ public void setNumParticles(final int numParticles) {
+ _numParticles = numParticles;
+ }
+
+ public double getReleaseVariance() {
+ if (_controller != null) {
+ return _controller.getReleaseVariance();
+ }
+ return 0;
+ }
+
+ public void setReleaseVariance(final double var) {
+ if (_controller != null) {
+ _controller.setReleaseVariance(var);
+ }
+ }
+
+ public ParticleAppearanceRamp getRamp() {
+ return _ramp;
+ }
+
+ public void setRamp(final ParticleAppearanceRamp ramp) {
+ if (ramp == null) {
+ logger.warning("Can not set a null ParticleAppearanceRamp.");
+ return;
+ }
+ _ramp = ramp;
+ }
+
+ public TexAnimation getTexAnimation() {
+ return _texAnimation;
+ }
+
+ public void setTexAnimation(final TexAnimation texAnimation) {
+ if (texAnimation == null) {
+ logger.warning("Can not set a null TexAnimation.");
+ return;
+ }
+ _texAnimation = texAnimation;
+ }
+
+ /**
+ * @return true if the particles are already in world coordinate space (default). When true, scene-graph transforms
+ * will only affect the emission of particles, not particles that are already living.
+ */
+ public boolean isParticlesInWorldCoords() {
+ return _particlesInWorldCoords;
+ }
+
+ public void setParticlesInWorldCoords(final boolean particlesInWorldCoords) {
+ _particlesInWorldCoords = particlesInWorldCoords;
+ }
+
+ /**
+ * Changes the number of particles in this particle mesh.
+ *
+ * @param count
+ * the desired number of particles to change to.
+ */
+ public void recreate(final int count) {
+ _numParticles = count;
+ initializeParticles(_numParticles);
+ }
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ ; // ignore this since we want it to happen only when we say it can
+ // happen due to world vectors not being used
+ }
+
+ public void updateWorldBoundManually() {
+ super.updateWorldBound(true);
+ }
+
+ @Override
+ public void updateGeometricState(final double time, final boolean initiator) {
+ super.updateGeometricState(time, initiator);
+ if (isRotateWithScene()) {
+ // XXX: Perhaps we can avoid this special case via an addition to the interface?
+ if (getParticleEmitter() instanceof MeshEmitter) {
+ ((MeshEmitter) getParticleEmitter()).getSource().getWorldRotation()
+ .applyPost(_emissionDirection, _worldEmit);
+ } else {
+ getWorldRotation().applyPost(_emissionDirection, _worldEmit);
+ }
+ } else {
+ _worldEmit.set(_emissionDirection);
+ }
+
+ if (_particlesInWorldCoords) {
+ final Transform t = Transform.fetchTempInstance();
+ t.setIdentity();
+ t.setTranslation(getWorldTranslation());
+ t.setScale(getScale());
+ if (getParent() != null) {
+ t.setRotation(getParent().getWorldRotation());
+ }
+ _emitterTransform.set(t);
+ Transform.releaseTempInstance(t);
+
+ _originCenter.set(getWorldTranslation()).addLocal(_originOffset);
+
+ setWorldTranslation(Vector3.ZERO);
+ } else {
+ _originCenter.set(_originOffset);
+ }
+
+ setWorldRotation(Matrix3.IDENTITY);
+ setWorldScale(getScale());
+ markDirty(DirtyType.Transform);
+ }
+
+ @Override
+ public ParticleSystem makeCopy(final boolean shareGeometricData) {
+ synchronized (this) {
+ // Do not call make copy on the "generated" particle geometry.
+ final Spatial geom = getParticleGeometry();
+ detachChild(geom);
+ final ParticleSystem copy = (ParticleSystem) super.makeCopy(shareGeometricData);
+ // Reattach
+ attachChild(geom);
+ return copy;
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ synchronized (this) {
+ // Do not save the "generated" particle geometry.
+ final Spatial geom = getParticleGeometry();
+ detachChild(geom);
+ super.write(capsule);
+ // Reattach
+ attachChild(geom);
+ }
+
+ capsule.write(_particleType, "particleType", ParticleType.Quad);
+ capsule.write(_particleEmitter, "particleEmitter", null);
+ capsule.write(_startSize, "startSize", DEFAULT_START_SIZE);
+ capsule.write(_endSize, "endSize", DEFAULT_END_SIZE);
+ capsule.write(_startColor, "startColor", new ColorRGBA(DEFAULT_START_COLOR));
+ capsule.write(_endColor, "endColor", new ColorRGBA(DEFAULT_END_COLOR));
+ capsule.write(_startSpin, "startSpin", 0);
+ capsule.write(_endSpin, "endSpin", 0);
+ capsule.write(_startMass, "startMass", 0);
+ capsule.write(_endMass, "endMass", 0);
+ capsule.write(_startTexIndex, "startTexIndex", 0);
+ capsule.write(_texQuantity, "texQuantity", 1);
+ capsule.write(_initialVelocity, "initialVelocity", 1);
+ capsule.write(_minimumLifeTime, "minimumLifeTime", DEFAULT_MIN_LIFE);
+ capsule.write(_maximumLifeTime, "maximumLifeTime", DEFAULT_MAX_LIFE);
+ capsule.write(_minimumAngle, "minimumAngle", 0);
+ capsule.write(_maximumAngle, "maximumAngle", DEFAULT_MAX_ANGLE);
+ capsule.write(_emissionDirection, "emissionDirection", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_worldEmit, "worldEmit", new Vector3(Vector3.ZERO));
+ capsule.write(_upVector, "upVector", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_leftVector, "leftVector", new Vector3(-1, 0, 0));
+ capsule.write(_facingUpVector, "facingUpVector", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_facingLeftVector, "facingLeftVector", new Vector3(-1, 0, 0));
+ capsule.write(_numParticles, "numParticles", 0);
+ capsule.write(_particleOrientation, "particleOrientation", 0);
+ capsule.write(_rotateWithScene, "rotateWithScene", false);
+ capsule.write(_geometryCoordinates, "geometryCoordinates", null);
+ capsule.write(_appearanceColors, "appearanceColors", null);
+ capsule.write(_releaseRate, "releaseRate", _numParticles);
+ capsule.write(_originCenter, "originCenter", new Vector3(Vector3.ZERO));
+ capsule.write(_originOffset, "originOffset", new Vector3(Vector3.ZERO));
+ capsule.write(_controller, "controller", null);
+ capsule.write(_cameraFacing, "cameraFacing", true);
+ capsule.write(_velocityAligned, "velocityAligned", false);
+ capsule.write(_particlesInWorldCoords, "particlesInWorldCoords", true);
+ capsule.write(_ramp, "ramp", new ParticleAppearanceRamp());
+ capsule.write(_texAnimation, "texAnimation", new TexAnimation());
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _particleType = capsule.readEnum("particleType", ParticleType.class, ParticleType.Quad);
+ _particleEmitter = (SavableParticleEmitter) capsule.readSavable("particleEmitter", null);
+ _startSize = capsule.readDouble("startSize", DEFAULT_START_SIZE);
+ _endSize = capsule.readDouble("endSize", DEFAULT_END_SIZE);
+ _startColor.set((ColorRGBA) capsule.readSavable("startColor", new ColorRGBA(DEFAULT_START_COLOR)));
+ _endColor.set((ColorRGBA) capsule.readSavable("endColor", new ColorRGBA(DEFAULT_END_COLOR)));
+ _startSpin = capsule.readDouble("startSpin", 0);
+ _endSpin = capsule.readDouble("endSpin", 0);
+ _startMass = capsule.readDouble("startMass", 0);
+ _endMass = capsule.readDouble("endMass", 0);
+ _startTexIndex = capsule.readInt("startTexIndex", 0);
+ _texQuantity = capsule.readInt("texQuantity", 1);
+ _initialVelocity = capsule.readDouble("initialVelocity", 1);
+ _minimumLifeTime = capsule.readDouble("minimumLifeTime", DEFAULT_MIN_LIFE);
+ _maximumLifeTime = capsule.readDouble("maximumLifeTime", DEFAULT_MAX_LIFE);
+ _minimumAngle = capsule.readDouble("minimumAngle", 0);
+ _maximumAngle = capsule.readDouble("maximumAngle", DEFAULT_MAX_ANGLE);
+ _emissionDirection.set((Vector3) capsule.readSavable("emissionDirection", new Vector3(Vector3.UNIT_Y)));
+ _worldEmit.set((Vector3) capsule.readSavable("worldEmit", new Vector3(Vector3.ZERO)));
+ _upVector.set((Vector3) capsule.readSavable("upVector", new Vector3(Vector3.UNIT_Y)));
+ _leftVector.set((Vector3) capsule.readSavable("leftVector", new Vector3(-1, 0, 0)));
+ _facingUpVector.set((Vector3) capsule.readSavable("facingUpVector", new Vector3(Vector3.UNIT_Y)));
+ _facingLeftVector.set((Vector3) capsule.readSavable("facingLeftVector", new Vector3(-1, 0, 0)));
+ _numParticles = capsule.readInt("numParticles", 0);
+ _rotateWithScene = capsule.readBoolean("rotateWithScene", false);
+ _geometryCoordinates = capsule.readFloatBuffer("geometryCoordinates", null);
+ _appearanceColors = capsule.readFloatBuffer("appearanceColors", null);
+
+ _releaseRate = capsule.readInt("releaseRate", _numParticles);
+ _particleOrientation = capsule.readDouble("particleOrientation", 0);
+ _originCenter.set((Vector3) capsule.readSavable("originCenter", new Vector3()));
+ _originOffset.set((Vector3) capsule.readSavable("originOffset", new Vector3()));
+ _controller = (ParticleController) capsule.readSavable("controller", null);
+ _cameraFacing = capsule.readBoolean("cameraFacing", true);
+ _velocityAligned = capsule.readBoolean("velocityAligned", false);
+ _particlesInWorldCoords = capsule.readBoolean("particlesInWorldCoords", true);
+ _ramp = (ParticleAppearanceRamp) capsule.readSavable("ramp", new ParticleAppearanceRamp());
+ _texAnimation = (TexAnimation) capsule.readSavable("texAnimation", new TexAnimation());
+
+ _invScale.zero();
+ _upXemit.zero();
+ _absUpVector.zero();
+ _abUpMinUp.zero();
+ _rotMatrix.setIdentity();
+ initializeParticles(_numParticles);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java
new file mode 100644
index 0000000..2e014d7
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/RampEntry.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * <code>RampEntry</code> defines an entry for a ParticleAppearanceRamp.
+ *
+ * @see ParticleAppearanceRamp
+ */
+public class RampEntry implements Savable {
+
+ public static final double DEFAULT_OFFSET = 0.05f; // (5% of lifetime)
+ public static final double DEFAULT_SIZE = -1; // special case -> negative = no size change at this entry
+ public static final double DEFAULT_SPIN = Float.MAX_VALUE; // special case -> no spin change
+ public static final double DEFAULT_MASS = Float.MAX_VALUE; // special case -> no mass change
+ public static final ColorRGBA DEFAULT_COLOR = null; // special case -> no color change
+
+ protected double _offset = DEFAULT_OFFSET;
+ protected ColorRGBA _color = DEFAULT_COLOR; // no color change at this entry
+ protected double _size = DEFAULT_SIZE;
+ protected double _spin = DEFAULT_SPIN;
+ protected double _mass = DEFAULT_MASS;
+
+ public RampEntry() {}
+
+ /**
+ * Construct new addition to color ramp
+ *
+ * @param offset
+ * amount of time (as a percent of total lifetime) between the last appearance and this one.
+ */
+ public RampEntry(final double offset) {
+ setOffset(offset);
+ }
+
+ public ReadOnlyColorRGBA getColor() {
+ return _color;
+ }
+
+ public void setColor(final ReadOnlyColorRGBA color) {
+ if (color != null) {
+ if (_color != null) {
+ _color.set(color);
+ } else {
+ _color = new ColorRGBA(color);
+ }
+ } else {
+ _color = null;
+ }
+ }
+
+ public boolean hasColorSet() {
+ return _color != null; // DEFAULT_COLOR is null
+ }
+
+ public double getSize() {
+ return _size;
+ }
+
+ public void setSize(final double size) {
+ _size = size;
+ }
+
+ public boolean hasSizeSet() {
+ return _size != DEFAULT_SIZE;
+ }
+
+ public double getSpin() {
+ return _spin;
+ }
+
+ public void setSpin(final double spin) {
+ _spin = spin;
+ }
+
+ public boolean hasSpinSet() {
+ return _spin != DEFAULT_SPIN;
+ }
+
+ public double getMass() {
+ return _mass;
+ }
+
+ public void setMass(final double mass) {
+ _mass = mass;
+ }
+
+ public boolean hasMassSet() {
+ return _mass != DEFAULT_MASS;
+ }
+
+ public double getOffset() {
+ return _offset;
+ }
+
+ public void setOffset(final double offset) {
+ _offset = offset;
+ }
+
+ public Class<? extends RampEntry> getClassTag() {
+ return getClass();
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _offset = capsule.readDouble("offsetMS", DEFAULT_OFFSET);
+ _size = capsule.readDouble("size", DEFAULT_SIZE);
+ _spin = capsule.readDouble("spin", DEFAULT_SPIN);
+ _mass = capsule.readDouble("mass", DEFAULT_MASS);
+ _color = (ColorRGBA) capsule.readSavable("color", DEFAULT_COLOR);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_offset, "offsetMS", DEFAULT_OFFSET);
+ capsule.write(_size, "size", DEFAULT_SIZE);
+ capsule.write(_spin, "spin", DEFAULT_SPIN);
+ capsule.write(_mass, "mass", DEFAULT_MASS);
+ capsule.write(_color, "color", DEFAULT_COLOR);
+ }
+
+ private static String convColorToHex(final ColorRGBA color) {
+ if (color == null) {
+ return null;
+ }
+ String sRed = Integer.toHexString((int) (color.getRed() * 255 + .5f));
+ if (sRed.length() == 1) {
+ sRed = "0" + sRed;
+ }
+ String sGreen = Integer.toHexString((int) (color.getGreen() * 255 + .5f));
+ if (sGreen.length() == 1) {
+ sGreen = "0" + sGreen;
+ }
+ String sBlue = Integer.toHexString((int) (color.getBlue() * 255 + .5f));
+ if (sBlue.length() == 1) {
+ sBlue = "0" + sBlue;
+ }
+ return "#" + sRed + sGreen + sBlue;
+ }
+
+ @Override
+ public String toString() {
+
+ final StringBuilder builder = new StringBuilder();
+ if (_offset > 0) {
+ builder.append("prev+");
+ builder.append((int) (_offset * 100));
+ builder.append("% age...");
+ }
+ if (_color != DEFAULT_COLOR) {
+ builder.append(" color:");
+ builder.append(convColorToHex(_color).toUpperCase());
+ builder.append(" a: ");
+ builder.append((int) (_color.getAlpha() * 100));
+ builder.append("%");
+ }
+
+ if (_size != DEFAULT_SIZE) {
+ builder.append(" size: " + _size);
+ }
+
+ if (_mass != DEFAULT_MASS) {
+ builder.append(" mass: " + _spin);
+ }
+
+ if (_spin != DEFAULT_SPIN) {
+ builder.append(" spin: " + _spin);
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java
new file mode 100644
index 0000000..09b422c
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SimpleParticleInfluenceFactory.java
@@ -0,0 +1,453 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Line3;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyLine3;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public final class SimpleParticleInfluenceFactory {
+
+ public static class BasicWind extends ParticleInfluence {
+ private double _strength;
+ private final Vector3 _windDirection = new Vector3();
+ private boolean _random, _rotateWithScene;
+ private final Vector3 _vector = new Vector3();
+
+ public BasicWind() {}
+
+ public BasicWind(final double windStr, final ReadOnlyVector3 windDir, final boolean addRandom,
+ final boolean rotateWithScene) {
+ _strength = windStr;
+ _windDirection.set(windDir);
+ _random = addRandom;
+ _rotateWithScene = rotateWithScene;
+ }
+
+ public double getStrength() {
+ return _strength;
+ }
+
+ public void setStrength(final double windStr) {
+ _strength = windStr;
+ }
+
+ public ReadOnlyVector3 getWindDirection() {
+ return _windDirection;
+ }
+
+ public void setWindDirection(final ReadOnlyVector3 windDir) {
+ _windDirection.set(windDir);
+ }
+
+ public boolean isRandom() {
+ return _random;
+ }
+
+ public void setRandom(final boolean addRandom) {
+ _random = addRandom;
+ }
+
+ public boolean isRotateWithScene() {
+ return _rotateWithScene;
+ }
+
+ public void setRotateWithScene(final boolean rotateWithScene) {
+ _rotateWithScene = rotateWithScene;
+ }
+
+ @Override
+ public void prepare(final ParticleSystem system) {
+ _vector.set(_windDirection);
+ final ReadOnlyMatrix3 mat = system.getEmitterTransform().getMatrix();
+ if (_rotateWithScene && !mat.isIdentity()) {
+ mat.applyPost(_vector, _vector);
+ }
+ }
+
+ @Override
+ public void apply(final double dt, final Particle p, final int index) {
+ final double tStr = (_random ? MathUtils.nextRandomFloat() * _strength : _strength);
+ _vector.scaleAdd(tStr * dt, p.getVelocity(), p.getVelocity());
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_strength, "strength", 1f);
+ capsule.write(_windDirection, "windDirection", new Vector3(Vector3.UNIT_X));
+ capsule.write(_random, "random", false);
+ capsule.write(_rotateWithScene, "rotateWithScene", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _strength = capsule.readDouble("strength", 1.0);
+ _windDirection.set((Vector3) capsule.readSavable("windDirection", new Vector3(Vector3.UNIT_X)));
+ _random = capsule.readBoolean("random", false);
+ _rotateWithScene = capsule.readBoolean("rotateWithScene", true);
+ }
+
+ @Override
+ public Class<? extends BasicWind> getClassTag() {
+ return this.getClass();
+ }
+ }
+
+ public static class BasicGravity extends ParticleInfluence {
+ private final Vector3 gravity = new Vector3();
+ private boolean rotateWithScene;
+ private final Vector3 vector = new Vector3();
+
+ public BasicGravity() {}
+
+ public BasicGravity(final ReadOnlyVector3 gravForce, final boolean rotateWithScene) {
+ gravity.set(gravForce);
+ this.rotateWithScene = rotateWithScene;
+ }
+
+ public ReadOnlyVector3 getGravityForce() {
+ return gravity;
+ }
+
+ public void setGravityForce(final ReadOnlyVector3 gravForce) {
+ gravity.set(gravForce);
+ }
+
+ public boolean isRotateWithScene() {
+ return rotateWithScene;
+ }
+
+ public void setRotateWithScene(final boolean rotateWithScene) {
+ this.rotateWithScene = rotateWithScene;
+ }
+
+ @Override
+ public void prepare(final ParticleSystem system) {
+ vector.set(gravity);
+ final ReadOnlyMatrix3 mat = system.getEmitterTransform().getMatrix();
+ if (rotateWithScene && !mat.isIdentity()) {
+ mat.applyPost(vector, vector);
+ }
+ }
+
+ @Override
+ public void apply(final double dt, final Particle p, final int index) {
+ vector.scaleAdd(dt, p.getVelocity(), p.getVelocity());
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(gravity, "gravity", new Vector3(Vector3.ZERO));
+ capsule.write(rotateWithScene, "rotateWithScene", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ gravity.set((Vector3) capsule.readSavable("gravity", new Vector3(Vector3.ZERO)));
+ rotateWithScene = capsule.readBoolean("rotateWithScene", true);
+ }
+
+ @Override
+ public Class<? extends BasicGravity> getClassTag() {
+ return this.getClass();
+ }
+ }
+
+ public static class BasicDrag extends ParticleInfluence {
+ private final Vector3 velocity = new Vector3();
+ private double dragCoefficient;
+
+ public BasicDrag() {}
+
+ public BasicDrag(final double dragCoef) {
+ dragCoefficient = dragCoef;
+ }
+
+ public double getDragCoefficient() {
+ return dragCoefficient;
+ }
+
+ public void setDragCoefficient(final double dragCoef) {
+ dragCoefficient = dragCoef;
+ }
+
+ @Override
+ public void apply(final double dt, final Particle p, final int index) {
+ // viscous drag
+ velocity.set(p.getVelocity());
+ p.getVelocity().addLocal(velocity.multiplyLocal(-dragCoefficient * dt * p.getInvMass()));
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(dragCoefficient, "dragCoefficient", 1.0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ dragCoefficient = capsule.readDouble("dragCoefficient", 1.0);
+ }
+
+ @Override
+ public Class<? extends BasicDrag> getClassTag() {
+ return this.getClass();
+ }
+ }
+
+ public static class BasicVortex extends ParticleInfluence {
+
+ public static final int VT_CYLINDER = 0;
+ public static final int VT_TORUS = 1;
+
+ private int _type = VT_CYLINDER;
+ private double _strength, _divergence, _height, _radius;
+ private final Line3 _axis = new Line3();
+ private boolean _random, _transformWithScene;
+ private final Vector3 _v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3();
+ private final Quaternion _rot = new Quaternion();
+ private final Line3 _line = new Line3();
+
+ public BasicVortex() {}
+
+ public BasicVortex(final double strength, final double divergence, final ReadOnlyLine3 axis,
+ final boolean random, final boolean transformWithScene) {
+ _strength = strength;
+ _divergence = divergence;
+ _axis.set(axis);
+ _height = 0f;
+ _radius = 1f;
+ _random = random;
+ _transformWithScene = transformWithScene;
+ }
+
+ public int getType() {
+ return _type;
+ }
+
+ public void setType(final int type) {
+ _type = type;
+ }
+
+ public double getStrength() {
+ return _strength;
+ }
+
+ public void setStrength(final double strength) {
+ _strength = strength;
+ }
+
+ public double getDivergence() {
+ return _divergence;
+ }
+
+ public void setDivergence(final double divergence) {
+ _divergence = divergence;
+ }
+
+ public ReadOnlyLine3 getAxis() {
+ return _axis;
+ }
+
+ public void setAxis(final ReadOnlyLine3 axis) {
+ _axis.set(axis);
+ }
+
+ public double getHeight() {
+ return _height;
+ }
+
+ public void setHeight(final double height) {
+ _height = height;
+ }
+
+ public double getRadius() {
+ return _radius;
+ }
+
+ public void setRadius(final double radius) {
+ _radius = radius;
+ }
+
+ public boolean isRandom() {
+ return _random;
+ }
+
+ public void setRandom(final boolean random) {
+ _random = random;
+ }
+
+ public boolean isTransformWithScene() {
+ return _transformWithScene;
+ }
+
+ public void setTransformWithScene(final boolean transformWithScene) {
+ _transformWithScene = transformWithScene;
+ }
+
+ @Override
+ public void prepare(final ParticleSystem system) {
+ _line.setOrigin(_axis.getOrigin());
+ _line.setDirection(_axis.getDirection());
+ final ReadOnlyMatrix3 mat = system.getEmitterTransform().getMatrix();
+ if (_transformWithScene && !mat.isIdentity()) {
+ final Vector3 temp = Vector3.fetchTempInstance();
+ mat.applyPost(_line.getOrigin(), temp);
+ _line.setOrigin(temp);
+ mat.applyPost(_line.getDirection(), temp);
+ _line.setDirection(temp);
+ Vector3.releaseTempInstance(temp);
+ }
+ if (_type == VT_CYLINDER) {
+ _rot.fromAngleAxis(-_divergence, _line.getDirection());
+ }
+ }
+
+ @Override
+ public void apply(final double dt, final Particle p, final int index) {
+ final double dtStr = dt * _strength * (_random ? MathUtils.nextRandomFloat() : 1f);
+ p.getPosition().subtract(_line.getOrigin(), _v1);
+ _line.getDirection().cross(_v1, v2);
+ if (v2.length() == 0) { // particle is on the axis
+ return;
+ }
+ v2.normalizeLocal();
+ if (_type == VT_CYLINDER) {
+ _rot.apply(v2, v2);
+ v2.scaleAdd(dtStr, p.getVelocity(), p.getVelocity());
+ return;
+ }
+ v2.cross(_line.getDirection(), _v1);
+ _v1.multiplyLocal(_radius);
+ _line.getDirection().scaleAdd(_height, _v1, _v1);
+ _v1.addLocal(_line.getOrigin());
+ _v1.subtractLocal(p.getPosition());
+ if (_v1.length() == 0) { // particle is on the ring
+ return;
+ }
+ _v1.normalizeLocal();
+ _v1.cross(v2, v3);
+ _rot.fromAngleAxis(-_divergence, v2);
+ _rot.apply(v3, v3);
+ v3.scaleAdd(dtStr, p.getVelocity(), p.getVelocity());
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_type, "type", VT_CYLINDER);
+ capsule.write(_strength, "strength", 1.0);
+ capsule.write(_divergence, "divergence", 0.0);
+ capsule.write(_axis, "axis", new Line3(new Vector3(), new Vector3(Vector3.UNIT_Y)));
+ capsule.write(_height, "height", 0.0);
+ capsule.write(_radius, "radius", 1.0);
+ capsule.write(_random, "random", false);
+ capsule.write(_transformWithScene, "transformWithScene", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _type = capsule.readInt("type", VT_CYLINDER);
+ _strength = capsule.readDouble("strength", 1.0);
+ _divergence = capsule.readDouble("divergence", 0.0);
+ _axis.set((Line3) capsule.readSavable("axis", new Line3(new Vector3(), new Vector3(Vector3.UNIT_Y))));
+ _height = capsule.readDouble("height", 0.0);
+ _radius = capsule.readDouble("radius", 1.0);
+ _random = capsule.readBoolean("random", false);
+ _transformWithScene = capsule.readBoolean("transformWithScene", true);
+ }
+
+ @Override
+ public Class<? extends BasicVortex> getClassTag() {
+ return this.getClass();
+ }
+ }
+
+ /**
+ * Not used.
+ */
+ private SimpleParticleInfluenceFactory() {}
+
+ /**
+ * Creates a basic wind that always blows in a single direction.
+ *
+ * @param windStr
+ * Max strength of wind.
+ * @param windDir
+ * Direction wind should blow.
+ * @param addRandom
+ * randomly alter the strength of the wind by 0-100%
+ * @param rotateWithScene
+ * rotate the wind direction with the particle system
+ * @return ParticleInfluence
+ */
+ public static ParticleInfluence createBasicWind(final double windStr, final ReadOnlyVector3 windDir,
+ final boolean addRandom, final boolean rotateWithScene) {
+ return new BasicWind(windStr, windDir, addRandom, rotateWithScene);
+ }
+
+ /**
+ * Create a basic gravitational force.
+ *
+ * @param rotateWithScene
+ * rotate the gravity vector with the particle system
+ * @return ParticleInfluence
+ */
+ public static ParticleInfluence createBasicGravity(final ReadOnlyVector3 gravForce, final boolean rotateWithScene) {
+ return new BasicGravity(gravForce, rotateWithScene);
+ }
+
+ /**
+ * Create a basic drag force that will use the given drag coefficient. Drag is determined by figuring the current
+ * velocity and reversing it, then multiplying by the drag coefficient and dividing by the particle mass.
+ *
+ * @param dragCoef
+ * Should be positive. Larger values mean more drag but possibly more instability.
+ * @return ParticleInfluence
+ */
+ public static ParticleInfluence createBasicDrag(final double dragCoef) {
+ return new BasicDrag(dragCoef);
+ }
+
+ /**
+ * Creates a basic vortex.
+ *
+ * @param strength
+ * Max strength of vortex.
+ * @param divergence
+ * The divergence in radians from the tangent vector
+ * @param axis
+ * The center of the vortex.
+ * @param random
+ * randomly alter the strength of the vortex by 0-100%
+ * @param transformWithScene
+ * transform the axis with the particle system
+ * @return ParticleInfluence
+ */
+ public static ParticleInfluence createBasicVortex(final double strength, final double divergence,
+ final ReadOnlyLine3 axis, final boolean random, final boolean transformWithScene) {
+ return new BasicVortex(strength, divergence, axis, random, transformWithScene);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java
new file mode 100644
index 0000000..08e4ffb
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/SwarmInfluence.java
@@ -0,0 +1,185 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * Simple swarming influence for use with particles.
+ */
+public class SwarmInfluence extends ParticleInfluence {
+
+ private double _swarmRangeSQ;
+ private final Vector3 _swarmOffset = new Vector3();
+ private final Vector3 _swarmPoint = new Vector3();
+
+ public static final double DEFAULT_SWARM_RANGE_SQ = 0.01;
+ public static final double DEFAULT_DEVIANCE = MathUtils.DEG_TO_RAD * 15;
+ public static final double DEFAULT_TURN_SPEED = MathUtils.DEG_TO_RAD * 180;
+ public static final double DEFAULT_SPEED_BUMP = .1;
+ public static final double DEFAULT_MAX_SPEED = .2;
+
+ private double _deviance = DEFAULT_DEVIANCE;
+ private double _turnSpeed = DEFAULT_TURN_SPEED;
+ private double _speedBump = DEFAULT_SPEED_BUMP;
+ private double _maxSpeed = DEFAULT_MAX_SPEED;
+
+ private transient double _maxSpeedSQ = DEFAULT_MAX_SPEED * DEFAULT_MAX_SPEED;
+
+ public SwarmInfluence() {
+ _swarmRangeSQ = DEFAULT_SWARM_RANGE_SQ;
+ }
+
+ public SwarmInfluence(final ReadOnlyVector3 offset, final double swarmRange) {
+ super();
+ _swarmRangeSQ = swarmRange * swarmRange;
+ _swarmOffset.set(offset);
+ }
+
+ @Override
+ public void prepare(final ParticleSystem system) {
+ super.prepare(system);
+ _swarmPoint.set(system.getOriginCenter()).addLocal(_swarmOffset);
+ }
+
+ @Override
+ public void apply(final double dt, final Particle particle, final int index) {
+ final Vector3 pVelocity = particle.getVelocity();
+ // determine if the particle is in the inner or outer zone
+ final double pDist = particle.getPosition().distanceSquared(_swarmPoint);
+ final Vector3 workVect = Vector3.fetchTempInstance();
+ final Vector3 workVect2 = Vector3.fetchTempInstance();
+ final Matrix3 workMat = Matrix3.fetchTempInstance();
+ workVect.set(_swarmPoint).subtractLocal(particle.getPosition()).normalizeLocal();
+ workVect2.set(pVelocity).normalizeLocal();
+ if (pDist > _swarmRangeSQ) {
+ // IN THE OUTER ZONE...
+ // Determine if the angle between particle velocity and a vector to
+ // the swarmPoint is less than the accepted deviance
+ final double angle = workVect.smallestAngleBetween(workVect2);
+ if (angle < _deviance) {
+ // if it is, increase the speed speedBump over time
+ if (pVelocity.lengthSquared() < _maxSpeedSQ) {
+ final double change = _speedBump * dt;
+ workVect2.multiplyLocal(change); // where workVector2 = pVelocity.normalizeLocal()
+ pVelocity.addLocal(workVect2);
+ }
+ } else {
+ final Vector3 axis = workVect2.crossLocal(workVect);
+ // if it is not, shift the velocity to bring it back in line
+ if ((Double.doubleToLongBits(pVelocity.lengthSquared()) & 0x1d) != 0) {
+ workMat.fromAngleAxis(_turnSpeed * dt, axis);
+ } else {
+ workMat.fromAngleAxis(-_turnSpeed * dt, axis);
+ }
+ workMat.applyPost(pVelocity, pVelocity);
+ }
+ } else {
+ final Vector3 axis = workVect2.crossLocal(workVect);
+ // IN THE INNER ZONE...
+ // Alter the heading based on how fast we are going
+ if ((index & 0x1f) != 0) {
+ workMat.fromAngleAxis(_turnSpeed * dt, axis);
+ } else {
+ workMat.fromAngleAxis(-_turnSpeed * dt, axis);
+ }
+ workMat.applyPost(pVelocity, pVelocity);
+ }
+ Vector3.releaseTempInstance(workVect);
+ Vector3.releaseTempInstance(workVect2);
+ Matrix3.releaseTempInstance(workMat);
+ }
+
+ public double getSwarmRange() {
+ return Math.sqrt(_swarmRangeSQ);
+ }
+
+ public void setSwarmRange(final double swarmRange) {
+ _swarmRangeSQ = swarmRange * swarmRange;
+ }
+
+ public ReadOnlyVector3 getSwarmOffset() {
+ return _swarmOffset;
+ }
+
+ public void setSwarmOffset(final ReadOnlyVector3 offset) {
+ _swarmOffset.set(offset);
+ }
+
+ public double getDeviance() {
+ return _deviance;
+ }
+
+ public void setDeviance(final double deviance) {
+ _deviance = deviance;
+ }
+
+ public double getSpeedBump() {
+ return _speedBump;
+ }
+
+ public void setSpeedBump(final double speedVariance) {
+ _speedBump = speedVariance;
+ }
+
+ public double getTurnSpeed() {
+ return _turnSpeed;
+ }
+
+ public void setTurnSpeed(final double turnSpeed) {
+ _turnSpeed = turnSpeed;
+ }
+
+ public double getMaxSpeed() {
+ return _maxSpeed;
+ }
+
+ public void setMaxSpeed(final double maxSpeed) {
+ _maxSpeed = maxSpeed;
+ _maxSpeedSQ = maxSpeed * maxSpeed;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ final OutputCapsule cap = capsule;
+ cap.write(_swarmRangeSQ, "swarmRangeSQ", DEFAULT_SWARM_RANGE_SQ);
+ cap.write(_deviance, "deviance", DEFAULT_DEVIANCE);
+ cap.write(_turnSpeed, "turnSpeed", DEFAULT_TURN_SPEED);
+ cap.write(_speedBump, "speedBump", DEFAULT_SPEED_BUMP);
+ cap.write(_maxSpeed, "maxSpeed", DEFAULT_MAX_SPEED);
+ cap.write(_swarmOffset, "swarmOffset", new Vector3());
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ final InputCapsule cap = capsule;
+ _swarmRangeSQ = cap.readDouble("swarmRangeSQ", DEFAULT_SWARM_RANGE_SQ);
+ _deviance = cap.readDouble("deviance", DEFAULT_DEVIANCE);
+ _turnSpeed = cap.readDouble("turnSpeed", DEFAULT_TURN_SPEED);
+ _speedBump = cap.readDouble("speedBump", DEFAULT_SPEED_BUMP);
+ _maxSpeed = cap.readDouble("maxSpeed", DEFAULT_MAX_SPEED);
+ _swarmOffset.set((Vector3) cap.readSavable("swarmOffset", new Vector3()));
+ }
+
+ @Override
+ public Class<? extends SwarmInfluence> getClassTag() {
+ return getClass();
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.java
new file mode 100644
index 0000000..3b73b03
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/TexAnimation.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public class TexAnimation implements Savable {
+
+ protected List<AnimationEntry> _entries = new ArrayList<AnimationEntry>();
+
+ public void addEntry(final AnimationEntry entry) {
+ _entries.add(entry);
+ }
+
+ public void addEntry(final int index, final AnimationEntry entry) {
+ _entries.add(index, entry);
+ }
+
+ public void clearEntries() {
+ _entries.clear();
+ }
+
+ public Iterator<AnimationEntry> getEntries() {
+ return _entries.iterator();
+ }
+
+ public void removeEntry(final AnimationEntry entry) {
+ _entries.remove(entry);
+ }
+
+ public void removeEntry(final int index) {
+ _entries.remove(index);
+ }
+
+ public int getTexIndexAtAge(double age, double maxAge, final ParticleSystem particles) {
+ // find what AnimationEntry we last passed...
+ double trAge = 0, lastAge = 0;
+ AnimationEntry latest = null;
+ maxAge /= 1000f;
+ age /= 1000f;
+ for (int i = 0; i < _entries.size(); i++) {
+ final AnimationEntry entry = _entries.get(i);
+ trAge += (entry.getOffset() * maxAge);
+ if (trAge <= age) {
+ latest = entry;
+ lastAge = trAge;
+ } else {
+ break;
+ }
+ }
+
+ if (latest == null) {
+ return particles.getStartTexIndex();
+ } else {
+ int index = (int) ((age - lastAge) / latest._rate);
+ index %= latest._frames.length;
+ return latest._frames[index];
+ }
+ }
+
+ public Class<? extends TexAnimation> getClassTag() {
+ return getClass();
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _entries = capsule.readSavableList("entries", null);
+ if (_entries == null) {
+ _entries = new ArrayList<AnimationEntry>();
+ }
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.writeSavableList(_entries, "entries", null);
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java
new file mode 100644
index 0000000..5a0481a
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/WanderInfluence.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class WanderInfluence extends ParticleInfluence {
+
+ public static final double DEFAULT_RADIUS = .03f;
+ public static final double DEFAULT_DISTANCE = .2f;
+ public static final double DEFAULT_JITTER = .005f;
+
+ private double _wanderRadius = DEFAULT_RADIUS;
+ private double _wanderDistance = DEFAULT_DISTANCE;
+ private double _wanderJitter = DEFAULT_JITTER;
+
+ private ArrayList<Vector3> _wanderTargets = new ArrayList<Vector3>(1);
+ private final Vector3 _workVect = new Vector3();
+
+ @Override
+ public void prepare(final ParticleSystem system) {
+ if (_wanderTargets.size() != system.getNumParticles()) {
+ _wanderTargets = new ArrayList<Vector3>(system.getNumParticles());
+ for (int x = system.getNumParticles(); --x >= 0;) {
+ _wanderTargets.add(new Vector3(system.getEmissionDirection()).normalizeLocal());
+ }
+ }
+ }
+
+ @Override
+ public void apply(final double dt, final Particle particle, final int index) {
+ if (_wanderRadius == 0 && _wanderDistance == 0 && _wanderJitter == 0) {
+ return;
+ }
+
+ final Vector3 wanderTarget = _wanderTargets.get(index);
+
+ wanderTarget.addLocal(calcNewJitter(), calcNewJitter(), calcNewJitter());
+ wanderTarget.normalizeLocal();
+ wanderTarget.multiplyLocal(_wanderRadius);
+
+ _workVect.set(particle.getVelocity()).normalizeLocal().multiplyLocal(_wanderDistance);
+ _workVect.addLocal(wanderTarget).normalizeLocal();
+ _workVect.multiplyLocal(particle.getVelocity().length());
+ particle.getVelocity().set(_workVect);
+ }
+
+ private double calcNewJitter() {
+ return ((MathUtils.nextRandomFloat() * 2.0f) - 1.0f) * _wanderJitter;
+ }
+
+ public double getWanderDistance() {
+ return _wanderDistance;
+ }
+
+ public void setWanderDistance(final double wanderDistance) {
+ _wanderDistance = wanderDistance;
+ }
+
+ public double getWanderJitter() {
+ return _wanderJitter;
+ }
+
+ public void setWanderJitter(final double wanderJitter) {
+ _wanderJitter = wanderJitter;
+ }
+
+ public double getWanderRadius() {
+ return _wanderRadius;
+ }
+
+ public void setWanderRadius(final double wanderRadius) {
+ _wanderRadius = wanderRadius;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ final OutputCapsule cap = capsule;
+ cap.write(_wanderRadius, "wanderRadius", DEFAULT_RADIUS);
+ cap.write(_wanderDistance, "wanderDistance", DEFAULT_DISTANCE);
+ cap.write(_wanderJitter, "wanderJitter", DEFAULT_JITTER);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ final InputCapsule cap = capsule;
+ _wanderRadius = cap.readDouble("wanderRadius", DEFAULT_RADIUS);
+ _wanderDistance = cap.readDouble("wanderDistance", DEFAULT_DISTANCE);
+ _wanderJitter = cap.readDouble("wanderJitter", DEFAULT_JITTER);
+ }
+
+ @Override
+ public Class<? extends WanderInfluence> getClassTag() {
+ return getClass();
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.java
new file mode 100644
index 0000000..9e2a75c
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/LineSegmentEmitter.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import java.io.IOException;
+
+import com.ardor3d.math.LineSegment3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class LineSegmentEmitter extends SavableParticleEmitter {
+
+ private LineSegment3 _source;
+
+ public LineSegmentEmitter() {
+ _source = new LineSegment3();
+ }
+
+ /**
+ * @param source
+ * the segment to use as our source
+ */
+ public LineSegmentEmitter(final LineSegment3 source) {
+ _source = source;
+ }
+
+ public void setSource(final LineSegment3 source) {
+ _source = source;
+ }
+
+ public LineSegment3 getSource() {
+ return _source;
+ }
+
+ public Vector3 randomEmissionPoint(final Vector3 store) {
+ Vector3 rVal = store;
+ if (rVal == null) {
+ rVal = new Vector3();
+ }
+
+ getSource().random(rVal);
+ return rVal;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _source = (LineSegment3) capsule.readSavable("source", null);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_source, "source", null);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.java
new file mode 100644
index 0000000..48fb32d
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/MeshEmitter.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class MeshEmitter extends SavableParticleEmitter {
+
+ private Mesh _source;
+ private boolean _onlyVertices;
+
+ public MeshEmitter() {}
+
+ /**
+ * @param source
+ * the mesh to use as our source
+ * @param onlyVertices
+ * if true, only the vertices of the emitter should be used for spawning particles. Otherwise, the mesh's
+ * face surfaces should be used.
+ */
+ public MeshEmitter(final Mesh source, final boolean onlyVertices) {
+ _source = source;
+ _onlyVertices = onlyVertices;
+ }
+
+ public void setSource(final Mesh source) {
+ _source = source;
+ }
+
+ public Mesh getSource() {
+ return _source;
+ }
+
+ public void setOnlyVertices(final boolean onlyVertices) {
+ _onlyVertices = onlyVertices;
+ }
+
+ public boolean isOnlyVertices() {
+ return _onlyVertices;
+ }
+
+ public Vector3 randomEmissionPoint(final Vector3 store) {
+ Vector3 rVal = store;
+ if (rVal == null) {
+ rVal = new Vector3();
+ }
+
+ if (_onlyVertices) {
+ getSource().getMeshData().randomVertex(rVal);
+ } else {
+ getSource().getMeshData().randomPointOnPrimitives(rVal);
+ }
+ return rVal;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _source = (Mesh) capsule.readSavable("source", null);
+ _onlyVertices = capsule.readBoolean("onlyVertices", false);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_source, "source", null);
+ capsule.write(_onlyVertices, "onlyVertices", false);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.java
new file mode 100644
index 0000000..b382596
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/ParticleEmitter.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import com.ardor3d.math.Vector3;
+
+public interface ParticleEmitter {
+
+ /**
+ * Get the next point from this emitter.
+ *
+ * @param store
+ * the vector to store our point in. If null, a new one is created.
+ * @return the vector we stored in
+ */
+ public Vector3 randomEmissionPoint(final Vector3 store);
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.java
new file mode 100644
index 0000000..9e6b84d
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/PointEmitter.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class PointEmitter extends SavableParticleEmitter {
+
+ public PointEmitter() {}
+
+ public Vector3 randomEmissionPoint(final Vector3 store) {
+ Vector3 rVal = store;
+ if (rVal == null) {
+ rVal = new Vector3();
+ }
+
+ rVal.set(0, 0, 0);
+ return rVal;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public void read(final InputCapsule capsule) throws IOException {}
+
+ public void write(final OutputCapsule capsule) throws IOException {}
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.java
new file mode 100644
index 0000000..18264f4
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RectangleEmitter.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Rectangle3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class RectangleEmitter extends SavableParticleEmitter {
+
+ private Rectangle3 _source;
+
+ public RectangleEmitter() {
+ _source = new Rectangle3();
+ }
+
+ /**
+ * @param source
+ * the rectangle to use as our source
+ */
+ public RectangleEmitter(final Rectangle3 source) {
+ _source = source;
+ }
+
+ public void setSource(final Rectangle3 source) {
+ _source = source;
+ }
+
+ public Rectangle3 getSource() {
+ return _source;
+ }
+
+ public Vector3 randomEmissionPoint(final Vector3 store) {
+ Vector3 rVal = store;
+ if (rVal == null) {
+ rVal = new Vector3();
+ }
+
+ getSource().random(rVal);
+ return rVal;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _source = (Rectangle3) capsule.readSavable("source", null);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_source, "source", null);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.java
new file mode 100644
index 0000000..738bb60
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/RingEmitter.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Ring;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class RingEmitter extends SavableParticleEmitter {
+
+ private Ring _source;
+
+ public RingEmitter() {
+ _source = new Ring();
+ }
+
+ /**
+ * @param source
+ * the ring to use as our source
+ */
+ public RingEmitter(final Ring source) {
+ _source = source;
+ }
+
+ public void setSource(final Ring source) {
+ _source = source;
+ }
+
+ public Ring getSource() {
+ return _source;
+ }
+
+ public Vector3 randomEmissionPoint(final Vector3 store) {
+ Vector3 rVal = store;
+ if (rVal == null) {
+ rVal = new Vector3();
+ }
+
+ getSource().random(rVal);
+ return rVal;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _source = (Ring) capsule.readSavable("source", null);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_source, "source", null);
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.java
new file mode 100644
index 0000000..a11413a
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/particle/emitter/SavableParticleEmitter.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.particle.emitter;
+
+import com.ardor3d.util.export.Savable;
+
+public abstract class SavableParticleEmitter implements Savable, ParticleEmitter {
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public Class<? extends SavableParticleEmitter> getClassTag() {
+ return this.getClass();
+ }
+
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java
new file mode 100644
index 0000000..7df9833
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/HeightGenerator.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+/**
+ * <code>HeightGenerator</code> Base interface for all waterheight generators used by the projected grid mesh.
+ */
+public interface HeightGenerator {
+ /**
+ * How to animate/set heights on a grid
+ *
+ * @param x
+ * x position to get height for
+ * @param z
+ * z position to get height for
+ * @param time
+ * time to get height for
+ * @return height for specified position
+ */
+ public double getHeight(double x, double z, double time);
+
+ public double getMaximumHeight();
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.java
new file mode 100644
index 0000000..9599306
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ImprovedNoise.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+import com.ardor3d.math.MathUtils;
+
+/**
+ * <code>ImprovedNoise</code> Fast perlin noise.
+ *
+ * @author Ken Perlin
+ */
+public final class ImprovedNoise {
+ public static double noise(double x, double y, double z) {
+ final int X = (int) MathUtils.floor(x) & 255, // FIND UNIT CUBE THAT
+ Y = (int) MathUtils.floor(y) & 255, // CONTAINS POINT.
+ Z = (int) MathUtils.floor(z) & 255;
+ x -= MathUtils.floor(x); // FIND RELATIVE X,Y,Z
+ y -= MathUtils.floor(y); // OF POINT IN CUBE.
+ z -= MathUtils.floor(z);
+ final double u = fade(x), // COMPUTE FADE CURVES
+ v = fade(y), // FOR EACH OF X,Y,Z.
+ w = fade(z);
+ final int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF
+ B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS,
+
+ return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), // AND ADD
+ grad(p[BA], x - 1, y, z)), // BLENDED
+ lerp(u, grad(p[AB], x, y - 1, z), // RESULTS
+ grad(p[BB], x - 1, y - 1, z))),// FROM 8
+ lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), // CORNERS
+ grad(p[BA + 1], x - 1, y, z - 1)), // OF CUBE
+ lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))));
+ }
+
+ private static double fade(final double t) {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+ }
+
+ private static double lerp(final double t, final double a, final double b) {
+ return a + t * (b - a);
+ }
+
+ private static double grad(final int hash, final double x, final double y, final double z) {
+ final int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
+ final double u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
+ v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+ }
+
+ private static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53,
+ 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0,
+ 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171,
+ 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
+ 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187,
+ 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226,
+ 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
+ 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129,
+ 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
+ 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199,
+ 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
+ 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 };
+
+ static {
+ for (int i = 0; i < 256; i++) {
+ p[256 + i] = p[i] = permutation[i];
+ }
+ }
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java
new file mode 100644
index 0000000..9667b81
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/ProjectedGrid.java
@@ -0,0 +1,662 @@
+/**
+ * Copyright (c) 2008 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Stack;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.ExtendedCamera;
+import com.ardor3d.util.Timer;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.geom.Debugger;
+
+/**
+ * <code>ProjectedGrid</code> Projected grid mesh
+ */
+public class ProjectedGrid extends Mesh {
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(ProjectedGrid.class.getName());
+
+ private final int sizeX;
+ private final int sizeY;
+
+ private FloatBuffer vertBuf;
+ private final FloatBuffer normBuf;
+ private final FloatBuffer texs;
+ private IntBuffer indexBuffer;
+
+ private final ExtendedCamera mainCamera = new ExtendedCamera();
+ private final Camera projectorCamera = new Camera();
+
+ private final Vector4 origin = new Vector4();
+ private final Vector4 direction = new Vector4();
+ private final Vector2 source = new Vector2();
+ private final Matrix4 rangeMatrix = new Matrix4();
+
+ private final Vector4 intersectBottomLeft = new Vector4();
+ private final Vector4 intersectTopLeft = new Vector4();
+ private final Vector4 intersectTopRight = new Vector4();
+ private final Vector4 intersectBottomRight = new Vector4();
+
+ private final Vector3 planeIntersection = new Vector3();
+
+ public boolean freezeProjector = false;
+ private final Timer timer;
+ private final Camera camera;
+
+ private final HeightGenerator heightGenerator;
+ private final float textureScale;
+
+ private double projectorMinHeight = 100.0;
+ private final Vector3[] intersections = new Vector3[24];
+
+ private final float[] vertBufArray;
+ private final float[] normBufArray;
+ private final float[] texBufArray;
+
+ private int nrUpdateThreads = 1;
+ private final ExecutorService executorService = Executors.newCachedThreadPool(new DeamonThreadFactory());
+ private final Stack<Future<?>> futureStack = new Stack<Future<?>>();
+
+ private final int connections[] = { 0, 1, 2, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 5, 6, 7, };
+
+ // Debug drawing
+ private boolean drawDebug = false;
+
+ public ProjectedGrid(final String name, final Camera camera, final int sizeX, final int sizeY,
+ final float textureScale, final HeightGenerator heightGenerator, final Timer timer) {
+ super(name);
+ this.sizeX = sizeX;
+ this.sizeY = sizeY;
+ this.textureScale = textureScale;
+ this.heightGenerator = heightGenerator;
+ this.camera = camera;
+ this.timer = timer;
+
+ buildVertices(sizeX * sizeY);
+ texs = BufferUtils.createVector2Buffer(_meshData.getVertexCount());
+ _meshData.setTextureBuffer(texs, 0);
+ normBuf = BufferUtils.createVector3Buffer(_meshData.getVertexCount());
+ _meshData.setNormalBuffer(normBuf);
+
+ vertBufArray = new float[_meshData.getVertexCount() * 3];
+ normBufArray = new float[_meshData.getVertexCount() * 3];
+ texBufArray = new float[_meshData.getVertexCount() * 2];
+
+ for (int i = 0; i < 24; i++) {
+ intersections[i] = new Vector3();
+ }
+ }
+
+ public void setNrUpdateThreads(final int nrUpdateThreads) {
+ this.nrUpdateThreads = nrUpdateThreads;
+ if (this.nrUpdateThreads < 1) {
+ this.nrUpdateThreads = 1;
+ }
+ }
+
+ public int getNrUpdateThreads() {
+ return nrUpdateThreads;
+ }
+
+ public void setFreezeUpdate(final boolean freeze) {
+ freezeProjector = freeze;
+ }
+
+ public boolean isFreezeUpdate() {
+ return freezeProjector;
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ final boolean doDraw = update();
+ if (doDraw) {
+ super.render(renderer);
+ }
+
+ if (drawDebug) {
+ Debugger.drawCameraFrustum(renderer, mainCamera, new ColorRGBA(1, 0, 0, 1), (short) 0xFFFF, true);
+ Debugger.drawCameraFrustum(renderer, projectorCamera, new ColorRGBA(0, 1, 1, 1), (short) 0xFFFF, true);
+ }
+ }
+
+ public boolean update() {
+ final double upperBound = heightGenerator.getMaximumHeight();
+
+ if (!freezeProjector) {
+ mainCamera.set(camera);
+
+ final Vector3 tmp = new Vector3();
+ getWorldTransform().applyInverse(mainCamera.getLocation(), tmp);
+ mainCamera.setLocation(tmp);
+ getWorldTransform().applyInverseVector(mainCamera.getLeft(), tmp);
+ mainCamera.setLeft(tmp);
+ getWorldTransform().applyInverseVector(mainCamera.getUp(), tmp);
+ mainCamera.setUp(tmp);
+ getWorldTransform().applyInverseVector(mainCamera.getDirection(), tmp);
+ mainCamera.setDirection(tmp);
+ }
+
+ final ReadOnlyVector3 mainCameraLocation = mainCamera.getLocation();
+ if (mainCameraLocation.getY() > 0.0 && mainCameraLocation.getY() < upperBound + mainCamera.getFrustumNear()) {
+ mainCamera.setLocation(mainCameraLocation.getX(), upperBound + mainCamera.getFrustumNear(),
+ mainCameraLocation.getZ());
+ } else if (mainCameraLocation.getY() < 0.0
+ && mainCameraLocation.getY() > -upperBound - mainCamera.getFrustumNear()) {
+ mainCamera.setLocation(mainCameraLocation.getX(), -upperBound - mainCamera.getFrustumNear(),
+ mainCameraLocation.getZ());
+ }
+ mainCamera.calculateFrustum();
+ final Vector3[] corners = mainCamera.getCorners();
+
+ int nrPoints = 0;
+
+ // check intersections of frustum connections with upper and lower bound
+ final Vector3 tmpStorage = Vector3.fetchTempInstance();
+ for (int i = 0; i < 8; i++) {
+ final int source = connections[i * 2];
+ final int destination = connections[i * 2 + 1];
+
+ if (corners[source].getY() > upperBound && corners[destination].getY() < upperBound
+ || corners[source].getY() < upperBound && corners[destination].getY() > upperBound) {
+ getWorldIntersection(upperBound, corners[source], corners[destination], intersections[nrPoints++],
+ tmpStorage);
+ }
+ if (corners[source].getY() > -upperBound && corners[destination].getY() < -upperBound
+ || corners[source].getY() < -upperBound && corners[destination].getY() > -upperBound) {
+ getWorldIntersection(-upperBound, corners[source], corners[destination], intersections[nrPoints++],
+ tmpStorage);
+ }
+ }
+ // check if any of the frustums corner vertices lie between the upper and lower bound planes
+ for (int i = 0; i < 8; i++) {
+ if (corners[i].getY() < upperBound && corners[i].getY() > -upperBound) {
+ intersections[nrPoints++].set(corners[i]);
+ }
+ }
+
+ if (nrPoints == 0) {
+ // No intersection, grid not visible
+ return false;
+ }
+
+ // set projector
+ projectorCamera.set(mainCamera);
+
+ // force the projector to point at the plane
+ if (projectorCamera.getLocation().getY() > 0.0 && projectorCamera.getDirection().getY() > 0.0
+ || projectorCamera.getLocation().getY() < 0.0 && projectorCamera.getDirection().getY() < 0.0) {
+ projectorCamera.setDirection(new Vector3(projectorCamera.getDirection().getX(), -projectorCamera
+ .getDirection().getY(), projectorCamera.getDirection().getZ()));
+ projectorCamera.setUp(projectorCamera.getDirection().cross(projectorCamera.getLeft(), null)
+ .normalizeLocal());
+ }
+
+ // find the plane intersection point
+ source.set(0.5, 0.5);
+ getWorldIntersection(0.0, source, projectorCamera.getModelViewProjectionInverseMatrix(), planeIntersection);
+
+ // force the projector to be a certain distance above the plane
+ final ReadOnlyVector3 cameraLocation = projectorCamera.getLocation();
+ if (cameraLocation.getY() > 0.0 && cameraLocation.getY() < projectorMinHeight * 2) {
+ final double delta = (projectorMinHeight * 2 - cameraLocation.getY()) / (projectorMinHeight * 2);
+
+ projectorCamera.setLocation(cameraLocation.getX(), projectorMinHeight * 2 - projectorMinHeight * delta,
+ cameraLocation.getZ());
+ } else if (cameraLocation.getY() < 0.0 && cameraLocation.getY() > -projectorMinHeight * 2) {
+ final double delta = (-projectorMinHeight * 2 - cameraLocation.getY()) / (-projectorMinHeight * 2);
+
+ projectorCamera.setLocation(cameraLocation.getX(), -projectorMinHeight * 2 + projectorMinHeight * delta,
+ cameraLocation.getZ());
+ }
+
+ // restrict the intersection point to be a certain distance from the camera in plane coords
+ planeIntersection.subtractLocal(projectorCamera.getLocation());
+ planeIntersection.setY(0.0);
+ final double length = planeIntersection.length();
+ if (length > Math.abs(projectorCamera.getLocation().getY())) {
+ planeIntersection.normalizeLocal();
+ planeIntersection.multiplyLocal(Math.abs(projectorCamera.getLocation().getY()));
+ } else if (length < MathUtils.EPSILON) {
+ planeIntersection.addLocal(projectorCamera.getUp());
+ planeIntersection.setY(0.0);
+ planeIntersection.normalizeLocal();
+ planeIntersection.multiplyLocal(0.1); // TODO: magic number
+ }
+ planeIntersection.addLocal(projectorCamera.getLocation());
+ planeIntersection.setY(0.0);
+
+ // point projector at the new intersection point
+ projectorCamera.lookAt(planeIntersection, Vector3.UNIT_Y);
+
+ // transform points to projector space
+ final ReadOnlyMatrix4 modelViewProjectionMatrix = projectorCamera.getModelViewProjectionMatrix();
+ final Vector4 spaceTransformation = new Vector4();
+ for (int i = 0; i < nrPoints; i++) {
+ spaceTransformation.set(intersections[i].getX(), 0.0, intersections[i].getZ(), 1.0);
+ modelViewProjectionMatrix.applyPre(spaceTransformation, spaceTransformation);
+ intersections[i].set(spaceTransformation.getX(), spaceTransformation.getY(), 0);
+ intersections[i].divideLocal(spaceTransformation.getW());
+ }
+
+ // find min/max in projector space
+ double minX = Double.MAX_VALUE;
+ double maxX = -Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+ for (int i = 0; i < nrPoints; i++) {
+ if (intersections[i].getX() < minX) {
+ minX = intersections[i].getX();
+ }
+ if (intersections[i].getX() > maxX) {
+ maxX = intersections[i].getX();
+ }
+ if (intersections[i].getY() < minY) {
+ minY = intersections[i].getY();
+ }
+ if (intersections[i].getY() > maxY) {
+ maxY = intersections[i].getY();
+ }
+ }
+
+ // create range matrix
+ rangeMatrix.setIdentity();
+ rangeMatrix.setM00(maxX - minX);
+ rangeMatrix.setM11(maxY - minY);
+ rangeMatrix.setM30(minX);
+ rangeMatrix.setM31(minY);
+
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix = projectorCamera.getModelViewProjectionInverseMatrix();
+ rangeMatrix.multiplyLocal(modelViewProjectionInverseMatrix);
+
+ // convert screen coords to homogenous world coords with new range matrix
+ source.set(0.5, 0.5);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectBottomLeft);
+ source.set(0.5, 1);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectTopLeft);
+ source.set(1, 1);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectTopRight);
+ source.set(1, 0.5);
+ getWorldIntersectionHomogenous(0.0, source, rangeMatrix, intersectBottomRight);
+
+ // update data
+ if (nrUpdateThreads <= 1) {
+ updateGrid(0, sizeY);
+ } else {
+ for (int i = 0; i < nrUpdateThreads; i++) {
+ final int from = sizeY * i / (nrUpdateThreads);
+ final int to = sizeY * (i + 1) / (nrUpdateThreads);
+ final Future<?> future = executorService.submit(new Runnable() {
+ public void run() {
+ updateGrid(from, to);
+ }
+ });
+ futureStack.push(future);
+ }
+ try {
+ while (!futureStack.isEmpty()) {
+ futureStack.pop().get();
+ }
+ } catch (final InterruptedException ex) {
+ logger.log(Level.SEVERE, "InterruptedException in thread execution", ex);
+ } catch (final ExecutionException ex) {
+ logger.log(Level.SEVERE, "ExecutionException in thread execution", ex);
+ }
+ }
+
+ vertBuf.rewind();
+ vertBuf.put(vertBufArray);
+
+ texs.rewind();
+ texs.put(texBufArray);
+
+ normBuf.rewind();
+ normBuf.put(normBufArray);
+
+ return true;
+ }
+
+ private boolean getWorldIntersection(final double planeHeight, final Vector3 source, final Vector3 destination,
+ final Vector3 store, final Vector3 tmpStorage) {
+ final Vector3 origin = store.set(source);
+ final Vector3 direction = tmpStorage.set(destination).subtractLocal(origin);
+
+ final double t = (planeHeight - origin.getY()) / (direction.getY());
+
+ direction.multiplyLocal(t);
+ origin.addLocal(direction);
+
+ return t >= 0.0 && t <= 1.0;
+ }
+
+ private void updateGrid(final int from, final int to) {
+ final double time = timer.getTimeInSeconds();
+ final double du = 1.0f / (double) (sizeX - 1);
+ final double dv = 1.0f / (double) (sizeY - 1);
+
+ final Vector4 pointTop = Vector4.fetchTempInstance();
+ final Vector4 pointFinal = Vector4.fetchTempInstance();
+ final Vector4 pointBottom = Vector4.fetchTempInstance();
+
+ int smallerFrom = from;
+ if (smallerFrom > 0) {
+ smallerFrom--;
+ }
+ int biggerTo = to;
+ if (biggerTo < sizeY) {
+ biggerTo++;
+ }
+ double u = 0, v = smallerFrom * dv;
+ int index = smallerFrom * sizeX * 3;
+ for (int y = smallerFrom; y < biggerTo; y++) {
+ for (int x = 0; x < sizeX; x++) {
+ pointTop.lerpLocal(intersectTopLeft, intersectTopRight, u);
+ pointBottom.lerpLocal(intersectBottomLeft, intersectBottomRight, u);
+ pointFinal.lerpLocal(pointTop, pointBottom, v);
+
+ pointFinal.setX(pointFinal.getX() / pointFinal.getW());
+ pointFinal.setZ(pointFinal.getZ() / pointFinal.getW());
+ pointFinal.setY(heightGenerator.getHeight(pointFinal.getX(), pointFinal.getZ(), time));
+
+ vertBufArray[index++] = pointFinal.getXf();
+ vertBufArray[index++] = pointFinal.getYf();
+ vertBufArray[index++] = pointFinal.getZf();
+
+ u += du;
+ }
+ v += dv;
+ u = 0;
+ }
+
+ Vector4.releaseTempInstance(pointTop);
+ Vector4.releaseTempInstance(pointFinal);
+ Vector4.releaseTempInstance(pointBottom);
+
+ final Vector3 oppositePoint = Vector3.fetchTempInstance();
+ final Vector3 adjacentPoint = Vector3.fetchTempInstance();
+ final Vector3 rootPoint = Vector3.fetchTempInstance();
+
+ int adj = 0, opp = 0;
+ int normalIndex = from * sizeX;
+ for (int row = from; row < to; row++) {
+ for (int col = 0; col < sizeX; col++) {
+ if (row == sizeY - 1) {
+ if (col == sizeX - 1) { // last row, last col
+ // up cross left
+ adj = normalIndex - sizeX;
+ opp = normalIndex - 1;
+ } else { // last row, except for last col
+ // right cross up
+ adj = normalIndex + 1;
+ opp = normalIndex - sizeX;
+ }
+ } else {
+ if (col == sizeX - 1) { // last column except for last row
+ // left cross down
+ adj = normalIndex - 1;
+ opp = normalIndex + sizeX;
+ } else { // most cases
+ // down cross right
+ adj = normalIndex + sizeX;
+ opp = normalIndex + 1;
+ }
+ }
+
+ final float x = vertBufArray[normalIndex * 3];
+ final float y = vertBufArray[normalIndex * 3 + 1];
+ final float z = vertBufArray[normalIndex * 3 + 2];
+
+ texBufArray[normalIndex * 2] = x * textureScale;
+ texBufArray[normalIndex * 2 + 1] = z * textureScale;
+
+ rootPoint.set(x, y, z);
+ adjacentPoint.set(vertBufArray[adj * 3], vertBufArray[adj * 3 + 1], vertBufArray[adj * 3 + 2]);
+ adjacentPoint.subtractLocal(rootPoint);
+ oppositePoint.set(vertBufArray[opp * 3], vertBufArray[opp * 3 + 1], vertBufArray[opp * 3 + 2]);
+ oppositePoint.subtractLocal(rootPoint);
+
+ adjacentPoint.crossLocal(oppositePoint).normalizeLocal();
+
+ normBufArray[normalIndex * 3] = adjacentPoint.getXf();
+ normBufArray[normalIndex * 3 + 1] = adjacentPoint.getYf();
+ normBufArray[normalIndex * 3 + 2] = adjacentPoint.getZf();
+
+ normalIndex++;
+ }
+ }
+
+ Vector3.releaseTempInstance(oppositePoint);
+ Vector3.releaseTempInstance(adjacentPoint);
+ Vector3.releaseTempInstance(rootPoint);
+ }
+
+ private void getWorldIntersectionHomogenous(final double planeHeight, final ReadOnlyVector2 screenPosition,
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix, final Vector4 store) {
+ calculateIntersection(planeHeight, screenPosition, modelViewProjectionInverseMatrix);
+ store.set(origin);
+ }
+
+ private void getWorldIntersection(final double planeHeight, final ReadOnlyVector2 screenPosition,
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix, final Vector3 store) {
+ calculateIntersection(planeHeight, screenPosition, modelViewProjectionInverseMatrix);
+ store.set(origin.getX(), origin.getY(), origin.getZ()).divideLocal(origin.getW());
+ }
+
+ private void calculateIntersection(final double planeHeight, final ReadOnlyVector2 screenPosition,
+ final ReadOnlyMatrix4 modelViewProjectionInverseMatrix) {
+ origin.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, -1, 1);
+ direction.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, 1, 1);
+
+ modelViewProjectionInverseMatrix.applyPre(origin, origin);
+ modelViewProjectionInverseMatrix.applyPre(direction, direction);
+
+ direction.subtractLocal(origin);
+
+ // final double t = (planeHeight * origin.getW() - origin.getY())
+ // / (direction.getY() - planeHeight * direction.getW());
+
+ if (Math.abs(direction.getY()) > MathUtils.EPSILON) {
+ final double t = (planeHeight - origin.getY()) / direction.getY();
+ direction.multiplyLocal(t);
+ } else {
+ direction.normalizeLocal();
+ direction.multiplyLocal(mainCamera.getFrustumFar());
+ }
+
+ origin.addLocal(direction);
+ }
+
+ /**
+ * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
+ * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
+ * the height map, null is returned.
+ *
+ * @param position
+ * the vector representing the location to find a normal at.
+ * @param store
+ * the Vector3 object to store the result in. If null, a new one is created.
+ * @return the normal vector at the provided location.
+ */
+ public Vector3 getSurfaceNormal(final Vector2 position, final Vector3 store) {
+ return getSurfaceNormal(position.getX(), position.getY(), store);
+ }
+
+ /**
+ * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
+ * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
+ * the height map, null is returned.
+ *
+ * @param position
+ * the vector representing the location to find a normal at. Only the x and z values are used.
+ * @param store
+ * the Vector3 object to store the result in. If null, a new one is created.
+ * @return the normal vector at the provided location.
+ */
+ public Vector3 getSurfaceNormal(final Vector3 position, final Vector3 store) {
+ return getSurfaceNormal(position.getX(), position.getZ(), store);
+ }
+
+ /**
+ * <code>getSurfaceNormal</code> returns the normal of an arbitrary point on the terrain. The normal is linearly
+ * interpreted from the normals of the 4 nearest defined points. If the point provided is not within the bounds of
+ * the height map, null is returned.
+ *
+ * @param x
+ * the x coordinate to check.
+ * @param z
+ * the z coordinate to check.
+ * @param store
+ * the Vector3 object to store the result in. If null, a new one is created.
+ * @return the normal unit vector at the provided location.
+ */
+ public Vector3 getSurfaceNormal(final double x, final double z, Vector3 store) {
+ final double col = MathUtils.floor(x);
+ final double row = MathUtils.floor(z);
+
+ if (col < 0 || row < 0 || col >= sizeX - 1 || row >= sizeY - 1) {
+ return null;
+ }
+ final double intOnX = x - col, intOnZ = z - row;
+
+ if (store == null) {
+ store = new Vector3();
+ }
+
+ final Vector3 topLeft = store, topRight = new Vector3(), bottomLeft = new Vector3(), bottomRight = new Vector3();
+
+ final int focalSpot = (int) (col + row * sizeX);
+
+ // find the heightmap point closest to this position (but will always
+ // be to the left ( < x) and above (< z) of the spot.
+ BufferUtils.populateFromBuffer(topLeft, normBuf, focalSpot);
+
+ // now find the next point to the right of topLeft's position...
+ BufferUtils.populateFromBuffer(topRight, normBuf, focalSpot + 1);
+
+ // now find the next point below topLeft's position...
+ BufferUtils.populateFromBuffer(bottomLeft, normBuf, focalSpot + sizeX);
+
+ // now find the next point below and to the right of topLeft's
+ // position...
+ BufferUtils.populateFromBuffer(bottomRight, normBuf, focalSpot + sizeX + 1);
+
+ // Use linear interpolation to find the height.
+ topLeft.lerpLocal(topRight, intOnX);
+ bottomLeft.lerpLocal(bottomRight, intOnX);
+ topLeft.lerpLocal(bottomLeft, intOnZ);
+ return topLeft.normalizeLocal();
+ }
+
+ /**
+ * <code>buildVertices</code> sets up the vertex and index arrays of the TriMesh.
+ */
+ private void buildVertices(final int vertexCount) {
+ vertBuf = BufferUtils.createVector3Buffer(vertBuf, vertexCount);
+ _meshData.setVertexBuffer(vertBuf);
+
+ final Vector3 point = new Vector3();
+ for (int x = 0; x < sizeX; x++) {
+ for (int y = 0; y < sizeY; y++) {
+ point.set(x, 0, y);
+ BufferUtils.setInBuffer(point, vertBuf, (x + (y * sizeX)));
+ }
+ }
+
+ // set up the indices
+ final int triangleQuantity = ((sizeX - 1) * (sizeY - 1)) * 2;
+ indexBuffer = BufferUtils.createIntBuffer(triangleQuantity * 3);
+ _meshData.setIndexBuffer(indexBuffer);
+
+ // go through entire array up to the second to last column.
+ for (int i = 0; i < (sizeX * (sizeY - 1)); i++) {
+ // we want to skip the top row.
+ if (i % ((sizeX * (i / sizeX + 1)) - 1) == 0 && i != 0) {
+ // logger.info("skip row: "+i+" cause: "+((sizeY * (i / sizeX + 1)) - 1));
+ continue;
+ } else {
+ // logger.info("i: "+i);
+ }
+ // set the top left corner.
+ indexBuffer.put(i);
+ // set the bottom right corner.
+ indexBuffer.put((1 + sizeX) + i);
+ // set the top right corner.
+ indexBuffer.put(1 + i);
+ // set the top left corner
+ indexBuffer.put(i);
+ // set the bottom left corner
+ indexBuffer.put(sizeX + i);
+ // set the bottom right corner
+ indexBuffer.put((1 + sizeX) + i);
+ }
+ }
+
+ static class DeamonThreadFactory implements ThreadFactory {
+ static final AtomicInteger poolNumber = new AtomicInteger(1);
+ final ThreadGroup group;
+ final AtomicInteger threadNumber = new AtomicInteger(1);
+ final String namePrefix;
+
+ DeamonThreadFactory() {
+ final SecurityManager s = System.getSecurityManager();
+ group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ namePrefix = "ProjectedGrid Pool-" + poolNumber.getAndIncrement() + "-thread-";
+ }
+
+ public Thread newThread(final Runnable r) {
+ final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
+ if (!t.isDaemon()) {
+ t.setDaemon(true);
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+ }
+
+ public double getProjectorMinHeight() {
+ return projectorMinHeight;
+ }
+
+ public void setProjectorMinHeight(final double projectorMinHeight) {
+ this.projectorMinHeight = projectorMinHeight;
+ }
+
+ public boolean isDrawDebug() {
+ return drawDebug;
+ }
+
+ public void setDrawDebug(final boolean drawDebug) {
+ this.drawDebug = drawDebug;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.java
new file mode 100644
index 0000000..46c348a
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterHeightGenerator.java
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+/**
+ * Sample implementation of a water height generator.
+ */
+public class WaterHeightGenerator implements HeightGenerator {
+ private double scalexsmall = 0.04;
+ private double scaleysmall = 0.02;
+ private double scalexbig = 0.015;
+ private double scaleybig = 0.01;
+ private double heightsmall = 3.0;
+ private double heightbig = 10.0;
+ private double speedsmall = 1.0;
+ private double speedbig = 0.5;
+ private int octaves = 2;
+
+ public double getHeight(final double x, final double z, final double time) {
+ final double zval = z * scaleybig * 4 + time * speedbig * 4;
+ double height = Math.sin(zval);
+ height *= heightbig;
+
+ if (octaves > 0) {
+ final double height2 = ImprovedNoise.noise(x * scaleybig, z * scalexbig, time * speedbig) * heightbig;
+ height = height * 0.4 + height2 * 0.6;
+ }
+ if (octaves > 1) {
+ height += ImprovedNoise.noise(x * scaleysmall, z * scalexsmall, time * speedsmall) * heightsmall;
+ }
+ if (octaves > 2) {
+ height += ImprovedNoise.noise(x * scaleysmall * 2.0, z * scalexsmall * 2.0, time * speedsmall * 1.5)
+ * heightsmall * 0.5;
+ }
+ if (octaves > 3) {
+ height += ImprovedNoise.noise(x * scaleysmall * 4.0, z * scalexsmall * 4.0, time * speedsmall * 2.0)
+ * heightsmall * 0.25;
+ }
+
+ return height; // + waterHeight
+ }
+
+ public double getScalexsmall() {
+ return scalexsmall;
+ }
+
+ public void setScalexsmall(final double scalexsmall) {
+ this.scalexsmall = scalexsmall;
+ }
+
+ public double getScaleysmall() {
+ return scaleysmall;
+ }
+
+ public void setScaleysmall(final double scaleysmall) {
+ this.scaleysmall = scaleysmall;
+ }
+
+ public double getScalexbig() {
+ return scalexbig;
+ }
+
+ public void setScalexbig(final double scalexbig) {
+ this.scalexbig = scalexbig;
+ }
+
+ public double getScaleybig() {
+ return scaleybig;
+ }
+
+ public void setScaleybig(final double scaleybig) {
+ this.scaleybig = scaleybig;
+ }
+
+ public double getHeightsmall() {
+ return heightsmall;
+ }
+
+ public void setHeightsmall(final double heightsmall) {
+ this.heightsmall = heightsmall;
+ }
+
+ public double getHeightbig() {
+ return heightbig;
+ }
+
+ public void setHeightbig(final double heightbig) {
+ this.heightbig = heightbig;
+ }
+
+ public double getSpeedsmall() {
+ return speedsmall;
+ }
+
+ public void setSpeedsmall(final double speedsmall) {
+ this.speedsmall = speedsmall;
+ }
+
+ public double getSpeedbig() {
+ return speedbig;
+ }
+
+ public void setSpeedbig(final double speedbig) {
+ this.speedbig = speedbig;
+ }
+
+ public int getOctaves() {
+ return octaves;
+ }
+
+ public void setOctaves(final int octaves) {
+ this.octaves = octaves;
+ }
+
+ @Override
+ public double getMaximumHeight() {
+ return 15.0;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java
new file mode 100644
index 0000000..1db0cdb
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/effect/water/WaterNode.java
@@ -0,0 +1,1085 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.effect.water;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.ClipState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.google.common.collect.Lists;
+
+/**
+ * The WaterNode handles rendering of a water effect on all of it's children. What is reflected in the water is
+ * controlled through setReflectedScene/addReflectedScene. The skybox (if any) needs to be explicitly set through
+ * setSkybox since it needs to be relocated when rendering the reflection. The water is treated as a plane no matter
+ * what the geometry is, which is controlled through the water node plane equation settings.
+ */
+public class WaterNode extends Node {
+ private static final Logger logger = Logger.getLogger(WaterNode.class.getName());
+
+ protected Camera cam;
+ protected double tpf;
+ protected double reflectionThrottle = 0f, refractionThrottle = 0f;
+ protected double reflectionTime = 0, refractionTime = 0;
+ protected boolean useFadeToFogColor = false;
+
+ protected TextureRenderer tRenderer;
+ protected Texture2D textureReflect;
+ protected Texture2D textureReflectBlur;
+ protected Texture2D textureRefract;
+ protected Texture2D textureDepth;
+
+ protected ArrayList<Spatial> renderList = Lists.newArrayList();
+ protected ArrayList<Texture> texArray = Lists.newArrayList();
+ protected Node skyBox;
+
+ protected GLSLShaderObjectsState waterShader;
+ protected CullState cullBackFace;
+ protected TextureState textureState;
+ protected TextureState fallbackTextureState;
+
+ private Texture normalmapTexture;
+ private Texture dudvTexture;
+ private Texture foamTexture;
+ private Texture fallbackTexture;
+ private Matrix4 fallbackTextureStateMatrix;
+
+ protected BlendState as1;
+ protected FogState noFog;
+
+ protected Plane waterPlane;
+ protected Vector3 tangent;
+ protected Vector3 binormal;
+ protected Vector3 calcVect = new Vector3();
+ protected double clipBias;
+ protected ColorRGBA waterColorStart;
+ protected ColorRGBA waterColorEnd;
+ protected double heightFalloffStart;
+ protected double heightFalloffSpeed;
+ protected double waterMaxAmplitude;
+ protected double speedReflection;
+ protected double speedRefraction;
+
+ protected boolean aboveWater;
+ protected double normalTranslation = 0.0;
+ protected double refractionTranslation = 0.0;
+ protected boolean supported = true;
+ protected boolean useProjectedShader = false;
+ protected boolean useRefraction = false;
+ protected boolean useReflection = true;
+ protected int renderScale;
+
+ protected String simpleShaderStr = "com/ardor3d/extension/effect/water/flatwatershader";
+ protected String simpleShaderRefractionStr = "com/ardor3d/extension/effect/water/flatwatershader_refraction";
+ protected String projectedShaderStr = "com/ardor3d/extension/effect/water/projectedwatershader";
+ protected String projectedShaderRefractionStr = "com/ardor3d/extension/effect/water/projectedwatershader_refraction";
+ protected String currentShaderStr;
+
+ protected String normalMapTextureString = "";
+ protected String dudvMapTextureString = "";
+ protected String foamMapTextureString = "";
+ protected String fallbackMapTextureString = "";
+
+ private GLSLShaderObjectsState blurShaderVertical = null;
+ private float blurSampleDistance = 0.002f;
+ private Quad fullScreenQuad = null;
+ private boolean doBlurReflection = true;
+
+ private boolean initialized;
+
+ /**
+ * Resets water parameters to default values
+ *
+ */
+ public void resetParameters() {
+ waterPlane = new Plane(new Vector3(0.0, 1.0, 0.0), 0.0);
+ tangent = new Vector3(1.0, 0.0, 0.0);
+ binormal = new Vector3(0.0, 0.0, 1.0);
+
+ waterMaxAmplitude = 0.0;
+ clipBias = 1.0;
+ waterColorStart = new ColorRGBA(0.0f, 0.0f, 0.1f, 1.0f);
+ waterColorEnd = new ColorRGBA(0.0f, 0.3f, 0.1f, 1.0f);
+ heightFalloffStart = 400.0;
+ heightFalloffSpeed = 500.0;
+ speedReflection = 0.1;
+ speedRefraction = -0.05;
+ }
+
+ /**
+ * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
+ */
+ public void cleanup() {
+ if (isSupported() && tRenderer != null) {
+ tRenderer.cleanup();
+ }
+ }
+
+ public boolean isSupported() {
+ return supported;
+ }
+
+ /**
+ * Creates a new WaterRenderPass
+ *
+ * @param cam
+ * main rendercam to use for reflection settings etc
+ * @param renderScale
+ * how many times smaller the reflection/refraction textures should be compared to the main display
+ * @param useProjectedShader
+ * true - use the projected setup for variable height water meshes, false - use the flast shader setup
+ * @param useRefraction
+ * enable/disable rendering of refraction textures
+ */
+ public WaterNode(final Camera cam, final int renderScale, final boolean useProjectedShader,
+ final boolean useRefraction) {
+ this.cam = cam;
+ this.useProjectedShader = useProjectedShader;
+ this.useRefraction = useRefraction;
+ this.renderScale = renderScale;
+ resetParameters();
+
+ waterShader = new GLSLShaderObjectsState();
+ blurShaderVertical = new GLSLShaderObjectsState();
+
+ cullBackFace = new CullState();
+ cullBackFace.setEnabled(true);
+ cullBackFace.setCullFace(CullState.Face.None);
+ }
+
+ /**
+ * Initialize texture renderers. Load water textures. Create shaders.
+ *
+ * @param r
+ */
+ private void initialize(final Renderer r) {
+ if (cam == null || initialized) {
+ return;
+ }
+ initialized = true;
+
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+
+ if (useRefraction && useProjectedShader && caps.getNumberOfFragmentTextureUnits() < 6 || useRefraction
+ && caps.getNumberOfFragmentTextureUnits() < 5) {
+ useRefraction = false;
+ logger.info("Not enough textureunits, falling back to non refraction water");
+ }
+
+ if (!caps.isGLSLSupported()) {
+ supported = false;
+ }
+ if (!(caps.isPbufferSupported() || caps.isFBOSupported())) {
+ supported = false;
+ }
+
+ if (isSupported()) {
+ tRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer( //
+ cam.getWidth() / renderScale, // width
+ cam.getHeight() / renderScale, // height
+ 8, // Depth bits... TODO: Make configurable?
+ 0, // Samples... TODO: Make configurable?
+ r, caps);
+
+ // blurSampleDistance = 1f / ((float) cam.getHeight() / renderScale);
+
+ tRenderer.setMultipleTargets(true);
+ tRenderer.setBackgroundColor(new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f));
+ tRenderer.getCamera().setFrustum(cam.getFrustumNear(), cam.getFrustumFar(), cam.getFrustumLeft(),
+ cam.getFrustumRight(), cam.getFrustumTop(), cam.getFrustumBottom());
+
+ textureState = new TextureState();
+ textureState.setEnabled(true);
+
+ setupTextures();
+
+ fullScreenQuad = new Quad("FullScreenQuad", cam.getWidth() / 4, cam.getHeight() / 4);
+ fullScreenQuad.setTranslation(cam.getWidth() / 2, cam.getHeight() / 2, 0);
+ fullScreenQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ fullScreenQuad.getSceneHints().setCullHint(CullHint.Never);
+ fullScreenQuad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ fullScreenQuad.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ final TextureState ts = new TextureState();
+ ts.setTexture(textureReflect);
+ fullScreenQuad.setRenderState(ts);
+ fullScreenQuad.setRenderState(blurShaderVertical);
+ fullScreenQuad.updateWorldRenderStates(false);
+ }
+
+ if (!isSupported()) {
+ createFallbackData();
+ } else {
+ noFog = new FogState();
+ noFog.setEnabled(false);
+ }
+
+ getSceneHints().setCullHint(CullHint.Never);
+
+ setWaterEffectOnSpatial(this);
+ }
+
+ /**
+ * Load water textures.
+ */
+ protected void setupTextures() {
+ textureReflect = new Texture2D();
+ textureReflect.setWrap(Texture.WrapMode.EdgeClamp);
+ textureReflect.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ Matrix4 matrix = new Matrix4();
+ matrix.setM00(-1.0);
+ matrix.setM30(1.0);
+ textureReflect.setTextureMatrix(matrix);
+ tRenderer.setupTexture(textureReflect);
+
+ normalmapTexture = TextureManager.load(normalMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessCompressedFormat, true);
+ textureState.setTexture(normalmapTexture, 0);
+ normalmapTexture.setWrap(Texture.WrapMode.Repeat);
+
+ textureReflectBlur = new Texture2D();
+ textureReflectBlur.setWrap(Texture.WrapMode.EdgeClamp);
+ textureReflectBlur.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ textureReflectBlur.setTextureMatrix(matrix);
+ tRenderer.setupTexture(textureReflectBlur);
+
+ textureState.setTexture(textureReflectBlur, 1);
+
+ dudvTexture = TextureManager.load(dudvMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessNoCompressedFormat, true);
+ matrix = new Matrix4();
+ matrix.setM00(0.8);
+ matrix.setM11(0.8);
+ dudvTexture.setTextureMatrix(matrix);
+ textureState.setTexture(dudvTexture, 2);
+ dudvTexture.setWrap(Texture.WrapMode.Repeat);
+
+ if (useRefraction) {
+ textureRefract = new Texture2D();
+ textureRefract.setWrap(Texture.WrapMode.EdgeClamp);
+ textureRefract.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ tRenderer.setupTexture(textureRefract);
+
+ textureDepth = new Texture2D();
+ textureDepth.setWrap(Texture.WrapMode.EdgeClamp);
+ textureDepth.setMagnificationFilter(Texture.MagnificationFilter.NearestNeighbor);
+ textureDepth.setTextureStoreFormat(TextureStoreFormat.Depth24);
+ tRenderer.setupTexture(textureDepth);
+
+ textureState.setTexture(textureRefract, 3);
+ textureState.setTexture(textureDepth, 4);
+ }
+
+ if (useProjectedShader) {
+ foamTexture = TextureManager.load(foamMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessCompressedFormat, true);
+ if (useRefraction) {
+ textureState.setTexture(foamTexture, 5);
+ } else {
+ textureState.setTexture(foamTexture, 3);
+ }
+ foamTexture.setWrap(Texture.WrapMode.Repeat);
+ }
+
+ reloadShader();
+ }
+
+ /**
+ * Create setup to use as fallback if fancy water is not supported.
+ */
+ private void createFallbackData() {
+ fallbackTextureState = new TextureState();
+ fallbackTextureState.setEnabled(true);
+
+ fallbackTexture = TextureManager.load(fallbackMapTextureString, Texture.MinificationFilter.Trilinear,
+ TextureStoreFormat.GuessCompressedFormat, true);
+ fallbackTextureState.setTexture(fallbackTexture, 0);
+ fallbackTexture.setWrap(Texture.WrapMode.Repeat);
+
+ fallbackTextureStateMatrix = new Matrix4();
+
+ as1 = new BlendState();
+ as1.setBlendEnabled(true);
+ as1.setTestEnabled(true);
+ as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ as1.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ as1.setEnabled(true);
+ }
+
+ public void update(final double tpf) {
+ this.tpf = tpf;
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ initialize(r);
+
+ updateTranslations();
+
+ final double camWaterDist = waterPlane.pseudoDistance(cam.getLocation());
+ aboveWater = camWaterDist >= 0;
+
+ if (isSupported()) {
+ waterShader.setUniform("tangent", tangent);
+ waterShader.setUniform("binormal", binormal);
+ waterShader.setUniform("useFadeToFogColor", useFadeToFogColor);
+ waterShader.setUniform("waterColor", waterColorStart);
+ waterShader.setUniform("waterColorEnd", waterColorEnd);
+ waterShader.setUniform("normalTranslation", (float) normalTranslation);
+ waterShader.setUniform("refractionTranslation", (float) refractionTranslation);
+ waterShader.setUniform("abovewater", aboveWater);
+ if (useProjectedShader) {
+ waterShader.setUniform("cameraPos", cam.getLocation());
+ waterShader.setUniform("waterHeight", (float) waterPlane.getConstant());
+ waterShader.setUniform("amplitude", (float) waterMaxAmplitude);
+ waterShader.setUniform("heightFalloffStart", (float) heightFalloffStart);
+ waterShader.setUniform("heightFalloffSpeed", (float) heightFalloffSpeed);
+ }
+
+ final double heightTotal = clipBias + waterMaxAmplitude - waterPlane.getConstant();
+ final Vector4 clipPlane = Vector4.fetchTempInstance();
+
+ if (useReflection) {
+ clipPlane.set(waterPlane.getNormal().getX(), waterPlane.getNormal().getY(), waterPlane.getNormal()
+ .getZ(), heightTotal);
+ renderReflection(clipPlane);
+ }
+
+ if (useRefraction && aboveWater) {
+ clipPlane.set(-waterPlane.getNormal().getX(), -waterPlane.getNormal().getY(), -waterPlane.getNormal()
+ .getZ(), -waterPlane.getConstant());
+ renderRefraction(clipPlane);
+ }
+ }
+
+ if (fallbackTextureState != null) {
+ fallbackTextureStateMatrix.setM31(normalTranslation);
+ fallbackTexture.setTextureMatrix(fallbackTextureStateMatrix);
+ }
+
+ super.draw(r);
+ }
+
+ protected void updateTranslations() {
+ normalTranslation += speedReflection * tpf;
+ refractionTranslation += speedRefraction * tpf;
+ }
+
+ public void reloadShader() {
+ if (useProjectedShader) {
+ if (useRefraction) {
+ currentShaderStr = projectedShaderRefractionStr;
+ } else {
+ currentShaderStr = projectedShaderStr;
+ }
+ } else {
+ if (useRefraction) {
+ currentShaderStr = simpleShaderRefractionStr;
+ } else {
+ currentShaderStr = simpleShaderStr;
+ }
+ }
+
+ try {
+ logger.info("loading " + currentShaderStr);
+ waterShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ currentShaderStr + ".vert"));
+ waterShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ currentShaderStr + ".frag"));
+ } catch (final IOException e) {
+ logger.log(Level.WARNING, "Error loading shader", e);
+ return;
+ }
+
+ waterShader.setUniform("normalMap", 0);
+ waterShader.setUniform("reflection", 1);
+ waterShader.setUniform("dudvMap", 2);
+ if (useRefraction) {
+ waterShader.setUniform("refraction", 3);
+ waterShader.setUniform("depthMap", 4);
+ }
+ if (useProjectedShader) {
+ if (useRefraction) {
+ waterShader.setUniform("foamMap", 5);
+ } else {
+ waterShader.setUniform("foamMap", 3);
+ }
+ }
+
+ waterShader._needSendShader = true;
+
+ try {
+ blurShaderVertical.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ "com/ardor3d/extension/effect/bloom/bloom_blur.vert"));
+ blurShaderVertical.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(WaterNode.class,
+ "com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ blurShaderVertical.setUniform("RT", 0);
+ blurShaderVertical.setUniform("sampleDist", blurSampleDistance);
+ blurShaderVertical._needSendShader = true;
+
+ logger.info("Shader reloaded...");
+ }
+
+ /**
+ * Sets a spatial up for being rendered with the watereffect
+ *
+ * @param spatial
+ * Spatial to use as base for the watereffect
+ */
+ public void setWaterEffectOnSpatial(final Spatial spatial) {
+ spatial.setRenderState(cullBackFace);
+ if (isSupported()) {
+ // spatial.setRenderBucketType(RenderBucketType.Skip);
+ spatial.setRenderState(waterShader);
+ spatial.setRenderState(textureState);
+ } else {
+ spatial.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ spatial.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ spatial.setRenderState(fallbackTextureState);
+ spatial.setRenderState(as1);
+ }
+ }
+
+ // temporary vectors for mem opt.
+ private final Vector3 tmpLocation = new Vector3();
+ private final Vector3 camReflectPos = new Vector3();
+ private final Vector3 camReflectDir = new Vector3();
+ private final Vector3 camReflectUp = new Vector3();
+ private final Vector3 camReflectLeft = new Vector3();
+ private final Vector3 camLocation = new Vector3();
+
+ /**
+ * Render water reflection RTT
+ */
+ private void renderReflection(final Vector4 clipPlane) {
+ if (renderList.isEmpty()) {
+ return;
+ }
+
+ reflectionTime += tpf;
+ if (reflectionTime < reflectionThrottle) {
+ return;
+ }
+ reflectionTime = 0;
+
+ if (aboveWater) {
+ camLocation.set(cam.getLocation());
+
+ double planeDistance = waterPlane.pseudoDistance(camLocation);
+ calcVect.set(waterPlane.getNormal()).multiplyLocal(planeDistance * 2.0f);
+ camReflectPos.set(camLocation.subtractLocal(calcVect));
+
+ camLocation.set(cam.getLocation()).addLocal(cam.getDirection());
+ planeDistance = waterPlane.pseudoDistance(camLocation);
+ calcVect.set(waterPlane.getNormal()).multiplyLocal(planeDistance * 2.0f);
+ camReflectDir.set(camLocation.subtractLocal(calcVect)).subtractLocal(camReflectPos).normalizeLocal();
+
+ camLocation.set(cam.getLocation()).addLocal(cam.getUp());
+ planeDistance = waterPlane.pseudoDistance(camLocation);
+ calcVect.set(waterPlane.getNormal()).multiplyLocal(planeDistance * 2.0f);
+ camReflectUp.set(camLocation.subtractLocal(calcVect)).subtractLocal(camReflectPos).normalizeLocal();
+
+ camReflectLeft.set(camReflectUp).crossLocal(camReflectDir).normalizeLocal();
+
+ tRenderer.getCamera().setLocation(camReflectPos);
+ tRenderer.getCamera().setDirection(camReflectDir);
+ tRenderer.getCamera().setUp(camReflectUp);
+ tRenderer.getCamera().setLeft(camReflectLeft);
+ } else {
+ tRenderer.getCamera().setLocation(cam.getLocation());
+ tRenderer.getCamera().setDirection(cam.getDirection());
+ tRenderer.getCamera().setUp(cam.getUp());
+ tRenderer.getCamera().setLeft(cam.getLeft());
+ }
+
+ if (skyBox != null) {
+ tmpLocation.set(skyBox.getTranslation());
+ skyBox.setTranslation(tRenderer.getCamera().getLocation());
+ skyBox.updateGeometricState(0.0f);
+ skyBox.getSceneHints().setCullHint(CullHint.Never);
+ }
+
+ texArray.clear();
+ if (doBlurReflection) {
+ texArray.add(textureReflect);
+ } else {
+ texArray.add(textureReflectBlur);
+ }
+
+ tRenderer.getCamera().setProjectionMode(ProjectionMode.Custom);
+ tRenderer.getCamera().setProjectionMatrix(cam.getProjectionMatrix());
+ tRenderer.render(skyBox, texArray, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ if (skyBox != null) {
+ skyBox.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ modifyProjectionMatrix(clipPlane);
+
+ tRenderer.render(renderList, texArray, Renderer.BUFFER_NONE);
+
+ if (doBlurReflection) {
+ blurReflectionTexture();
+ }
+
+ if (skyBox != null) {
+ skyBox.setTranslation(tmpLocation);
+ skyBox.updateGeometricState(0.0f);
+ skyBox.getSceneHints().setCullHint(CullHint.Never);
+ }
+ }
+
+ private void blurReflectionTexture() {
+ tRenderer.render(fullScreenQuad, textureReflectBlur, Renderer.BUFFER_NONE);
+ }
+
+ /**
+ * Render water refraction RTT
+ */
+ private void renderRefraction(final Vector4 clipPlane) {
+ if (renderList.isEmpty()) {
+ return;
+ }
+
+ refractionTime += tpf;
+ if (refractionTime < refractionThrottle) {
+ return;
+ }
+ refractionTime = 0;
+
+ // tRenderer.getCamera().set(cam);
+ tRenderer.getCamera().setLocation(cam.getLocation());
+ tRenderer.getCamera().setDirection(cam.getDirection());
+ tRenderer.getCamera().setUp(cam.getUp());
+ tRenderer.getCamera().setLeft(cam.getLeft());
+
+ CullHint cullMode = CullHint.Dynamic;
+ if (skyBox != null) {
+ cullMode = skyBox.getSceneHints().getCullHint();
+ skyBox.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ tRenderer.getCamera().setProjectionMatrix(cam.getProjectionMatrix());
+
+ texArray.clear();
+ texArray.add(textureRefract);
+ texArray.add(textureDepth);
+
+ tRenderer.getCamera().update();
+ tRenderer.getCamera().getModelViewMatrix();
+ tRenderer.getCamera().getProjectionMatrix();
+
+ tRenderer.render(renderList, texArray, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ if (skyBox != null) {
+ skyBox.getSceneHints().setCullHint(cullMode);
+ }
+ }
+
+ private double sign(final double a) {
+ if (a > 0.0) {
+ return 1.0;
+ }
+ if (a < 0.0) {
+ return -1.0;
+ }
+ return 0.0;
+ }
+
+ private double projectionMatrix[] = new double[16];
+ private final Vector4 cornerPoint = new Vector4();
+ private final Matrix4 tmpMatrix = new Matrix4();
+
+ private void modifyProjectionMatrix(final Vector4 clipPlane) {
+ // Get the current projection matrix
+ projectionMatrix = cam.getProjectionMatrix().toArray(projectionMatrix);
+
+ // Get the inverse transpose of the current modelview matrix
+ final ReadOnlyMatrix4 modelViewMatrixInvTrans = tRenderer.getCamera().getModelViewMatrix().invert(tmpMatrix)
+ .transposeLocal();
+ modelViewMatrixInvTrans.applyPre(clipPlane, clipPlane);
+
+ // Calculate the clip-space corner point opposite the clipping plane
+ // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and
+ // transform it into camera space by multiplying it
+ // by the inverse of the projection matrix
+ cornerPoint.setX((sign(clipPlane.getX()) + projectionMatrix[8]) / projectionMatrix[0]);
+ cornerPoint.setY((sign(clipPlane.getY()) + projectionMatrix[9]) / projectionMatrix[5]);
+ cornerPoint.setZ(-1.0);
+ cornerPoint.setW((1.0 + projectionMatrix[10]) / projectionMatrix[14]);
+
+ // Calculate the scaled plane vector
+ final Vector4 scaledPlaneVector = clipPlane.multiply((2.0 / clipPlane.dot(cornerPoint)), cornerPoint);
+
+ // Replace the third row of the projection matrix
+ projectionMatrix[2] = scaledPlaneVector.getX();
+ projectionMatrix[6] = scaledPlaneVector.getY();
+ projectionMatrix[10] = scaledPlaneVector.getZ() + 1.0;
+ projectionMatrix[14] = scaledPlaneVector.getW();
+
+ // Load it back into OpenGL
+ final Matrix4 newProjectionMatrix = tmpMatrix.fromArray(projectionMatrix);
+ tRenderer.getCamera().setProjectionMatrix(newProjectionMatrix);
+ }
+
+ public void removeReflectedScene(final Spatial renderNode) {
+ if (logger.isLoggable(Level.INFO)) {
+ logger.info("Removed reflected scene: " + renderList.remove(renderNode));
+ }
+ }
+
+ public void clearReflectedScene() {
+ renderList.clear();
+ }
+
+ /**
+ * Adds a spatial to the list of spatials used as reflection in the water
+ *
+ * @param renderNode
+ * Spatial to add to the list of objects used as reflection in the water
+ */
+ public void addReflectedScene(final Spatial renderNode) {
+ if (renderNode == null) {
+ return;
+ }
+
+ if (!renderList.contains(renderNode)) {
+ renderList.add(renderNode);
+ }
+ }
+
+ /**
+ * Sets up a node to be transformed and clipped for skybox usage
+ *
+ * @param skyBox
+ * Handle to a node to use as skybox
+ */
+ public void setSkybox(final Node skyBox) {
+ if (skyBox != null) {
+ final ClipState skyboxClipState = new ClipState();
+ skyboxClipState.setEnabled(false);
+ skyBox.setRenderState(skyboxClipState);
+ }
+
+ this.skyBox = skyBox;
+ }
+
+ public Camera getCam() {
+ return cam;
+ }
+
+ public void setCam(final Camera cam) {
+ this.cam = cam;
+ }
+
+ public ColorRGBA getWaterColorStart() {
+ return waterColorStart;
+ }
+
+ /**
+ * Color to use when the incident angle to the surface is low
+ */
+ public void setWaterColorStart(final ColorRGBA waterColorStart) {
+ this.waterColorStart = waterColorStart;
+ }
+
+ public ColorRGBA getWaterColorEnd() {
+ return waterColorEnd;
+ }
+
+ /**
+ * Color to use when the incident angle to the surface is high
+ */
+ public void setWaterColorEnd(final ColorRGBA waterColorEnd) {
+ this.waterColorEnd = waterColorEnd;
+ }
+
+ public double getHeightFalloffStart() {
+ return heightFalloffStart;
+ }
+
+ /**
+ * Set at what distance the waveheights should start to fade out(for projected water only)
+ *
+ * @param heightFalloffStart
+ */
+ public void setHeightFalloffStart(final double heightFalloffStart) {
+ this.heightFalloffStart = heightFalloffStart;
+ }
+
+ public double getHeightFalloffSpeed() {
+ return heightFalloffSpeed;
+ }
+
+ /**
+ * Set the fadeout length of the waveheights, when over falloff start(for projected water only)
+ *
+ * @param heightFalloffStart
+ */
+ public void setHeightFalloffSpeed(final double heightFalloffSpeed) {
+ this.heightFalloffSpeed = heightFalloffSpeed;
+ }
+
+ public double getWaterHeight() {
+ return waterPlane.getConstant();
+ }
+
+ /**
+ * Set base height of the waterplane(Used for reflecting the camera for rendering reflection)
+ *
+ * @param waterHeight
+ * Waterplane height
+ */
+ public void setWaterHeight(final double waterHeight) {
+ waterPlane.setConstant(waterHeight);
+ }
+
+ public ReadOnlyVector3 getNormal() {
+ return waterPlane.getNormal();
+ }
+
+ /**
+ * Set the normal of the waterplane(Used for reflecting the camera for rendering reflection)
+ *
+ * @param normal
+ * Waterplane normal
+ */
+ public void setNormal(final Vector3 normal) {
+ waterPlane.setNormal(normal);
+ }
+
+ public double getSpeedReflection() {
+ return speedReflection;
+ }
+
+ /**
+ * Set the movement speed of the reflectiontexture
+ *
+ * @param speedReflection
+ * Speed of reflectiontexture
+ */
+ public void setSpeedReflection(final double speedReflection) {
+ this.speedReflection = speedReflection;
+ }
+
+ public double getSpeedRefraction() {
+ return speedRefraction;
+ }
+
+ /**
+ * Set the movement speed of the refractiontexture
+ *
+ * @param speedRefraction
+ * Speed of refractiontexture
+ */
+ public void setSpeedRefraction(final double speedRefraction) {
+ this.speedRefraction = speedRefraction;
+ }
+
+ public double getWaterMaxAmplitude() {
+ return waterMaxAmplitude;
+ }
+
+ /**
+ * Maximum amplitude of the water, used for clipping correctly(projected water only)
+ *
+ * @param waterMaxAmplitude
+ * Maximum amplitude
+ */
+ public void setWaterMaxAmplitude(final double waterMaxAmplitude) {
+ this.waterMaxAmplitude = waterMaxAmplitude;
+ }
+
+ public double getClipBias() {
+ return clipBias;
+ }
+
+ public void setClipBias(final double clipBias) {
+ this.clipBias = clipBias;
+ }
+
+ public Plane getWaterPlane() {
+ return waterPlane;
+ }
+
+ public void setWaterPlane(final Plane waterPlane) {
+ this.waterPlane = waterPlane;
+ }
+
+ public Vector3 getTangent() {
+ return tangent;
+ }
+
+ public void setTangent(final Vector3 tangent) {
+ this.tangent = tangent;
+ }
+
+ public Vector3 getBinormal() {
+ return binormal;
+ }
+
+ public void setBinormal(final Vector3 binormal) {
+ this.binormal = binormal;
+ }
+
+ public Texture getTextureReflect() {
+ return textureReflect;
+ }
+
+ public Texture getTextureRefract() {
+ return textureRefract;
+ }
+
+ public Texture getTextureDepth() {
+ return textureDepth;
+ }
+
+ /**
+ * If true, fade to fogcolor. If false, fade to 100% reflective surface
+ *
+ * @param value
+ */
+ public void useFadeToFogColor(final boolean value) {
+ useFadeToFogColor = value;
+ }
+
+ public boolean isUseFadeToFogColor() {
+ return useFadeToFogColor;
+ }
+
+ public boolean isUseReflection() {
+ return useReflection;
+ }
+
+ /**
+ * Turn reflection on and off
+ *
+ * @param useReflection
+ */
+ public void setUseReflection(final boolean useReflection) {
+ if (useReflection == this.useReflection) {
+ return;
+ }
+ this.useReflection = useReflection;
+ reloadShader();
+ }
+
+ public boolean isUseRefraction() {
+ return useRefraction;
+ }
+
+ /**
+ * Turn refraction on and off
+ *
+ * @param useRefraction
+ */
+ public void setUseRefraction(final boolean useRefraction) {
+ if (useRefraction == this.useRefraction) {
+ return;
+ }
+ this.useRefraction = useRefraction;
+ reloadShader();
+ }
+
+ public int getRenderScale() {
+ return renderScale;
+ }
+
+ public void setRenderScale(final int renderScale) {
+ this.renderScale = renderScale;
+ }
+
+ public boolean isUseProjectedShader() {
+ return useProjectedShader;
+ }
+
+ public void setUseProjectedShader(final boolean useProjectedShader) {
+ if (useProjectedShader == this.useProjectedShader) {
+ return;
+ }
+ this.useProjectedShader = useProjectedShader;
+ reloadShader();
+ }
+
+ public double getReflectionThrottle() {
+ return reflectionThrottle;
+ }
+
+ public void setReflectionThrottle(final double reflectionThrottle) {
+ this.reflectionThrottle = reflectionThrottle;
+ }
+
+ public double getRefractionThrottle() {
+ return refractionThrottle;
+ }
+
+ public void setRefractionThrottle(final double refractionThrottle) {
+ this.refractionThrottle = refractionThrottle;
+ }
+
+ public TextureState getTextureState() {
+ return textureState;
+ }
+
+ public void setTextureState(final TextureState textureState) {
+ this.textureState = textureState;
+ }
+
+ public void updateCamera() {
+ if (isSupported()) {
+ tRenderer.getCamera().setFrustum(cam.getFrustumNear(), cam.getFrustumFar(), cam.getFrustumLeft(),
+ cam.getFrustumRight(), cam.getFrustumTop(), cam.getFrustumBottom());
+ }
+ }
+
+ public void setFallbackTexture(final Texture fallbackTexture) {
+ this.fallbackTexture = fallbackTexture;
+ }
+
+ public Texture getFallbackTexture() {
+ return fallbackTexture;
+ }
+
+ public void setNormalmapTexture(final Texture normalmapTexture) {
+ this.normalmapTexture = normalmapTexture;
+ }
+
+ public Texture getNormalmapTexture() {
+ return normalmapTexture;
+ }
+
+ public void setDudvTexture(final Texture dudvTexture) {
+ this.dudvTexture = dudvTexture;
+ }
+
+ public Texture getDudvTexture() {
+ return dudvTexture;
+ }
+
+ public void setFoamTexture(final Texture foamTexture) {
+ this.foamTexture = foamTexture;
+ }
+
+ public Texture getFoamTexture() {
+ return foamTexture;
+ }
+
+ /**
+ * @return the normalMapTextureString
+ */
+ public String getNormalMapTextureString() {
+ return normalMapTextureString;
+ }
+
+ /**
+ * @param normalMapTextureString
+ * the normalMapTextureString to set
+ */
+ public void setNormalMapTextureString(final String normalMapTextureString) {
+ this.normalMapTextureString = normalMapTextureString;
+ }
+
+ /**
+ * @return the dudvMapTextureString
+ */
+ public String getDudvMapTextureString() {
+ return dudvMapTextureString;
+ }
+
+ /**
+ * @param dudvMapTextureString
+ * the dudvMapTextureString to set
+ */
+ public void setDudvMapTextureString(final String dudvMapTextureString) {
+ this.dudvMapTextureString = dudvMapTextureString;
+ }
+
+ /**
+ * @return the foamMapTextureString
+ */
+ public String getFoamMapTextureString() {
+ return foamMapTextureString;
+ }
+
+ /**
+ * @param foamMapTextureString
+ * the foamMapTextureString to set
+ */
+ public void setFoamMapTextureString(final String foamMapTextureString) {
+ this.foamMapTextureString = foamMapTextureString;
+ }
+
+ /**
+ * @return the fallbackMapTextureString
+ */
+ public String getFallbackMapTextureString() {
+ return fallbackMapTextureString;
+ }
+
+ /**
+ * @param fallbackMapTextureString
+ * the fallbackMapTextureString to set
+ */
+ public void setFallbackMapTextureString(final String fallbackMapTextureString) {
+ this.fallbackMapTextureString = fallbackMapTextureString;
+ }
+
+ public boolean isDoBlurReflection() {
+ return doBlurReflection;
+ }
+
+ public void setDoBlurReflection(final boolean doBlurReflection) {
+ this.doBlurReflection = doBlurReflection;
+ }
+
+ public float getBlurSampleDistance() {
+ return blurSampleDistance;
+ }
+
+ public void setBlurSampleDistance(final float blurSampleDistance) {
+ this.blurSampleDistance = blurSampleDistance;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java
new file mode 100644
index 0000000..b3e7b82
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/PSSMCamera.java
@@ -0,0 +1,285 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.shadow.map;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+
+/**
+ * Camera with additional pssm related functionality.
+ */
+public class PSSMCamera extends Camera {
+
+ /** The storage place for calculated split distances. */
+ protected double _splitDistances[] = new double[2];
+
+ /** The lambda value used in split distance calculations. */
+ protected double _lambda = 0.5;
+
+ /** The corners of the camera frustum. */
+ protected final Vector3[] _corners = new Vector3[8];
+
+ /** The center of the camera frustum. */
+ protected final Vector3 _center = new Vector3();
+
+ /** Temporary vector used for storing extents during corner calculations. */
+ protected final Vector3 _extents = new Vector3();
+
+ /** The maximum far plane distance used when packing the frustum. */
+ protected double _maxFarPlaneDistance = 2000.0;
+
+ /**
+ * Instantiates a new PSSM camera.
+ */
+ public PSSMCamera() {
+ super(0, 0); // copy later
+ init();
+ }
+
+ /**
+ * Instantiates a new PSSM camera.
+ *
+ * @param width
+ * the width
+ * @param height
+ * the height
+ */
+ public PSSMCamera(final int width, final int height) {
+ super(width, height);
+ init();
+ }
+
+ /**
+ * Instantiates a new PSSM camera.
+ *
+ * @param source
+ * the source
+ */
+ public PSSMCamera(final Camera source) {
+ super(source);
+ init();
+ }
+
+ /**
+ * Initialize structures.
+ */
+ private void init() {
+ for (int i = 0; i < _corners.length; i++) {
+ _corners[i] = new Vector3();
+ }
+ }
+
+ /**
+ * Calculates the distances from view location for view frustum splits using the "practical split scheme".
+ *
+ * @param splitCount
+ * the split count
+ *
+ * @see <a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/keypoint1.htm">technique paper</a>
+ */
+ public void calculateSplitDistances(final int splitCount) {
+ // ensure correct size.
+ if (_splitDistances.length != splitCount + 1) {
+ _splitDistances = new double[splitCount + 1];
+ }
+
+ final double nearPlane = getFrustumNear();
+ final double farPlane = getFrustumFar();
+
+ // setup intermediate splits
+ for (int i = 1; i < splitCount; i++) {
+ final double part = i / (double) splitCount;
+ final double logsplit = nearPlane * Math.pow((farPlane / nearPlane), part);
+ final double uniformSplit = nearPlane + (farPlane - nearPlane) * part;
+ _splitDistances[i] = logsplit * _lambda + uniformSplit * (1 - _lambda);
+ }
+
+ // setup first and last split (near/far planes)
+ _splitDistances[0] = nearPlane;
+ _splitDistances[splitCount] = farPlane;
+ }
+
+ /**
+ * Compress this camera's near and far frustum planes to be smaller if possible, using the given bounds as a
+ * measure.
+ *
+ * @param sceneBounds
+ * the scene bounds
+ */
+ public void pack(final BoundingVolume sceneBounds) {
+ final ReadOnlyVector3 center = sceneBounds.getCenter();
+ for (int i = 0; i < _corners.length; i++) {
+ _corners[i].set(center);
+ }
+
+ if (sceneBounds instanceof BoundingBox) {
+ final BoundingBox bbox = (BoundingBox) sceneBounds;
+ bbox.getExtent(_extents);
+ } else if (sceneBounds instanceof BoundingSphere) {
+ final BoundingSphere bsphere = (BoundingSphere) sceneBounds;
+ _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius());
+ }
+
+ _corners[0].addLocal(_extents.getX(), _extents.getY(), _extents.getZ());
+ _corners[1].addLocal(_extents.getX(), -_extents.getY(), _extents.getZ());
+ _corners[2].addLocal(_extents.getX(), _extents.getY(), -_extents.getZ());
+ _corners[3].addLocal(_extents.getX(), -_extents.getY(), -_extents.getZ());
+ _corners[4].addLocal(-_extents.getX(), _extents.getY(), _extents.getZ());
+ _corners[5].addLocal(-_extents.getX(), -_extents.getY(), _extents.getZ());
+ _corners[6].addLocal(-_extents.getX(), _extents.getY(), -_extents.getZ());
+ _corners[7].addLocal(-_extents.getX(), -_extents.getY(), -_extents.getZ());
+
+ final ReadOnlyMatrix4 mvMatrix = getModelViewMatrix();
+ double optimalCameraNear = Double.MAX_VALUE;
+ double optimalCameraFar = -Double.MAX_VALUE;
+ final Vector4 position = Vector4.fetchTempInstance();
+ for (int i = 0; i < _corners.length; i++) {
+ position.set(_corners[i].getX(), _corners[i].getY(), _corners[i].getZ(), 1);
+ mvMatrix.applyPre(position, position);
+
+ optimalCameraNear = Math.min(-position.getZ(), optimalCameraNear);
+ optimalCameraFar = Math.max(-position.getZ(), optimalCameraFar);
+ }
+ Vector4.releaseTempInstance(position);
+
+ // XXX: use of getFrustumNear and getFrustumFar seems suspicious...
+ // XXX: It depends on the frustum being reset each update
+ optimalCameraNear = Math.min(Math.max(getFrustumNear(), optimalCameraNear), getFrustumFar());
+ optimalCameraFar = Math.max(optimalCameraNear, Math.min(_maxFarPlaneDistance, optimalCameraFar));
+
+ final double change = optimalCameraNear / _frustumNear;
+ setFrustumLeft(getFrustumLeft() * change);
+ setFrustumRight(getFrustumRight() * change);
+ setFrustumTop(getFrustumTop() * change);
+ setFrustumBottom(getFrustumBottom() * change);
+
+ setFrustumNear(optimalCameraNear);
+ setFrustumFar(optimalCameraFar);
+ }
+
+ public void updateMaxCameraFar() {
+ double optimalCameraFar = getFrustumFar();
+ optimalCameraFar = Math.max(getFrustumNear() + 1.0, Math.min(_maxFarPlaneDistance, optimalCameraFar));
+ setFrustumFar(optimalCameraFar);
+ }
+
+ /**
+ * Calculate frustum corners and center.
+ *
+ * @param fNear
+ * the near distance
+ * @param fFar
+ * the far distance
+ */
+ public void calculateFrustum(final double fNear, final double fFar) {
+ final double fNearPlaneHeight, fNearPlaneWidth, fFarPlaneHeight, fFarPlaneWidth;
+ if (getProjectionMode() == ProjectionMode.Parallel) {
+ fNearPlaneHeight = (_frustumTop - _frustumBottom) * 0.5;
+ fNearPlaneWidth = (_frustumRight - _frustumLeft) * 0.5;
+
+ fFarPlaneHeight = (_frustumTop - _frustumBottom) * 0.5;
+ fFarPlaneWidth = (_frustumRight - _frustumLeft) * 0.5;
+ } else {
+ fNearPlaneHeight = (_frustumTop - _frustumBottom) * fNear * 0.5 / _frustumNear;
+ fNearPlaneWidth = (_frustumRight - _frustumLeft) * fNear * 0.5 / _frustumNear;
+
+ fFarPlaneHeight = (_frustumTop - _frustumBottom) * fFar * 0.5 / _frustumNear;
+ fFarPlaneWidth = (_frustumRight - _frustumLeft) * fFar * 0.5 / _frustumNear;
+ }
+
+ final Vector3 vNearPlaneCenter = Vector3.fetchTempInstance();
+ final Vector3 vFarPlaneCenter = Vector3.fetchTempInstance();
+ final Vector3 direction = Vector3.fetchTempInstance();
+ final Vector3 left = Vector3.fetchTempInstance();
+ final Vector3 up = Vector3.fetchTempInstance();
+
+ direction.set(getDirection()).multiplyLocal(fNear);
+ vNearPlaneCenter.set(getLocation()).addLocal(direction);
+ direction.set(getDirection()).multiplyLocal(fFar);
+ vFarPlaneCenter.set(getLocation()).addLocal(direction);
+
+ left.set(getLeft()).multiplyLocal(fNearPlaneWidth);
+ up.set(getUp()).multiplyLocal(fNearPlaneHeight);
+ _corners[0].set(vNearPlaneCenter).subtractLocal(left).subtractLocal(up);
+ _corners[1].set(vNearPlaneCenter).subtractLocal(left).addLocal(up);
+ _corners[2].set(vNearPlaneCenter).addLocal(left).addLocal(up);
+ _corners[3].set(vNearPlaneCenter).addLocal(left).subtractLocal(up);
+
+ left.set(getLeft()).multiplyLocal(fFarPlaneWidth);
+ up.set(getUp()).multiplyLocal(fFarPlaneHeight);
+ _corners[4].set(vFarPlaneCenter).subtractLocal(left).subtractLocal(up);
+ _corners[5].set(vFarPlaneCenter).subtractLocal(left).addLocal(up);
+ _corners[6].set(vFarPlaneCenter).addLocal(left).addLocal(up);
+ _corners[7].set(vFarPlaneCenter).addLocal(left).subtractLocal(up);
+
+ direction.set(getDirection()).multiplyLocal((fFar + fNear) * 0.5);
+ _center.set(getLocation()).addLocal(direction);
+
+ Vector3.releaseTempInstance(vNearPlaneCenter);
+ Vector3.releaseTempInstance(vFarPlaneCenter);
+ Vector3.releaseTempInstance(direction);
+ Vector3.releaseTempInstance(left);
+ Vector3.releaseTempInstance(up);
+ }
+
+ /**
+ * Gets the lambda.
+ *
+ * @return the lambda
+ */
+ public double getLambda() {
+ return _lambda;
+ }
+
+ /**
+ * Sets the lambda.
+ *
+ * @param lambda
+ * the new lambda
+ */
+ public void setLambda(final double lambda) {
+ _lambda = lambda;
+ }
+
+ /**
+ * Gets the split distances.
+ *
+ * @return the split distances
+ */
+ public double[] getSplitDistances() {
+ return _splitDistances;
+ }
+
+ /**
+ * Gets the max far plane distance.
+ *
+ * @return the max far plane distance
+ */
+ public double getMaxFarPlaneDistance() {
+ return _maxFarPlaneDistance;
+ }
+
+ /**
+ * Sets the max far plane distance.
+ *
+ * @param maxFarPlaneDistance
+ * the new max far plane distance
+ */
+ public void setMaxFarPlaneDistance(final double maxFarPlaneDistance) {
+ _maxFarPlaneDistance = maxFarPlaneDistance;
+ }
+}
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java
new file mode 100644
index 0000000..cf07f8a
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ParallelSplitShadowMapPass.java
@@ -0,0 +1,1365 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.shadow.map;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.bounding.OrientedBoundingBox;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.DepthTextureCompareFunc;
+import com.ardor3d.image.Texture.DepthTextureCompareMode;
+import com.ardor3d.image.Texture.DepthTextureMode;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.light.DirectionalLight;
+import com.ardor3d.light.Light;
+import com.ardor3d.light.PointLight;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.RenderLogic;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.pass.Pass;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.ClipState;
+import com.ardor3d.renderer.state.ColorMaskState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.CullState.Face;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.LightState;
+import com.ardor3d.renderer.state.OffsetState;
+import com.ardor3d.renderer.state.OffsetState.OffsetType;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.renderer.state.ShadingState;
+import com.ardor3d.renderer.state.ShadingState.ShadingMode;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.WireframeState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Renderable;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.shape.Sphere;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.google.common.collect.Lists;
+
+/**
+ * A pass providing a parallel split shadow mapping (PSSM) layer across the top of an existing scene.
+ */
+public class ParallelSplitShadowMapPass extends Pass {
+
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(ParallelSplitShadowMapPass.class.getName());
+
+ /** The Constant serialVersionUID. */
+ private static final long serialVersionUID = 1L;
+
+ /** Bias matrix from [-1, 1] to [0, 1]. */
+ public static final ReadOnlyMatrix4 SCALE_BIAS_MATRIX = new Matrix4( //
+ 0.5, 0.0, 0.0, 0.0, //
+ 0.0, 0.5, 0.0, 0.0, //
+ 0.0, 0.0, 0.5, 0.0, //
+ 0.5, 0.5, 0.5, 1.0);//
+
+ /** The renderer used to produce the shadow map. */
+ private TextureRenderer _shadowMapRenderer;
+
+ /** The textures storing the shadow maps. */
+ private Texture2D _shadowMapTexture[];
+
+ /** The list of occluding nodes. */
+ private final List<Spatial> _occluderNodes = Lists.newArrayList();
+
+ /** Extra bounds receivers, when rendering shadows other ways than through overlay */
+ private final List<Spatial> _boundsReceiver = Lists.newArrayList();
+
+ // Various optimizations for rendering shadow maps...
+ /** Culling front faces when rendering shadow maps. */
+ private final CullState _cullFrontFace;
+
+ /** Turn off textures when rendering shadow maps. */
+ private final TextureState _noTexture;
+
+ /** Turn off lighting when rendering shadow maps. */
+ private final LightState _noLights;
+
+ /** set flat shading when rendering shadow maps. */
+ private final ShadingState _flat;
+
+ // Important pieces for rendering shadow maps
+ /** Turn off colors when rendering shadow maps. */
+ private final ColorMaskState _colorDisabled;
+
+ /** The state applying the depth offset for the shadow. */
+ private final OffsetState _shadowOffsetState;
+
+ /**
+ * The blending to both discard the fragments that have been determined to be free of shadows and to blend into the
+ * background scene.
+ */
+ private final BlendState _discardShadowFragments;
+
+ /** The state applying the shadow map. */
+ private final TextureState _shadowTextureState;
+
+ /** Don't perform any plane clipping when rendering the shadowed scene. */
+ private final ClipState _noClip;
+
+ /** True once the pass has been initialized. */
+ protected boolean _initialised = false;
+
+ /** Flag for updating texture splits when number of splits changed. */
+ protected boolean _reinitSplitsDirty = true;
+
+ /** Flag for updating texture renderer when texture size changed. */
+ protected boolean _reinitTextureSizeDirty = true;
+
+ /** The size of the shadow map texture. */
+ private int _shadowMapSize;
+
+ /** Minimum number of splits allowed */
+ public static final int _MIN_SPLITS = 1;
+
+ /** Maximum number of splits allowed */
+ public static final int _MAX_SPLITS = 4;
+
+ /** Number of splits used. */
+ protected int _numOfSplits;
+
+ /** Minimum light z distance from target. */
+ private double _minimumLightDistance = 1000.0;
+
+ /** Shadow color and transparency. */
+ private final ColorRGBA _shadowColor = new ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f);
+
+ /** Light -> Camera transformation matrix. */
+ private final Matrix4 _shadowMatrix = new Matrix4();
+
+ /** Bounding of scene for packing frustum. */
+ protected BoundingBox _receiverBounds = new BoundingBox();
+
+ /** Indicates if we have any valid reciever bounds to use for frustum packing */
+ protected boolean hasValidBounds = false;
+
+ /** Special camera with functionality for packing frustum etc. */
+ protected PSSMCamera _pssmCam;
+
+ /** Light that casts the shadow. */
+ protected Light _light;
+
+ /** Shader for rendering pssm shadows in one pass. */
+ private GLSLShaderObjectsState _pssmShader;
+
+ /** Might need to keep main shader when doing both multitexturing shadows and overlays */
+ private GLSLShaderObjectsState _mainShader;
+
+ /** Shader for debugging pssm shadows drawing splits in different colors. */
+ private GLSLShaderObjectsState _pssmDebugShader;
+
+ /** Debug for stopping camera update. */
+ private boolean _updateMainCamera = true;
+
+ /** Debug drawing frustums. */
+ private boolean _drawDebug = false;
+
+ /** Debug drawing shader. */
+ private boolean _drawShaderDebug = false;
+
+ /** Do we want to keep the main shader to do both multitexturing shadows and overlays? */
+ private boolean _keepMainShader = false;
+
+ /**
+ * True if we want to factor in texturing to shadows; useful for casting shadows against alpha-tested textures.
+ * Default is false.
+ */
+ private boolean _useSceneTexturing = false;
+
+ /**
+ * True if we want to use the culling set on the objects instead of always culling front face (which is done for
+ * shadow precision). Default is false.
+ */
+ private boolean _useObjectCullFace = false;
+
+ /**
+ * When true (the default) the generated shadow map textures are drawn over the scene in a separate blend pass. If
+ * false, the shadows are generated, but not applied.
+ */
+ private boolean _renderShadowedScene = true;
+
+ /** Store format to use for the shadow textures. */
+ protected TextureStoreFormat _shadowTextureStoreFormat = TextureStoreFormat.Depth16;
+
+ /** Shadow filter techniques */
+ public enum Filter {
+ None, Pcf
+ }
+
+ private Filter filter = Filter.None;
+
+ private ShadowRenderCallback shadowRenderCallback;
+
+ /**
+ * Create a pssm shadow map pass casting shadows from a light with the direction given.
+ *
+ * @param shadowMapSize
+ * The size of the shadow map texture
+ * @param numOfSplits
+ * the num of splits
+ */
+ public ParallelSplitShadowMapPass(final Light light, final int shadowMapSize, final int numOfSplits) {
+ _light = light;
+ _shadowMapSize = shadowMapSize;
+ setNumOfSplits(numOfSplits);
+ _pssmCam = new PSSMCamera();
+
+ _noClip = new ClipState();
+ _noClip.setEnabled(false);
+ _noTexture = new TextureState();
+ _noTexture.setEnabled(false);
+ _colorDisabled = new ColorMaskState();
+ _colorDisabled.setAll(false);
+ _cullFrontFace = new CullState();
+ _cullFrontFace.setEnabled(true);
+ _cullFrontFace.setCullFace(CullState.Face.Front);
+ _noLights = new LightState();
+ _noLights.setEnabled(false);
+
+ _shadowOffsetState = new OffsetState();
+ _shadowOffsetState.setEnabled(true);
+ _shadowOffsetState.setTypeEnabled(OffsetType.Fill, true);
+ _shadowOffsetState.setFactor(1.1f);
+ _shadowOffsetState.setUnits(4.0f);
+
+ _flat = new ShadingState();
+ _flat.setShadingMode(ShadingMode.Flat);
+
+ // When rendering and comparing the shadow map with the current depth, the result will be set to alpha 1 if in
+ // shadow and to 0 if not in shadow.
+ _discardShadowFragments = new BlendState();
+ _discardShadowFragments.setEnabled(true);
+ _discardShadowFragments.setBlendEnabled(true);
+ _discardShadowFragments.setTestEnabled(true);
+ _discardShadowFragments.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ _discardShadowFragments.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+
+ _shadowTextureState = new TextureState();
+ }
+
+ /**
+ * Add a spatial that will occlude light and hence cast a shadow.
+ *
+ * @param occluder
+ * The spatial to add as an occluder
+ */
+ public void addOccluder(final Spatial occluder) {
+ if (!_occluderNodes.contains(occluder)) {
+ _occluderNodes.add(occluder);
+ }
+ }
+
+ /**
+ * Remove a spatial from the list of occluders.
+ *
+ * @param occluder
+ * The spatial to remove from the occluderlist
+ */
+ public void removeOccluder(final Spatial occluder) {
+ _occluderNodes.remove(occluder);
+ }
+
+ /**
+ * Initialize the pass render states.
+ *
+ * @param r
+ * the r
+ */
+ public void init(final Renderer r) {
+ if (_initialised) {
+ return;
+ }
+
+ _initialised = true; // now it's initialized
+
+ // render states to use when rendering into the shadow map, no textures or colors are required since we're only
+ // interested in recording depth. Also only need back faces when rendering the shadow maps
+
+ // Load PSSM shader.
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+ if (caps.isGLSLSupported()) {
+ _pssmShader = new GLSLShaderObjectsState();
+ try {
+ _pssmShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssm.vert"));
+ if (filter == Filter.None) {
+ _pssmShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssm.frag"));
+ } else if (filter == Filter.Pcf) {
+ _pssmShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmPCF.frag"));
+ }
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ _mainShader = _pssmShader;
+
+ _pssmDebugShader = new GLSLShaderObjectsState();
+ try {
+ _pssmDebugShader.setVertexShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmDebug.vert"));
+ _pssmDebugShader.setFragmentShader(ResourceLocatorTool.getClassPathResourceAsStream(
+ ParallelSplitShadowMapPass.class, "com/ardor3d/extension/shadow/map/pssmDebug.frag"));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, getClass().getName(), "init(Renderer)", "Could not load shaders.", ex);
+ }
+ }
+
+ // Setup texture renderer.
+ reinitTextureSize(r);
+
+ // Setup textures and shader for all splits
+ reinitSplits();
+ }
+
+ public void reinit(final Renderer r) {
+ reinitTextureSize(r);
+ reinitSplits();
+ }
+
+ /**
+ * Reinit texture size. Sets up texture renderer.
+ *
+ * @param r
+ * the Renderer
+ */
+ private void reinitTextureSize(final Renderer r) {
+ if (!_reinitTextureSizeDirty) {
+ return;
+ }
+
+ _reinitTextureSizeDirty = false;
+
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+
+ // Create texture renderer
+ _shadowMapRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(_shadowMapSize, _shadowMapSize, r,
+ caps);
+
+ // Enforce performance enhancing states on the renderer.
+ _shadowMapRenderer.enforceState(_noClip);
+ _shadowMapRenderer.enforceState(_colorDisabled);
+ if (!_useObjectCullFace) {
+ _shadowMapRenderer.enforceState(_cullFrontFace);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(StateType.Cull);
+ }
+ _shadowMapRenderer.enforceState(_noLights);
+ _shadowMapRenderer.enforceState(_flat);
+ _shadowMapRenderer.enforceState(_shadowOffsetState);
+ if (!_useSceneTexturing) {
+ _shadowMapRenderer.enforceState(_noTexture);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(RenderState.StateType.Texture);
+ }
+
+ if (_light instanceof DirectionalLight) {
+ _shadowMapRenderer.getCamera().setProjectionMode(ProjectionMode.Parallel);
+ }
+ }
+
+ /**
+ * Reinit splits. Setup textures and shader for all splits
+ */
+ private void reinitSplits() {
+ if (!_reinitSplitsDirty) {
+ return;
+ }
+
+ _reinitSplitsDirty = false;
+
+ // render state to apply the shadow map texture
+ _shadowMapTexture = new Texture2D[_numOfSplits];
+ for (int i = 0; i < _numOfSplits; i++) {
+ _shadowMapTexture[i] = new Texture2D();
+ _shadowMapTexture[i].setWrap(Texture.WrapMode.BorderClamp);
+ _shadowMapTexture[i].setMinificationFilter(Texture.MinificationFilter.BilinearNoMipMaps);
+ _shadowMapTexture[i].setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ _shadowMapTexture[i].setBorderColor(ColorRGBA.WHITE);
+
+ _shadowMapTexture[i].setEnvironmentalMapMode(Texture.EnvironmentalMapMode.EyeLinear);
+
+ _shadowMapTexture[i].setTextureStoreFormat(_shadowTextureStoreFormat);
+ _shadowMapTexture[i].setDepthCompareMode(DepthTextureCompareMode.RtoTexture);
+ _shadowMapTexture[i].setDepthCompareFunc(DepthTextureCompareFunc.GreaterThanEqual);
+ _shadowMapTexture[i].setDepthMode(DepthTextureMode.Intensity);
+
+ _shadowMapRenderer.setupTexture(_shadowMapTexture[i]);
+ _shadowTextureState.setTexture(_shadowMapTexture[i], i);
+ }
+ if (_pssmShader != null) {
+ for (int i = 0; i < _MAX_SPLITS; i++) {
+ _pssmShader.setUniform("shadowMap" + i, i);
+ _pssmDebugShader.setUniform("shadowMap" + i, i);
+ _mainShader.setUniform("shadowMap" + i, i);
+ }
+ }
+ }
+
+ /**
+ * Render the pass.
+ *
+ * @param r
+ * the Renderer
+ */
+ @Override
+ protected void doRender(final Renderer r) {
+ updateShadowMaps(r);
+
+ if (_renderShadowedScene) {
+ // Render overlay scene
+ renderShadowedScene(r);
+ }
+ }
+
+ public void updateShadowMaps(final Renderer r) {
+ init(r);
+ reinitTextureSize(r);
+ reinitSplits();
+
+ // Update receiving scene bounds to prepare for frustum packing
+ updateReceiverBounds();
+
+ // If updating camera is true (can be turned off for debugging), copy from main
+ // camera and pack frustum.
+ if (_updateMainCamera) {
+ // Copy our active camera to our working pssm camera.
+ final Camera cam = ContextManager.getCurrentContext().getCurrentCamera();
+ _pssmCam.set(cam);
+
+ // Calculate the closest fitting near and far planes.
+ if (hasValidBounds) {
+ _pssmCam.pack(_receiverBounds);
+ } else {
+ _pssmCam.updateMaxCameraFar();
+ }
+ }
+
+ // Calculate the split distances
+ _pssmCam.calculateSplitDistances(_numOfSplits);
+
+ // Render our scene in sections.
+ // For each part...
+ for (int iSplit = 0; iSplit < _numOfSplits; iSplit++) {
+ // Figure out the appropriate frustum.
+ final double fNear = _pssmCam.getSplitDistances()[iSplit];
+ final double fFar = _pssmCam.getSplitDistances()[iSplit + 1];
+
+ // Calculate the frustum corners for the current split
+ _pssmCam.calculateFrustum(fNear, fFar);
+
+ // Debug draw main camera frustum for current split
+ if (_drawDebug) {
+ drawFrustum(r, _pssmCam, fNear, fFar, new ColorRGBA(0, 1, (float) (iSplit + 1) / _numOfSplits, 1),
+ (short) 0xF000, iSplit == 0);
+ }
+
+ // Calculate the appropriate lightview and projection matrices for the current split
+ if (_light instanceof DirectionalLight) {
+ calculateOptimalLightFrustum(_pssmCam._corners, _pssmCam._center);
+
+ if (_drawDebug) {
+ boundingSphere
+ .setData(frustumBoundingSphere.getCenter(), 10, 10, frustumBoundingSphere.getRadius());
+ boundingSphere.draw(r);
+ }
+ } else if (_light instanceof PointLight) {
+ calculateOptimalLightFrustumOld(_pssmCam._corners, _pssmCam._center);
+ }
+
+ // Debug draw light frustum for current split
+ if (_drawDebug) {
+ drawFrustum(r, _shadowMapRenderer.getCamera(), new ColorRGBA(1, 1, (iSplit + 1) / (float) _numOfSplits,
+ 1), (short) 0xFFFF, true);
+ }
+
+ // Render shadowmap from light view for current split
+ updateShadowMap(iSplit, r);
+
+ // Update texture matrix for shadowmap
+ updateTextureMatrix(iSplit);
+ }
+
+ updateShaderVariables();
+ }
+
+ private void updateShaderVariables() {
+ if (_pssmShader != null && ContextManager.getCurrentContext().getCapabilities().isGLSLSupported()) {
+ final float split1 = (float) _pssmCam.getSplitDistances()[1];
+ final float split2 = (float) (_pssmCam.getSplitDistances().length > 2 ? _pssmCam.getSplitDistances()[2]
+ : 0f);
+ final float split3 = (float) (_pssmCam.getSplitDistances().length > 3 ? _pssmCam.getSplitDistances()[3]
+ : 0f);
+ final float split4 = (float) (_pssmCam.getSplitDistances().length > 4 ? _pssmCam.getSplitDistances()[4]
+ : 0f);
+
+ GLSLShaderObjectsState currentShader = _drawShaderDebug ? _pssmDebugShader : _pssmShader;
+ if (_drawShaderDebug) {
+ currentShader = _pssmDebugShader;
+ }
+
+ currentShader.setUniform("sampleDist", split1, split2, split3, split4);
+ if (filter == Filter.Pcf) {
+ // TODO
+ // currentShader.setUniform("_shadowSize", 1f / _shadowMapSize);
+ }
+
+ if (!_drawShaderDebug) {
+ currentShader.setUniform("shadowColor", _shadowColor);
+ }
+
+ if (_keepMainShader) {
+ _mainShader.setUniform("sampleDist", split1, split2, split3, split4);
+ if (filter == Filter.Pcf) {
+ // TODO
+ // _mainShader.setUniform("_shadowSize", 1f / _shadowMapSize);
+ }
+
+ if (!_drawShaderDebug) {
+ _mainShader.setUniform("shadowColor", _shadowColor);
+ }
+ }
+ }
+ }
+
+ // TODO
+ final FloatBuffer frustumBoundingBuffer = BufferUtils.createVector3Buffer(8);
+ final BoundingSphere frustumBoundingSphere = new BoundingSphere();
+
+ /**
+ * Calculate optimal light frustum perspective.
+ *
+ * @param frustumCorners
+ * the frustum corners
+ * @param center
+ * the center
+ */
+ private void calculateOptimalLightFrustum(final Vector3[] frustumCorners, final ReadOnlyVector3 centerFrustum) {
+ for (int i = 0; i < 8; i++) {
+ BufferUtils.setInBuffer(frustumCorners[i], frustumBoundingBuffer, i);
+ }
+ frustumBoundingSphere.computeFromPoints(frustumBoundingBuffer);
+
+ final Camera shadowCam = _shadowMapRenderer.getCamera();
+
+ final ReadOnlyVector3 center = frustumBoundingSphere.getCenter();
+ final double radius = frustumBoundingSphere.getRadius();
+
+ Vector3 direction = new Vector3();
+ final DirectionalLight dl = (DirectionalLight) _light;
+ direction = direction.set(dl.getDirection());
+ final double distance = Math.max(radius, _minimumLightDistance);
+
+ final Vector3 tmpVec = Vector3.fetchTempInstance();
+ tmpVec.set(direction);
+ tmpVec.negateLocal();
+ tmpVec.multiplyLocal(distance);
+ tmpVec.addLocal(center);
+
+ // temporary location
+ shadowCam.setLocation(tmpVec);
+ shadowCam.lookAt(center, Vector3.UNIT_Y);
+
+ {
+ // determine
+ final double texelSizeW = (2 * radius) / _shadowMapRenderer.getWidth();
+ final double texelSizeH = (2 * radius) / _shadowMapRenderer.getHeight();
+
+ // build a Quaternion from camera axes to move
+ final Quaternion q = Quaternion.fetchTempInstance();
+ q.fromAxes(shadowCam.getLeft(), shadowCam.getUp(), shadowCam.getDirection());
+
+ // invert to calculate in light space
+ final Vector3 lightSpace = q.invert(null).apply(tmpVec, Vector3.fetchTempInstance());
+
+ // snap to nearest texel
+ lightSpace.setX(lightSpace.getX() - (lightSpace.getX() % texelSizeW));
+ lightSpace.setY(lightSpace.getY() - (lightSpace.getY() % texelSizeH));
+
+ // convert back
+ q.apply(lightSpace, tmpVec);
+ Vector3.releaseTempInstance(lightSpace);
+
+ Quaternion.releaseTempInstance(q);
+ }
+
+ // updated location
+ final double x = tmpVec.getX();
+ final double y = tmpVec.getY();
+ final double z = tmpVec.getZ();
+ final double farZ = tmpVec.subtractLocal(center).length() + radius;
+ Vector3.releaseTempInstance(tmpVec);
+
+ // set frustum, then location
+ shadowCam.setFrustum(1, farZ, -radius, radius, radius, -radius);
+ shadowCam.setLocation(x, y, z);
+ }
+
+ /**
+ * Saving this around until we fully support a good solution for non-directional lights. Like dual paraboloid shadow
+ * maps...
+ *
+ * @param frustumCorners
+ * @param center
+ */
+ private void calculateOptimalLightFrustumOld(final Vector3[] frustumCorners, final ReadOnlyVector3 center) {
+ final Camera shadowCam = _shadowMapRenderer.getCamera();
+
+ double distance = _minimumLightDistance;
+
+ final Vector3 tmpVec = Vector3.fetchTempInstance();
+
+ // Update shadow camera from light
+ final PointLight pl = (PointLight) _light;
+
+ shadowCam.setLocation(pl.getLocation());
+
+ // Point light at split center
+ shadowCam.lookAt(center, Vector3.UNIT_Y);
+
+ // Reset frustum
+ distance = center.subtract(shadowCam.getLocation(), tmpVec).length();
+ shadowCam.setFrustum(1, distance, -1, 1, 1, -1);
+
+ double fMinX = Double.POSITIVE_INFINITY;
+ double fMaxX = Double.NEGATIVE_INFINITY;
+ double fMinY = Double.POSITIVE_INFINITY;
+ double fMaxY = Double.NEGATIVE_INFINITY;
+ double fMinZ = Double.POSITIVE_INFINITY;
+ double fMaxZ = Double.NEGATIVE_INFINITY;
+
+ final ReadOnlyMatrix4 lightviewproj = shadowCam.getModelViewProjectionMatrix();
+
+ final Vector4 position = Vector4.fetchTempInstance();
+ for (final Vector3 frustumCorner : frustumCorners) {
+ position.set(frustumCorner.getX(), frustumCorner.getY(), frustumCorner.getZ(), 1);
+ lightviewproj.applyPre(position, position);
+
+ position.setX(position.getX() / position.getW());
+ position.setY(position.getY() / position.getW());
+ position.setZ(position.getZ());
+
+ fMinX = Math.min(position.getX(), fMinX);
+ fMaxX = Math.max(position.getX(), fMaxX);
+
+ fMinY = Math.min(position.getY(), fMinY);
+ fMaxY = Math.max(position.getY(), fMaxY);
+
+ fMinZ = Math.min(position.getZ(), fMinZ);
+ fMaxZ = Math.max(position.getZ(), fMaxZ);
+ }
+
+ double width = 0;
+ double height = 0;
+ fMinX = clamp(fMinX, -1.0, 1.0);
+ fMaxX = clamp(fMaxX, -1.0, 1.0);
+ fMinY = clamp(fMinY, -1.0, 1.0);
+ fMaxY = clamp(fMaxY, -1.0, 1.0);
+
+ // Make sure the minimum z is at least a specified distance from
+ // the target.
+ fMinZ = Math.min(fMinZ, distance - _minimumLightDistance);
+ fMinZ = Math.max(10.0, fMinZ);
+
+ width = fMinZ * (fMaxX - fMinX) * 0.5;
+ height = fMinZ * (fMaxY - fMinY) * 0.5;
+
+ final Vector3 newCenter = Vector3.fetchTempInstance();
+ position.set((fMinX + fMaxX) * 0.5, (fMinY + fMaxY) * 0.5, 1.0, 1);
+ shadowCam.getModelViewProjectionInverseMatrix().applyPre(position, position);
+ position.divideLocal(position.getW());
+ newCenter.set(position.getX(), position.getY(), position.getZ());
+
+ shadowCam.lookAt(newCenter, Vector3.UNIT_Y);
+
+ Vector3.releaseTempInstance(newCenter);
+ Vector4.releaseTempInstance(position);
+
+ shadowCam.setFrustum(fMinZ, fMaxZ, -width, width, height, -height);
+ shadowCam.update();
+ }
+
+ /**
+ * Render the overlay scene with shadows.
+ *
+ * @param r
+ * The renderer to use
+ */
+ public void renderShadowedScene(final Renderer r) {
+ boolean reset = false;
+ if (_context == null) {
+ _context = ContextManager.getCurrentContext();
+ reset = true;
+ }
+
+ _context.pushEnforcedStates();
+ _context.enforceState(_shadowTextureState);
+ _context.enforceState(_discardShadowFragments);
+
+ if (_pssmShader != null && _context.getCapabilities().isGLSLSupported()) {
+ GLSLShaderObjectsState currentShader = _drawShaderDebug ? _pssmDebugShader : _pssmShader;
+ if (_drawShaderDebug) {
+ currentShader = _pssmDebugShader;
+ }
+ if (_keepMainShader) {
+ currentShader = _mainShader;
+ }
+ _context.enforceState(currentShader);
+ }
+
+ for (final Spatial spat : _spatials) {
+ spat.onDraw(r);
+ }
+ r.renderBuckets();
+
+ _context.popEnforcedStates();
+
+ if (reset) {
+ // _context = null;
+ }
+ }
+
+ private static RenderLogic logic = new RenderLogic() {
+ private CullState cullState;
+ private Face cullFace;
+ private boolean isVisible;
+
+ public void apply(final Renderable renderable) {
+ if (renderable instanceof Mesh) {
+ final Mesh mesh = (Mesh) renderable;
+
+ isVisible = mesh.isVisible();
+ if (!mesh.getSceneHints().isCastsShadows()) {
+ mesh.setVisible(false);
+ }
+
+ cullState = (CullState) mesh.getWorldRenderState(StateType.Cull);
+ if (cullState != null) {
+ cullFace = cullState.getCullFace();
+ if (cullFace != Face.None) {
+ cullState.setCullFace(Face.Front);
+ }
+ }
+ }
+ }
+
+ public void restore(final Renderable renderable) {
+ if (renderable instanceof Mesh) {
+ final Mesh mesh = (Mesh) renderable;
+
+ mesh.setVisible(isVisible);
+
+ if (cullState != null) {
+ cullState.setCullFace(cullFace);
+ }
+ }
+ }
+ };
+
+ /**
+ * Update the shadow map.
+ *
+ * @param index
+ * shadow map texture index to update
+ */
+ private void updateShadowMap(final int index, final Renderer r) {
+
+ if (shadowRenderCallback != null) {
+ shadowRenderCallback.onRender(index, r, this, _shadowMapRenderer.getCamera());
+ }
+
+ r.setRenderLogic(logic);
+ if (!_useSceneTexturing) {
+ Mesh.RENDER_VERTEX_ONLY = true;
+ }
+ _shadowMapRenderer.render(_occluderNodes, _shadowMapTexture[index], Renderer.BUFFER_COLOR_AND_DEPTH);
+ if (!_useSceneTexturing) {
+ Mesh.RENDER_VERTEX_ONLY = false;
+ }
+ r.setRenderLogic(null);
+ }
+
+ /**
+ * Update texture matrix.
+ *
+ * @param index
+ * the index
+ */
+ private void updateTextureMatrix(final int index) {
+ // Create a matrix going from light to camera space
+ final Camera cam = ContextManager.getCurrentContext().getCurrentCamera();
+ _shadowMatrix.set(cam.getModelViewMatrix()).invertLocal();
+ _shadowMatrix.multiplyLocal(_shadowMapRenderer.getCamera().getModelViewProjectionMatrix()).multiplyLocal(
+ SCALE_BIAS_MATRIX);
+ _shadowMapTexture[index].setTextureMatrix(_shadowMatrix);
+ }
+
+ /**
+ * Checks if this pass is initialized.
+ *
+ * @return true, if is initialized
+ */
+ public boolean isInitialised() {
+ return _initialised;
+ }
+
+ /**
+ *
+ * @return the offset state used for drawing the shadow textures.
+ */
+ public OffsetState getShadowOffsetState() {
+ return _shadowOffsetState;
+ }
+
+ /**
+ * Update receiver bounds.
+ */
+ private void updateReceiverBounds() {
+ hasValidBounds = false;
+ boolean firstRun = true;
+
+ for (int i = 0, cSize = _boundsReceiver.size(); i < cSize; i++) {
+ final Spatial child = _boundsReceiver.get(i);
+ if (child != null && child.getWorldBound() != null && boundIsValid(child.getWorldBound())) {
+ if (firstRun) {
+ _receiverBounds.setCenter(child.getWorldBound().getCenter());
+ _receiverBounds.setXExtent(0);
+ _receiverBounds.setYExtent(0);
+ _receiverBounds.setZExtent(0);
+ firstRun = false;
+ }
+ _receiverBounds.mergeLocal(child.getWorldBound());
+ hasValidBounds = true;
+ }
+ }
+
+ for (int i = 0, cSize = _spatials.size(); i < cSize; i++) {
+ final Spatial child = _spatials.get(i);
+ if (child != null && child.getWorldBound() != null && boundIsValid(child.getWorldBound())) {
+ if (firstRun) {
+ _receiverBounds.setCenter(child.getWorldBound().getCenter());
+ _receiverBounds.setXExtent(0);
+ _receiverBounds.setYExtent(0);
+ _receiverBounds.setZExtent(0);
+ firstRun = false;
+ }
+ _receiverBounds.mergeLocal(child.getWorldBound());
+ hasValidBounds = true;
+ }
+ }
+ }
+
+ /**
+ * Checks if a bounding volume is valid.
+ *
+ * @param volume
+ * @return
+ */
+ private boolean boundIsValid(final BoundingVolume volume) {
+ if (!Vector3.isValid(volume.getCenter())) {
+ return false;
+ }
+ switch (volume.getType()) {
+ case AABB: {
+ final BoundingBox vBox = (BoundingBox) volume;
+ return !(Double.isInfinite(vBox.getXExtent()) || Double.isInfinite(vBox.getYExtent())
+ || Double.isInfinite(vBox.getZExtent()) || Double.isNaN(vBox.getXExtent())
+ || Double.isNaN(vBox.getYExtent()) || Double.isNaN(vBox.getZExtent()));
+ }
+
+ case Sphere: {
+ final BoundingSphere vSphere = (BoundingSphere) volume;
+ return !(Double.isInfinite(vSphere.getRadius()) || Double.isNaN(vSphere.getRadius()));
+ }
+
+ case OBB: {
+ final OrientedBoundingBox obb = (OrientedBoundingBox) volume;
+ return Vector3.isValid(obb.getExtent());
+ }
+
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Gets the shadow map texture.
+ *
+ * @param index
+ * the index
+ *
+ * @return the shadow map texture
+ */
+ public Texture2D getShadowMapTexture(final int index) {
+ return _shadowMapTexture[index];
+ }
+
+ /**
+ * Gets the number of splits.
+ *
+ * @return the number of splits
+ */
+ public int getNumOfSplits() {
+ return _numOfSplits;
+ }
+
+ /**
+ * Sets the number of frustum splits and thus the number of shadow textures created by this pass. More splits
+ * creates crisper shadows at the cost of increased texture memory.
+ *
+ * @param numOfSplits
+ * the new number of splits
+ */
+ public void setNumOfSplits(final int numOfSplits) {
+ _numOfSplits = Math.min(Math.max(_MIN_SPLITS, numOfSplits), _MAX_SPLITS);
+ _reinitSplitsDirty = true;
+
+ if (_numOfSplits != numOfSplits) {
+ logger.warning("Valid range for number of splits is " + _MIN_SPLITS + " to " + _MAX_SPLITS
+ + ". Tried to set it to " + numOfSplits);
+ }
+ }
+
+ /**
+ * Gets the shadow map size.
+ *
+ * @return the shadow map size
+ */
+ public int getShadowMapSize() {
+ return _shadowMapSize;
+ }
+
+ /**
+ * Sets the shadow map size.
+ *
+ * @param shadowMapSize
+ * the new shadow map size
+ */
+ public void setShadowMapSize(final int shadowMapSize) {
+ _shadowMapSize = shadowMapSize;
+ _reinitTextureSizeDirty = true;
+ _reinitSplitsDirty = true;
+ }
+
+ /**
+ * Gets the maximum distance for shadowing.
+ *
+ * @return max distance
+ * @see com.ardor3d.extension.shadow.map.PSSMCamera#getMaxFarPlaneDistance()
+ */
+ public double getMaxShadowDistance() {
+ return _pssmCam.getMaxFarPlaneDistance();
+ }
+
+ /**
+ * Sets the maximum distance for shadowing.
+ *
+ * @param maxShadowDistance
+ * distance to set
+ * @see com.ardor3d.extension.shadow.map.PSSMCamera#setMaxFarPlaneDistance(double)
+ */
+ public void setMaxShadowDistance(final double maxShadowDistance) {
+ _pssmCam.setMaxFarPlaneDistance(maxShadowDistance);
+ }
+
+ /**
+ * Gets the minimum z distance for the light.
+ *
+ * @return the minimumLightDistance
+ */
+ public double getMinimumLightDistance() {
+ return _minimumLightDistance;
+ }
+
+ /**
+ * Sets the minimum z distance for the light.
+ *
+ * @param minimumLightDistance
+ * the minimumLightDistance to set
+ */
+ public void setMinimumLightDistance(final double minimumLightDistance) {
+ _minimumLightDistance = minimumLightDistance;
+ }
+
+ /**
+ * Gets shadow color and transparency.
+ *
+ * @return the shadowColor
+ */
+ public ReadOnlyColorRGBA getShadowColor() {
+ return _shadowColor;
+ }
+
+ /**
+ * Sets shadow color and transparency.
+ *
+ * @param shadowColor
+ * the shadowColor to set
+ */
+ public void setShadowColor(final ReadOnlyColorRGBA shadowColor) {
+ _shadowColor.set(shadowColor);
+ }
+
+ public ShadowRenderCallback getShadowRenderCallback() {
+ return shadowRenderCallback;
+ }
+
+ public void setShadowRenderCallback(final ShadowRenderCallback callback) {
+ shadowRenderCallback = callback;
+ }
+
+ /**
+ * Clean up.
+ *
+ * @see com.ardor3d.renderer.pass.Pass#cleanUp()
+ */
+ @Override
+ public void cleanUp() {
+ super.cleanUp();
+
+ if (_shadowMapRenderer != null) {
+ _shadowMapRenderer.cleanup();
+ }
+ }
+
+ /**
+ * Remove the contents of the pass.
+ */
+ public void clear() {
+ _occluderNodes.clear();
+ _spatials.clear();
+ }
+
+ /**
+ * Simple clamp.
+ *
+ * @param val
+ * value to clamp
+ * @param from
+ * minimum value after clamping
+ * @param to
+ * maximum value after clamping
+ * @return Math.min(to, Math.max(from, val))
+ */
+ private double clamp(final double val, final double from, final double to) {
+ return Math.min(to, Math.max(from, val));
+ }
+
+ /**
+ * @return the updateMainCamera
+ */
+ public boolean isUpdateMainCamera() {
+ return _updateMainCamera;
+ }
+
+ /**
+ * @param updateMainCamera
+ * True (the default) if we want to copy the current rendering camera into this pass for use in shadow
+ * generation. False if we will manage our shadow camera elsewhere.
+ * @see #getPssmCam()
+ */
+ public void setUpdateMainCamera(final boolean updateMainCamera) {
+ _updateMainCamera = updateMainCamera;
+ }
+
+ /**
+ * @return the drawDebug
+ */
+ public boolean isDrawDebug() {
+ return _drawDebug;
+ }
+
+ /**
+ * @param drawDebug
+ * True if we want to draw camera and light frustums for debugging purposes. Default is false.
+ */
+ public void setDrawDebug(final boolean drawDebug) {
+ _drawDebug = drawDebug;
+ }
+
+ /**
+ * @return the drawShaderDebug
+ */
+ public boolean isDrawShaderDebug() {
+ return _drawShaderDebug;
+ }
+
+ /**
+ * @param drawShaderDebug
+ * True if we want to draw debug colors over the shadows, showing which level they come from.
+ */
+ public void setDrawShaderDebug(final boolean drawShaderDebug) {
+ _drawShaderDebug = drawShaderDebug;
+ }
+
+ /**
+ * @return the useSceneTexturing
+ */
+ public boolean isUseSceneTexturing() {
+ return _useSceneTexturing;
+ }
+
+ /**
+ * @param useSceneTexturing
+ * True if we want to factor in texturing to shadows; useful for casting shadows against alpha-tested
+ * textures. Default is false.
+ */
+ public void setUseSceneTexturing(final boolean useSceneTexturing) {
+ _useSceneTexturing = useSceneTexturing;
+ if (_shadowMapRenderer != null) {
+ if (!_useSceneTexturing) {
+ _shadowMapRenderer.enforceState(_noTexture);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(RenderState.StateType.Texture);
+ }
+ }
+
+ }
+
+ public boolean isUseObjectCullFace() {
+ return _useObjectCullFace;
+ }
+
+ /**
+ * @param useObjectCullFace
+ * True if we want to use the culling set on the objects instead of always culling front face (which is
+ * done for shadow precision). Default is false.
+ */
+ public void setUseObjectCullFace(final boolean useObjectCullFace) {
+ _useObjectCullFace = useObjectCullFace;
+ if (_shadowMapRenderer != null) {
+ if (!_useObjectCullFace) {
+ _shadowMapRenderer.enforceState(_cullFrontFace);
+ } else {
+ _shadowMapRenderer.clearEnforcedState(StateType.Cull);
+ }
+ }
+ }
+
+ public boolean isRenderShadowedScene() {
+ return _renderShadowedScene;
+ }
+
+ /**
+ * @param renderShadowedScene
+ * When true (the default) the generated shadow map textures are drawn over the scene in a separate blend
+ * pass. If false, the shadows are generated, but not applied.
+ */
+ public void setRenderShadowedScene(final boolean renderShadowedScene) {
+ _renderShadowedScene = renderShadowedScene;
+ }
+
+ /**
+ * @return the camera used internally to generate shadows.
+ */
+ public PSSMCamera getPssmCam() {
+ return _pssmCam;
+ }
+
+ /**
+ * @return the texture store format to use for the shadow textures on the next call to init/setNumOfSplits.
+ */
+ public TextureStoreFormat getShadowTextureStoreFormat() {
+ return _shadowTextureStoreFormat;
+ }
+
+ /**
+ * @param shadowTextureStoreFormat
+ * - the texture store format to use for the shadow textures. Only has an affect if called prior to
+ * calling init on this pass (or if called before calling {@link #setNumOfSplits(int)}).
+ */
+ public void setShadowTextureStoreFormat(final TextureStoreFormat shadowTextureStoreFormat) {
+ _shadowTextureStoreFormat = shadowTextureStoreFormat;
+ }
+
+ /** The debug line frustum. */
+ private static Line lineFrustum;
+
+ /** The debug test cam. */
+ private static final PSSMCamera testCam = new PSSMCamera();
+
+ /** Debug bounding sphere */
+ private static final Sphere boundingSphere = new Sphere("bsphere", 10, 10, 1);
+ static {
+ boundingSphere.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ boundingSphere.setRenderState(new WireframeState());
+ boundingSphere.setRenderState(new ZBufferState());
+ boundingSphere.updateWorldRenderStates(false);
+ }
+
+ /**
+ * Draw debug frustum.
+ *
+ * @param r
+ * the r
+ * @param cam
+ * the cam
+ * @param color
+ * the color
+ * @param drawOriginConnector
+ * whether or not to draw a connector
+ */
+ private static void drawFrustum(final Renderer r, final Camera cam, final ReadOnlyColorRGBA color,
+ final short pattern, final boolean drawOriginConnector) {
+ drawFrustum(r, cam, cam.getFrustumNear(), cam.getFrustumFar(), color, pattern, drawOriginConnector);
+ }
+
+ /**
+ * Draw debug frustum.
+ *
+ * @param r
+ * the r
+ * @param cam
+ * the cam
+ * @param fNear
+ * the f near
+ * @param fFar
+ * the f far
+ * @param color
+ * the color
+ * @param drawOriginConnector
+ * whether or not to draw a connector
+ */
+ private static void drawFrustum(final Renderer r, final Camera cam, final double fNear, final double fFar,
+ final ReadOnlyColorRGBA color, final short pattern, final boolean drawOriginConnector) {
+ if (lineFrustum == null) {
+ final FloatBuffer verts = BufferUtils.createVector3Buffer(24);
+ final FloatBuffer colors = BufferUtils.createColorBuffer(24);
+
+ lineFrustum = new Line("Lines", verts, null, colors, null);
+ lineFrustum.getMeshData().setIndexModes(
+ new IndexMode[] { IndexMode.LineLoop, IndexMode.LineLoop, IndexMode.Lines, IndexMode.Lines });
+ lineFrustum.getMeshData().setIndexLengths(new int[] { 4, 4, 8, 8 });
+ lineFrustum.setLineWidth(2);
+ lineFrustum.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ final BlendState lineBlendState = new BlendState();
+ lineBlendState.setEnabled(true);
+ lineBlendState.setBlendEnabled(true);
+ lineBlendState.setTestEnabled(true);
+ lineBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ lineBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ lineFrustum.setRenderState(lineBlendState);
+
+ final ZBufferState zstate = new ZBufferState();
+ lineFrustum.setRenderState(zstate);
+ lineFrustum.updateGeometricState(0.0);
+
+ lineFrustum.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ }
+
+ lineFrustum.setDefaultColor(color);
+ lineFrustum.setStipplePattern(pattern);
+
+ testCam.set(cam);
+ testCam.update();
+ testCam.calculateFrustum(fNear, fFar);
+
+ final FloatBuffer colors = lineFrustum.getMeshData().getColorBuffer();
+ for (int i = 0; i < 16; i++) {
+ BufferUtils.setInBuffer(color, colors, i);
+ }
+ final float alpha = drawOriginConnector ? 0.4f : 0.0f;
+ for (int i = 16; i < 24; i++) {
+ colors.position(i * 4);
+ colors.put(color.getRed());
+ colors.put(color.getGreen());
+ colors.put(color.getBlue());
+ colors.put(alpha);
+ }
+
+ final FloatBuffer verts = lineFrustum.getMeshData().getVertexBuffer();
+ BufferUtils.setInBuffer(testCam._corners[0], verts, 0);
+ BufferUtils.setInBuffer(testCam._corners[1], verts, 1);
+ BufferUtils.setInBuffer(testCam._corners[2], verts, 2);
+ BufferUtils.setInBuffer(testCam._corners[3], verts, 3);
+
+ BufferUtils.setInBuffer(testCam._corners[4], verts, 4);
+ BufferUtils.setInBuffer(testCam._corners[5], verts, 5);
+ BufferUtils.setInBuffer(testCam._corners[6], verts, 6);
+ BufferUtils.setInBuffer(testCam._corners[7], verts, 7);
+
+ BufferUtils.setInBuffer(testCam._corners[0], verts, 8);
+ BufferUtils.setInBuffer(testCam._corners[4], verts, 9);
+ BufferUtils.setInBuffer(testCam._corners[1], verts, 10);
+ BufferUtils.setInBuffer(testCam._corners[5], verts, 11);
+ BufferUtils.setInBuffer(testCam._corners[2], verts, 12);
+ BufferUtils.setInBuffer(testCam._corners[6], verts, 13);
+ BufferUtils.setInBuffer(testCam._corners[3], verts, 14);
+ BufferUtils.setInBuffer(testCam._corners[7], verts, 15);
+
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 16);
+ BufferUtils.setInBuffer(testCam._corners[0], verts, 17);
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 18);
+ BufferUtils.setInBuffer(testCam._corners[1], verts, 19);
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 20);
+ BufferUtils.setInBuffer(testCam._corners[2], verts, 21);
+ BufferUtils.setInBuffer(testCam.getLocation(), verts, 22);
+ BufferUtils.setInBuffer(testCam._corners[3], verts, 23);
+
+ lineFrustum.draw(r);
+ }
+
+ public void setPssmShader(final GLSLShaderObjectsState pssmShader) {
+ _pssmShader = pssmShader;
+ }
+
+ public boolean isKeepMainShader() {
+ return _keepMainShader;
+ }
+
+ public void setKeepMainShader(final boolean keepMainShader) {
+ _keepMainShader = keepMainShader;
+ }
+
+ public void addBoundsReceiver(final Spatial spatial) {
+ _boundsReceiver.add(spatial);
+ }
+
+ public void setFiltering(final Filter filter) {
+ this.filter = filter;
+ }
+
+ public BlendState getDiscardShadowFragmentsBlendState() {
+ return _discardShadowFragments;
+ }
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.java b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.java
new file mode 100644
index 0000000..931f3ca
--- /dev/null
+++ b/ardor3d-effects/src/main/java/com/ardor3d/extension/shadow/map/ShadowRenderCallback.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.shadow.map;
+
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+
+public interface ShadowRenderCallback {
+
+ void onRender(int splitIndex, Renderer renderer, ParallelSplitShadowMapPass pass, Camera renderCamera);
+
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag
new file mode 100644
index 0000000..e88109e
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/add2textures.frag
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D tex1;
+uniform sampler2D tex2;
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec4 color1 = texture2D(tex1, texCoord);
+ vec4 color2 = texture2D(tex2, texCoord);
+
+ gl_FragColor = vec4(color1.rgb + color2.rgb, min(color1.a + color2.a, 1.0));
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag
new file mode 100644
index 0000000..09ec326
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.frag
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec2 samples00 = vec2(-0.326212, -0.405805);
+ vec2 samples01 = vec2(-0.840144, -0.073580);
+ vec2 samples02 = vec2(-0.695914, 0.457137);
+ vec2 samples03 = vec2(-0.203345, 0.620716);
+ vec2 samples04 = vec2( 0.962340, -0.194983);
+ vec2 samples05 = vec2( 0.473434, -0.480026);
+ vec2 samples06 = vec2( 0.519456, 0.767022);
+ vec2 samples07 = vec2( 0.185461, -0.893124);
+ vec2 samples08 = vec2( 0.507431, 0.064425);
+ vec2 samples09 = vec2( 0.896420, 0.412458);
+ vec2 samples10 = vec2(-0.321940, -0.932615);
+ vec2 samples11 = vec2(-0.791559, -0.597705);
+
+ vec2 newCoord;
+ vec4 sum = texture2D(RT, texCoord);
+
+ newCoord = texCoord + sampleDist * samples00;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples01;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples02;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples03;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples04;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples05;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples06;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples07;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples08;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples09;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples10;
+ sum += texture2D(RT, newCoord);
+
+ newCoord = texCoord + sampleDist * samples11;
+ sum += texture2D(RT, newCoord);
+
+ sum /= 13.0;
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert
new file mode 100644
index 0000000..02bda22
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur.vert
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ texCoord.x = 0.5 * (1.0 + Pos.x);
+ texCoord.y = 0.5 * (1.0 + Pos.y);
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag
new file mode 100644
index 0000000..bb8d7db
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal5.frag
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x - 1.0*sampleDist, texCoord.y)) * 1.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.5*sampleDist, texCoord.y)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 3.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.5*sampleDist, texCoord.y)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x + 1.0*sampleDist, texCoord.y)) * 1.0/9.0;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag
new file mode 100644
index 0000000..6ee9508
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal7.frag
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x - 1.0*sampleDist, texCoord.y)) * 0.25/4.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.666*sampleDist, texCoord.y)) * 0.5/4.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.333*sampleDist, texCoord.y)) * 0.75/4.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 1.0/4.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.333*sampleDist, texCoord.y)) * 0.75/4.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.666*sampleDist, texCoord.y)) * 0.5/4.0;
+ sum += texture2D(RT, vec2(texCoord.x + 1.0*sampleDist, texCoord.y)) * 0.25/4.0;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag
new file mode 100644
index 0000000..d447dfa
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_horizontal9.frag
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x - 1.0*sampleDist, texCoord.y)) * 1.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.75*sampleDist, texCoord.y)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.5*sampleDist, texCoord.y)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x - 0.25*sampleDist, texCoord.y)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 5.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.25*sampleDist, texCoord.y)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.5*sampleDist, texCoord.y)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 0.75*sampleDist, texCoord.y)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x + 1.0*sampleDist, texCoord.y)) * 1.0/25.0;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag
new file mode 100644
index 0000000..ca44225
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5.frag
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 1.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.5*sampleDist)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 3.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.5*sampleDist)) * 2.0/9.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 1.0*sampleDist)) * 1.0/9.0;
+
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag
new file mode 100644
index 0000000..08f7fa1
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical5_down.frag
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 6.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 4.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 2.0*sampleDist)) * 3.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 3.0*sampleDist)) * 2.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 4.0*sampleDist)) * 1.0/16.0;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag
new file mode 100644
index 0000000..385afc8
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical7.frag
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 1.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.666*sampleDist)) * 2.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.333*sampleDist)) * 3.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 4.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.333*sampleDist)) * 3.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.666*sampleDist)) * 2.0/16.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 1.0*sampleDist)) * 1.0/16.0;
+
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag
new file mode 100644
index 0000000..55fbf26
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_blur_vertical9.frag
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float sampleDist;
+uniform float blurIntensityMultiplier;
+uniform sampler2D RT;
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 1.0*sampleDist)) * 1.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.75*sampleDist)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.5*sampleDist)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y - 0.25*sampleDist)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y)) * 5.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.25*sampleDist)) * 4.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.5*sampleDist)) * 3.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 0.75*sampleDist)) * 2.0/25.0;
+ sum += texture2D(RT, vec2(texCoord.x, texCoord.y + 1.0*sampleDist)) * 1.0/25.0;
+
+ sum *= blurIntensityMultiplier;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag
new file mode 100644
index 0000000..88e4630
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.frag
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float exposurePow;
+uniform float exposureCutoff;
+uniform sampler2D RT;
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ vec4 sum = texture2D(RT, vTexCoord);
+ if ( (sum.r+sum.g+sum.b)/3.0 < exposureCutoff ) {
+ sum = vec4(0.0);
+ }
+ sum = pow(sum,vec4(exposurePow));
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert
new file mode 100644
index 0000000..1ed3f16
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_extract.vert
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ vTexCoord.x = 0.5 * (1.0 + Pos.x);
+ vTexCoord.y = 0.5 * (1.0 + Pos.y);
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag
new file mode 100644
index 0000000..e33042b
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.frag
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D RT;
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ gl_FragColor = texture2D(RT, vTexCoord);
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert
new file mode 100644
index 0000000..1ed3f16
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom/bloom_final.vert
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 vTexCoord;
+
+void main(void)
+{
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ vTexCoord.x = 0.5 * (1.0 + Pos.x);
+ vTexCoord.y = 0.5 * (1.0 + Pos.y);
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag
new file mode 100644
index 0000000..42ccb9c
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/bloom_extract.frag
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D inputTex;
+uniform float exposureIntensity;
+uniform float exposureCutoff;
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec4 color = texture2D(inputTex, texCoord);
+ float lum = dot(vec3(0.3086, 0.6094, 0.0820), color.rgb);
+
+ if (lum < exposureCutoff ) {
+ color = vec4(0.0, 0.0, 0.0, color.a);
+ }
+
+ gl_FragColor = vec4(color.rgb * exposureIntensity, color.a);
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag
new file mode 100644
index 0000000..7caf050
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/color_replace.frag
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D inputTex;
+uniform sampler2D colorRampTex;
+
+uniform float redWeight;
+uniform float greenWeight;
+uniform float blueWeight;
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec4 color = texture2D(inputTex, texCoord);
+ vec3 convert = vec3(redWeight, greenWeight, blueWeight);
+
+ float luminance = dot(convert, color.rgb);
+
+ vec4 finalColor = texture2D( colorRampTex, vec2(luminance, .5) );
+ finalColor.a = color.a;
+ gl_FragColor = finalColor;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert
new file mode 100644
index 0000000..9396569
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/fsq.vert
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec2 Pos = sign(gl_Vertex.xy);
+ gl_Position = vec4(Pos.xy, 0, 1);
+ texCoord.x = 0.5 * (1.0 + Pos.x);
+ texCoord.y = 0.5 * (1.0 + Pos.y);
+}
+
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag
new file mode 100644
index 0000000..4ed36dc
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_horizontal9.frag
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D inputTex;
+uniform float sampleDist;
+
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(inputTex, vec2(texCoord.x - 1.0 * sampleDist, texCoord.y)) * 0.04;
+ sum += texture2D(inputTex, vec2(texCoord.x - 0.75 * sampleDist, texCoord.y)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x - 0.5 * sampleDist, texCoord.y)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x - 0.25 * sampleDist, texCoord.y)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x , texCoord.y)) * 0.20;
+ sum += texture2D(inputTex, vec2(texCoord.x + 0.25 * sampleDist, texCoord.y)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x + 0.5 * sampleDist, texCoord.y)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x + 0.75 * sampleDist, texCoord.y)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x + 1.0 * sampleDist, texCoord.y)) * 0.04;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag
new file mode 100644
index 0000000..73b3472
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/gausian_blur_vertical9.frag
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D inputTex;
+uniform float sampleDist;
+
+varying vec2 texCoord;
+
+void main(void)
+{
+ vec4 sum = vec4(0.0);
+
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 1.0 * sampleDist)) * 0.04;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 0.75 * sampleDist)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 0.5 * sampleDist)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y - 0.25 * sampleDist)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y )) * 0.20;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 0.25 * sampleDist)) * 0.16;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 0.5 * sampleDist)) * 0.12;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 0.75 * sampleDist)) * 0.08;
+ sum += texture2D(inputTex, vec2(texCoord.x, texCoord.y + 1.0 * sampleDist)) * 0.04;
+
+ gl_FragColor = sum;
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag
new file mode 100644
index 0000000..1d6916a
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/luminance.frag
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2D inputTex;
+
+varying vec2 texCoord;
+
+void main(void) {
+ vec4 color = texture2D(inputTex, texCoord);
+ vec3 convert = vec3(0.3086, 0.6094, 0.0820);
+
+ float luminance = dot(convert, color.rgb);
+
+ gl_FragColor = vec4(luminance, luminance, luminance, 1.0);
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.png b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.png
new file mode 100644
index 0000000..14751d3
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/sepiatone.png
Binary files differ
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag
new file mode 100644
index 0000000..a69fbcc
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.frag
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler3D texture;
+uniform float levels;
+uniform vec2 sliceOffset[16];
+//uniform float maxLevel;
+uniform float validLevels;
+uniform float textureSize;
+uniform float texelSize;
+uniform float showDebug;
+
+varying vec2 vVertex;
+
+
+vec4 texture3DBilinear( const in sampler3D textureSampler, const in vec3 uv, const in vec2 offset )
+{
+ vec4 tl = texture3D(textureSampler, uv);
+ vec4 tr = texture3D(textureSampler, uv + vec3(texelSize, 0, 0));
+ vec4 bl = texture3D(textureSampler, uv + vec3(0, texelSize, 0));
+ vec4 br = texture3D(textureSampler, uv + vec3(texelSize , texelSize, 0));
+
+ vec2 f = fract( uv.xy * textureSize ); // get the decimal part
+ vec4 tA = mix( tl, tr, f.x ); // will interpolate the red dot in the image
+ vec4 tB = mix( bl, br, f.x ); // will interpolate the blue dot in the image
+ return mix( tA, tB, f.y ); // will interpolate the green dot in the image
+}
+
+
+void main()
+{
+ float unit = (max(abs(vVertex.x), abs(vVertex.y)));
+
+ unit = floor(unit);
+ unit = log2(unit);
+ unit = floor(unit);
+
+// unit = max(unit, maxLevel);
+ if (unit > validLevels) {
+ discard;
+ }
+// unit = min(unit, validLevels);
+
+ unit = max(unit, 0.0);
+
+ vec2 offset = sliceOffset[int(unit)];
+ float frac = unit;
+ frac = exp2(frac);
+ frac *= 4.0; //Magic number
+ vec2 texCoord = vVertex/vec2(frac);
+ vec2 fadeCoord = texCoord;
+ texCoord += vec2(0.5);
+ texCoord *= vec2(1.0 - texelSize);
+ texCoord += offset;
+
+ float unit2 = unit + 1.0;
+ unit2 = min(unit2, validLevels);
+ vec2 offset2 = sliceOffset[int(unit2)];
+ float frac2 = unit2;
+ frac2 = exp2(frac2);
+ frac2 *= 4.0; //Magic number
+ vec2 texCoord2 = vVertex/vec2(frac2);
+ texCoord2 += vec2(0.5);
+ texCoord2 *= vec2(1.0 - texelSize);
+ texCoord2 += offset2;
+
+ unit /= levels;
+ unit = clamp(unit, 0.0, 0.99);
+
+ unit2 /= levels;
+ unit2 = clamp(unit2, 0.0, 0.99);
+
+ //vec4 tex = texture3D(texture, vec3(texCoord.x, texCoord.y, unit));
+ vec4 tex = texture3DBilinear(texture, vec3(texCoord.x, texCoord.y, unit), offset);
+ vec4 tex2 = texture3DBilinear(texture, vec3(texCoord2.x, texCoord2.y, unit2), offset2);
+
+ float fadeVal1 = abs(fadeCoord.x)*2.05;
+ float fadeVal2 = abs(fadeCoord.y)*2.05;
+ float fadeVal = max(fadeVal1, fadeVal2);
+ fadeVal = max(0.0, fadeVal-0.8)*5.0;
+ fadeVal = min(1.0, fadeVal);
+ gl_FragColor = mix(tex, tex2, fadeVal)+vec4(fadeVal*showDebug);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert
new file mode 100644
index 0000000..9f2cd70
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/texture/textureClipmapShader.vert
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform float scale;
+uniform vec3 eyePosition;
+
+varying vec2 vVertex;
+
+void main(void){
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+
+ vVertex = (gl_Vertex.xz - eyePosition.xz) * vec2(scale);
+
+ gl_Position = ftransform();
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag
new file mode 100644
index 0000000..9e268fb
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.frag
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ if ( abovewater == false ) {
+ fresnel = -fresnel;
+ }
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 endColor = mix(waterColorNew,reflectionColor,fresnelTerm);
+
+ if( useFadeToFogColor == false) {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist);
+ } else {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert
new file mode 100644
index 0000000..62d4f91
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader.vert
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+//uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+
+void main()
+{
+ // Because we have a flat plane for water we already know the vectors for tangent space
+// vec3 normal = gl_Normal;
+ vec3 normal = gl_NormalMatrix * gl_Normal;
+ normal = normalize(normal);
+ vec3 tangent2 = gl_NormalMatrix * tangent;
+ tangent2 = normalize(tangent2);
+ vec3 binormal2 = gl_NormalMatrix * binormal;
+ binormal2 = normalize(binormal2);
+
+ // Calculate the vector coming from the vertex to the camera
+// vec3 viewDir = cameraPos - gl_Vertex.xyz;
+ vec4 v = gl_ModelViewMatrix * gl_Vertex;
+ vec3 viewDir = -(v.xyz/v.w);
+ viewDir = normalize(viewDir);
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent2);
+ viewTangetSpace.y = dot(viewDir, binormal2);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+
+ // This calculates our current projection coordinates
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ gl_Position = viewCoords;
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag
new file mode 100644
index 0000000..039a30d
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.frag
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+uniform sampler2D refraction;
+uniform sampler2D depthMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ if ( abovewater == false ) {
+ fresnel = -fresnel;
+ }
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+ fresnel = 1.0 - fresnelTerm;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ vec2 projCoordDepth = projCoord;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ projCoordDepth += (dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoordDepth = clamp(projCoordDepth, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 refractionColor = texture2D(refraction, projCoordDepth);
+ float depth = texture2D(depthMap, projCoordDepth).r;
+ depth = pow(depth,15.0);
+ float invDepth = 1.0-depth;
+
+ vec4 endColor = refractionColor*vec4(invDepth*fresnel) + waterColorNew*vec4(depth*fresnel);
+
+ if( useFadeToFogColor == false) {
+ gl_FragColor = endColor + reflectionColor * vec4(fresnelTerm);
+ } else {
+ gl_FragColor = (endColor + reflectionColor * vec4(fresnelTerm)) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert
new file mode 100644
index 0000000..62d4f91
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/flatwatershader_refraction.vert
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+
+//uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+
+void main()
+{
+ // Because we have a flat plane for water we already know the vectors for tangent space
+// vec3 normal = gl_Normal;
+ vec3 normal = gl_NormalMatrix * gl_Normal;
+ normal = normalize(normal);
+ vec3 tangent2 = gl_NormalMatrix * tangent;
+ tangent2 = normalize(tangent2);
+ vec3 binormal2 = gl_NormalMatrix * binormal;
+ binormal2 = normalize(binormal2);
+
+ // Calculate the vector coming from the vertex to the camera
+// vec3 viewDir = cameraPos - gl_Vertex.xyz;
+ vec4 v = gl_ModelViewMatrix * gl_Vertex;
+ vec3 viewDir = -(v.xyz/v.w);
+ viewDir = normalize(viewDir);
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent2);
+ viewTangetSpace.y = dot(viewDir, binormal2);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+
+ // This calculates our current projection coordinates
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ gl_Position = viewCoords;
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag
new file mode 100644
index 0000000..3975051
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.frag
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+uniform sampler2D foamMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+uniform float amplitude;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (vnormal + dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 endColor = mix(waterColorNew,reflectionColor,fresnelTerm);
+
+ float foamVal = (vVertex.y-vVertex.w) / (amplitude * 2.0);
+ foamVal = clamp(foamVal,0.0,1.0);
+ vec4 foamTex = texture2D(foamMap, foamCoords + vnormal * 0.6 + normalVector.xy * 0.05);
+ float normLength = length(vnormal*5.0);
+ foamVal *= 1.0-normLength;
+ foamVal *= foamTex.a;
+ endColor = mix(endColor,foamTex,clamp(foamVal,0.0,0.95));
+
+ if( useFadeToFogColor == false) {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist);
+ } else {
+ gl_FragColor = mix(endColor,reflectionColor,fogDist) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert
new file mode 100644
index 0000000..1de2252
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader.vert
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+uniform float waterHeight;
+uniform float heightFalloffStart;
+uniform float heightFalloffSpeed;
+
+void main()
+{
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ vVertex = gl_Vertex;
+ float heightAdjust = 1.0 - clamp((viewCoords.z-heightFalloffStart)/heightFalloffSpeed,0.0,1.0);
+ vVertex.y = mix(waterHeight,vVertex.y,heightAdjust);
+ viewCoords = gl_ModelViewProjectionMatrix * vVertex;
+ gl_Position = viewCoords;
+ vVertex.w = waterHeight;
+
+ // Because we have a flat plane for water we already know the vectors for tangent space
+ vec3 normal = vec3(gl_Normal.x*heightAdjust,gl_Normal.y,gl_Normal.z*heightAdjust);
+ vnormal = normal.xz * 0.15;
+
+ // Calculate the vector coming from the vertex to the camera
+ vec3 viewDir = cameraPos - gl_Vertex.xyz;
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent);
+ viewTangetSpace.y = dot(viewDir, binormal);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ //todo test 0.8
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+ foamCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation*0.4);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag
new file mode 100644
index 0000000..16f2513
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.frag
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform sampler2D normalMap;
+uniform sampler2D reflection;
+uniform sampler2D dudvMap;
+uniform sampler2D refraction;
+uniform sampler2D depthMap;
+uniform sampler2D foamMap;
+
+uniform vec4 waterColor;
+uniform vec4 waterColorEnd;
+uniform bool abovewater;
+uniform bool useFadeToFogColor;
+uniform float amplitude;
+//uniform float dudvPower; //0.005
+//uniform float dudvColorPower; //0.01
+//uniform float normalPower; //0.5
+//uniform float normalOffsetPower; //0.6
+
+void main()
+{
+ float fogDist = clamp((viewCoords.z-gl_Fog.start)*gl_Fog.scale,0.0,1.0);
+
+ vec2 distOffset = texture2D(dudvMap, refrCoords).xy * 0.01;
+ vec3 dudvColor = texture2D(dudvMap, normCoords + distOffset).xyz;
+ dudvColor = normalize(dudvColor * 2.0 - 1.0) * 0.015;
+
+ vec3 normalVector = texture2D(normalMap, normCoords + distOffset * 0.6).xyz;
+ normalVector = normalVector * 2.0 - 1.0;
+ normalVector = normalize(normalVector);
+ normalVector.xy *= 0.5;
+
+ vec3 localView = normalize(viewTangetSpace);
+ float fresnel = dot(normalVector, localView);
+ fresnel *= 1.0 - fogDist;
+ float fresnelTerm = 1.0 - fresnel;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm *= fresnelTerm;
+ fresnelTerm = fresnelTerm * 0.9 + 0.1;
+ fresnel = 1.0 - fresnelTerm;
+
+ vec2 projCoord = viewCoords.xy / viewCoords.q;
+ projCoord = (projCoord + 1.0) * 0.5;
+ vec2 projCoordDepth = projCoord;
+ if ( abovewater == true ) {
+ projCoord.x = 1.0 - projCoord.x;
+ }
+
+ projCoord += (vnormal + dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoord = clamp(projCoord, 0.001, 0.999);
+
+ projCoordDepth += (vnormal + dudvColor.xy * 0.5 + normalVector.xy * 0.2);
+ projCoordDepth = clamp(projCoordDepth, 0.001, 0.999);
+
+ vec4 reflectionColor = texture2D(reflection, projCoord);
+ if ( abovewater == false ) {
+ reflectionColor *= vec4(0.8,0.9,1.0,1.0);
+ vec4 endColor = mix(reflectionColor,waterColor,fresnelTerm);
+ gl_FragColor = mix(endColor,waterColor,fogDist);
+ }
+ else {
+ vec4 waterColorNew = mix(waterColor,waterColorEnd,fresnelTerm);
+ vec4 endColor = mix(waterColorNew,reflectionColor,fresnelTerm);
+
+ float foamVal = (vVertex.y-vVertex.w) / (amplitude * 2.0);
+ foamVal = clamp(foamVal,0.0,1.0);
+ vec4 foamTex = texture2D(foamMap, foamCoords + vnormal * 0.6 + normalVector.xy * 0.05);
+ float normLength = length(vnormal*5.0);
+ foamVal *= 1.0-normLength;
+ foamVal *= foamTex.a;
+ endColor = mix(endColor,foamTex,clamp(foamVal,0.0,0.95));
+
+ vec4 refractionColor = texture2D(refraction, projCoordDepth);
+ float depth = texture2D(depthMap, projCoordDepth).r;
+ depth = pow(depth,15.0);
+ float invDepth = 1.0-depth;
+
+ endColor = refractionColor*vec4(invDepth*fresnel) + endColor*vec4(depth*fresnel);
+ if( useFadeToFogColor == false) {
+ gl_FragColor = endColor + reflectionColor * vec4(fresnelTerm);
+ } else {
+ gl_FragColor = (endColor + reflectionColor * vec4(fresnelTerm)) * (1.0-fogDist) + gl_Fog.color * fogDist;
+ }
+ }
+} \ No newline at end of file
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert
new file mode 100644
index 0000000..1de2252
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/effect/water/projectedwatershader_refraction.vert
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying vec2 refrCoords;
+varying vec2 normCoords;
+varying vec2 foamCoords;
+varying vec4 viewCoords;
+varying vec3 viewTangetSpace;
+varying vec2 vnormal;
+varying vec4 vVertex;
+
+uniform vec3 cameraPos;
+uniform vec3 tangent;
+uniform vec3 binormal;
+uniform float normalTranslation, refractionTranslation;
+uniform float waterHeight;
+uniform float heightFalloffStart;
+uniform float heightFalloffSpeed;
+
+void main()
+{
+ viewCoords = gl_ModelViewProjectionMatrix * gl_Vertex;
+ vVertex = gl_Vertex;
+ float heightAdjust = 1.0 - clamp((viewCoords.z-heightFalloffStart)/heightFalloffSpeed,0.0,1.0);
+ vVertex.y = mix(waterHeight,vVertex.y,heightAdjust);
+ viewCoords = gl_ModelViewProjectionMatrix * vVertex;
+ gl_Position = viewCoords;
+ vVertex.w = waterHeight;
+
+ // Because we have a flat plane for water we already know the vectors for tangent space
+ vec3 normal = vec3(gl_Normal.x*heightAdjust,gl_Normal.y,gl_Normal.z*heightAdjust);
+ vnormal = normal.xz * 0.15;
+
+ // Calculate the vector coming from the vertex to the camera
+ vec3 viewDir = cameraPos - gl_Vertex.xyz;
+
+ // Compute tangent space for the view direction
+ viewTangetSpace.x = dot(viewDir, tangent);
+ viewTangetSpace.y = dot(viewDir, binormal);
+ viewTangetSpace.z = dot(viewDir, normal);
+
+ //todo test 0.8
+ refrCoords = (gl_TextureMatrix[2] * gl_MultiTexCoord0).xy + vec2(0.0,refractionTranslation);
+ normCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation);
+ foamCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy + vec2(0.0,normalTranslation*0.4);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag
new file mode 100644
index 0000000..34323d2
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.frag
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2011 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2DShadow shadowMap0;
+uniform sampler2DShadow shadowMap1;
+uniform sampler2DShadow shadowMap2;
+uniform sampler2DShadow shadowMap3;
+
+varying float zDist;
+uniform vec4 sampleDist;
+uniform vec4 shadowColor;
+
+void main()
+{
+ float shade = 0.0;
+ if (zDist < sampleDist.x) {
+ shade = shadow2DProj(shadowMap0, gl_TexCoord[0]).x;
+ } else if (zDist < sampleDist.y) {
+ shade = shadow2DProj(shadowMap1, gl_TexCoord[1]).x;
+ } else if (zDist < sampleDist.z) {
+ shade = shadow2DProj(shadowMap2, gl_TexCoord[2]).x;
+ } else if (zDist < sampleDist.w) {
+ shade = shadow2DProj(shadowMap3, gl_TexCoord[3]).x;
+ }
+
+ gl_FragColor = vec4(shadowColor.rgb, shadowColor.a * shade);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert
new file mode 100644
index 0000000..077f216
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssm.vert
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying float zDist;
+
+void main(void){
+ vec4 ePos = gl_ModelViewMatrix * gl_Vertex;
+
+ gl_TexCoord[0] = gl_TextureMatrix[0] * ePos;
+ gl_TexCoord[1] = gl_TextureMatrix[1] * ePos;
+ gl_TexCoord[2] = gl_TextureMatrix[2] * ePos;
+ gl_TexCoord[3] = gl_TextureMatrix[3] * ePos;
+
+ zDist = -ePos.z;
+
+ gl_Position = ftransform();
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag
new file mode 100644
index 0000000..4a02b80
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.frag
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2DShadow shadowMap0;
+uniform sampler2DShadow shadowMap1;
+uniform sampler2DShadow shadowMap2;
+uniform sampler2DShadow shadowMap3;
+
+varying float zDist;
+uniform vec4 sampleDist;
+
+void main()
+{
+ float shade = 0.0;
+ vec3 col = vec3(0.0);
+ if (zDist < sampleDist.x) {
+ shade = shadow2DProj(shadowMap0, gl_TexCoord[0]).x;
+ col.r = 0.5;
+ } else if (zDist < sampleDist.y) {
+ shade = shadow2DProj(shadowMap1, gl_TexCoord[1]).x;
+ col.g = 0.5;
+ } else if (zDist < sampleDist.z) {
+ shade = shadow2DProj(shadowMap2, gl_TexCoord[2]).x;
+ col.b = 0.5;
+ } else if (zDist < sampleDist.w) {
+ shade = shadow2DProj(shadowMap3, gl_TexCoord[3]).x;
+ col.r = 0.5;
+ col.b = 0.5;
+ }
+
+ gl_FragColor = vec4(col.rgb, 0.5 * shade);
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert
new file mode 100644
index 0000000..077f216
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmDebug.vert
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+varying float zDist;
+
+void main(void){
+ vec4 ePos = gl_ModelViewMatrix * gl_Vertex;
+
+ gl_TexCoord[0] = gl_TextureMatrix[0] * ePos;
+ gl_TexCoord[1] = gl_TextureMatrix[1] * ePos;
+ gl_TexCoord[2] = gl_TextureMatrix[2] * ePos;
+ gl_TexCoord[3] = gl_TextureMatrix[3] * ePos;
+
+ zDist = -ePos.z;
+
+ gl_Position = ftransform();
+}
diff --git a/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag
new file mode 100644
index 0000000..ecbac9a
--- /dev/null
+++ b/ardor3d-effects/src/main/resources/com/ardor3d/extension/shadow/map/pssmPCF.frag
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+uniform sampler2DShadow shadowMap0;
+uniform sampler2DShadow shadowMap1;
+uniform sampler2DShadow shadowMap2;
+uniform sampler2DShadow shadowMap3;
+
+varying float zDist;
+uniform vec4 sampleDist;
+uniform vec4 shadowColor;
+//uniform float _shadowSize;
+
+float offset_lookup(const in sampler2DShadow map,
+ const in vec4 loc,
+ const in vec2 offset,
+ const in float shadowSize)
+{
+ return shadow2DProj(map, vec4(loc.xy + offset * shadowSize * loc.w, loc.z, loc.w)).x;
+}
+
+float shadowLookup(const in sampler2DShadow shadowmap, const in vec4 sCoord, const in float shadowSize) {
+ vec2 offset = mod(sCoord.xy, 0.5);
+ offset.y += offset.x; // y ^= x in floating point
+
+ if (offset.y > 1.1) {
+ offset.y = 0.0;
+ }
+ offset = vec2(0.0);
+
+ return (offset_lookup(shadowmap, sCoord, offset +
+ vec2(-1.5, 0.5), shadowSize) +
+ offset_lookup(shadowmap, sCoord, offset +
+ vec2(0.5, 0.5), shadowSize) +
+ offset_lookup(shadowmap, sCoord, offset +
+ vec2(-1.5, -1.5), shadowSize) +
+ offset_lookup(shadowmap, sCoord, offset +
+ vec2(0.5, -1.5), shadowSize) ) * 0.25;
+}
+
+float shadowLookup33(const in sampler2DShadow shadowmap, const in vec4 sCoord, const in float shadowSize) {
+ float x,y;
+ float shadow = 0.0;
+ for (y = -1.5; y <= 1.5; y += 1.0) {
+ for (x = -1.5; x <= 1.5; x += 1.0) {
+ shadow += offset_lookup(shadowmap, sCoord, vec2(x,y), shadowSize);
+ }
+ }
+
+ shadow /= 16.0;
+
+ return shadow;
+}
+
+void main()
+{
+ float shade = 0.0;
+ if (zDist < sampleDist.x) {
+ shade = shadowLookup33(shadowMap0, gl_TexCoord[0], 1.0/1024.0);
+ } else if (zDist < sampleDist.y) {
+ shade = shadowLookup33(shadowMap1, gl_TexCoord[1], 1.0/1024.0);
+ } else if (zDist < sampleDist.z) {
+ shade = shadowLookup(shadowMap2, gl_TexCoord[2], 1.0/1024.0);
+ } else if (zDist < sampleDist.w) {
+ shade = shadow2DProj(shadowMap3, gl_TexCoord[3]).x;
+ }
+
+ gl_FragColor = vec4(shadowColor.rgb, shadowColor.a * shade);
+}
diff --git a/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.java b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.java
new file mode 100644
index 0000000..70dfa5e
--- /dev/null
+++ b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/MockPSSMCamera.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.shadow.map;
+
+import com.ardor3d.math.Vector3;
+
+public class MockPSSMCamera extends PSSMCamera {
+ public Vector3 getExtents() {
+ return _extents;
+ }
+}
diff --git a/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.java b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.java
new file mode 100644
index 0000000..fb8bdc0
--- /dev/null
+++ b/ardor3d-effects/src/test/java/com/ardor3d/extension/shadow/map/TestPSSMCamera.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.shadow.map;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.math.Vector3;
+
+public class TestPSSMCamera {
+ @Test
+ public void testBoxSphereCameraPack() {
+ MockPSSMCamera camera = new MockPSSMCamera();
+ camera.setLocation(0, 0, -10);
+ camera.setFrustumPerspective(50, 1, 1, 100);
+
+ final BoundingBox boundingBox = new BoundingBox();
+ boundingBox.setCenter(new Vector3(0, 0, 10));
+ boundingBox.setXExtent(2);
+ boundingBox.setYExtent(2);
+ boundingBox.setZExtent(2);
+
+ camera.pack(boundingBox);
+
+ final double boxNear1 = camera.getFrustumNear();
+ final double boxFar1 = camera.getFrustumFar();
+
+ Assert.assertEquals(new Vector3(2, 2, 2), camera.getExtents());
+
+ camera = new MockPSSMCamera();
+ camera.setLocation(0, 0, -10);
+ camera.setFrustumPerspective(50, 1, 1, 100);
+
+ final BoundingSphere boundingSphere = new BoundingSphere();
+ boundingSphere.setCenter(new Vector3(0, 0, 10));
+ boundingSphere.setRadius(2);
+
+ camera.pack(boundingSphere);
+
+ final double boxNear2 = camera.getFrustumNear();
+ final double boxFar2 = camera.getFrustumFar();
+
+ Assert.assertEquals(new Vector3(2, 2, 2), camera.getExtents());
+
+ Assert.assertEquals(boxNear1, boxNear2);
+ Assert.assertEquals(boxFar1, boxFar2);
+ }
+}