ソースを参照

Merge remote-tracking branch 'official/master' into #181-create-3.8-wiki-version

# Conflicts:
#	docs/modules/ROOT/pages/getting-started/maven.adoc
Richard Tingle 2 ヶ月 前
コミット
b5eaff74e4
35 ファイル変更3260 行追加612 行削除
  1. 1 1
      .github/workflows/main.yml
  2. 3 5
      README.adoc
  3. 55 2
      docs/modules/ROOT/pages/getting-started/maven.adoc
  4. 18 1
      docs/modules/ROOT/pages/release.adoc
  5. 2 0
      docs/modules/contributions/nav.adoc
  6. 28 0
      docs/modules/contributions/pages/contributions.adoc
  7. 5 0
      docs/modules/contributions/pages/vr/topic_contributions_vr.adoc
  8. 7 2
      docs/modules/core/nav.adoc
  9. 79 0
      docs/modules/core/pages/animation/animation_new.adoc
  10. 1 1
      docs/modules/core/pages/app/state/application_states.adoc
  11. 40 0
      docs/modules/core/pages/material/normal_types.adoc
  12. 168 0
      docs/modules/core/pages/renderer/render_pipeline.adoc
  13. 2 0
      docs/modules/core/pages/system/appsettings.adoc
  14. 105 0
      docs/modules/core/pages/vr/legacyOpenVr.adoc
  15. 6 94
      docs/modules/core/pages/vr/virtualreality.adoc
  16. 1 3
      docs/modules/core/pages/vr/virtualrealitycontrollers.adoc
  17. 1 0
      docs/modules/sdk/nav.adoc
  18. 26 0
      docs/modules/sdk/pages/assetbrowser.adoc
  19. 0 11
      docs/modules/sdk/pages/model_loader_and_viewer.adoc
  20. 19 3
      docs/modules/sdk/pages/project_creation.adoc
  21. 11 2
      docs/modules/sdk/pages/scene_composer.adoc
  22. 0 5
      docs/modules/tutorials/nav.adoc
  23. 121 134
      docs/modules/tutorials/pages/beginner/hello_animation.adoc
  24. 21 25
      docs/modules/tutorials/pages/beginner/hello_asset.adoc
  25. 8 11
      docs/modules/tutorials/pages/beginner/hello_audio.adoc
  26. 53 62
      docs/modules/tutorials/pages/beginner/hello_collision.adoc
  27. 22 0
      docs/modules/tutorials/pages/beginner/hello_effects.adoc
  28. 26 32
      docs/modules/tutorials/pages/beginner/hello_input_system.adoc
  29. 1 1
      docs/modules/tutorials/pages/beginner/hello_main_event_loop.adoc
  30. 20 39
      docs/modules/tutorials/pages/beginner/hello_material.adoc
  31. 45 52
      docs/modules/tutorials/pages/beginner/hello_physics.adoc
  32. 10 13
      docs/modules/tutorials/pages/beginner/hello_picking.adoc
  33. 19 13
      docs/modules/tutorials/pages/beginner/hello_terrain.adoc
  34. 2333 97
      package-lock.json
  35. 3 3
      package.json

+ 1 - 1
.github/workflows/main.yml

@@ -30,7 +30,7 @@ jobs:
   build:
 
     #Static version is used to maintain stability.
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-22.04
 
     strategy:
       matrix:

+ 3 - 5
README.adoc

@@ -17,14 +17,12 @@ NOTE: Read the link:https://wiki.jmonkeyengine.org/docs-wiki/3.8/wiki_contributo
 
 == Build & Preview
 
-To set up the Antora environment, you'll need Nodejs (tested with node 12).
+To set up the Antora environment, you'll need Node.js and Nvm.
 
-From your local wiki directory.
-
-Run:
+From your local wiki directory, run:
 ```
 npm install
-npm run buildDocs
+npx antora wiki-playbook.yml
 ```
 
 This will install the needed dependencies and run the static site generator. The documentation will be output to the directory `build/site`.

+ 55 - 2
docs/modules/ROOT/pages/getting-started/maven.adoc

@@ -51,7 +51,7 @@ repositories {
     mavenCentral()
 }
 
-def jme3 = [v:'3.8.0-stable', g:'org.jmonkeyengine']
+def jme3 = [v:'3.8.1-stable', g:'org.jmonkeyengine']
 dependencies {
     implementation "${jme3.g}:jme3-core:${jme3.v}"
     runtimeOnly "${jme3.g}:jme3-desktop:${jme3.v}"
@@ -65,7 +65,7 @@ dependencies {
 ----
   <properties>
     <jme3_g>org.jmonkeyengine</jme3_g>
-    <jme3_v>3.8.0-stable</jme3_v>
+    <jme3_v>3.8.1-stable</jme3_v>
   </properties>
 
   <repositories>
@@ -94,3 +94,56 @@ dependencies {
     </dependency>
   </dependencies>
 ----
+
+== Snapshots
+
+Typically, you will want to develop against the latest stable version of the engine. For testing
+purposes, snapshot builds are generated and updated every time that changes are commited to the
+master branch.
+
+You can add the snapshot repository to your build files, and set the version to the snapshot build:
+
+
+=== Gradle
+
+[source,groovy]
+----
+repositories {
+    mavenCentral()
+    maven {url 'https://s01.oss.sonatype.org/content/repositories/snapshots/'}
+}
+
+/*
+* Gradle defaults to cacheing artifacts for 24 hours. This entry makes sure that
+* you are always using the absolute latest snapshot, but it does mean that the engine
+* gets downloaded on every build.
+*/
+configurations.all {
+   resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+def jme3 = [v:'3.8.1-SNAPSHOT', g:'org.jmonkeyengine']
+----
+
+=== Maven
+
+[source,xml]
+----
+  <properties>
+    <jme3_g>org.jmonkeyengine</jme3_g>
+    <jme3_v>3.7.0-SNAPSHOT</jme3_v>
+  </properties>
+
+  <repositories>
+    <repository>
+      <id>mvnrepository</id>
+      <url>https://repo1.maven.org/maven2/</url>
+    </repository>
+    <repository>
+      <id>snapshots</id>
+      <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
+    </repository>
+  </repositories>
+----
+
+

+ 18 - 1
docs/modules/ROOT/pages/release.adoc

@@ -33,7 +33,7 @@ workflow to run at GitHub Actions.
 The workflow is defined by
 https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/.github/workflows/main.yml[the "main.yml" script].
 It performs clean builds of the Engine
-across a matrix of 4 operating systems and 2 Java versions.
+across a matrix of 3 operating systems and 3 Java versions.
 It also performs a clean build of the merged javadoc.
 
 Any failure of the CI workflow causes notifications to be sent.
@@ -233,6 +233,23 @@ until a "Repository closed" message appears in the progress.
 
 image::sonatype/repo_closed.png[repo_closed.png]
 
+Once the repository is closed, its artifacts become publicly
+visble at SonaType, but they aren't yet synched
+to Maven Central.
+This is your last opportunity to test the proposed release,
+using (for example)
+
+[source,groovy]
+----
+repositories {
+    maven { url 'https://s01.oss.sonatype.org/content/groups/staging' }
+    mavenCentral()
+}
+----
+
+If the staged artifacts don't work for some reason, drop them,
+address the issue(s), and start over with a new release name.
+
 To begin the synching process,
 click on the "Release" button and then the "Confirm" button.
 The process usually takes about 20 minutes.

+ 2 - 0
docs/modules/contributions/nav.adoc

@@ -21,5 +21,7 @@
 ** Tools
 *** xref:tools/navigation.adoc[Mercator Projection Tool (Marine Navigation)]
 *** xref:tools/charts.adoc[Visualizing Maps in JME3 (Marine Charts)]
+** xref:vr/topic_contributions_vr.adoc[Virtual Reality (And augmented reality)]
+*** xref:contributions.adoc#tamarin-openxr[Tamarin OpenXR]
 ** Projects
 *** xref:projects/rise_of_mutants_project.adoc[Rise of Mutants Project]

+ 28 - 0
docs/modules/contributions/pages/contributions.adoc

@@ -112,6 +112,13 @@ GroupID:ArtifactID
 {url-mcentral}q=g:com.github.stephengold%20AND%20a:tonegodgui[com.github.stephengold:tonegodgui]
 |{url-github}/stephengold/tonegodgui[GitHub]
 
+|{url-github}/jack-bradshaw/monorepo/tree/main/java/io/jackbradshaw/kmonkey[KMonkey]
+|Support for Kotlin Coroutines
+|{url-forum-user}/jackbradshaw[jackbradshaw]
+|\https://repo1.maven.org/maven2 +
+{url-mcentral}q=g:io.jackbradshaw:kmonkey[io.jackbradshaw:kmonkey]
+|{url-github}/jack-bradshaw/monorepo/tree/main/java/io/jackbradshaw/kmonkey[GitHub]
+
 
 |===
 
@@ -310,6 +317,27 @@ a| Yes
 
 |===
 
+=== Tamarin OpenXR
+
+Tamarin provides OpenXR functionality to enable jMonkey applications to run on VR headsets. It provides full support for the headset, controller actions, haptic feedback and a sample set of vr hands.
+
+[cols="2", options="header"]
+|===
+
+a| *Contact person*
+a| {url-forum-user}/richtea[richtea]
+
+a| *Documentation*
+a| {url-github}/oneMillionWorlds/Tamarin/wiki[Tamarin wiki]
+
+a| *Available as SDK plugin*
+a| No
+
+a| *Work in progress*
+a| No (Actively maintained and improved)
+
+|===
+
 == Assets packs
 
 _No contributions yet_

+ 5 - 0
docs/modules/contributions/pages/vr/topic_contributions_vr.adoc

@@ -0,0 +1,5 @@
+= VR Contributions
+:description: VR contributed libraries for the jmonkey engine.
+:keywords: vr, documentation, ar, openxr, contributions
+
+This topic contains user contributed Virtual Reality (and augmented reality) libraries.

+ 7 - 2
docs/modules/core/nav.adoc

@@ -22,7 +22,8 @@
 ** xref:collision/collision_and_intersection.adoc[Collision and Intersection]
 ** xref:scene/control/level_of_detail.adoc[Level of Detail]
 * Animation, Scene
-** xref:animation/animation.adoc[Animation-Old]
+** xref:animation/animation_new.adoc[Animation with AnimComposer]
+** xref:animation/animation.adoc[Animation-Old (AnimControl)]
 // ** xref:anim/animation.adoc[Animation-New]
 ** xref:cinematic/cinematics.adoc[Cinematics (cutscenes, fake destruction physics)]
 ** xref:cinematic/motionpath.adoc[MotionPaths and Waypoints]
@@ -37,6 +38,7 @@
 ** xref:light/light_and_shadow.adoc[Light and Shadow]
 ** xref:texture/anisotropic_filtering.adoc[Anisotropic Filtering]
 ** xref:system/jme3_srgbpipeline.adoc[Gamma Correction]
+** xref:material/normal_types.adoc[Normal Map Conventions]
 * Audio, Video
 ** xref:audio/audio.adoc[Playing Sounds]
 ** xref:audio/audio_environment_presets.adoc[Audio Environment Presets]
@@ -58,6 +60,8 @@
 ** xref:renderer/remote-controlling_the_camera.adoc[Remote-Controlling]
 ** xref:renderer/multiple_camera_views.adoc[Multiple Camera Views]
 ** xref:renderer/jme3_renderbuckets.adoc[Render Buckets]
+* Rendering
+** xref:renderer/render_pipeline.adoc[Render Pipelines]
 * User Interaction
 ** xref:input/input_handling.adoc[Input Handling]
 ** xref:input/combo_moves.adoc[Combo Moves]
@@ -70,4 +74,5 @@
 ** xref:ui/hud.adoc[Head-Up Display (HUD)]
 * Virtual Reality
 ** xref:vr/virtualreality.adoc[Virtual Reality]
-** xref:vr/virtualrealitycontrollers.adoc[Virtual Reality Controllers]
+** xref:vr/legacyOpenVr.adoc[Virtual Reality Legacy Support]
+** xref:vr/virtualrealitycontrollers.adoc[Virtual Reality Legacy Controller Support]

+ 79 - 0
docs/modules/core/pages/animation/animation_new.adoc

@@ -0,0 +1,79 @@
+= Animation in jME3
+:revnumber: 2.1
+:revdate: 2020/07/24
+
+
+In 3D games, you do not only load static 3D models, you also want to be able to trigger animations in the model from the Java code.
+
+
+== Requirements
+
+JME3 only loads and plays animated models, it does not create them.
+
+What is required for an animated model? (<<tutorials:concepts/terminology.adoc#animation,See also: Animation terminology>>
+
+.  For each model, you have to segment the model into a skeleton (*bone rigging*).
+.  For each motion, you have to specify how the animation distorts parts of the model (*skinning*).
+.  For each animation, you have to specify a series of snapshots of how the bones are positioned (*keyframes*).
+.  One model can contain several animations. You give every animation a name when you save it in the mesh editor.
+
+Unless you download free models, or buy them from a 3D artist, you must create your animated models in an *external mesh editor* (for example, Blender) yourself.
+
+*  <<ROOT:getting-started/features.adoc#supported-external-file-types,Supported External File Types>>
+*  xref:tutorials:how-to/modeling/blender/blender.adoc[Creating assets in Blender3D]
+*  link:http://www.youtube.com/watch?v=IDHMWsu_PqA[Video: Creating Worlds with Instances in Blender]
+
+What is required in your JME3-based Java class?
+
+*  One AnimationComposer per animated model.
+*  As many Layer per Composer as you need to play your animations. In simple cases one layer is enough to play animations for the whole model, sometimes you need two or more layers per model to play gestures and motions in parallel.
+
+
+== Code Samples
+*  link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/1296eb25a6f42d2c42a3b0427904dac40d8d4017/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java[TestAnimSerialization.java]
+*  link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/1296eb25a6f42d2c42a3b0427904dac40d8d4017/jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java#L96[TestAttachmentsNode.jav]
+
+== Controlling Animations
+
+
+=== The AnimComposer
+A model should get an AnimComposer automatically when you convert your model to j3o. At the same time animations will be created and accessible in the AnimComposer.
+Animations will be named the same they were in your editor.
+
+==== Playing animations
+[source,java]
+----
+  AnimComposer animComposer = animatedModel.getControl(AnimComposer.class);
+  animComposer.setCurrentAction("Walk");  
+  ...
+
+----
+When you tell the AnimComposer to play the animation it will be looped by default. If you want it to only play an animation once, you can do the following:
+
+[source,java]
+----
+  Action walk = animComposer.action("Walk");
+  Tween doneTween = Tweens.callMethod(animComposer, "setCurrentAction", "Idle");
+  Action walkOnce = animComposer.actionSequence("WalkOnce", walk, doneTween);
+  animComposer.setCurrentAction("WalkOnce");   
+----
+
+==== Playing animation on part of body
+If you want to play an animation on part of the body, you need to create layers in the AnimComposer. You do this with the help of a SkinningControl, which should
+also have been created if your imported model had an armature.
+
+[source,java]
+----
+  SkinningControl sc = animatedModel.getControl(SkinningControl.class);
+  animComposer.makeLayer("UpperBody", ArmatureMask.createMask(sc.getArmature(), "Spine"));
+  animComposer.makeLayer("LowerBody", ArmatureMask.createMask(sc.getArmature(), "Hips"));
+  // Play the animation
+  animComposer.setCurrentAction("Walk", "UpperBody");
+----
+
+== Further reading:
+Some forum topics that contain more information on the animation system:
+
+*  link:https://hub.jmonkeyengine.org/t/animation-action-is-complete/44577/2[animation-action-is-complete]
+*  link:https://hub.jmonkeyengine.org/t/a-tip-for-animation-blending/44617/11[a-tip-for-animation-blending]
+

+ 1 - 1
docs/modules/core/pages/app/state/application_states.adoc

@@ -72,7 +72,7 @@ When you add several AppStates to one Application and activate them, their initi
 
 JME3 comes with a BulletAppState that implements Physical behaviour (using the jBullet library). You, for example, could write an Artificial Intelligence AppState to control all your enemy units. Existing examples in the code base include:
 
-*  link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java[BulletAppState] controls physical behaviour in PhysicsControl'ed Spatials.
+*  link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-jbullet/src/main/java/com/jme3/bullet/BulletAppState.java[BulletAppState] controls physical behaviour in PhysicsControl'ed Spatials.
 *  link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java[TestAppStates.java] an example of a custom AppState
 **  link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java[RootNodeState.java]
 

+ 40 - 0
docs/modules/core/pages/material/normal_types.adoc

@@ -0,0 +1,40 @@
+= Normal Map Conventions
+:revnumber: 1.0
+:revdate: 2025/04/25
+:keywords: material, texture, light, normals
+
+There are two conventions for normal maps:
+
+* OpenGL (known as OpenGL-Style or Y+)
+* DirectX (known as DirectX-Style or Y-)
+
+The difference between OpenGL style and DirectX style is the Y value (green channel) is flipped: OpenGL uses Y+ and DirectX uses Y-.
+This can cause shading on models to not look quite right if a material tries to use the OpenGL convention on a normal map designed for DirectX.
+
+Luckily, JME supports both OpenGL and DirectX conventions by using a `NormalType` parameter in the Lighting and PBRLighting material
+definitions. The parameter can be set to either 1 (to use OpenGL) or -1 (to use DirectX).
+By default, JME uses the DirectX convention (NormalType is -1).
+
+== Using NormalType
+
+If you're using Lighting.j3md or PBRLighting.j3md for your materials, you can set the NormalType parameter to indicate
+which normal map convention materials should use on your normal maps.
+
+[source,java,opts=novalidate]
+----
+Material mat = new Material(assetManager,
+        "Common/MatDefs/Light/Lighting.j3md");
+mat.setInt("NormalType", 1); //set the type to OpenGL
+----
+
+TIP: If you're loading a model which already contains a material, you do not have to set the normal type on the material. Jme will do this automatically for you.
+
+== Which Convention are My Normal Maps?
+
+There is no easy way to determine that. Sometimes the name of the texture itself might contain a clue determining its type. For example, "Material0_NormalMapY+.png" has Y+ in the name, so it is likely an OpenGL-type normal map.
+If the name doesn't give any clue, the next step is to research the tool used to generate the normal map or look at where the normal map was downloaded from. Authors may indicate which normal map convention they used in their release notes.
+
+== Troubleshooting Normal Maps
+
+If the shading on your models seems a little wrong when lit, that's a good indicator that you're using the wrong
+normal map convention. Changing the NormalType on your materials should fix it (if it does not, there is something else wrong).

+ 168 - 0
docs/modules/core/pages/renderer/render_pipeline.adoc

@@ -0,0 +1,168 @@
+= Render Pipelines
+:revnumber: 1.0
+:revdate: 2025/04/22
+:keywords: rendering, viewport, pipeline
+
+Since JMonkeyEngine 3.8, ViewPorts are rendered using render pipelines. Before, the RenderManager was entirely responsible for processing each ViewPort's scenes and rendering their geometries. Only basic forward rendering could really be used without great difficulty, so render pipelines were introduced to allow developers to implement whatever rendering techniques their game demands.
+
+The sky is the limit with render pipelines, however, because they control every aspect of rendering a ViewPort (including SceneProcessors and profiling), a great deal goes into implementing a pipeline.
+
+== PipelineContext
+
+Before diving into building render pipelines, one must first have at least a rudimentary knowledge of PipelineContexts, which are responsible for handling global objects for pipelines. Pipelines themselves cannot manage these global objects because they are localized to specific ViewPorts. PipelineContexts are stored directly by the RenderManager, so they are better suited for this task.
+
+[source,java]
+----
+public class MyPipelineContext implements PipelineContext {
+
+    // an example of a global object to manage
+    private GlobalResources resources;
+
+    @Override
+    public boolean startViewPortRender(RenderManager rm, ViewPort vp) {
+        // Called when a ViewPort begins rendering that
+        // this context is involved in.
+        // Must return true if this context was already involved
+        // in a ViewPort rendering this frame.
+    }
+
+    @Override
+    public void endViewPortRender(RenderManager rm, ViewPort vp) {
+        // Called when a ViewPort ends rendering that
+        // this context was involved in.
+    }
+
+    @Override
+    public void endContextRenderFrame(RenderManager rm) {
+        // Called after all rendering this frame is complete AND this
+        // context was involved in rendering a ViewPort this frame.
+    }
+
+    // give pipelines access to the example global object
+    public GlobalResources getResources() {
+        return resources;
+    }
+
+}
+----
+
+Note that PipelineContexts get run only if they become involved in rendering a ViewPort. They do so when a pipeline specifically selects them for rendering, which will be covered later.
+
+In order to be selected at all, PipelineContexts must be registered with the RenderManager at the time. This can either be done manually, or the pipeline itself can create and register the context if it does not yet exist.
+
+[source,java,opts=novalidate]
+----
+// register context manually
+renderManager.registerContext(MyPipelineContext.class, new MyPipelineContext());
+----
+
+Contexts are registered by a class type by which they can then be retrieved.
+
+== RenderPipeline
+
+The RenderPipeline interface is the primary element of the pipeline system, as it is directly responsible for rendering a ViewPort. RenderPipeline provides five methods to implement:
+
+[source,java]
+----
+public class MyPipeline implements RenderPipeline<MyPipelineContext> {
+
+    @Override
+    public MyPipelineContext fetchPipelineContext(RenderManager rm) {
+        // Returns a PipelineContext from the RenderManager
+        // that handles global objects for this pipeline. The
+        // returned context is passed to pipelineRender.
+    }
+
+    @Override
+    public boolean hasRenderedThisFrame() {
+        // Returns true if this context has performed any
+        // rendering previously on this frame.
+    }
+
+    @Override
+    public void startRenderFrame() {
+        // Called before pipelineRender on the first rendering
+        // this pipeline is to perform this frame.
+    }
+
+    @Override
+    public void pipelineRender(RenderManager rm, MyPipelineContext context, ViewPort vp, float tpf) {
+        // Does the actual rendering of the ViewPort.
+    }
+
+    @Override
+    public void endRenderFrame(RenderManager rm) {
+        // Called after all rendering is complete in a frame in
+        // which this pipeline rendered a ViewPort.
+    }
+
+}
+----
+
+The `pipelineRender` method can get quite complicated, as rendering is a complicated process. Fortunately, the pipeline system imposes little to no restriction on what pipelines actually do during rendering. Here is a quick `renderPipeline` implementation to get started with:
+
+[source,java]
+----
+@Override
+public void pipelineRender(RenderManager rm, MyPipelineContext context, ViewPort vp, float tpf) {
+    // apply viewport to rendering context
+    rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
+    rm.getRenderer().clearBuffers(true, true, true);
+    rm.getRenderer().setBackgroundColor(vp.getBackgroundColor());
+    rm.setCamera(vp.getCamera(), false);
+    // render each geometry in all viewport scenes
+    for (Spatial scene : vp.getScenes()) {
+        scene.depthFirstTraversal(s -> {
+            if (s instanceof Geometry) {
+                rm.renderGeometry((Geometry)s);
+            }
+        });
+    }
+    // reset clip rect
+    rm.getRenderer().clearClipRect();
+}
+----
+
+As previously mentioned, there is very little restriction over what `pipelineRender` does, so following the above example is not required. In fact, JMonkeyEngine's default renderer, https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java[ForwardPipeline], looks vastly different.
+
+=== Fetching Contexts to Use
+
+Since pipelines often depend on global objects (as stated before), the RenderPipeline interface has a generic specifying the type of context to be expected (set as MyPipelineContext in the example above), and the interface provides the `fetchPipelineContext` method to select the context to use during rendering. The context returned by `fetchPipelineContext` will then be passed to `pipelineRender` to actually be used.
+
+For example, if the pipeline wanted to select MyPipelineContext that is already registered with the RenderManager:
+
+[source,java]
+----
+@Override
+public MyPipelineContext fetchPipelineContext(RenderManager rm) {
+    // assuming MyPipelineContext is registered under MyPipelineContext.class
+    return rm.getContext(MyPipelineContext.class);
+}
+----
+
+Even if a RenderPipeline does not need to use a PipelineContext, it is still required that `fetchPipelineContext` return a non-null context. For such cases, returning `rm.getDefaultContext()` is acceptable.
+
+== Usage
+
+In order to get a RenderPipeline to render a ViewPort, simply assign the pipeline to the ViewPort. When the rendering step occurs, the RenderManager uses each ViewPort's assigned pipeline to render the ViewPort.
+
+[source,java,opts=novalidate]
+----
+viewPort.setPipeline(new MyRenderPipeline());
+----
+
+Note that RenderPipelines (unless otherwise specified) can be assigned to multiple ViewPorts at once.
+
+[source,java,opts=novalidate]
+----
+MyRenderPipeline p = new MyRenderPipeline();
+viewPort.setPipeline(p);
+guiViewPort.setPipeline(p);
+----
+
+If no pipeline is assigned to a ViewPort, the RenderManager uses a default pipeline to render that ViewPort. The default pipeline can be set as so:
+
+[source,java,opts=novalidate]
+----
+renderManager.setPipeline(new MyRenderPipeline());
+----

+ 2 - 0
docs/modules/core/pages/system/appsettings.adoc

@@ -36,6 +36,8 @@ WARNING: The settings are saved based on the title of your game (default = "`jMo
 
 This example toggles the settings to fullscreen while the game is already running. Then it restarts the game context (not the whole game) which applies the changed settings.
 
+WARNING: The code below uses the Java AWT, which in incompatible with LWJGL3 on the Mac. Attempting to use both may cause an application UI to become unresponsive. Exact results may vary depending on what AWT features are used, when they are used, and/or which version of the MacOS, Java, and jME is used.
+
 [source,java]
 ----
 public void toggleToFullscreen() {

+ 105 - 0
docs/modules/core/pages/vr/legacyOpenVr.adoc

@@ -0,0 +1,105 @@
+= Virtual Reality Legacy support
+:revnumber: 2.0
+:revdate: 2020/07/27
+
+
+== Introduction
+
+The jMonkeyEngine module jme3-vr is deprecated and will be removed in a future version. This documents that deprecated functionality.
+
+jMonkeyEngine 3 supports several VR specifications. The most modern of those is OpenVR, which is currently a widely supported standard. However, vendors are beginning to move towards OpenXR as a fully open cross-platform standard. OpenXR is available with jMonkeyEngine via xref:contributions:vr/topic_contributions_vr.adoc[user contributed virtual reality libraries].
+
+The known supported systems are:
+
+HTC Vive and systems supporting SteamVR/OpenVR
+
+Native Oculus Rift support (and through SteamVR)
+
+Oculus Quest 2 (through SteamVR with Virtual Desktop)
+
+Razer HDK and systems supporting OSVR
+
+Google Cardboard / GoogleVR
+
+Two implementations exist for OpenVR. A community maintained JNA based binding and LWJGL's JNI based.
+
+To use the JNA based bindings, put:
+
+    settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_VALUE);
+
+in your settings. To use LWJGL, instead put:
+
+    settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE);
+
+Note that the LWJGL bindings require LWJGL3 (jme3-lwjgl3) to be used.
+
+== Required dependencies
+
+    - org.jmonkeyengine:jme3-core
+    - org.jmonkeyengine:jme3-lwjgl3
+    - org.jmonkeyengine:jme3-vr
+
+== Sample Application
+
+[source,java]
+----
+public class Main extends SimpleApplication {
+
+    public static void main(String[] args) {
+        AppSettings settings = new AppSettings(true);
+        settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE);
+        settings.put(VRConstants.SETTING_ENABLE_MIRROR_WINDOW, true);
+
+        VREnvironment env = new VREnvironment(settings);
+        env.initialize();
+
+    	// Checking if the VR environment is well initialized
+    	// (access to the underlying VR system is effective, VR devices are detected).
+    	if (env.isInitialized()){
+            VRAppState vrAppState = new VRAppState(settings, env);
+            vrAppState.setMirrorWindowSize(1024, 800);
+            Main app = new Main(vrAppState);
+            app.setLostFocusBehavior(LostFocusBehavior.Disabled);
+            app.setSettings(settings);
+            app.setShowSettings(false);
+            app.start();
+        }
+    }
+
+    public Main(AppState... appStates) {
+        super(appStates);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Box b = new Box(1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Blue);
+        geom.setMaterial(mat);
+
+        rootNode.attachChild(geom);
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        //TODO: add update code
+    }
+
+    @Override
+    public void simpleRender(RenderManager rm) {
+        //TODO: add render code
+    }
+}
+----
+Project source: https://github.com/neph1/VRSampleApplication
+
+
+== Google Cardboard VR SDK 1.0 integration
+gvr-android-jme (https://github.com/nordfalk/gvr-android-jme)
+
+
+== Legacy
+The following projects are not up to date, but may provide functionality not found in the other packages.
+Google Cardboard up to version 0.6: https://github.com/neph1/jme-cardboard

+ 6 - 94
docs/modules/core/pages/vr/virtualreality.adoc

@@ -1,100 +1,12 @@
 = Virtual Reality
-:revnumber: 2.0
-:revdate: 2020/07/27
-
-Please see this link:https://hub.jmonkeyengine.org/t/official-vr-module/37830/67[forum post] for additional information on JME Official VR module.
+:revnumber: 3.0
+:revdate: 2024/01/01
 
 == Introduction
 
-jMonkeyEngine 3 has a wide range of support for Virtual Reality (VR). The known supported systems are:
-
-HTC Vive and systems supporting SteamVR/OpenVR
-
-Native Oculus Rift support (and through SteamVR)
-
-Razer HDK and systems supporting OSVR
-
-Google Cardboard / GoogleVR
-
-Two implementations exist for OpenVR. A community maintained JNA based binding and LWJGL's JNI based.
-
-To use the JNA based bindings, put:
-
-    settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_VALUE);
-
-in your settings. To use LWJGL, instead put:
-
-    settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE);
-
-Note that the LWJGL bindings require LWJGL3 (jme3-lwjgl3) to be used.
-
-== Required dependencies
-
-    - org.jmonkeyengine:jme3-core
-    - org.jmonkeyengine:jme3-lwjgl3
-    - org.jmonkeyengine:jme3-vr
-
-== Sample Application
-
-[source,java]
-----
-public class Main extends SimpleApplication {
-
-    public static void main(String[] args) {
-        AppSettings settings = new AppSettings(true);
-        settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE);
-        settings.put(VRConstants.SETTING_ENABLE_MIRROR_WINDOW, true);
-
-        VREnvironment env = new VREnvironment(settings);
-        env.initialize();
-
-    	// Checking if the VR environment is well initialized
-    	// (access to the underlying VR system is effective, VR devices are detected).
-    	if (env.isInitialized()){
-            VRAppState vrAppState = new VRAppState(settings, env);
-            vrAppState.setMirrorWindowSize(1024, 800);
-            Main app = new Main(vrAppState);
-            app.setLostFocusBehavior(LostFocusBehavior.Disabled);
-            app.setSettings(settings);
-            app.setShowSettings(false);
-            app.start();
-        }
-    }
-
-    public Main(AppState... appStates) {
-        super(appStates);
-    }
-
-    @Override
-    public void simpleInitApp() {
-        Box b = new Box(1, 1, 1);
-        Geometry geom = new Geometry("Box", b);
-
-        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-        mat.setColor("Color", ColorRGBA.Blue);
-        geom.setMaterial(mat);
-
-        rootNode.attachChild(geom);
-    }
-
-    @Override
-    public void simpleUpdate(float tpf) {
-        //TODO: add update code
-    }
-
-    @Override
-    public void simpleRender(RenderManager rm) {
-        //TODO: add render code
-    }
-}
-----
-Project source: https://github.com/neph1/VRSampleApplication
-
-
-== Google Cardboard VR SDK 1.0 integration
-gvr-android-jme (https://github.com/nordfalk/gvr-android-jme)
-
+Virtual reality is well-supported in jMonkeyEngine 3. The VR support is provided by xref:contributions:vr/topic_contributions_vr.adoc[user contributed virtual reality libraries], for
+example xref:contributions:contributions.adoc#tamarin-openxr[Tamarin OpenXR].
 
 == Legacy
-The following projects are not up to date, but may provide functionality not found in the other packages.
-Google Cardboard up to version 0.6: https://github.com/neph1/jme-cardboard
+
+OpenVr support (and other old virtual reality standards) are documented at xref:vr/legacyOpenVr.adoc[Virtual Reality Legacy support] but will be removed in a future release.

+ 1 - 3
docs/modules/core/pages/vr/virtualrealitycontrollers.adoc

@@ -1,9 +1,7 @@
-= Virtual Reality Controllers
+= Virtual Reality Controllers Legacy Support
 :revnumber: 1.0
 :revdate: 2021/12/29
 
-Having successfully seen a VR cube in the xref:vr/virtualreality.adoc[Virtual reality hello world] a common next step is to put hands in the world so players can start interacting.
-
 == Where are we, what are we pointing at
 
 Be aware that the controllers positions and rotations are in world coordinates, not relative to the camera

+ 1 - 0
docs/modules/sdk/nav.adoc

@@ -42,6 +42,7 @@
 ***  xref:core:app/state/application_states.adoc[Application States]
 ***  xref:core:scene/control/custom_controls.adoc[Custom Controls]
 ***  xref:vehicle_creator.adoc[Vehicle Creator]
+***  xref:assetbrowser.adoc[Asset Browser]
 ** Advanced Usage
 ***  xref:build_platform.adoc[Building jMonkeyEngine SDK]
 ***  xref:use_own_jme#.adoc[Using your own (modified) version of jME3 in jMonkeyEngine SDK]

+ 26 - 0
docs/modules/sdk/pages/assetbrowser.adoc

@@ -0,0 +1,26 @@
+= jMonkeyEngine SDK: Assetbrowser
+:revnumber: 1.0
+:revdate: 2023/09/16
+:keywords: documentation, sdk, assets, assetbrowser
+
+The assetbrowser is a part of WYSIWYG. It aims to show a miniature of all assets in a project, and supports drag and drop to many of the components and editors in the SDk.
+
+== How to use it
+
+When it's first loaded, you will see a box that says "No project selected". This is because Netbeans, as opposed to many other IDE's can have many projects open at once.
+The Assetbrowser doesn't know which you will be working on, so you need to choose it.
+Once done, it will be populated with the assets inside the /assets folder.
+
+* Double clicking a Texture will open the Texture Editor
+* Double clicking a Material will open the Material Editor
+* Double clicking a Spatial will open the Scene Composer
+
+* You can drag and drop a Texture from the Assetbrowser to a Texture slot in the Material Editor
+* You can drag and drop a Material from the Assetbrowser to a Spatial in the SceneComposer to set that Material.
+* You can drag and drop a Spatial from the Assetbrowser to the SceneComposer to place it. It will ray cast and place it roughly where it hits something.
+
+
+== Clearing cache
+
+If you for some reason want to remove all the thumbnails, you can find them in a folder called .assetbrowser in the root of the project. It is safe to delete the folder.
+

+ 0 - 11
docs/modules/sdk/pages/model_loader_and_viewer.adoc

@@ -15,17 +15,6 @@ Blender no longer supports Ogre exporting as of version 2.8. It's highly recomme
 
 Also, see this link:http://www.youtube.com/watch?v=nL7woH40i5c[demo video] on importing models.
 
-== Installing the OgreXML Exporter in Blender
-
-The jMonkeyEngine SDK includes a tool to install the correct exporter tools in Blender to export OgreXML files. To install the exporter, do the following:
-
-.  Select `menu:Tools[OgreXML>Install>Blender OgreXML]` in the jMonkeyEngine SDK Menu.
-.  If you are presented a filechooser, select the folder where your blender scripts reside.
-.  Press "`Install`" in the window that opens.
-
-Also check out xref:tutorials:how-to/modeling/blender/blender.adoc[how to create compatible models in blender] and xref:tutorials:concepts/multi-media_asset_pipeline.adoc[how to organize your assets].
-
-
 == Using the model files directly
 
 [.right]

+ 19 - 3
docs/modules/sdk/pages/project_creation.adoc

@@ -10,7 +10,8 @@ The jMonkeyEngine SDK makes it easy to get started with developing 3-D games bas
 == Creating a New jMonkeyEngine Project
 
 .  Choose `menu:File[New Project]` from the main menu.
-.  In the New Project Wizard, select the template `menu:JME3[Basic Game]`.
+.  In the New Project Wizard, select a template `menu:JME3[Basic Game (with Gradle)]` or `menu:JME3[Basic Game (with Ant)]`.
+.  Gradle is the recommended build system since 3.6 (although there may still be many references to Ant projects, and they still work fine).
 .  Click next to specify a project name, and the path where to store your new project.
 .  Click Finish. A skeleton application is created and opens in the Project Explorer.
 **  This basic jme3 application is based on the SimpleApplication class to allow an easy start with jme3.
@@ -48,8 +49,10 @@ This is the recommended internal structure:
 **  `assets/Textures`
 
 *  *src* – This directory corresponds to the Source Packages node. Your sources code goes here.
-*  *nbproject* – This is meta data used by the jMonkeyEngine SDK (don't edit).
-*  *build.xml* – This is an Ant build script that is hooked up to the clean/build/run/test actions in the jMonkeyEngine SDK. It loads a default build script, and allows you to further customize the build process. The Ant script also assures that you are able to clean/build/run/test your application outside of the jMonkeyEngine SDK – e.g. from the command line.
+*  *nbproject* – (Ant only) This is meta data used by the jMonkeyEngine SDK (don't edit).
+*  *build.xml* – (Ant only) This is an Ant build script that is hooked up to the clean/build/run/test actions in the jMonkeyEngine SDK. It loads a default build script, and allows you to further customize the build process. The Ant script also assures that you are able to clean/build/run/test your application outside of the jMonkeyEngine SDK – e.g. from the command line.
+*  *build.gradle* – (Gradle only) This is the gradle build script that will build your app.
+*  *settings.gradle*  – (Gradle only) This contains some properties of your project, like the name, and asset folder.
 *  *build* – This directory contains the compiled classes. (Will be generated by the jMonkeyEngine SDK when you build the project.)
 *  *dist* – This directory contains the executable JAR files. (Will be generated by the jMonkeyEngine SDK when you build the project.)
 *  *test* – The jMonkeyEngine SDK will store JUnit tests here if you create any. (Optional.)
@@ -120,6 +123,18 @@ FIXME
 
 You may want to use external Java libraries in your jME project, for example content generators or artificial intelligence implementations.
 
+==== Gradle based projects:
+
+For gradle projects, you usually don't download a .jar beforehand. Instead, you speficy the library as a dependency, and gradle will download it.
+
+*  Open `Build Scripts/build.gradle`.
+*  Find `dependencies`.
+*  Add the library as a classpath. You can usually find the definition at link:https://mvnrepository.com[Maven Central], if it's a common java library.
+
+More info: link:https://docs.gradle.org/current/userguide/declaring_dependencies.html[https://docs.gradle.org/current/userguide/declaring_dependencies.html]
+
+==== Ant based projects:
+
 Add the library to the global library list:
 
 *  Select menu:Tools[Libraries] in the main menu.
@@ -135,6 +150,7 @@ Add the library to a project:
 *  Select "`Libraries`" on the left and then press "`Add Library`".
 *  Select the library from the list and press btn:[OK].
 
+
 That's it, your project can now use the external library. If you also linked the javadoc and sources, the SDK will assist you with javadoc popups, code completion (kbd:[Ctrl]+kbd:[Space]) and source navigation (kbd:[Ctrl]+btn:[LMB] ).
 
 

+ 11 - 2
docs/modules/sdk/pages/scene_composer.adoc

@@ -14,11 +14,20 @@ Most buttons in the SceneComposer have tooltips that appear when you hover the m
 *  Left-click and drag to rotate the camera around the cam center
 *  Right-click and drag to move the cam center
 *  Scroll the mouse wheel to zoom in/out the cam center
-*  Left-click a geometry to select it
-*  Right-click a geometry to place the cursor
+*  Right-click a geometry to select it
+*  Left-click a geometry to place the cursor
 
 In the SceneComposer toolbar are buttons to snap the camera to the cursor, snap the cursor to the selection and etc.
 
+Hot keys for manipulating objects:
+
+G - Move
+R - Rotate
+S - Scale
+
+In addition, you can limit the manipulation to a certain axis by pressing X, Y, or Z.
+By default the object's local reference will be used. Global or Camera reference can be selected in the SceneComposer window.
+
 
 == Creating a scene file
 

+ 0 - 5
docs/modules/tutorials/nav.adoc

@@ -39,11 +39,6 @@
 ** How to Animate
 *** Mixamo
 **** xref:how-to/modeling/blender/mixamo.adoc[Blender Models]
-**** Video
-***** link:https://youtu.be/jHgAgTWIers?list=PLv6qR9TGkz8RcUr-fOHI2SksWA4BAU9TS[Part 1- Download Model]
-***** link:https://youtu.be/GQJSrOpNQwI?list=PLv6qR9TGkz8RcUr-fOHI2SksWA4BAU9TS[Part 2- Rig and Animate]
-***** link:https://youtu.be/JzRe2Dxbcmc?list=PLv6qR9TGkz8RcUr-fOHI2SksWA4BAU9TS[Part 3- Import to JME]
-***** link:https://youtu.be/8wwDRDJop7k?list=PLv6qR9TGkz8RcUr-fOHI2SksWA4BAU9TS[Part 4- Play Animation]
 ** xref:how-to/debugging.adoc[Debugging with Wireframes]
 ** xref:how-to/util/free_skymaps.adoc[How to create free skymaps]
 ** Java Tips

+ 121 - 134
docs/modules/tutorials/pages/beginner/hello_animation.adoc

@@ -1,11 +1,11 @@
 = jMonkeyEngine 3 Tutorial (7) - Hello Animation
 :author:
 :revnumber:
-:revdate: 2020/07/06
+:revdate: 2024/10/05
 :keywords: beginner, intro, animation, documentation, keyinput, input, node, model
 
 
-This tutorial shows how to add an animation controller and channels, and how to respond to user input by triggering an animation in a loaded model.
+This tutorial shows how to add an animation controller and how to respond to user input by triggering an animation in a loaded model.
 
 image::beginner/beginner-animation.png[beginner-animation.png,width="",height="",align="center"]
 
@@ -18,10 +18,12 @@ include::partial$add-testdata-tip.adoc[]
 
 package jme3test.helloworld;
 
-import com.jme3.animation.AnimChannel;
-import com.jme3.animation.AnimControl;
-import com.jme3.animation.AnimEventListener;
-import com.jme3.animation.LoopMode;
+import com.jme3.anim.AnimComposer;
+import com.jme3.anim.tween.Tween;
+import com.jme3.anim.tween.Tweens;
+import com.jme3.anim.tween.action.Action;
+import com.jme3.anim.tween.action.BlendSpace;
+import com.jme3.anim.tween.action.LinearBlendSpace;
 import com.jme3.app.SimpleApplication;
 import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
@@ -32,60 +34,71 @@ import com.jme3.math.Vector3f;
 import com.jme3.scene.Node;
 
 /** Sample 7 - how to load an OgreXML model and play an animation,
- * using channels, a controller, and an AnimEventListener. */
-public class HelloAnimation extends SimpleApplication
-  implements AnimEventListener {
-  private AnimChannel channel;
-  private AnimControl control;
-  Node player;
-  public static void main(String[] args) {
-    HelloAnimation app = new HelloAnimation();
-    app.start();
-  }
-
-  @Override
-  public void simpleInitApp() {
-    viewPort.setBackgroundColor(ColorRGBA.LightGray);
-    initKeys();
-    DirectionalLight dl = new DirectionalLight();
-    dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());
-    rootNode.addLight(dl);
-    player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
-    player.setLocalScale(0.5f);
-    rootNode.attachChild(player);
-    control = player.getControl(AnimControl.class);
-    control.addListener(this);
-    channel = control.createChannel();
-    channel.setAnim("stand");
-  }
-
-  public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
-    if (animName.equals("Walk")) {
-      channel.setAnim("stand", 0.50f);
-      channel.setLoopMode(LoopMode.DontLoop);
-      channel.setSpeed(1f);
+ * using AnimComposer */
+public class HelloAnimation extends SimpleApplication{
+
+    private AnimComposer control;
+    private Action advance;
+
+    Node player;
+    public static void main(String[] args) {
+        HelloAnimation app = new HelloAnimation();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.LightGray);
+        initKeys();
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());
+        rootNode.addLight(dl);
+        player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        player.setLocalScale(0.5f);
+        rootNode.attachChild(player);
+        control = player.getControl(AnimComposer.class);
+        control.setCurrentAction("stand");
+
+        /* Compose an animation action named "halt"
+        that transitions from "Walk" to "stand" in half a second. */
+        BlendSpace quickBlend = new LinearBlendSpace(0f, 0.5f);
+        Action halt = control.actionBlended("halt", quickBlend, "stand", "Walk");
+        halt.setLength(0.5);
+
+        /* Compose an animation action named "advance"
+        that walks for one cycle, then halts, then invokes onAdvanceDone(). */
+        Action walk = control.action("Walk");
+        Tween doneTween = Tweens.callMethod(this, "onAdvanceDone");
+        advance = control.actionSequence("advance", walk, halt, doneTween);
     }
-  }
-
-  public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
-    // unused
-  }
-
-  /** Custom Keybinding: Map named actions to inputs. */
-  private void initKeys() {
-    inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
-    inputManager.addListener(actionListener, "Walk");
-  }
-  private ActionListener actionListener = new ActionListener() {
-    public void onAction(String name, boolean keyPressed, float tpf) {
-      if (name.equals("Walk") && !keyPressed) {
-        if (!channel.getAnimationName().equals("Walk")) {
-          channel.setAnim("Walk", 0.50f);
-          channel.setLoopMode(LoopMode.Loop);
-        }
-      }
+
+    /**
+     * Callback to indicate that the "advance" animation action has completed.
+     */
+    void onAdvanceDone() {
+        /*
+         * Play the "stand" animation action.
+         */
+        control.setCurrentAction("stand");
+    }
+
+    /**
+     * Map the spacebar to the "Walk" input action, and add a listener to initiate
+     * the "advance" animation action each time it's pressed.
+     */
+    private void initKeys() {
+        inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
+
+        ActionListener handler = new ActionListener() {
+            @Override
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (keyPressed && control.getCurrentAction() != advance) {
+                    control.setCurrentAction("advance");
+                }
+            }
+        };
+        inputManager.addListener(handler, "Walk");
     }
-  };
 }
 
 ----
@@ -128,59 +141,36 @@ After you load the animated model, you register it to the Animation Controller.
 [source,java]
 ----
 
-  private AnimChannel channel;
-  private AnimControl control;
+  private AnimComposer control;
 
   public void simpleInitApp() {
     ...
     /* Load the animation controls, listen to animation events,
      * create an animation channel, and bring the model in its default position.
      */
-    control = player.getControl(AnimControl.class);
-    control.addListener(this);
-    channel = control.createChannel();
-    channel.setAnim("stand");
+     control = player.getControl(AnimComposer.class);
+     control.setCurrentAction("stand");
     ...
 }
 ----
 
-This line of code will return NULL if the AnimControl is not in the main node of your model.
+This line of code will return NULL if the AnimComposer is not in the main node of your model.
 
 [source,java]
 ----
-control = player.getControl(AnimControl.class);
+control = player.getControl(AnimComposer.class);
 ----
 
 To check this, btn:[RMB] select your model and click "`Edit in SceneComposer`" if the models file extension is .j3o, or "`View`" if not. You can then see the tree for the model so you can locate the node the control resides in. You can access the subnode with the following code.
 
 [source,java]
 ----
-player.getChild("Subnode").getControl(AnimControl.class);
+player.getChild("Subnode").getControl(AnimComposer.class);
 ----
 
-[NOTE]
-====
-In response to a question about animations on different channels interfering with each other, *Nehon*, on the jME forum wrote,
-
-[quote, Nehon, Team Leader: Retired]
-____
-You have to consider channels as part of the skeleton that are animated. The default behavior is to use the whole skeleton for a channel.
-
-In your example the first channel plays the walk anim, then the second channel plays the dodge animation.
-
-Arms and feet are probably not affected by the doge animation so you can see the walk anim for them, but the rest of the body plays the dodge animation.
-
-Usually multiple channels are used to animate different part of the body. For example you create one channel for the lower part of the body and one for the upper part. This allow you to play a walk animation with the lower part and for example a shoot animation with the upper part. This way your character can walk while shooting.
-
-In your case, where you want animations to chain for the whole skeleton, you just have to use one channel.
-____
-====
-
-
-
 == Responding to Animation Events
 
-Add `implements AnimEventListener` to the class declaration. This interface gives you access to events that notify you when a sequence is done, or when you change from one sequence to another, so you can respond to it. In this example, you reset the character to a standing position after a `Walk` cycle is done.
+A Tween (part of an action sequence) can call a method on a class, allowing your application code to be informed of the animation state. In this example, you reset the character to a standing position after a `Walk` cycle is done.
 
 [source,java]
 ----
@@ -189,17 +179,24 @@ public class HelloAnimation extends SimpleApplication
                          implements AnimEventListener {
   ...
 
-  public void onAnimCycleDone(AnimControl control,
-                              AnimChannel channel, String animName) {
-    if (animName.equals("Walk")) {
-      channel.setAnim("stand", 0.50f);
-      channel.setLoopMode(LoopMode.DontLoop);
-      channel.setSpeed(1f);
+      @Override
+    public void simpleInitApp() {
+      ...
+      Action walk = control.action("Walk");
+      Tween doneTween = Tweens.callMethod(this, "onAdvanceDone");
+      advance = control.actionSequence("advance", walk, halt, doneTween);
+      ...
+    }
+
+    /**
+     * Callback to indicate that the "advance" animation action has completed.
+     */
+    void onAdvanceDone() {
+        /*
+         * Play the "stand" animation action.
+         */
+        control.setCurrentAction("stand");
     }
-  }
-  public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
-    // unused
-  }
  ...
 }
 ----
@@ -220,37 +217,31 @@ There are ambient animations like animals or trees that you may want to trigger
 [source,java]
 ----
 
-  private void initKeys() {
-    inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
-    inputManager.addListener(actionListener, "Walk");
-  }
-
-----
-
-To use the input controller, you need to implement the actionListener by testing for each action by name, then set the channel to the corresponding animation to run.
-
-*  The second parameter of setAnim() is the blendTime (how long the current animation should overlap with the last one).
-*  LoopMode can be Loop (repeat), Cycle (forward then backward), and DontLoop (only once).
-*  If needed, use channel.setSpeed() to set the speed of this animation.
-*  Optionally, use channel.setTime() to Fast-forward or rewind to a certain moment in time of this animation.
-
-[source,java]
-----
-
-  private ActionListener actionListener = new ActionListener() {
-    public void onAction(String name, boolean keyPressed, float tpf) {
-        if (name.equals("Walk") && !keyPressed) {
-            if (!channel.getAnimationName().equals("Walk")){
-                channel.setAnim("Walk", 0.50f);
-                channel.setLoopMode(LoopMode.Cycle);
+    /**
+     * Map the spacebar to the "Walk" input action, and add a listener to initiate
+     * the "advance" animation action each time it's pressed.
+     */
+    private void initKeys() {
+        inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
+
+        ActionListener handler = new ActionListener() {
+            @Override
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                if (keyPressed && control.getCurrentAction() != advance) {
+                    control.setCurrentAction("advance");
+                }
             }
-        }
+        };
+        inputManager.addListener(handler, "Walk");
     }
-  };
 
 ----
 
 
+*  By default, the animation will loop, there is an overloaded `setCurrentAction` method that allows you to set the loop mode.
+*  If needed, use Action::setSpeed to set the speed of this animation.
+*  Optionally, use AnimComposer::.setTime to Fast-forward or rewind to a certain moment in time of this animation.
+
 == Exercises
 
 
@@ -258,7 +249,7 @@ To use the input controller, you need to implement the actionListener by testing
 
 Make a mouse click trigger another animation sequence!
 
-.  Create a second channel in the controller.
+.  Create a second layer in the controller.
 .  Create a new key trigger mapping and action. (see: xref:beginner/hello_input_system.adoc[Hello Input])
 +
 [TIP]
@@ -269,7 +260,7 @@ Use:
 
 [source,java]
 ----
-for (String anim : control.getAnimationNames()) {
+for (String anim : control.getAnimClipsNames()) {
     System.out.println(anim);
 }
 ----
@@ -293,8 +284,8 @@ Add the following import statements for the SkeletonDebugger and Material classe
 [source,java]
 ----
 
-     import com.jme3.scene.debug.SkeletonDebugger;
-     import com.jme3.material.Material;
+     import com.jme3.scene.debug.custom.ArmatureDebugAppState;
+     import com.jme3.anim.SkinningControl;
 
 ----
 
@@ -303,13 +294,9 @@ Add the following code snippet to `simpleInitApp()` to make the bones (that you
 [source,java]
 ----
 
-     SkeletonDebugger skeletonDebug =
-         new SkeletonDebugger("skeleton", control.getSkeleton());
-     Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-     mat.setColor("Color", ColorRGBA.Green);
-     mat.getAdditionalRenderState().setDepthTest(false);
-     skeletonDebug.setMaterial(mat);
-     player.attachChild(skeletonDebug);
+    ArmatureDebugAppState armatureDebugAppState = new ArmatureDebugAppState();
+    armatureDebugAppState.addArmatureFrom(player.getControl(SkinningControl.class));
+    this.getStateManager().attach(armatureDebugAppState);
 
 ----
 

+ 21 - 25
docs/modules/tutorials/pages/beginner/hello_asset.adoc

@@ -40,42 +40,40 @@ public class HelloAssets extends SimpleApplication {
 
     @Override
     public void simpleInitApp() {
-
+    
+        /* Load a teapot model (OBJ file from jme3-testdata) */ 
         Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
-        Material mat_default = new Material(
-            assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+        Material mat_default = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
         teapot.setMaterial(mat_default);
         rootNode.attachChild(teapot);
 
-        // Create a wall with a simple texture from test_data
+        /* Create a wall (Box with material and texture from jme3-testdata) */
         Box box = new Box(2.5f,2.5f,1.0f);
         Spatial wall = new Geometry("Box", box );
-        Material mat_brick = new Material(
-            assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-        mat_brick.setTexture("ColorMap",
-            assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+        Material mat_brick = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
         wall.setMaterial(mat_brick);
         wall.setLocalTranslation(2.0f,-2.5f,0.0f);
         rootNode.attachChild(wall);
 
-        // Display a line of text with a default font
-        guiNode.detachAllChildren();
+        /* Display a line of text (default font from jme3-testdata) */
+        setDisplayStatView(false); 
         guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
-        BitmapText helloText = new BitmapText(guiFont, false);
+        BitmapText helloText = new BitmapText(guiFont);
         helloText.setSize(guiFont.getCharSet().getRenderedSize());
         helloText.setText("Hello World");
         helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
         guiNode.attachChild(helloText);
 
-        // Load a model from test_data (OgreXML + material + texture)
+        /* Load a Ninja model (OgreXML + material + texture from test_data) */
         Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
         ninja.scale(0.05f, 0.05f, 0.05f);
         ninja.rotate(0.0f, -3.0f, 0.0f);
         ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
         rootNode.attachChild(ninja);
-        // You must add a light to make the model visible
+        /* You must add a light to make the model visible. */
         DirectionalLight sun = new DirectionalLight();
-        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
+        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());
         rootNode.addLight(sun);
 
     }
@@ -126,13 +124,11 @@ Place your textures in a subdirectory of `assets/Textures/`. Load the texture in
 [source,java]
 ----
 
-// Create a wall with a simple texture from test_data
+/* Create a wall (Box with material and texture from jme3-testdata) */
 Box box = new Box(2.5f,2.5f,1.0f);
 Spatial wall = new Geometry("Box", box );
-Material mat_brick = new Material(
-    assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-mat_brick.setTexture("ColorMap",
-    assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+Material mat_brick = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+mat_brick.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
 wall.setMaterial(mat_brick);
 wall.setLocalTranslation(2.0f,-2.5f,0.0f);
 rootNode.attachChild(wall);
@@ -149,10 +145,10 @@ The following code sample goes into the `simpleInitApp()` method.
 
 [source,java]
 ----
-// Display a line of text with a default font
-guiNode.detachAllChildren();
+/* Display a line of text (default font from jme3-testdata) */
+setDisplayStatView(false); 
 guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
-BitmapText helloText = new BitmapText(guiFont, false);
+BitmapText helloText = new BitmapText(guiFont);
 helloText.setSize(guiFont.getCharSet().getRenderedSize());
 helloText.setText("Hello World");
 helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
@@ -162,7 +158,7 @@ guiNode.attachChild(helloText);
 
 [TIP]
 ====
-Clear existing text in the guiNode by detaching all its children.
+You can use `setDisplayStatView(false);` to deactivate the HUD display statistics.
 ====
 
 
@@ -173,13 +169,13 @@ Export your 3D model in a <<ROOT:getting-started/features.adoc#supported-externa
 [source,java]
 ----
 
-// Load a model from test_data (OgreXML + material + texture)
+/* Load a Ninja model (OgreXML + material + texture from test_data) */
 Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
 ninja.scale(0.05f, 0.05f, 0.05f);
 ninja.rotate(0.0f, -3.0f, 0.0f);
 ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
 rootNode.attachChild(ninja);
-// You must add a directional light to make the model visible!
+/* You must add a light to make the model visible. */
 DirectionalLight sun = new DirectionalLight();
 sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());
 rootNode.addLight(sun);

+ 8 - 11
docs/modules/tutorials/pages/beginner/hello_audio.adoc

@@ -30,8 +30,6 @@ import com.jme3.scene.shape.Box;
 public class HelloAudio extends SimpleApplication {
 
   private AudioNode audio_gun;
-  private AudioNode audio_nature;
-  private Geometry player;
 
   public static void main(String[] args) {
     HelloAudio app = new HelloAudio();
@@ -44,7 +42,7 @@ public class HelloAudio extends SimpleApplication {
 
     /** just a blue box floating in space */
     Box box1 = new Box(1, 1, 1);
-    player = new Geometry("Player", box1);
+    Geometry player = new Geometry("Player", box1);
     Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
     mat1.setColor("Color", ColorRGBA.Blue);
     player.setMaterial(mat1);
@@ -65,7 +63,7 @@ public class HelloAudio extends SimpleApplication {
     rootNode.attachChild(audio_gun);
 
     /* nature sound - keeps playing in a loop. */
-    audio_nature = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Stream);
+    AudioNode audio_nature = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Stream);
     audio_nature.setLooping(true);  // activate continuous playing
     audio_nature.setPositional(true);
     audio_nature.setVolume(3);
@@ -80,7 +78,7 @@ public class HelloAudio extends SimpleApplication {
   }
 
   /** Defining the "Shoot" action: Play a gun sound. */
-  private ActionListener actionListener = new ActionListener() {
+  final private ActionListener actionListener = new ActionListener() {
     @Override
     public void onAction(String name, boolean keyPressed, float tpf) {
       if (name.equals("Shoot") && !keyPressed) {
@@ -120,8 +118,7 @@ For each sound, you create an AudioNode. You can use an AudioNode like any node
 ----
 
   private AudioNode audio_gun;
-  private AudioNode audio_nature;
-
+  
 ----
 
 Look at the custom `initAudio()` method: Here you initialize the sound objects and set their parameters.
@@ -131,7 +128,7 @@ Look at the custom `initAudio()` method: Here you initialize the sound objects a
 
 audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer);
     ...
-audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream);
+AudioNode audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream);
 
 ----
 
@@ -213,7 +210,7 @@ Setting up the ActionListener should also be familiar from previous tutorials. Y
 ----
 
   /** Defining the "Shoot" action: Play a gun sound. */
-  private ActionListener actionListener = new ActionListener() {
+  final private ActionListener actionListener = new ActionListener() {
     @Override
     public void onAction(String name, boolean keyPressed, float tpf) {
       if (name.equals("Shoot") && !keyPressed) {
@@ -253,7 +250,7 @@ Apart from the looping boolean, another difference is where `play().playInstance
 ----
 
   /** Defining the "Shoot" action: Play a gun sound. */
-  private ActionListener actionListener = new ActionListener() {
+  final private ActionListener actionListener = new ActionListener() {
     @Override
     public void onAction(String name, boolean keyPressed, float tpf) {
       if (name.equals("Shoot") && !keyPressed) {
@@ -273,7 +270,7 @@ The Enum in the AudioNode constructor defines whether the audio is buffered or s
 ----
 audio_gunshot = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer); // buffered
 ...
-audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream); // streamed
+AudioNode audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream); // streamed
 ----
 
 Typically, you stream long sounds, and buffer short sounds.

+ 53 - 62
docs/modules/tutorials/pages/beginner/hello_collision.adoc

@@ -35,7 +35,7 @@ Place town.zip in the root directory of your JME3 project. Here is the code:
 package jme3test.helloworld;
 
 import com.jme3.app.SimpleApplication;
-import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.asset.plugins.HttpZipLocator;
 import com.jme3.bullet.BulletAppState;
 import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
 import com.jme3.bullet.collision.shapes.CollisionShape;
@@ -49,7 +49,6 @@ import com.jme3.light.AmbientLight;
 import com.jme3.light.DirectionalLight;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
-import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 
 /**
@@ -60,28 +59,25 @@ import com.jme3.scene.Spatial;
 public class HelloCollision extends SimpleApplication
         implements ActionListener {
 
-  private Spatial sceneModel;
-  private BulletAppState bulletAppState;
-  private RigidBodyControl landscape;
   private CharacterControl player;
-  private Vector3f walkDirection = new Vector3f();
+  final private Vector3f walkDirection = new Vector3f();
   private boolean left = false, right = false, up = false, down = false;
 
   //Temporary vectors used on each frame.
-  //They here to avoid instanciating new vectors on each frame
-  private Vector3f camDir = new Vector3f();
-  private Vector3f camLeft = new Vector3f();
+  //They here to avoid instantiating new vectors on each frame
+  final private Vector3f camDir = new Vector3f();
+  final private Vector3f camLeft = new Vector3f();
 
   public static void main(String[] args) {
     HelloCollision app = new HelloCollision();
     app.start();
   }
-
+  
+  @Override
   public void simpleInitApp() {
     /** Set up Physics */
-    bulletAppState = new BulletAppState();
+    BulletAppState bulletAppState = new BulletAppState();
     stateManager.attach(bulletAppState);
-    //bulletAppState.setDebugEnabled(true);
 
     // We re-use the flyby camera for rotation, while positioning is handled by physics
     viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
@@ -90,40 +86,38 @@ public class HelloCollision extends SimpleApplication
     setUpLight();
 
     // We load the scene from the zip file and adjust its size.
-    assetManager.registerLocator("town.zip", ZipLocator.class);
-    sceneModel = assetManager.loadModel("main.scene");
+    assetManager.registerLocator(
+                    "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/town.zip",
+                    HttpZipLocator.class);
+    Spatial sceneModel = assetManager.loadModel("main.scene");
     sceneModel.setLocalScale(2f);
 
     // We set up collision detection for the scene by creating a
     // compound collision shape and a static RigidBodyControl with mass zero.
     CollisionShape sceneShape =
             CollisionShapeFactory.createMeshShape(sceneModel);
-    landscape = new RigidBodyControl(sceneShape, 0);
+    RigidBodyControl landscape = new RigidBodyControl(sceneShape, 0);
     sceneModel.addControl(landscape);
-
-    /**
-     * We set up collision detection for the player by creating
-     * a capsule collision shape and a CharacterControl.
-     * The CharacterControl offers extra settings for
-     * size, stepheight, jumping, falling, and gravity.
-     * We also put the player in its starting position.
-     */
+    
+    // We set up collision detection for the player by creating
+    // a capsule collision shape and a CharacterControl.
+    // The CharacterControl offers extra settings for
+    // size, step height, jumping, falling, and gravity.
+    // We also put the player in its starting position.
+    
     CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
     player = new CharacterControl(capsuleShape, 0.05f);
     player.setJumpSpeed(20);
     player.setFallSpeed(30);
+    player.setGravity(30);
+    player.setPhysicsLocation(new Vector3f(0, 10, 0));
 
     // We attach the scene and the player to the rootnode and the physics space,
     // to make them appear in the game world.
     rootNode.attachChild(sceneModel);
     bulletAppState.getPhysicsSpace().add(landscape);
     bulletAppState.getPhysicsSpace().add(player);
-
-    // You can change the gravity of individual physics objects before or after
-    //they are added to the PhysicsSpace, but it must be set before MOVING the
-    //physics location.
-    player.setGravity(new Vector3f(0,-30f,0));
-    player.setPhysicsLocation(new Vector3f(0, 10, 0));
+    
   }
 
   private void setUpLight() {
@@ -155,17 +149,18 @@ public class HelloCollision extends SimpleApplication
 
   /** These are our custom actions triggered by key presses.
    * We do not walk yet, we just keep track of the direction the user pressed. */
-  public void onAction(String binding, boolean isPressed, float tpf) {
+   @Override
+  public void onAction(String binding, boolean value, float tpf) {
     if (binding.equals("Left")) {
-      left = isPressed;
+      if (value) { left = true; } else { left = false; }
     } else if (binding.equals("Right")) {
-      right= isPressed;
+      if (value) { right = true; } else { right = false; }
     } else if (binding.equals("Up")) {
-      up = isPressed;
+      if (value) { up = true; } else { up = false; }
     } else if (binding.equals("Down")) {
-      down = isPressed;
+      if (value) { down = true; } else { down = false; }
     } else if (binding.equals("Jump")) {
-      if (isPressed) { player.jump(new Vector3f(0,20f,0));}
+      player.jump();
     }
   }
 
@@ -218,25 +213,19 @@ You already know that SimpleApplication is the base class for all jME3 games. Yo
 [source,java]
 ----
 
-  private Spatial sceneModel;
-  private BulletAppState bulletAppState;
-  private RigidBodyControl landscape;
   private CharacterControl player;
-  private Vector3f walkDirection = new Vector3f();
+  final private Vector3f walkDirection = new Vector3f();
   private boolean left = false, right = false, up = false, down = false;
 
   //Temporary vectors used on each frame.
-  //They here to avoid instanciating new vectors on each frame
-  private Vector3f camDir = new Vector3f();
-  private Vector3f camLeft = new Vector3f();
+  //They here to avoid instantiating new vectors on each frame
+  final private Vector3f camDir = new Vector3f();
+  final private Vector3f camLeft = new Vector3f();
 
 ----
 
 You initialize a few private fields:
 
-*  The BulletAppState gives this SimpleApplication access to physics features (such as collision detection) supplied by jME3's Bullet integration
-*  The Spatial sceneModel is for loading an OgreXML model of a town.
-*  You need a RigidBodyControl to make the town model solid.
 *  The (invisible) first-person player is represented by a CharacterControl object.
 *  The fields `walkDirection` and the four Booleans are used for physics-controlled navigation.
 *  camDir and camLeft are temporary vectors used later when computing the walkingDirection from the cam position and rotation
@@ -270,41 +259,43 @@ Currently, jMonkeyEngine has two versions of link:https://pybullet.org/wordpress
 
 include::ROOT:partial$source-structure-link.adoc[]
 
-How you initialize each is the same, only the methods used for manipulating objects is different. The first thing you do in every physics game is create a BulletAppState object. It gives you access to the jME3 Bullet integration which handles physical forces and collisions.
+How you initialize each is the same, only the methods used for manipulating objects is different. The first thing you do in every physics game is create a BulletAppState object. It gives your Simple Application access to the jME3 Bullet integration which handles physical forces and collisions.
 
 [source,java]
 ----
 
-    bulletAppState = new BulletAppState();
+    BulletAppState bulletAppState = new BulletAppState();
     stateManager.attach(bulletAppState);
 
 ----
 
-For the scene, you load the `sceneModel` from a zip file, and adjust the size.
+For the scene, you load the Spatial `sceneModel` from a zip file, and adjust the size.
 
 [source,java]
 ----
 
-    assetManager.registerLocator("town.zip", ZipLocator.class);
-    sceneModel = assetManager.loadModel("main.scene");
+    assetManager.registerLocator(
+                    "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/town.zip",
+                    HttpZipLocator.class);
+    Spatial sceneModel = assetManager.loadModel("main.scene");
     sceneModel.setLocalScale(2f);
 
 ----
 
-The file `town.zip` is included as a sample model in the JME3 sources – you can link:https://wiki.jmonkeyengine.org/Scenes/Town/town.zip[Download the town.zip]. (Optionally, use any OgreXML scene of your own.) For this sample, place the zip file in the application's top level directory (that is, next to src/, assets/, build.xml).
+The file `town.zip` is an OgreXML model of a town and is included as a sample model in the JME3 sources – you can link:https://wiki.jmonkeyengine.org/Scenes/Town/town.zip[Download the town.zip]. (Optionally, use any OgreXML scene of your own.) For this sample, place the zip file in the application's top level directory (that is, next to src/, assets/, build.xml).
 
 [source,java]
 ----
 
     CollisionShape sceneShape =
       CollisionShapeFactory.createMeshShape((Node) sceneModel);
-    landscape = new RigidBodyControl(sceneShape, 0);
+    RigidBodyControl landscape = new RigidBodyControl(sceneShape, 0);
     sceneModel.addControl(landscape);
     rootNode.attachChild(sceneModel);
 
 ----
 
-To use collision detection, you add a RigidBodyControl to the `sceneModel` Spatial. The RigidBodyControl for a complex model takes two arguments: A Collision Shape, and the object's mass.
+To make the town model solid and use collision detection, you add a RigidBodyControl to the `sceneModel` Spatial. The RigidBodyControl for a complex model takes two arguments: A Collision Shape, and the object's mass.
 
 *  JME3 offers a `CollisionShapeFactory` that precalculates a mesh-accurate collision shape for a Spatial. You choose to generate a `CompoundCollisionShape` (which has MeshCollisionShapes as its children) because this type of collision shape is optimal for immobile objects, such as terrain, houses, and whole shooter levels.
 *  You set the mass to zero since a scene is static and its mass is irrelevant.
@@ -363,7 +354,7 @@ Now you use the CollisionShape to create a `CharacterControl` that represents th
 
     player.setJumpSpeed(20);
     player.setFallSpeed(30);
-    player.setGravity(new Vector3f(0,-30f,0));
+    player.setGravity(30);
 
 ----
 
@@ -385,7 +376,7 @@ but gravity must be set BEFORE moving the physics location.
 
 [source, java]
 ----
-player.setGravity(new Vector3f(0,-30f,0));
+player.setGravity(30);
 player.setPhysicsLocation(new Vector3f(0, 10, 0));
 ----
 
@@ -447,18 +438,18 @@ Remember that this class implements the `ActionListener` interface, so you can c
 
 [source,java]
 ----
-
-  public void onAction(String binding, boolean isPressed, float tpf) {
+@Override
+  public void onAction(String binding, boolean value, float tpf) {
     if (binding.equals("Left")) {
-      left = isPressed;
+      if (value) { left = true; } else { left = false; }
     } else if (binding.equals("Right")) {
-      right= isPressed;
+      if (value) { right = true; } else { right = false; }
     } else if (binding.equals("Up")) {
-      up = isPressed;
+      if (value) { up = true; } else { up = false; }
     } else if (binding.equals("Down")) {
-      down = isPressed;
+      if (value) { down = true; } else { down = false; }
     } else if (binding.equals("Jump")) {
-      if (isPressed) { player.jump(new Vector3f(0,20f,0));}
+      player.jump();
     }
   }
 ----

+ 22 - 0
docs/modules/tutorials/pages/beginner/hello_effects.adoc

@@ -88,6 +88,28 @@ public class HelloEffects extends SimpleApplication {
     debris.getParticleInfluencer().setVelocityVariation(.60f);
     rootNode.attachChild(debris);
     debris.emitAllParticles();
+    
+//    ParticleEmitter water = 
+//            new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 20);
+//    Material mat_blue = new Material(assetManager, 
+//            "Common/MatDefs/Misc/Particle.j3md");
+//    mat_blue.setTexture("Texture", assetManager.loadTexture(
+//            "Effects/Explosion/flame.png"));
+//    water.setMaterial(mat_blue);
+//    water.setImagesX(2); 
+//    water.setImagesY(2); // 2x2 texture animation
+//    water.setStartColor( ColorRGBA.Blue); 
+//    water.setEndColor( ColorRGBA.Cyan); 
+//    water.getParticleInfluencer().setInitialVelocity(new Vector3f(0, -4, 0));
+//    water.setStartSize(1f);
+//    water.setEndSize(1.5f);
+//    water.setGravity(0,1,0);
+//    water.setLowLife(1f);
+//    water.setHighLife(1f);
+//    water.getParticleInfluencer().setVelocityVariation(0.1f);
+//    water.setLocalTranslation(0, 6, 0);
+//    rootNode.attachChild(water);
+
   }
 }
 ----

+ 26 - 32
docs/modules/tutorials/pages/beginner/hello_input_system.adoc

@@ -29,9 +29,8 @@ import com.jme3.input.controls.AnalogListener;
 import com.jme3.input.controls.KeyTrigger;
 import com.jme3.input.controls.MouseButtonTrigger;
 
-/**
- * Sample 5 - how to map keys and mousebuttons to actions
- */
+/** Sample 5 - how to map keys and mouse buttons to actions */
+
 public class HelloInput extends SimpleApplication {
 
     public static void main(String[] args) {
@@ -39,8 +38,8 @@ public class HelloInput extends SimpleApplication {
         app.start();
     }
 
-    protected Geometry player;
-    private boolean isRunning = true;
+    private Geometry player;
+    private Boolean isRunning = true;
 
     @Override
     public void simpleInitApp() {
@@ -53,23 +52,22 @@ public class HelloInput extends SimpleApplication {
         initKeys(); // load my custom keybinding
     }
 
-    /**
-     * Custom Keybinding: Map named actions to inputs.
-     */
+    /** Custom Keybinding: Map named actions to inputs. */ 
     private void initKeys() {
-        // You can map one or several inputs to one named action
+        /* You can map one or several inputs to one named mapping. */ 
         inputManager.addMapping("Pause",  new KeyTrigger(KeyInput.KEY_P));
         inputManager.addMapping("Left",   new KeyTrigger(KeyInput.KEY_J));
         inputManager.addMapping("Right",  new KeyTrigger(KeyInput.KEY_K));
         inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE),
                                           new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
-        // Add the names to the action listener.
+        /* Add the named mappings to the action listeners. */ 
         inputManager.addListener(actionListener, "Pause");
         inputManager.addListener(analogListener, "Left", "Right", "Rotate");
 
     }
 
-    private final ActionListener actionListener = new ActionListener() {
+  /** Use this listener for KeyDown/KeyUp events */ 
+    final private ActionListener actionListener = new ActionListener() {
         @Override
         public void onAction(String name, boolean keyPressed, float tpf) {
             if (name.equals("Pause") && !keyPressed) {
@@ -78,20 +76,19 @@ public class HelloInput extends SimpleApplication {
         }
     };
 
-    private final AnalogListener analogListener = new AnalogListener() {
+  /** Use this listener for continuous events */ 
+    final private AnalogListener analogListener = new AnalogListener() {
         @Override
         public void onAnalog(String name, float value, float tpf) {
             if (isRunning) {
                 if (name.equals("Rotate")) {
-                    player.rotate(0, value * speed, 0);
+                    player.rotate(0, value, 0);
                 }
                 if (name.equals("Right")) {
-                    Vector3f v = player.getLocalTranslation();
-                    player.setLocalTranslation(v.x + value * speed, v.y, v.z);
+                    player.move((new Vector3f(value, 0,0)) ); 
                 }
                 if (name.equals("Left")) {
-                    Vector3f v = player.getLocalTranslation();
-                    player.setLocalTranslation(v.x - value * speed, v.y, v.z);
+                    player.move(new Vector3f(-value, 0,0)); 
                 }
             } else {
                 System.out.println("Press P to unpause.");
@@ -131,7 +128,7 @@ Have a look at the code:
 [source,java]
 ----
 
-    // You can map one or several inputs to one named action
+    /* You can map one or several inputs to one named mapping. */ 
     inputManager.addMapping("Pause",  new KeyTrigger(KeyInput.KEY_P));
     inputManager.addMapping("Left",   new KeyTrigger(KeyInput.KEY_J));
     inputManager.addMapping("Right",  new KeyTrigger(KeyInput.KEY_K));
@@ -148,7 +145,7 @@ Now you need to register your trigger mappings.
 [source,java]
 ----
 
-    // Add the names to the action listener.
+    /* Add the named mappings to the action listeners. */ 
     inputManager.addListener(actionListener,"Pause");
     inputManager.addListener(analogListener,"Left", "Right", "Rotate");
 
@@ -173,8 +170,8 @@ In this example, we trigger the following actions:
 [source,java]
 ----
 
-
-    private final ActionListener actionListener = new ActionListener() {
+  /** Use this listener for KeyDown/KeyUp events */ 
+    final private ActionListener actionListener = new ActionListener() {
         @Override
         public void onAction(String name, boolean keyPressed, float tpf) {
             if (name.equals("Pause") && !keyPressed) {
@@ -183,20 +180,19 @@ In this example, we trigger the following actions:
         }
     };
 
-    private final AnalogListener analogListener = new AnalogListener() {
+  /** Use this listener for continuous events */ 
+    final private AnalogListener analogListener = new AnalogListener() {
         @Override
         public void onAnalog(String name, float value, float tpf) {
             if (isRunning) {
                 if (name.equals("Rotate")) {
-                    player.rotate(0, value * speed, 0);
+                    player.rotate(0, value, 0);
                 }
                 if (name.equals("Right")) {
-                    Vector3f v = player.getLocalTranslation();
-                    player.setLocalTranslation(v.x + value * speed, v.y, v.z);
+                    player.move((new Vector3f(value, 0,0)) ); 
                 }
                 if (name.equals("Left")) {
-                    Vector3f v = player.getLocalTranslation();
-                    player.setLocalTranslation(v.x - value * speed, v.y, v.z);
+                    player.move(new Vector3f(-value, 0,0)); 
                 }
             } else {
                 System.out.println("Press P to unpause.");
@@ -225,15 +221,13 @@ private class MyCombinedListener implements AnalogListener, ActionListener {
     public void onAnalog(String name, float value, float tpf) {
         if (isRunning) {
             if (name.equals("Rotate")) {
-                player.rotate(0, value * speed, 0);
+                player.rotate(0, value, 0);
             }
             if (name.equals("Right")) {
-                Vector3f v = player.getLocalTranslation();
-                player.setLocalTranslation(v.x + value * speed, v.y, v.z);
+                player.move((new Vector3f(value, 0,0)) ); 
             }
             if (name.equals("Left")) {
-                Vector3f v = player.getLocalTranslation();
-                player.setLocalTranslation(v.x - value * speed, v.y, v.z);
+                player.move(new Vector3f(-value, 0,0)); 
             }
         } else {
             System.out.println("Press P to unpause.");

+ 1 - 1
docs/modules/tutorials/pages/beginner/hello_main_event_loop.adoc

@@ -30,7 +30,7 @@ public class HelloLoop extends SimpleApplication {
         app.start();
     }
 
-    protected Geometry player;
+    private Geometry player;
 
     @Override
     public void simpleInitApp() {

+ 20 - 39
docs/modules/tutorials/pages/beginner/hello_material.adoc

@@ -47,10 +47,8 @@ public class HelloMaterial extends SimpleApplication {
     Box cube1Mesh = new Box( 1f,1f,1f);
     Geometry cube1Geo = new Geometry("My Textured Box", cube1Mesh);
     cube1Geo.setLocalTranslation(new Vector3f(-3f,1.1f,0f));
-    Material cube1Mat = new Material(assetManager,
-        "Common/MatDefs/Misc/Unshaded.j3md");
-    Texture cube1Tex = assetManager.loadTexture(
-        "Interface/Logo/Monkey.jpg");
+    Material cube1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    Texture cube1Tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
     cube1Mat.setTexture("ColorMap", cube1Tex);
     cube1Geo.setMaterial(cube1Mat);
     rootNode.attachChild(cube1Geo);
@@ -58,31 +56,27 @@ public class HelloMaterial extends SimpleApplication {
     /** A translucent/transparent texture, similar to a window frame. */
     Box cube2Mesh = new Box( 1f,1f,0.01f);
     Geometry cube2Geo = new Geometry("window frame", cube2Mesh);
-    Material cube2Mat = new Material(assetManager,
-        "Common/MatDefs/Misc/Unshaded.j3md");
-    cube2Mat.setTexture("ColorMap",
-        assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
-    cube2Mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+    Material cube2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    cube2Mat.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+    cube2Mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);  // activate transparency
     cube2Geo.setQueueBucket(Bucket.Transparent);
     cube2Geo.setMaterial(cube2Mat);
     rootNode.attachChild(cube2Geo);
 
-    /** A bumpy rock with a shiny light effect.*/
+    /* A bumpy rock with a shiny light effect. To make bumpy objects you must create a NormalMap. */
     Sphere sphereMesh = new Sphere(32,32, 2f);
     Geometry sphereGeo = new Geometry("Shiny rock", sphereMesh);
     sphereMesh.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres
     TangentBinormalGenerator.generate(sphereMesh);           // for lighting effect
-    Material sphereMat = new Material(assetManager,
-        "Common/MatDefs/Light/Lighting.j3md");
-    sphereMat.setTexture("DiffuseMap",
-        assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
-    sphereMat.setTexture("NormalMap",
-        assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
+    Material sphereMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+    sphereMat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
+    sphereMat.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
     sphereMat.setBoolean("UseMaterialColors",true);
     sphereMat.setColor("Diffuse",ColorRGBA.White);
     sphereMat.setColor("Specular",ColorRGBA.White);
     sphereMat.setFloat("Shininess", 64f);  // [0,128]
     sphereGeo.setMaterial(sphereMat);
+    //sphereGeo.setMaterial((Material) assetManager.loadMaterial("Materials/MyCustomMaterial.j3m"));
     sphereGeo.setLocalTranslation(0,2,-2); // Move it a bit
     sphereGeo.rotate(1.6f, 0, 0);          // Rotate it a bit
     rootNode.attachChild(sphereGeo);
@@ -117,10 +111,8 @@ Typically you want to give objects in your scene textures: It can be rock, grass
     Box cube1Mesh = new Box( 1f,1f,1f);
     Geometry cube1Geo = new Geometry("My Textured Box", cube1Mesh);
     cube1Geo.setLocalTranslation(new Vector3f(-3f,1.1f,0f));
-    Material cube1Mat = new Material(assetManager,
-        "Common/MatDefs/Misc/Unshaded.j3md");
-    Texture cube1Tex = assetManager.loadTexture(
-        "Interface/Logo/Monkey.jpg");
+    Material cube1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    Texture cube1Tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg");
     cube1Mat.setTexture("ColorMap", cube1Tex);
     cube1Geo.setMaterial(cube1Mat);
     rootNode.attachChild(cube1Geo);
@@ -153,12 +145,10 @@ This bucket ensures that the transparent object is drawn on top of objects behin
     /** A translucent/transparent texture, similar to a window frame. */
     Box cube2Mesh = new Box( 1f,1f,0.01f);
     Geometry cube2Geo = new Geometry("window frame", cube2Mesh);
-    Material cube2Mat = new Material(assetManager,
-    "Common/MatDefs/Misc/Unshaded.j3md");
-    cube2Mat.setTexture("ColorMap",
-        assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
-    cube2Mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);  // !
-    cube2Geo.setQueueBucket(Bucket.Transparent);                        // !
+    Material cube2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+    cube2Mat.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+    cube2Mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);  // activate transparency
+    cube2Geo.setQueueBucket(Bucket.Transparent);                        
     cube2Geo.setMaterial(cube2Mat);
     rootNode.attachChild(cube2Geo);
 
@@ -223,9 +213,7 @@ Let's have a look at the part of the code example where you create the shiny bum
 +
 [source,java]
 ----
-
-    Material sphereMat = new Material(assetManager,
-        "Common/MatDefs/Light/Lighting.j3md");
+    Material sphereMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
 ----
 
 ..  Set a standard rocky texture in the `DiffuseMap` layer.
@@ -234,10 +222,7 @@ image::https://github.com/jMonkeyEngine/jmonkeyengine/raw/445f7ed010199d30c484fe
 +
 [source,java]
 ----
-
-    sphereMat.setTexture("DiffuseMap",
-        assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
-
+    sphereMat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
 ----
 
 ..  Set the `NormalMap` layer that contains the bumpiness. The NormalMap was generated for this particular DiffuseMap with a special tool (e.g. Blender).
@@ -246,9 +231,7 @@ image::https://github.com/jMonkeyEngine/jmonkeyengine/raw/445f7ed010199d30c484fe
 +
 [source,java]
 ----
-
-    sphereMat.setTexture("NormalMap",
-        assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
+    sphereMat.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));
 ----
 
 ..  Set the Material's Shininess to a value between 1 and 128. For a rock, a low fuzzy shininess is appropriate. Use material colors to define the shiny Specular color.
@@ -363,9 +346,7 @@ Material My shiny custom material : Common/MatDefs/Light/Lighting.j3md {
 +
 [source,java]
 ----
-sphereGeo.setMaterial((Material) assetManager.loadMaterial(
-    "Materials/MyCustomMaterial.j3m"));
-
+    sphereGeo.setMaterial((Material) assetManager.loadMaterial("Materials/MyCustomMaterial.j3m"));
 ----
 
 .  Run the app. The result is the same.

+ 45 - 52
docs/modules/tutorials/pages/beginner/hello_physics.adoc

@@ -63,16 +63,13 @@ public class HelloPhysics extends SimpleApplication {
   private BulletAppState bulletAppState;
 
   /** Prepare Materials */
-  Material wall_mat;
-  Material stone_mat;
-  Material floor_mat;
+  private Material wall_mat;
+  private Material stone_mat;
+  private Material floor_mat;
 
-  /** Prepare geometries and physical nodes for bricks and cannon balls. */
-  private RigidBodyControl    brick_phy;
+  /** Prepare geometries for bricks and cannonballs. */
   private static final Box    box;
-  private RigidBodyControl    ball_phy;
   private static final Sphere sphere;
-  private RigidBodyControl    floor_phy;
   private static final Box    floor;
 
   /** dimensions used for bricks and wall */
@@ -97,27 +94,31 @@ public class HelloPhysics extends SimpleApplication {
     /** Set up Physics Game */
     bulletAppState = new BulletAppState();
     stateManager.attach(bulletAppState);
-    //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
 
     /** Configure cam to look at scene */
     cam.setLocation(new Vector3f(0, 4f, 6f));
     cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
-    /** Add InputManager action: Left click triggers shooting. */
-    inputManager.addMapping("shoot",
-            new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
-    inputManager.addListener(actionListener, "shoot");
-    /** Initialize the scene, materials, and physics space */
+    /** Initialize the scene, materials, inputs, and physics space */
+    initInputs();
     initMaterials();
     initWall();
     initFloor();
     initCrossHairs();
   }
-
+  
+    /** Add InputManager action: Left click triggers shooting. */
+    private void initInputs() {
+     inputManager.addMapping("shoot", 
+              new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+     inputManager.addListener(actionListener, "shoot");
+  }
+  
   /**
    * Every time the shoot action is triggered, a new cannon ball is produced.
    * The ball is set up to fly from the camera position in the camera direction.
    */
-  private ActionListener actionListener = new ActionListener() {
+  final private ActionListener actionListener = new ActionListener() {
+  @Override
     public void onAction(String name, boolean keyPressed, float tpf) {
       if (name.equals("shoot") && !keyPressed) {
         makeCannonBall();
@@ -154,42 +155,42 @@ public class HelloPhysics extends SimpleApplication {
     floor_geo.setLocalTranslation(0, -0.1f, 0);
     this.rootNode.attachChild(floor_geo);
     /* Make the floor physical with mass 0.0f! */
-    floor_phy = new RigidBodyControl(0.0f);
+    RigidBodyControl floor_phy = new RigidBodyControl(0.0f);
     floor_geo.addControl(floor_phy);
     bulletAppState.getPhysicsSpace().add(floor_phy);
   }
 
   /** This loop builds a wall out of individual bricks. */
   public void initWall() {
-    float startpt = brickLength / 4;
+    float startX = brickLength / 4;
     float height = 0;
     for (int j = 0; j < 15; j++) {
       for (int i = 0; i < 6; i++) {
         Vector3f vt =
-         new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
+         new Vector3f(i * brickLength * 2 + startX, brickHeight + height, 0);
         makeBrick(vt);
       }
-      startpt = -startpt;
+      startX = -startX;
       height += 2 * brickHeight;
     }
   }
 
-  /** This method creates one individual physical brick. */
-  public void makeBrick(Vector3f loc) {
+  /** Creates one physical brick. */
+  private void makeBrick(Vector3f loc) {
     /** Create a brick geometry and attach to scene graph. */
     Geometry brick_geo = new Geometry("brick", box);
     brick_geo.setMaterial(wall_mat);
     rootNode.attachChild(brick_geo);
     /** Position the brick geometry  */
     brick_geo.setLocalTranslation(loc);
-    /** Make brick physical with a mass > 0.0f. */
-    brick_phy = new RigidBodyControl(2f);
+    /* Make brick physical with a mass > 0. */
+    RigidBodyControl brick_phy = new RigidBodyControl(2f);
     /** Add physical brick to physics space. */
     brick_geo.addControl(brick_phy);
     bulletAppState.getPhysicsSpace().add(brick_phy);
   }
 
-  /** This method creates one individual physical cannon ball.
+  /** Creates one physical cannonball.
    * By default, the ball is accelerated and flies
    * from the camera position in the camera direction.*/
    public void makeCannonBall() {
@@ -199,25 +200,25 @@ public class HelloPhysics extends SimpleApplication {
     rootNode.attachChild(ball_geo);
     /** Position the cannon ball  */
     ball_geo.setLocalTranslation(cam.getLocation());
-    /** Make the ball physcial with a mass > 0.0f */
-    ball_phy = new RigidBodyControl(1f);
+    /* Make the ball physical with a mass > 0.0f */
+    RigidBodyControl ball_phy = new RigidBodyControl(1f);
     /** Add physical ball to physics space. */
     ball_geo.addControl(ball_phy);
     bulletAppState.getPhysicsSpace().add(ball_phy);
-    /** Accelerate the physcial ball to shoot it. */
+    /* Accelerate the physical ball to shoot it. */
     ball_phy.setLinearVelocity(cam.getDirection().mult(25));
   }
 
   /** A plus sign used as crosshairs to help the player with aiming.*/
   protected void initCrossHairs() {
-    guiNode.detachAllChildren();
-    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
-    BitmapText ch = new BitmapText(guiFont, false);
+    setDisplayStatView(false);
+    //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+    BitmapText ch = new BitmapText(guiFont);
     ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
     ch.setText("+");        // fake crosshairs :)
     ch.setLocalTranslation( // center
-      settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
-      settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+      settings.getWidth() / 2,
+      settings.getHeight() / 2, 0);
     guiNode.attachChild(ch);
   }
 }
@@ -261,7 +262,7 @@ In this "`shoot`" at the wall example, you use Geometries such as cannon balls a
 [source,java]
 ----
 
-  /** Prepare geometries and physical nodes for bricks and cannon balls. */
+  /** Prepare geometries for bricks and cannonballs. */
   private static final Box    box;
   private static final Sphere sphere;
   private static final Box    floor;
@@ -285,32 +286,23 @@ In this "`shoot`" at the wall example, you use Geometries such as cannon balls a
 
 === RigidBodyControl: Brick
 
-We want to create brick Geometries from those boxes. For each Geometry with physical properties, you create a RigidBodyControl.
-
-[source,java]
-----
-
-  private RigidBodyControl brick_phy;
-
-----
-
-The custom `makeBrick(loc)` methods creates individual bricks at the location `loc`. A brick has the following properties:
+We want to create brick Geometries from those boxes. The custom `makeBrick(loc)` methods creates individual bricks at the location `loc`. A brick has the following properties:
 
 *  It has a visible Geometry `brick_geo` (Box Shape Geometry).
-*  It has physical properties `brick_phy` (RigidBodyControl)
+*  It has physical properties `brick_phy` (RigidBodyControl). Since this is a Geometry with physical properties you create a RigidBodyControl.
 
 [source,java]
 ----
 
-  public void makeBrick(Vector3f loc) {
+  private void makeBrick(Vector3f loc) {
     /** Create a brick geometry and attach to scene graph. */
     Geometry brick_geo = new Geometry("brick", box);
     brick_geo.setMaterial(wall_mat);
     rootNode.attachChild(brick_geo);
     /** Position the brick geometry  */
     brick_geo.setLocalTranslation(loc);
-    /** Make brick physical with a mass > 0.0f. */
-    brick_phy = new RigidBodyControl(2f);
+    /* Make brick physical with a mass > 0. */
+    RigidBodyControl brick_phy = new RigidBodyControl(2f);
     /** Add physical brick to physics space. */
     brick_geo.addControl(brick_phy);
     bulletAppState.getPhysicsSpace().add(brick_phy);
@@ -348,12 +340,12 @@ You notice that the cannon ball is created in the same way, using the custom `ma
     rootNode.attachChild(ball_geo);
     /** Position the cannon ball  */
     ball_geo.setLocalTranslation(cam.getLocation());
-    /** Make the ball physcial with a mass > 0.0f */
-    ball_phy = new RigidBodyControl(1f);
+    /* Make the ball physical with a mass > 0.0f */
+    RigidBodyControl ball_phy = new RigidBodyControl(1f);
     /** Add physical ball to physics space. */
     ball_geo.addControl(ball_phy);
     bulletAppState.getPhysicsSpace().add(ball_phy);
-    /** Accelerate the physcial ball to shoot it. */
+    /* Accelerate the physical ball to shoot it. */
     ball_phy.setLinearVelocity(cam.getDirection().mult(25));
 
 ----
@@ -392,7 +384,7 @@ As before, you write a custom `initFloor()` method that creates a flat box with
     floor_geo.setLocalTranslation(0, -0.1f, 0);
     this.rootNode.attachChild(floor_geo);
     /* Make the floor physical with mass 0.0f! */
-    floor_phy = new RigidBodyControl(0.0f);
+    RigidBodyControl floor_phy = new RigidBodyControl(0.0f);
     floor_geo.addControl(floor_phy);
     bulletAppState.getPhysicsSpace().add(floor_phy);
   }
@@ -444,7 +436,8 @@ You define the actual action of shooting a new cannon ball as follows:
 [source,java]
 ----
 
-    private ActionListener actionListener = new ActionListener() {
+    final private ActionListener actionListener = new ActionListener() {
+    @Override
         public void onAction(String name, boolean keyPressed, float tpf) {
             if (name.equals("shoot") && !keyPressed) {
                 makeCannonBall();

+ 10 - 13
docs/modules/tutorials/pages/beginner/hello_picking.adoc

@@ -78,8 +78,8 @@ public class HelloPicking extends SimpleApplication {
     inputManager.addListener(actionListener, "Shoot");
   }
   /** Defining the "Shoot" action: Determine what was hit and how to respond. */
-  private ActionListener actionListener = new ActionListener() {
-
+  final private ActionListener actionListener = new ActionListener() {
+    @Override
     public void onAction(String name, boolean keyPressed, float tpf) {
       if (name.equals("Shoot") && !keyPressed) {
         // 1. Reset results list.
@@ -87,8 +87,6 @@ public class HelloPicking extends SimpleApplication {
         // 2. Aim the ray from cam loc to cam direction.
         Ray ray = new Ray(cam.getLocation(), cam.getDirection());
         // 3. Collect intersections between Ray and Shootables in results list.
-        // DO NOT check collision with the root node, or else ALL collisions will hit the
-        // skybox! Always make a separate node for objects you want to collide with.
         shootables.collideWith(ray, results);
         // 4. Print the results
         System.out.println("----- Collisions? " + results.size() + "-----");
@@ -116,7 +114,7 @@ public class HelloPicking extends SimpleApplication {
   };
 
   /** A cube object for target practice */
-  protected Geometry makeCube(String name, float x, float y, float z) {
+  private Geometry makeCube(String name, float x, float y, float z) {
     Box box = new Box(1, 1, 1);
     Geometry cube = new Geometry(name, box);
     cube.setLocalTranslation(x, y, z);
@@ -127,7 +125,7 @@ public class HelloPicking extends SimpleApplication {
   }
 
   /** A floor to show that the "shot" can go through several objects. */
-  protected Geometry makeFloor() {
+  private Geometry makeFloor() {
     Box box = new Box(15, .2f, 15);
     Geometry floor = new Geometry("the Floor", box);
     floor.setLocalTranslation(0, -4, -5);
@@ -138,7 +136,7 @@ public class HelloPicking extends SimpleApplication {
   }
 
   /** A red ball that marks the last spot that was "hit" by the "shot". */
-  protected void initMark() {
+  private void initMark() {
     Sphere sphere = new Sphere(30, 30, 0.2f);
     mark = new Geometry("BOOM!", sphere);
     Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
@@ -147,19 +145,18 @@ public class HelloPicking extends SimpleApplication {
   }
 
   /** A centred plus sign to help the player aim. */
-  protected void initCrossHairs() {
+  private void initCrossHairs() {
     setDisplayStatView(false);
     guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
-    BitmapText ch = new BitmapText(guiFont, false);
+    BitmapText ch = new BitmapText(guiFont);
     ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
     ch.setText("+"); // crosshairs
     ch.setLocalTranslation( // center
-      settings.getWidth() / 2 - ch.getLineWidth()/2,
-      settings.getHeight() / 2 + ch.getLineHeight()/2, 0);
+    settings.getWidth() / 2 - ch.getLineWidth()/2, settings.getHeight() / 2 + ch.getLineHeight()/2, 0);
     guiNode.attachChild(ch);
   }
 
-  protected Spatial makeCharacter() {
+  private Spatial makeCharacter() {
     // load a character from jme3test-test-data
     Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
     golem.scale(0.5f);
@@ -274,7 +271,7 @@ Note how it prints a lot of output to show you which hits were registered.
 [source,java]
 ----
   /** Defining the "Shoot" action: Determine what was hit and how to respond. */
-  private ActionListener actionListener = new ActionListener() {
+  final private ActionListener actionListener = new ActionListener() {
     @Override
     public void onAction(String name, boolean keyPressed, float tpf) {
       if (name.equals("Shoot") && !keyPressed) {

+ 19 - 13
docs/modules/tutorials/pages/beginner/hello_terrain.adoc

@@ -22,7 +22,6 @@ package jme3test.helloworld;
 
 import com.jme3.app.SimpleApplication;
 import com.jme3.material.Material;
-import com.jme3.renderer.Camera;
 import com.jme3.terrain.geomipmap.TerrainLodControl;
 import com.jme3.terrain.heightmap.AbstractHeightMap;
 import com.jme3.terrain.geomipmap.TerrainQuad;
@@ -31,16 +30,11 @@ import com.jme3.terrain.heightmap.HillHeightMap; // for exercise 2
 import com.jme3.terrain.heightmap.ImageBasedHeightMap;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
-import java.util.ArrayList;
-import java.util.List;
 
 /** Sample 10 - How to create fast-rendering terrains from heightmaps,
 and how to use texture splatting to make the terrain look good.  */
 public class HelloTerrain extends SimpleApplication {
 
-  private TerrainQuad terrain;
-  Material mat_terrain;
-
   public static void main(String[] args) {
     HelloTerrain app = new HelloTerrain();
     app.start();
@@ -51,7 +45,7 @@ public class HelloTerrain extends SimpleApplication {
     flyCam.setMoveSpeed(50);
 
     /** 1. Create terrain material and load four textures into it. */
-    mat_terrain = new Material(assetManager,
+    Material mat_terrain = new Material(assetManager,
             "Common/MatDefs/Terrain/Terrain.j3md");
 
     /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
@@ -79,11 +73,21 @@ public class HelloTerrain extends SimpleApplication {
     mat_terrain.setTexture("Tex3", rock);
     mat_terrain.setFloat("Tex3Scale", 128f);
 
-    /** 2. Create the height map */
+    /* 2.a Create a custom height map from an image */
     AbstractHeightMap heightmap = null;
     Texture heightMapImage = assetManager.loadTexture(
             "Textures/Terrain/splat/mountains512.png");
     heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
+    
+    /* 2.b Create a random height map */
+//      HillHeightMap heightmap = null;
+//      HillHeightMap.NORMALIZE_RANGE = 100;
+//      try {
+//          heightmap = new HillHeightMap(513, 1000, 50, 100, (byte) 3);
+//      } catch (Exception ex) {
+//          ex.printStackTrace();
+//      }
+
     heightmap.load();
 
     /** 3. We have prepared material and heightmap.
@@ -95,7 +99,7 @@ public class HelloTerrain extends SimpleApplication {
      * 3.5) We supply the prepared heightmap itself.
      */
     int patchSize = 65;
-    terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());
+    TerrainQuad terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());
 
     /** 4. We give the terrain its material, position & scale it, and attach it. */
     terrain.setMaterial(mat_terrain);
@@ -105,6 +109,7 @@ public class HelloTerrain extends SimpleApplication {
 
     /** 5. The LOD (level of detail) depends on were the camera is: */
     TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+    control.setLodCalculator( new DistanceLodCalculator(patchSize, 2.7f) ); // patch size, and a multiplier
     terrain.addControl(control);
   }
 }
@@ -228,8 +233,8 @@ Load four textures into this material. The first one, `Alpha`, is the alphamap t
 
 [source,java]
 ----
-mat_terrain.setTexture("Alpha",
-    assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+mat_terrain.setTexture("Alpha", assetManager.loadTexture(
+          "Textures/Terrain/splat/alphamap.png"));
 ----
 
 The three other textures are the layers that you have previously decided to paint: grass, dirt, and road. You create texture objects and load the three textures as usual. Note how you assign them to their respective texture layers (Tex1, Tex2, and Tex3) inside the Material!
@@ -290,9 +295,9 @@ Here's the code:
 
 [source]
 ----
-terrain = new TerrainQuad(
+TerrainQuad terrain = new TerrainQuad(
   "my terrain",               // name
-  65,                         // tile size
+  patchSize,                  // tile size
   513,                        // block size
   heightmap.getHeightMap());  // heightmap
 
@@ -331,6 +336,7 @@ JME3 includes an optimization that adjusts the level of detail (LOD) of the rend
 ----
 
     TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+    control.setLodCalculator( new DistanceLodCalculator(patchSize, 2.7f) ); // patch size, and a multiplier
     terrain.addControl(control);
 
 ----

ファイルの差分が大きいため隠しています
+ 2333 - 97
package-lock.json


+ 3 - 3
package.json

@@ -23,8 +23,8 @@
   },
   "homepage": "https://github.com/jMonkeyEngine/wiki#readme",
   "dependencies": {
-    "@antora/cli": "^2.3.3",
-    "@antora/site-generator-default": "^2.3.3",
-    "asciidoctor-emoji": "^0.2.2"
+    "@antora/cli": "^3.0.1",
+    "@antora/site-generator": "^3.0.1",
+    "asciidoctor-emoji": "^0.3.4"
   }
 }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません