Browse Source

* refactored terrain tools into individual tools and actions
* added terrain tool undo/redo support

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7718 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

bre..ns 14 years ago
parent
commit
044fe2366b
18 changed files with 1732 additions and 567 deletions
  1. 105 0
      jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/AbstractStatefulGLToolAction.java
  2. 2 2
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/Bundle.properties
  3. 60 378
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorController.java
  4. 62 42
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java
  5. 49 145
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java
  6. 66 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/AbstractTerrainToolAction.java
  7. 65 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/EraseTerrainTool.java
  8. 84 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LevelTerrainTool.java
  9. 159 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LevelTerrainToolAction.java
  10. 65 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LowerTerrainTool.java
  11. 64 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/PaintTerrainTool.java
  12. 260 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/PaintTerrainToolAction.java
  13. 69 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/RaiseTerrainTool.java
  14. 113 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/RaiseTerrainToolAction.java
  15. 65 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/SmoothTerrainTool.java
  16. 159 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/SmoothTerrainToolAction.java
  17. 184 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/TerrainTool.java
  18. 101 0
      jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/ToolUtils.java

+ 105 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/AbstractStatefulGLToolAction.java

@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes.actions;
+
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.core.undoredo.SceneUndoRedoManager;
+import java.util.concurrent.Callable;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import org.openide.loaders.DataObject;
+import org.openide.util.Lookup;
+
+/**
+ * Similar as AbstractToolAction but this one is executed from the GL thread.
+ * This is also allowed to be stateful.
+ * 
+ * @author Brent Owens
+ */
+public abstract class AbstractStatefulGLToolAction {
+
+    protected String name = "*";
+
+    public void actionPerformed(final AbstractSceneExplorerNode rootNode, final DataObject dataObject) {
+        SceneApplication.getApplication().enqueue(new Callable<Void>() {
+
+            public Void call() throws Exception {
+                doActionPerformed(rootNode, dataObject);
+                return null;
+            }
+        });
+    }
+
+    public void doActionPerformed(final AbstractSceneExplorerNode rootNode, final DataObject dataObject) {
+
+        final Object object = doApplyTool(rootNode);
+        if (object!=null) {
+            Lookup lookup = Lookup.getDefault() ;
+            SceneUndoRedoManager manager = lookup.lookup(SceneUndoRedoManager.class);
+
+            AbstractUndoableSceneEdit undoer = new AbstractUndoableSceneEdit() {
+
+                @Override
+                public void sceneUndo() throws CannotUndoException {
+                    doUndoTool(rootNode,object);
+                    setModified(rootNode, dataObject);
+                }
+
+                @Override
+                public void sceneRedo() throws CannotRedoException {
+                    doApplyTool(rootNode);
+                    setModified(rootNode, dataObject);
+                }
+
+            };
+            manager.addEdit(this, undoer);
+            setModified(rootNode, dataObject);
+        }
+
+    }
+
+    protected void setModified(final AbstractSceneExplorerNode rootNode, final DataObject dataObject) {
+        java.awt.EventQueue.invokeLater(new Runnable() {
+
+            public void run() {
+                dataObject.setModified(true);
+                rootNode.refresh(true);
+            }
+        });
+    }
+
+    protected abstract Object doApplyTool(AbstractSceneExplorerNode rootNode);
+
+    protected abstract void doUndoTool(AbstractSceneExplorerNode rootNode, Object undoObject);
+}

+ 2 - 2
jme3-terrain-editor/src/com/jme3/gde/terraineditor/Bundle.properties

@@ -99,9 +99,9 @@ TerrainEditorTopComponent.triPlanarCheckBox.toolTipText=Enable if you have a lot
 TerrainEditorTopComponent.triPlanarCheckBox.text=Tri-planar mapping
 TerrainEditorTopComponent.jButton1.text=Create Skybox
 TerrainEditorTopComponent.levelTerrainButton.text=
+TerrainEditorTopComponent.levelTerrainButton.toolTipText=Level terrain
 
 TerrainEditorTopComponent.toolHint.none=
 TerrainEditorTopComponent.toolHint.default=Switch between camera and tool controls by holding down SHIFT
-TerrainEditorTopComponent.toolHint.smooth=
 TerrainEditorTopComponent.toolHint.level=Right click to set desired height value, left click to adjust height to that desired value.
-TerrainEditorTopComponent.levelTerrainButton.toolTipText=Level terrain
+

+ 60 - 378
jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorController.java

@@ -38,9 +38,11 @@ import com.jme3.bounding.BoundingBox;
 import com.jme3.gde.core.assets.AssetDataObject;
 import com.jme3.gde.core.assets.ProjectAssetManager;
 import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.core.undoredo.SceneUndoRedoManager;
 import com.jme3.material.MatParam;
-import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
 import com.jme3.renderer.Camera;
@@ -50,25 +52,25 @@ import com.jme3.terrain.ProgressMonitor;
 import com.jme3.terrain.Terrain;
 import com.jme3.terrain.geomipmap.TerrainLodControl;
 import com.jme3.terrain.geomipmap.TerrainQuad;
-import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
 import com.jme3.util.SkyFactory;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
-import java.util.logging.Logger;
 import javax.imageio.ImageIO;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
 import jme3tools.converters.ImageToAwt;
 import org.openide.cookies.SaveCookie;
 import org.openide.filesystems.FileObject;
 import org.openide.loaders.DataObject;
 import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
 
 /**
  * Modifies the actual terrain in the scene.
@@ -89,10 +91,6 @@ public class TerrainEditorController {
     private final int BASE_TEXTURE_COUNT = NUM_ALPHA_TEXTURES; // add any others here, like a global specular map
     protected final int MAX_TEXTURE_LAYERS = 7-BASE_TEXTURE_COUNT; // 16 max, minus the ones we are reserving
 
-    // level terrain settings
-    private Vector3f levelTerrainDesiredHeight;
-    private float levelTerrainSnapThreshold = 0.01f;
-
     
 
     protected SaveCookie terrainSaveCookie = new SaveCookie() {
@@ -133,7 +131,7 @@ public class TerrainEditorController {
         currentFileObject.setModified(state);
     }
 
-    protected Node getTerrain(Spatial root) {
+    public Node getTerrain(Spatial root) {
         if (terrainNode != null)
             return terrainNode;
 
@@ -166,7 +164,7 @@ public class TerrainEditorController {
      * @param radius of the tool, terrain in this radius will be affected
      * @param heightFactor the amount to adjust the height by
      */
-    protected void doModifyTerrainHeight(Vector3f worldLoc, float radius, float heightFactor) {
+    public void doModifyTerrainHeight(Vector3f worldLoc, float radius, float heightFactor) {
 
         Terrain terrain = (Terrain) getTerrain(null);
         if (terrain == null)
@@ -180,6 +178,9 @@ public class TerrainEditorController {
         float xStepAmount = ((Node)terrain).getLocalScale().x;
         float zStepAmount = ((Node)terrain).getLocalScale().z;
 
+        List<Vector2f> locs = new ArrayList<Vector2f>();
+        List<Float> heights = new ArrayList<Float>();
+
         for (int z=-radiusStepsZ; z<radiusStepsZ; z++) {
             for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
 
@@ -191,11 +192,15 @@ public class TerrainEditorController {
                     // adjust height based on radius of the tool
                     float h = calculateHeight(radius, heightFactor, locX-worldLoc.x, locZ-worldLoc.z);
                     // increase the height
-                    terrain.adjustHeight(new Vector2f(locX, locZ), h);
+                    locs.add(new Vector2f(locX, locZ));
+                    heights.add(h);
                 }
             }
         }
 
+        // do the actual height adjustment
+        terrain.adjustHeight(locs, heights);
+
         ((Node)terrain).updateModelBound(); // or else we won't collide with it where we just edited
         
     }
@@ -676,13 +681,14 @@ public class TerrainEditorController {
                                 final int patchSize,
                                 final int alphaTextureSize,
                                 final float[] heightmapData,
-                                final String sceneName) throws IOException
+                                final String sceneName,
+                                final JmeSpatial jmeNodeParent) throws IOException
     {
         try {
             Terrain terrain =
             SceneApplication.getApplication().enqueue(new Callable<Terrain>() {
                 public Terrain call() throws Exception {
-                    return doCreateTerrain(parent, totalSize, patchSize, alphaTextureSize, heightmapData, sceneName);
+                    return doCreateTerrain(parent, totalSize, patchSize, alphaTextureSize, heightmapData, sceneName, jmeNodeParent);
                 }
             }).get();
             return terrain;
@@ -700,7 +706,8 @@ public class TerrainEditorController {
                                     int patchSize,
                                     int alphaTextureSize,
                                     float[] heightmapData,
-                                    String sceneName) throws IOException
+                                    String sceneName,
+                                    JmeSpatial jmeNodeParent) throws IOException
     {
         AssetManager manager = SceneApplication.getApplication().getAssetManager();
 
@@ -750,18 +757,52 @@ public class TerrainEditorController {
 
         // add the lod control
         List<Camera> cameras = new ArrayList<Camera>();
-		cameras.add(SceneApplication.getApplication().getCamera());
+	cameras.add(SceneApplication.getApplication().getCamera());
         TerrainLodControl control = new TerrainLodControl(terrain, cameras);
-		//terrain.addControl(control); // removing this until we figure out a way to have it get the cameras when saved/loaded
+	//terrain.addControl(control); // removing this until we figure out a way to have it get the cameras when saved/loaded
 
         parent.attachChild(terrain);
 
         setNeedsSave(true);
 
+        addSpatialUndo(parent, terrain, jmeNodeParent);
+        
         return terrain;
     }
 
-    
+    private void addSpatialUndo(final Node undoParent, final Spatial undoSpatial, final AbstractSceneExplorerNode parentNode) {
+        //add undo
+        if (undoParent != null && undoSpatial != null) {
+            Lookup.getDefault().lookup(SceneUndoRedoManager.class).addEdit(this, new AbstractUndoableSceneEdit() {
+
+                @Override
+                public void sceneUndo() throws CannotUndoException {
+                    //undo stuff here
+                    undoSpatial.removeFromParent();
+                }
+
+                @Override
+                public void sceneRedo() throws CannotRedoException {
+                    //redo stuff here
+                    undoParent.attachChild(undoSpatial);
+                }
+
+                @Override
+                public void awtRedo() {
+                    if (parentNode != null) {
+                        parentNode.refresh(true);
+                    }
+                }
+
+                @Override
+                public void awtUndo() {
+                    if (parentNode != null) {
+                        parentNode.refresh(true);
+                    }
+                }
+            });
+        }
+    }
 
     /**
      * Save the terrain's alpha maps to disk, in the Textures/terrain-alpha/ directory
@@ -894,181 +935,7 @@ public class TerrainEditorController {
         }
         return false;
     }
-
-    /**
-     * Paint the texture at the specified location
-     * @param selectedTextureIndex the texture to paint
-     * @param markerLocation the location
-     * @param toolRadius radius of the brush tool
-     * @param toolWeight brush weight [0,1]
-     */
-    public void doPaintTexture(int selectedTextureIndex, Vector3f markerLocation, float toolRadius, float toolWeight) {
-        if (selectedTextureIndex < 0 || markerLocation == null)
-            return;
-
-        Terrain terrain = (Terrain) getTerrain(null);
-        if (terrain == null)
-            return;
-
-        
-        setNeedsSave(true);
-        
-        Texture tex = doGetAlphaTextureFromDiffuse(terrain, selectedTextureIndex);
-        Image image = tex.getImage();
-
-        Vector2f UV = terrain.getPointPercentagePosition(markerLocation.x, markerLocation.z);
-
-        // get the radius of the brush in pixel-percent
-        float brushSize = toolRadius/((TerrainQuad)terrain).getTotalSize();
-        int texIndex = selectedTextureIndex - ((selectedTextureIndex/4)*4); // selectedTextureIndex/4 is an int floor, do not simplify the equation
-        boolean erase = toolWeight<0;
-        if (erase)
-            toolWeight *= -1;
-
-        doPaintAction(texIndex, image, UV, true, brushSize, erase, toolWeight);
-
-        tex.getImage().setUpdateNeeded();
-    }
-
-    /**
-	 * Goes through each pixel in the image. At each pixel it looks to see if the UV mouse coordinate is within the
- 	 * of the brush. If it is in the brush radius, it gets the existing color from that pixel so it can add/subtract to/from it.
- 	 * Essentially it does a radius check and adds in a fade value. It does this to the color value returned by the
- 	 * first pixel color query.
-	 * Next it sets the color of that pixel. If it was within the radius, the color will change. If it was outside
-	 * the radius, then nothing will change, the color will be the same; but it will set it nonetheless. Not efficient.
-	 *
-	 * If the mouse is being dragged with the button down, then the dragged value should be set to true. This will reduce
-	 * the intensity of the brush to 10% of what it should be per spray. Otherwise it goes to 100% opacity within a few pixels.
-	 * This makes it work a little more realistically.
-	 *
-	 * @param image to manipulate
-	 * @param uv the world x,z coordinate
-	 * @param dragged true if the mouse button is down and it is being dragged, use to reduce brush intensity
-	 * @param radius in percentage so it can be translated to the image dimensions
-	 * @param erase true if the tool should remove the paint instead of add it
-	 * @param fadeFalloff the percentage of the radius when the paint begins to start fading
-	 */
-    protected void doPaintAction(int texIndex, Image image, Vector2f uv, boolean dragged, float radius, boolean erase, float fadeFalloff){
-        Vector2f texuv = new Vector2f();
-        ColorRGBA color = ColorRGBA.Black;
-        
-        float width = image.getWidth();
-        float height = image.getHeight();
-
-        int minx = (int) (uv.x*width - radius*width); // convert percents to pixels to limit how much we iterate
-        int maxx = (int) (uv.x*width + radius*width);
-        int miny = (int) (uv.y*height - radius*height);
-        int maxy = (int) (uv.y*height + radius*height);
-
-        Logger.getLogger(TerrainEditorTopComponent.class.getName()).info("Paint "+uv );
-        float radiusSquared = radius*radius;
-        float radiusFalloff = radius*fadeFalloff;
-        // go through each pixel, in the radius of the tool, in the image
-        for (int y = miny; y < maxy; y++){
-            for (int x = minx; x < maxx; x++){
-                
-                texuv.set((float)x / width, (float)y / height);// gets the position in percentage so it can compare with the mouse UV coordinate
-
-                float dist = texuv.distanceSquared(uv);
-                if (dist < radiusSquared ) { // if the pixel is within the distance of the radius, set a color (distance times intensity)
-                	manipulatePixel(image, x, y, color, false); // gets the color at that location (false means don't write to the buffer)
-
-                	// calculate the fade falloff intensity
-                	float intensity = 0.1f;
-                	if (dist > radiusFalloff) {
-                		float dr = radius - radiusFalloff; // falloff to radius length
-                		float d2 = dist - radiusFalloff; // dist minus falloff
-                		d2 = d2/dr; // dist percentage of falloff length
-                		intensity = 1-d2; // fade out more the farther away it is
-                	}
-
-                	//if (dragged)
-                	//	intensity = intensity*0.1f; // magical divide it by 10 to reduce its intensity when mouse is dragged
-
-                	if (erase) {
-                        switch (texIndex) {
-                            case 0:
-                                color.r -= intensity; break;
-                            case 1:
-                                color.g -= intensity; break;
-                            case 2:
-                                color.b -= intensity; break;
-                            case 3:
-                                color.a -= intensity; break;
-                        }
-                	} else {
-	                    switch (texIndex) {
-                            case 0:
-                                color.r += intensity; break;
-                            case 1:
-                                color.g += intensity; break;
-                            case 2:
-                                color.b += intensity; break;
-                            case 3:
-                                color.a += intensity; break;
-                        }
-                	}
-                    color.clamp();
-
-                    manipulatePixel(image, x, y, color, true); // set the new color
-                }
-
-            }
-        }
-
-        image.getData(0).rewind();
-    }
-
-    /**
-     * We are only using RGBA8 images for alpha textures right now.
-     * @param image to get/set the color on
-     * @param x location
-     * @param y location
-     * @param color color to get/set
-     * @param write to write the color or not
-     */
-    protected void manipulatePixel(Image image, int x, int y, ColorRGBA color, boolean write){
-        ByteBuffer buf = image.getData(0);
-        int width = image.getWidth();
-
-        int position = (y * width + x) * 4;
-
-        if ( position> buf.capacity()-1 || position<0 )
-            return;
-        
-        if (write) {
-            switch (image.getFormat()){
-                case RGBA8:
-                    buf.position( position );
-                    buf.put(float2byte(color.r))
-                       .put(float2byte(color.g))
-                       .put(float2byte(color.b))
-                       .put(float2byte(color.a));
-                    return;
-                default:
-                    throw new UnsupportedOperationException("Image format: "+image.getFormat());
-            }
-        } else {
-            switch (image.getFormat()){
-                case RGBA8:
-                    buf.position( position );
-                    color.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()));
-                    return;
-                default:
-                    throw new UnsupportedOperationException("Image format: "+image.getFormat());
-            }
-        }
-        
-    }
-
-    private float byte2float(byte b){
-        return ((float)(b & 0xFF)) / 255f;
-    }
-
-    private byte float2byte(float f){
-        return (byte) (f * 255f);
-    }
+    
 
     /**
      * How many textures are currently being used.
@@ -1182,191 +1049,6 @@ public class TerrainEditorController {
         setNeedsSave(true);
     }
 
-    /**
-     * Level the terrain to the desired height.
-     * It will pull down or raise terrain towards the desired height, still
-     * using the radius of the tool and the weight. There are some slight rounding
-     * errors that are coorected with float epsilon testing.
-     * @param markerLocation
-     * @param heightToolRadius
-     * @param heightAmount
-     */
-    protected void doLevelTerrain(Vector3f worldLoc, float radius, float heightWeight) {
-        if (levelTerrainDesiredHeight == null)
-            return;
-
-        float desiredHeight = levelTerrainDesiredHeight.y;
-
-        Terrain terrain = (Terrain) getTerrain(null);
-        if (terrain == null)
-            return;
-
-        setNeedsSave(true);
-
-        int radiusStepsX = (int)(radius / ((Node)terrain).getLocalScale().x);
-        int radiusStepsZ = (int)(radius / ((Node)terrain).getLocalScale().z);
-
-        float xStepAmount = ((Node)terrain).getLocalScale().x;
-        float zStepAmount = ((Node)terrain).getLocalScale().z;
-
-        List<Vector2f> locs = new ArrayList<Vector2f>();
-        List<Float> heights = new ArrayList<Float>();
-
-        for (int z=-radiusStepsZ; z<radiusStepsZ; z++) {
-            for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
-
-                float locX = worldLoc.x + (x*xStepAmount);
-                float locZ = worldLoc.z + (z*zStepAmount);
-                
-                // see if it is in the radius of the tool
-                if (isInRadius(locX-worldLoc.x,locZ-worldLoc.z,radius)) {
-
-                    Vector2f terrainLoc = new Vector2f(locX, locZ);
-                    // adjust height based on radius of the tool
-                    float terrainHeightAtLoc = terrain.getHeightmapHeight(terrainLoc)*terrain.getSpatial().getWorldScale().y;
-                    float radiusWeight = calculateRadiusPercent(radius, locX-worldLoc.x, locZ-worldLoc.z);
-
-                    float epsilon = 0.1f*heightWeight; // rounding error for snapping
-                    
-                    float adj = 0;
-                    if (terrainHeightAtLoc < desiredHeight)
-                        adj = 1;
-                    else if (terrainHeightAtLoc > desiredHeight)
-                        adj = -1;
-                            
-                    adj *= radiusWeight * heightWeight;
-
-                    // test if adjusting too far and then cap it
-                    if (adj > 0 && floatGreaterThan((terrainHeightAtLoc + adj), desiredHeight, epsilon))
-                        adj = desiredHeight - terrainHeightAtLoc;
-                    else if (adj < 0 && floatLessThan((terrainHeightAtLoc + adj), desiredHeight, epsilon))
-                        adj = terrainHeightAtLoc - desiredHeight;
-  
-                    if (!floatEquals(adj, 0, 0.001f)) {
-                        locs.add(terrainLoc);
-                        heights.add(adj);
-                    }
-                    
-                }
-            }
-        }
-        // do the actual height adjustment
-        terrain.adjustHeight(locs, heights);
-        
-        ((Node)terrain).updateModelBound(); // or else we won't collide with it where we just edited
-
-    }
-
-    private int compareFloat(float a, float b, float epsilon) {
-        if (floatEquals(a, b, epsilon))
-            return 0;
-        else if (floatLessThan(a, b, epsilon))
-            return -1;
-        else
-            return 1;
-    }
-
-    private boolean floatEquals(float a, float b, float epsilon) {
-        return a == b ? true : Math.abs(a - b) < epsilon;
-    }
-
-    private boolean floatLessThan(float a, float b, float epsilon) {
-        return b - a > epsilon;
-    }
-
-    private boolean floatGreaterThan(float a, float b, float epsilon) {
-        return a - b > epsilon;
-    }
-
-    protected void doSetLevelTerrainDesiredHeight(Vector3f point) {
-        this.levelTerrainDesiredHeight = point;
-    }
-
-    public Vector3f doGetLevelTerrainDesiredHeight() {
-        return levelTerrainDesiredHeight;
-    }
-
-    /**
-     * Smooth bumps in the terrain by averaging the height in the tool radius.
-     * The smoothAmount affects how many neighbour points are averaged, The smaller
-     * the value, then only the smaller bumps will disappear. A large value will
-     * smooth larger hills
-     * @param markerLocation
-     * @param heightToolRadius
-     * @param smoothAmount
-     */
-    protected void doSmoothTerrain(Vector3f worldLoc, float radius, float weight) {
-        Terrain terrain = (Terrain) getTerrain(null);
-        if (terrain == null)
-            return;
-
-        setNeedsSave(true);
-
-        int radiusStepsX = (int)(radius / ((Node)terrain).getLocalScale().x);
-        int radiusStepsZ = (int)(radius / ((Node)terrain).getLocalScale().z);
-
-        float xStepAmount = ((Node)terrain).getLocalScale().x;
-        float zStepAmount = ((Node)terrain).getLocalScale().z;
-
-        List<Vector2f> locs = new ArrayList<Vector2f>();
-        List<Float> heights = new ArrayList<Float>();
-
-        for (int z=-radiusStepsZ; z<radiusStepsZ; z++) {
-            for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
-
-                float locX = worldLoc.x + (x*xStepAmount);
-                float locZ = worldLoc.z + (z*zStepAmount);
-
-                // see if it is in the radius of the tool
-                if (isInRadius(locX-worldLoc.x,locZ-worldLoc.z,radius)) {
-
-                    Vector2f terrainLoc = new Vector2f(locX, locZ);
-                    // adjust height based on radius of the tool
-                    float center = terrain.getHeightmapHeight(terrainLoc);
-                    float left = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x-1, terrainLoc.y));
-                    float right = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x+1, terrainLoc.y));
-                    float up = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x, terrainLoc.y+1));
-                    float down = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x, terrainLoc.y-1));
-                    int count = 1;
-                    float amount = center;
-                    if (left != Float.NaN) {
-                        amount += left;
-                        count++;
-                    }
-                    if (right != Float.NaN) {
-                        amount += right;
-                        count++;
-                    }
-                    if (up != Float.NaN) {
-                        amount += up;
-                        count++;
-                    }
-                    if (down != Float.NaN) {
-                        amount += down;
-                        count++;
-                    }
-
-                    amount /= count; // take average
-
-                    // weigh it
-                    float diff = amount-center;
-                    diff *= weight;
-                    amount = center+diff;
-                        
-                    locs.add(terrainLoc);
-                    heights.add(amount);
-                }
-            }
-        }
-
-        // do the actual height adjustment
-        terrain.setHeight(locs, heights);
-
-        ((Node)terrain).updateModelBound(); // or else we won't collide with it where we just edited
-
-    }
-
-
-
+    
 
 }

+ 62 - 42
jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java

@@ -42,8 +42,17 @@ import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeTerrainQuad;
 import com.jme3.gde.core.sceneexplorer.nodes.NodeUtility;
 import com.jme3.gde.core.sceneexplorer.nodes.properties.TexturePropertyEditor;
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
+import com.jme3.gde.core.undoredo.SceneUndoRedoManager;
 import com.jme3.gde.core.util.DataObjectSaveNode;
 import com.jme3.gde.terraineditor.sky.SkyboxWizardAction;
+import com.jme3.gde.terraineditor.tools.EraseTerrainTool;
+import com.jme3.gde.terraineditor.tools.LevelTerrainTool;
+import com.jme3.gde.terraineditor.tools.LowerTerrainTool;
+import com.jme3.gde.terraineditor.tools.PaintTerrainTool;
+import com.jme3.gde.terraineditor.tools.RaiseTerrainTool;
+import com.jme3.gde.terraineditor.tools.SmoothTerrainTool;
+import com.jme3.gde.terraineditor.tools.TerrainTool;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
@@ -73,6 +82,8 @@ import javax.swing.filechooser.FileSystemView;
 import javax.swing.table.DefaultTableModel;
 import javax.swing.table.TableCellEditor;
 import javax.swing.table.TableCellRenderer;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
 import jme3tools.converters.ImageToAwt;
 import org.netbeans.api.progress.ProgressHandle;
 import org.netbeans.api.progress.ProgressHandleFactory;
@@ -88,9 +99,12 @@ import org.openide.DialogDisplayer;
 import org.openide.NotifyDescriptor;
 import org.openide.NotifyDescriptor.Confirmation;
 import org.openide.WizardDescriptor;
+import org.openide.explorer.ExplorerManager;
+import org.openide.explorer.ExplorerUtils;
 import org.openide.nodes.NodeListener;
 import org.openide.util.Exceptions;
 import org.openide.util.HelpCtx;
+import org.openide.util.Lookup;
 import org.openide.util.Lookup.Result;
 import org.openide.util.LookupEvent;
 import org.openide.util.LookupListener;
@@ -120,14 +134,13 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
     private TerrainNodeListener terrainDeletedNodeListener;
     
 
-    public enum TerrainEditButton {none, raiseTerrain, lowerTerrain, smoothTerrain, levelTerrain, paintTerrain, eraseTerrain};
-
     private HelpCtx ctx = new HelpCtx("sdk.terrain_editor");
 
     public TerrainEditorTopComponent() {
         initComponents();
         setName(NbBundle.getMessage(TerrainEditorTopComponent.class, "CTL_TerrainEditorTopComponent"));
         setToolTipText(NbBundle.getMessage(TerrainEditorTopComponent.class, "HINT_TerrainEditorTopComponent"));
+        associateLookup(ExplorerUtils.createLookup(new ExplorerManager(), getActionMap()));
         setIcon(ImageUtilities.loadImage(ICON_PATH, true));
         result = Utilities.actionsGlobalContext().lookupResult(JmeSpatial.class);
     }
@@ -176,14 +189,10 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
     private void setHintText(String text) {
         hintTextArea.setText(text);
     }
-
-    private void setHintText(TerrainEditButton terrainEditButton) {
-        if (TerrainEditButton.none.equals(terrainEditButton) )
-            hintTextArea.setText(org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, "TerrainEditorTopComponent.toolHint.none"));
-        else if (TerrainEditButton.levelTerrain.equals(terrainEditButton) )
-            hintTextArea.setText(org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, "TerrainEditorTopComponent.toolHint.level"));
-        else if (TerrainEditButton.smoothTerrain.equals(terrainEditButton) )
-            hintTextArea.setText(org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, "TerrainEditorTopComponent.toolHint.smooth"));
+    
+    private void setHintText(TerrainTool tool) {
+        if (tool != null)
+            hintTextArea.setText(org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, tool.getToolHintTextKey() ));
         else
             hintTextArea.setText(org.openide.util.NbBundle.getMessage(TerrainEditorTopComponent.class, "TerrainEditorTopComponent.toolHint.default"));
     }
@@ -575,21 +584,23 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
         //toolController.setShowGrid(true);
 
         if (raiseTerrainButton.isSelected()) {
-            toolController.setTerrainEditButtonState(TerrainEditButton.raiseTerrain);
-            setHintText(TerrainEditButton.raiseTerrain);
+            RaiseTerrainTool tool = new RaiseTerrainTool();
+            toolController.setTerrainEditButtonState(tool);
+            setHintText(tool);
         } else {
-            toolController.setTerrainEditButtonState(TerrainEditButton.none);
-            setHintText(TerrainEditButton.none);
+            toolController.setTerrainEditButtonState(null);
+            setHintText((TerrainTool)null);
         }
     }//GEN-LAST:event_raiseTerrainButtonActionPerformed
 
     private void lowerTerrainButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_lowerTerrainButtonActionPerformed
         if (lowerTerrainButton.isSelected()) {
-            toolController.setTerrainEditButtonState(TerrainEditButton.lowerTerrain);
-            setHintText(TerrainEditButton.lowerTerrain);
+            LowerTerrainTool tool = new LowerTerrainTool();
+            toolController.setTerrainEditButtonState(tool);
+            setHintText(tool);
         } else {
-            toolController.setTerrainEditButtonState(TerrainEditButton.none);
-            setHintText(TerrainEditButton.none);
+            toolController.setTerrainEditButtonState(null);
+            setHintText((TerrainTool)null);
         }
     }//GEN-LAST:event_lowerTerrainButtonActionPerformed
 
@@ -603,11 +614,12 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
 
     private void paintButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_paintButtonActionPerformed
         if (paintButton.isSelected()) {
-            toolController.setTerrainEditButtonState(TerrainEditButton.paintTerrain);
-            setHintText(TerrainEditButton.paintTerrain);
+            PaintTerrainTool tool = new PaintTerrainTool();
+            toolController.setTerrainEditButtonState(tool);
+            setHintText(tool);
         } else {
-            toolController.setTerrainEditButtonState(TerrainEditButton.none);
-            setHintText(TerrainEditButton.none);
+            toolController.setTerrainEditButtonState(null);
+            setHintText((TerrainTool)null);
         }
     }//GEN-LAST:event_paintButtonActionPerformed
 
@@ -639,11 +651,12 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
 
     private void eraseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_eraseButtonActionPerformed
         if (eraseButton.isSelected()) {
-            toolController.setTerrainEditButtonState(TerrainEditButton.eraseTerrain);
-            setHintText(TerrainEditButton.eraseTerrain);
+            EraseTerrainTool tool = new EraseTerrainTool();
+            toolController.setTerrainEditButtonState(tool);
+            setHintText(tool);
         } else {
-            toolController.setTerrainEditButtonState(TerrainEditButton.none);
-            setHintText(TerrainEditButton.none);
+            toolController.setTerrainEditButtonState(null);
+            setHintText((TerrainTool)null);
         }
     }//GEN-LAST:event_eraseButtonActionPerformed
 
@@ -660,31 +673,33 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
 
     private void levelTerrainButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_levelTerrainButtonActionPerformed
         if (levelTerrainButton.isSelected()) {
-            toolController.setTerrainEditButtonState(TerrainEditButton.levelTerrain);
-            setHintText(TerrainEditButton.levelTerrain);
+            LevelTerrainTool tool = new LevelTerrainTool();
+            toolController.setTerrainEditButtonState(tool);
+            setHintText(tool);
         } else {
-            toolController.setTerrainEditButtonState(TerrainEditButton.none);
-            setHintText(TerrainEditButton.none);
+            toolController.setTerrainEditButtonState(null);
+            setHintText((TerrainTool)null);
         }
     }//GEN-LAST:event_levelTerrainButtonActionPerformed
 
     private void radiusSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_radiusSliderStateChanged
         if (toolController != null)
-            toolController.setHeightToolRadius(radiusSlider.getValue());
+            toolController.setHeightToolRadius((float)radiusSlider.getValue() / (float)radiusSlider.getMaximum());
     }//GEN-LAST:event_radiusSliderStateChanged
 
     private void heightSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_heightSliderStateChanged
         if (toolController != null)
-            toolController.setHeightToolHeight(heightSlider.getValue()); // should always be values upto and over 100, because it will be divided by 100
+            toolController.setHeightToolHeight((float)heightSlider.getValue() / (float)heightSlider.getMaximum());
     }//GEN-LAST:event_heightSliderStateChanged
 
     private void smoothTerrainButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_smoothTerrainButtonActionPerformed
         if (smoothTerrainButton.isSelected()) {
-            toolController.setTerrainEditButtonState(TerrainEditButton.smoothTerrain);
-            setHintText(TerrainEditButton.smoothTerrain);
+            SmoothTerrainTool tool = new SmoothTerrainTool();
+            toolController.setTerrainEditButtonState(tool);
+            setHintText(tool);
         } else {
-            toolController.setTerrainEditButtonState(TerrainEditButton.none);
-            setHintText(TerrainEditButton.none);
+            toolController.setTerrainEditButtonState(null);
+            setHintText((TerrainTool)null);
         }
     }//GEN-LAST:event_smoothTerrainButtonActionPerformed
 
@@ -790,7 +805,8 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
                                                     patchSize,
                                                     alphaTextureSize,
                                                     heightmapData,
-                                                    split2[0]);
+                                                    split2[0],
+                                                    selectedSpat);
         } catch (IOException ex) {
             Exceptions.printStackTrace(ex);
         }
@@ -808,7 +824,8 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
 
         refreshSelected();
         
-        createTerrainButton.setEnabled(false); // only let the user add one terrain
+        //createTerrainButton.setEnabled(false); // only let the user add one terrain
+        
     }
 
     public void generateSkybox(WizardDescriptor wiz) {
@@ -986,6 +1003,8 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
 
         addSaveNode(jmeNode);
         
+        SceneUndoRedoManager m = Lookup.getDefault().lookup(SceneUndoRedoManager.class);//TODO remove this line
+            
         Logger.getLogger(TerrainEditorTopComponent.class.getName()).finer("Terrain openScene "+file.getName());
         
         if (editorController != null) {
@@ -1044,14 +1063,15 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce
             toolController.setCameraController(camController);
             editorController.setToolController(toolController);
 
-            toolController.setHeightToolRadius(radiusSlider.getValue());
-            toolController.setHeightToolHeight(heightSlider.getValue()); // should always be values upto and over 100, because it will be divided by 100
+            toolController.setHeightToolRadius((float)radiusSlider.getValue()/(float)radiusSlider.getMaximum());
+            toolController.setHeightToolHeight((float)heightSlider.getValue()/(float)heightSlider.getMaximum());
 
             java.awt.EventQueue.invokeLater(new Runnable() {
                 public void run() {
                     reinitTextureTable(); // update the UI
-                    if (editorController.getTerrain(null) != null)
-                        createTerrainButton.setEnabled(false); // only let the user add one terrain
+                    if (editorController.getTerrain(null) != null) {
+                        //createTerrainButton.setEnabled(false); // only let the user add one terrain
+                    }
                 }
             });
             //editorController.getAlphaSaveDataObject(this);

+ 49 - 145
jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java

@@ -37,18 +37,15 @@ import com.jme3.gde.core.scene.SceneApplication;
 import com.jme3.gde.core.scene.controller.SceneToolController;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
 import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
-import com.jme3.gde.terraineditor.TerrainEditorTopComponent.TerrainEditButton;
+import com.jme3.gde.terraineditor.tools.TerrainTool;
 import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
-import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.shape.Sphere;
-import com.jme3.util.IntMap.Entry;
 import java.util.concurrent.Callable;
-import java.util.logging.Logger;
 
 /**
  * The controller for the terrain modification tools. It will in turn interact
@@ -61,24 +58,14 @@ import java.util.logging.Logger;
 public class TerrainToolController extends SceneToolController {
 
     private JmeSpatial jmeRootNode;
-    private TerrainEditButton currentEditButtonState = TerrainEditButton.none;
-    private Geometry marker;
-    private Geometry markerSmall;
+    private TerrainTool terrainTool;
     private TerrainEditorController editorController;
     private TerrainCameraController cameraController;
-    private float heightToolRadius;
-    private float heightAmount;
-    private float levelAmount;
-    private float smoothAmount;
-    private float paintAmount;
+    
+    private float toolRadius;
+    private float toolWeight;
     private int selectedTextureIndex = -1;
-
-    private final ColorRGBA terrainHeightColor = ColorRGBA.Green;
-    private final ColorRGBA terrainPaintColor = ColorRGBA.Yellow;
-    private final ColorRGBA terrainEraseColor = ColorRGBA.Cyan;
-    private final ColorRGBA terrainSmoothColor = ColorRGBA.Brown;
-    private final ColorRGBA terrainLevelColor = ColorRGBA.Orange;
-    private final ColorRGBA terrainLevelMarkColor = ColorRGBA.Red;
+    
 
     public TerrainToolController(Node toolsNode, AssetManager manager, JmeNode rootNode) {
         super(toolsNode, manager);
@@ -94,17 +81,19 @@ public class TerrainToolController extends SceneToolController {
     }
 
     /**
-     * assumes [0,200]
+     * @param heightToolHeight percent of the slider
      */
-    public void setHeightToolHeight(float heightToolHeight) {
-        this.heightAmount = heightToolHeight/100f;
-        this.levelAmount = heightToolHeight/200f;
-        this.smoothAmount = heightToolHeight/200f;
-        this.paintAmount = heightToolHeight/200f;
+    public void setHeightToolHeight(float weight) {
+        this.toolWeight = weight;
+        if (terrainTool != null)
+            terrainTool.weightChanged(weight);
     }
 
+    /**
+     * @param radius  percent of the slider
+     */
     public void setHeightToolRadius(float radius) {
-        this.heightToolRadius = radius;
+        this.toolRadius = radius;
         setEditToolSize(radius);
     }
 
@@ -112,72 +101,12 @@ public class TerrainToolController extends SceneToolController {
         this.selectedTextureIndex = index;
     }
 
-
-    @Override
-    protected void initTools() {
-        super.initTools();
-
-        marker = new Geometry("edit marker");
-        Mesh m = new Sphere(8, 8, 3);
-        marker.setMesh(m);
-        Material mat = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
-        mat.getAdditionalRenderState().setWireframe(true);
-        mat.setColor("Color", ColorRGBA.LightGray);
-        marker.setMaterial(mat);
-        marker.setLocalTranslation(0,0,0);
-
-        markerSmall = new Geometry("edit marker");
-        Mesh m2 = new Sphere(8, 8, 0.5f);
-        markerSmall.setMesh(m2);
-        Material mat2 = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
-        mat2.getAdditionalRenderState().setWireframe(false);
-        mat2.setColor("Color", ColorRGBA.Red);
-        markerSmall.setMaterial(mat2);
-        markerSmall.setLocalTranslation(0,0,0);
-    }
-
-    protected void setMarkerRadius(float radius) {
-        //((Sphere)marker.getMesh()).set;
-    }
-
-    public TerrainEditButton getCurrentEditButtonState() {
-        return currentEditButtonState;
-    }
-
-    public void setTerrainEditButtonState(final TerrainEditButton state) {
-        Logger.getLogger(this.getClass().getName()).info("Edit button state set: "+state);
-        currentEditButtonState = state;
-        if (state == TerrainEditButton.none) {
-            hideEditTool();
-        } else if (state == TerrainEditButton.raiseTerrain || state == TerrainEditButton.lowerTerrain) {
-            showEditTool(state);
-        } else if (state == TerrainEditButton.levelTerrain) {
-            showEditTool(state);
-        } else if (state == TerrainEditButton.smoothTerrain) {
-            showEditTool(state);
-        } else if (state == TerrainEditButton.paintTerrain || state == TerrainEditButton.eraseTerrain) {
-            showEditTool(state);
-        }
-    }
-
-
-    public void hideEditTool() {
-        SceneApplication.getApplication().enqueue(new Callable<Object>() {
-            
-            public Object call() throws Exception {
-                doHideEditTool();
-                return null;
-            }
-        });
-    }
-
-    private void doHideEditTool() {
-        marker.removeFromParent();
-        markerSmall.removeFromParent();
-        editorController.doSetLevelTerrainDesiredHeight(null);
+    public void setTerrainEditButtonState(final TerrainTool tool) {
+        showEditTool(tool);
+        
     }
 
-    public void showEditTool(final TerrainEditButton terrainEditButton) {
+    public void showEditTool(final TerrainTool terrainEditButton) {
         SceneApplication.getApplication().enqueue(new Callable<Object>() {
 
             public Object call() throws Exception {
@@ -190,25 +119,17 @@ public class TerrainToolController extends SceneToolController {
 
     /**
      * show different tool marker depending on terrainEditButton type
-     * @param state
      */
-    private void doShowEditTool(TerrainEditButton state) {
+    private void doShowEditTool(TerrainTool tool) {
+        // remove the old tool markers
+        if (terrainTool != null)
+            terrainTool.hideMarkers();
         
-        toolsNode.attachChild(marker);
-        markerSmall.removeFromParent(); // reset, turn it off
-        editorController.doSetLevelTerrainDesiredHeight(null); // disable the level marker height
-
-        if (state == TerrainEditButton.raiseTerrain || state == TerrainEditButton.lowerTerrain) {
-            marker.getMaterial().setColor("Color", terrainHeightColor);
-        } else if (state == TerrainEditButton.paintTerrain) {
-            marker.getMaterial().setColor("Color", terrainPaintColor);
-        } else if (state == TerrainEditButton.eraseTerrain) {
-            marker.getMaterial().setColor("Color", terrainEraseColor);
-        } else if (state == TerrainEditButton.levelTerrain) {
-            toolsNode.attachChild(markerSmall);
-            marker.getMaterial().setColor("Color", terrainLevelColor);
-        } else if (state == TerrainEditButton.smoothTerrain) {
-            marker.getMaterial().setColor("Color", terrainSmoothColor);
+        terrainTool = tool;
+        if (terrainTool != null) {
+            terrainTool.radiusChanged(toolRadius);
+            terrainTool.weightChanged(toolWeight);
+            terrainTool.activate(manager, toolsNode);
         }
     }
 
@@ -223,57 +144,39 @@ public class TerrainToolController extends SceneToolController {
     }
 
     private void doSetEditToolSize(float size) {
-        for (Entry e: marker.getMesh().getBuffers())
-            ((VertexBuffer)e.getValue()).resetObject();
-        ((Sphere)marker.getMesh()).updateGeometry(8, 8, size);
+        if (terrainTool != null)
+            terrainTool.radiusChanged(size);
     }
 
     public void doMoveEditTool(Vector3f pos) {
-        if (marker != null) {
-            marker.setLocalTranslation(pos);
-            //System.out.println(marker.getLocalTranslation());
+        if (terrainTool != null) {
+            terrainTool.markerMoved(pos);
         }
     }
 
     public Vector3f getMarkerLocation() {
-        if (marker != null)
-            return marker.getLocalTranslation();
-        else
-            return null;
+        if (terrainTool != null) {
+            return terrainTool.getMarkerPrimaryLocation();
+        }
+        return null;
     }
 
     public boolean isTerrainEditButtonEnabled() {
-        return getCurrentEditButtonState() != TerrainEditButton.none;
+        return terrainTool != null;
     }
 
     /**
      * Primary mouse button hit.
-     * raise/lower/paint the terrain
+     * raise/lower/paint... the terrain
      */
     public void doTerrainEditToolActivated() {
 
-        if (TerrainEditButton.raiseTerrain == getCurrentEditButtonState() ) {
-            editorController.doModifyTerrainHeight(getMarkerLocation(), heightToolRadius, heightAmount);
-        }
-        else if (TerrainEditButton.lowerTerrain == getCurrentEditButtonState() ) {
-            editorController.doModifyTerrainHeight(getMarkerLocation(), heightToolRadius, -heightAmount);
-        }
-        else if (TerrainEditButton.smoothTerrain == getCurrentEditButtonState() ) {
-            editorController.doSmoothTerrain(getMarkerLocation(), heightToolRadius, smoothAmount);
-        }
-        else if (TerrainEditButton.levelTerrain == getCurrentEditButtonState() ) {
-            if (editorController.doGetLevelTerrainDesiredHeight() == null) {
-                Vector3f point = cameraController.getTerrainCollisionPoint();
-                if (point != null)
-                     editorController.doSetLevelTerrainDesiredHeight(point);
+        if (terrainTool != null) {
+            Vector3f point = getMarkerLocation();
+            if (point != null) {
+                terrainTool.actionPrimary(point, selectedTextureIndex, jmeRootNode, editorController.getCurrentDataObject());
             }
-            editorController.doLevelTerrain(getMarkerLocation(), heightToolRadius, levelAmount);
-        }
-        else if(TerrainEditButton.paintTerrain == getCurrentEditButtonState()) {
-            editorController.doPaintTexture(selectedTextureIndex, getMarkerLocation(), heightToolRadius, paintAmount);
-        }
-        else if (TerrainEditButton.eraseTerrain == getCurrentEditButtonState() ) {
-            editorController.doPaintTexture(selectedTextureIndex, getMarkerLocation(), heightToolRadius, -paintAmount);
+            
         }
     }
 
@@ -281,14 +184,15 @@ public class TerrainToolController extends SceneToolController {
      * Alternate mouse button hit.
      */
     public void doTerrainEditToolAlternateActivated() {
-        Logger.getLogger(this.getClass().getName()).info("Alternate tool activated ");
-        if (TerrainEditButton.levelTerrain == getCurrentEditButtonState() ) {
-            
+        
+        if (terrainTool != null) {
             Vector3f point = cameraController.getTerrainCollisionPoint();
             if (point != null) {
-                editorController.doSetLevelTerrainDesiredHeight(point);
-                markerSmall.setLocalTranslation(point);
+                terrainTool.actionSecondary(point, selectedTextureIndex, jmeRootNode, editorController.getCurrentDataObject());
             }
+            
         }
+
     }
+
 }

+ 66 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/AbstractTerrainToolAction.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.actions.AbstractStatefulGLToolAction;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.terrain.Terrain;
+
+/**
+ * Helps find the terrain in the scene
+ * @author Brent Owens
+ */
+public abstract class AbstractTerrainToolAction extends AbstractStatefulGLToolAction {
+
+    protected Terrain getTerrain(Spatial root) {
+
+        // is this the terrain?
+        if (root instanceof Terrain && root instanceof Node) {
+            return (Terrain)root;
+        }
+
+        if (root instanceof Node) {
+            Node n = (Node) root;
+            for (Spatial c : n.getChildren()) {
+                if (c instanceof Node){
+                    Terrain res = getTerrain(c);
+                    if (res != null)
+                        return res;
+                }
+            }
+        }
+
+        return null;
+    }
+}

+ 65 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/EraseTerrainTool.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import org.openide.loaders.DataObject;
+
+/**
+ * Erase the texture on the terrain
+ * 
+ * @author Brent Owens
+ */
+public class EraseTerrainTool extends TerrainTool {
+
+    @Override
+    public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        if (radius == 0 || weight == 0)
+            return;
+        PaintTerrainToolAction action = new PaintTerrainToolAction(point, radius, -weight, textureIndex); // negate the weight
+        action.doActionPerformed(rootNode, dataObject);
+    }
+
+    @Override
+    public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        // do nothing
+    }
+    
+    @Override
+    public void addMarkerPrimary(Node parent) {
+        super.addMarkerPrimary(parent);
+        markerPrimary.getMaterial().setColor("Color", ColorRGBA.Cyan);
+    }
+}

+ 84 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LevelTerrainTool.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import org.openide.loaders.DataObject;
+
+/**
+ * Level the terrain. Uses a desired height point set by the secondary
+ * action (right mouse button) and raises/lowers the terrain to that
+ * desired height.
+ * 
+ * @author Brent Owens
+ */
+public class LevelTerrainTool extends TerrainTool {
+
+    private Vector3f desiredHeight;
+    
+    
+    public LevelTerrainTool() {
+        toolHintTextKey = "TerrainEditorTopComponent.toolHint.level";
+    }
+    
+    @Override
+    public void activate(AssetManager manager, Node parent) {
+        super.activate(manager, parent);
+        addMarkerSecondary(parent);
+    }
+    
+    @Override
+    public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        if (radius == 0 || weight == 0)
+            return;
+        if (desiredHeight == null)
+            desiredHeight = point.clone();
+        LevelTerrainToolAction action = new LevelTerrainToolAction(point, radius, weight, desiredHeight);
+        action.doActionPerformed(rootNode, dataObject);
+    }
+
+    @Override
+    public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        desiredHeight = point;
+        markerSecondary.setLocalTranslation(desiredHeight);
+    }
+    
+    @Override
+    public void addMarkerPrimary(Node parent) {
+        super.addMarkerPrimary(parent);
+        markerPrimary.getMaterial().setColor("Color", ColorRGBA.Red);
+    }
+}

+ 159 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LevelTerrainToolAction.java

@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.terrain.Terrain;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Level the terrain to a desired height, executed from the OpenGL thread.
+ * It will pull down or raise terrain towards the desired height, still
+ * using the radius of the tool and the weight. There are some slight rounding
+ * errors that are corrected with float epsilon testing.
+ * 
+ * @author Brent Owens
+ */
+public class LevelTerrainToolAction extends AbstractTerrainToolAction {
+    
+    private Vector3f worldLoc;
+    private float radius;
+    private float height;
+    private Vector3f levelTerrainLocation;
+    
+    List<Vector2f> undoLocs;
+    List<Float> undoHeights;
+
+    public LevelTerrainToolAction(Vector3f markerLocation, float radius, float height, Vector3f levelTerrainLocation) {
+        this.worldLoc = markerLocation.clone();
+        this.radius = radius;
+        this.height = height;
+        this.levelTerrainLocation = levelTerrainLocation;
+        name = "Level terrain";
+    }
+
+    @Override
+    protected Object doApplyTool(AbstractSceneExplorerNode rootNode) {
+        Terrain terrain = getTerrain(rootNode.getLookup().lookup(Node.class));
+        if (terrain == null)
+            return null;
+        modifyHeight(terrain, radius, height);
+        return terrain;
+    }
+    
+    @Override
+    protected void doUndoTool(AbstractSceneExplorerNode rootNode, Object undoObject) {
+        if (undoObject == null)
+            return;
+        if (undoLocs == null || undoHeights == null)
+            return;
+        resetHeight((Terrain)undoObject, undoLocs, undoHeights);
+    }
+
+    private void modifyHeight(Terrain terrain, float radius, float height) {
+        if (levelTerrainLocation == null)
+            return;
+
+        float desiredHeight = levelTerrainLocation.y;
+
+        int radiusStepsX = (int)(radius / ((Node)terrain).getLocalScale().x);
+        int radiusStepsZ = (int)(radius / ((Node)terrain).getLocalScale().z);
+
+        float xStepAmount = ((Node)terrain).getLocalScale().x;
+        float zStepAmount = ((Node)terrain).getLocalScale().z;
+
+        List<Vector2f> locs = new ArrayList<Vector2f>();
+        List<Float> heights = new ArrayList<Float>();
+
+        for (int z=-radiusStepsZ; z<radiusStepsZ; z++) {
+            for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
+
+                float locX = worldLoc.x + (x*xStepAmount);
+                float locZ = worldLoc.z + (z*zStepAmount);
+                
+                // see if it is in the radius of the tool
+                if (ToolUtils.isInRadius(locX-worldLoc.x,locZ-worldLoc.z,radius)) {
+
+                    Vector2f terrainLoc = new Vector2f(locX, locZ);
+                    // adjust height based on radius of the tool
+                    float terrainHeightAtLoc = terrain.getHeightmapHeight(terrainLoc)*terrain.getSpatial().getWorldScale().y;
+                    float radiusWeight = ToolUtils.calculateRadiusPercent(radius, locX-worldLoc.x, locZ-worldLoc.z);
+
+                    float epsilon = 0.1f*height; // rounding error for snapping
+                    
+                    float adj = 0;
+                    if (terrainHeightAtLoc < desiredHeight)
+                        adj = 1;
+                    else if (terrainHeightAtLoc > desiredHeight)
+                        adj = -1;
+                            
+                    adj *= radiusWeight * height;
+
+                    // test if adjusting too far and then cap it
+                    if (adj > 0 && ToolUtils.floatGreaterThan((terrainHeightAtLoc + adj), desiredHeight, epsilon))
+                        adj = desiredHeight - terrainHeightAtLoc;
+                    else if (adj < 0 && ToolUtils.floatLessThan((terrainHeightAtLoc + adj), desiredHeight, epsilon))
+                        adj = terrainHeightAtLoc - desiredHeight;
+  
+                    if (!ToolUtils.floatEquals(adj, 0, 0.001f)) {
+                        locs.add(terrainLoc);
+                        heights.add(adj);
+                    }
+                    
+                }
+            }
+        }
+        undoLocs = locs;
+        undoHeights = heights;
+        
+        // do the actual height adjustment
+        terrain.adjustHeight(locs, heights);
+        
+        ((Node)terrain).updateModelBound(); // or else we won't collide with it where we just edited
+
+    }
+
+    
+    private void resetHeight(Terrain terrain, List<Vector2f> undoLocs, List<Float> undoHeights) {
+        List<Float> neg = new ArrayList<Float>();
+        for (Float f : undoHeights)
+            neg.add( f * -1f );
+        
+        terrain.adjustHeight(undoLocs, neg);
+        ((Node)terrain).updateModelBound();
+    }
+    
+}

+ 65 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LowerTerrainTool.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import org.openide.loaders.DataObject;
+
+/**
+ * Lowers the terrain
+ * 
+ * @author Brent Owens
+ */
+public class LowerTerrainTool extends TerrainTool {
+
+    @Override
+    public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        if (radius == 0 || weight == 0)
+            return;
+        RaiseTerrainToolAction action = new RaiseTerrainToolAction(point, radius, -weight); // negative weight
+        action.doActionPerformed(rootNode, dataObject);
+    }
+
+    @Override
+    public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        // no secondary option
+    }
+    
+    @Override
+    public void addMarkerPrimary(Node parent) {
+        super.addMarkerPrimary(parent);
+        markerPrimary.getMaterial().setColor("Color", ColorRGBA.Green);
+    }
+}

+ 64 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/PaintTerrainTool.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import org.openide.loaders.DataObject;
+
+/**
+ *
+ * @author Brent Owens
+ */
+public class PaintTerrainTool extends TerrainTool {
+
+    @Override
+    public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        if (radius == 0 || weight == 0)
+            return;
+        PaintTerrainToolAction action = new PaintTerrainToolAction(point, radius, weight, textureIndex);
+        action.doActionPerformed(rootNode, dataObject);
+    }
+
+    @Override
+    public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        // do nothing
+    }
+    
+    @Override
+    public void addMarkerPrimary(Node parent) {
+        super.addMarkerPrimary(parent);
+        markerPrimary.getMaterial().setColor("Color", ColorRGBA.Cyan);
+    }
+}

+ 260 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/PaintTerrainToolAction.java

@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.material.MatParam;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import java.nio.ByteBuffer;
+
+/**
+ * Paint the texture at the specified location.
+ * 
+ * @author Brent Owens
+ */
+public class PaintTerrainToolAction extends AbstractTerrainToolAction {
+    
+    private Vector3f worldLoc;
+    private float radius;
+    private float weight;
+    private int selectedTextureIndex;
+    
+    public PaintTerrainToolAction(Vector3f markerLocation, float radius, float weight, int selectedTextureIndex) {
+        this.worldLoc = markerLocation.clone();
+        this.radius = radius;
+        this.weight = weight;
+        this.selectedTextureIndex = selectedTextureIndex;
+        name = "Paint terrain";
+    }
+    
+    @Override
+    protected Object doApplyTool(AbstractSceneExplorerNode rootNode) {
+        Terrain terrain = getTerrain(rootNode.getLookup().lookup(Node.class));
+        if (terrain == null)
+            return null;
+        paintTexture(terrain, worldLoc, radius, weight, selectedTextureIndex);
+        return terrain;
+    }
+    
+    @Override
+    protected void doUndoTool(AbstractSceneExplorerNode rootNode, Object undoObject) {
+        if (undoObject == null)
+            return;
+        paintTexture((Terrain)undoObject, worldLoc, radius, -weight, selectedTextureIndex);
+    }
+    
+    public void paintTexture(Terrain terrain, Vector3f markerLocation, float toolRadius, float toolWeight, int selectedTextureIndex) {
+        if (selectedTextureIndex < 0 || markerLocation == null)
+            return;
+        
+        int alphaIdx = selectedTextureIndex/4; // 4 = rgba = 4 textures
+        Texture tex = getAlphaTexture(terrain, alphaIdx);
+        Image image = tex.getImage();
+
+        Vector2f UV = terrain.getPointPercentagePosition(markerLocation.x, markerLocation.z);
+
+        // get the radius of the brush in pixel-percent
+        float brushSize = toolRadius/((TerrainQuad)terrain).getTotalSize();
+        int texIndex = selectedTextureIndex - ((selectedTextureIndex/4)*4); // selectedTextureIndex/4 is an int floor, do not simplify the equation
+        boolean erase = toolWeight<0;
+        if (erase)
+            toolWeight *= -1;
+
+        doPaintAction(texIndex, image, UV, true, brushSize, erase, toolWeight);
+
+        tex.getImage().setUpdateNeeded();
+    }
+    
+    private Texture getAlphaTexture(Terrain terrain, int alphaLayer) {
+        if (terrain == null)
+            return null;
+        MatParam matParam = null;
+        if (alphaLayer == 0)
+            matParam = terrain.getMaterial().getParam("AlphaMap");
+        else if(alphaLayer == 1)
+            matParam = terrain.getMaterial().getParam("AlphaMap_1");
+        else if(alphaLayer == 2)
+            matParam = terrain.getMaterial().getParam("AlphaMap_2");
+        
+        if (matParam == null || matParam.getValue() == null) {
+            return null;
+        }
+        Texture tex = (Texture) matParam.getValue();
+        return tex;
+    }
+    
+    /**
+     * Goes through each pixel in the image. At each pixel it looks to see if the UV mouse coordinate is within the
+     * of the brush. If it is in the brush radius, it gets the existing color from that pixel so it can add/subtract to/from it.
+     * Essentially it does a radius check and adds in a fade value. It does this to the color value returned by the
+     * first pixel color query.
+     * Next it sets the color of that pixel. If it was within the radius, the color will change. If it was outside
+     * the radius, then nothing will change, the color will be the same; but it will set it nonetheless. Not efficient.
+     *
+     * If the mouse is being dragged with the button down, then the dragged value should be set to true. This will reduce
+     * the intensity of the brush to 10% of what it should be per spray. Otherwise it goes to 100% opacity within a few pixels.
+     * This makes it work a little more realistically.
+     *
+     * @param image to manipulate
+     * @param uv the world x,z coordinate
+     * @param dragged true if the mouse button is down and it is being dragged, use to reduce brush intensity
+     * @param radius in percentage so it can be translated to the image dimensions
+     * @param erase true if the tool should remove the paint instead of add it
+     * @param fadeFalloff the percentage of the radius when the paint begins to start fading
+     */
+    protected void doPaintAction(int texIndex, Image image, Vector2f uv, boolean dragged, float radius, boolean erase, float fadeFalloff){
+        Vector2f texuv = new Vector2f();
+        ColorRGBA color = ColorRGBA.Black;
+        
+        float width = image.getWidth();
+        float height = image.getHeight();
+
+        int minx = (int) (uv.x*width - radius*width); // convert percents to pixels to limit how much we iterate
+        int maxx = (int) (uv.x*width + radius*width);
+        int miny = (int) (uv.y*height - radius*height);
+        int maxy = (int) (uv.y*height + radius*height);
+
+        float radiusSquared = radius*radius;
+        float radiusFalloff = radius*fadeFalloff;
+        // go through each pixel, in the radius of the tool, in the image
+        for (int y = miny; y < maxy; y++){
+            for (int x = minx; x < maxx; x++){
+                
+                texuv.set((float)x / width, (float)y / height);// gets the position in percentage so it can compare with the mouse UV coordinate
+
+                float dist = texuv.distanceSquared(uv);
+                if (dist < radiusSquared ) { // if the pixel is within the distance of the radius, set a color (distance times intensity)
+                    manipulatePixel(image, x, y, color, false); // gets the color at that location (false means don't write to the buffer)
+
+                    // calculate the fade falloff intensity
+                    float intensity = 0.1f;
+                    if (dist > radiusFalloff) {
+                        float dr = radius - radiusFalloff; // falloff to radius length
+                        float d2 = dist - radiusFalloff; // dist minus falloff
+                        d2 = d2/dr; // dist percentage of falloff length
+                        intensity = 1-d2; // fade out more the farther away it is
+                    }
+
+                    //if (dragged)
+                    //	intensity = intensity*0.1f; // magical divide it by 10 to reduce its intensity when mouse is dragged
+
+                    if (erase) {
+                        switch (texIndex) {
+                            case 0:
+                                color.r -= intensity; break;
+                            case 1:
+                                color.g -= intensity; break;
+                            case 2:
+                                color.b -= intensity; break;
+                            case 3:
+                                color.a -= intensity; break;
+                        }
+                    } else {
+                        switch (texIndex) {
+                            case 0:
+                                color.r += intensity; break;
+                            case 1:
+                                color.g += intensity; break;
+                            case 2:
+                                color.b += intensity; break;
+                            case 3:
+                                color.a += intensity; break;
+                        }
+                    }
+                    color.clamp();
+
+                    manipulatePixel(image, x, y, color, true); // set the new color
+                }
+
+            }
+        }
+
+        image.getData(0).rewind();
+    }
+    
+    /**
+     * We are only using RGBA8 images for alpha textures right now.
+     * @param image to get/set the color on
+     * @param x location
+     * @param y location
+     * @param color color to get/set
+     * @param write to write the color or not
+     */
+    protected void manipulatePixel(Image image, int x, int y, ColorRGBA color, boolean write){
+        ByteBuffer buf = image.getData(0);
+        int width = image.getWidth();
+
+        int position = (y * width + x) * 4;
+
+        if ( position> buf.capacity()-1 || position<0 )
+            return;
+        
+        if (write) {
+            switch (image.getFormat()){
+                case RGBA8:
+                    buf.position( position );
+                    buf.put(float2byte(color.r))
+                       .put(float2byte(color.g))
+                       .put(float2byte(color.b))
+                       .put(float2byte(color.a));
+                    return;
+                default:
+                    throw new UnsupportedOperationException("Image format: "+image.getFormat());
+            }
+        } else {
+            switch (image.getFormat()){
+                case RGBA8:
+                    buf.position( position );
+                    color.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()));
+                    return;
+                default:
+                    throw new UnsupportedOperationException("Image format: "+image.getFormat());
+            }
+        }
+        
+    }
+
+    private float byte2float(byte b){
+        return ((float)(b & 0xFF)) / 255f;
+    }
+
+    private byte float2byte(float f){
+        return (byte) (f * 255f);
+    }
+}

+ 69 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/RaiseTerrainTool.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import org.openide.loaders.DataObject;
+
+/**
+ * Raise the terrain
+ * 
+ * @author Brent Owens
+ */
+public class RaiseTerrainTool extends TerrainTool {
+
+
+    @Override
+    public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        if (radius == 0 || weight == 0)
+            return;
+        RaiseTerrainToolAction action = new RaiseTerrainToolAction(point, radius, weight);
+        action.doActionPerformed(rootNode, dataObject);
+    }
+
+    @Override
+    public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        // no secondary option
+    }
+    
+    
+    @Override
+    public void addMarkerPrimary(Node parent) {
+        super.addMarkerPrimary(parent);
+        markerPrimary.getMaterial().setColor("Color", ColorRGBA.Green);
+    }
+    
+}

+ 113 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/RaiseTerrainToolAction.java

@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.terrain.Terrain;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Raise/lower the terrain, executed from the OpenGL thread.
+ * 
+ * @author Brent Owens
+ */
+public class RaiseTerrainToolAction extends AbstractTerrainToolAction {
+
+    private Vector3f worldLoc;
+    private float radius;
+    private float height;
+
+    public RaiseTerrainToolAction(Vector3f markerLocation, float radius, float height) {
+        this.worldLoc = markerLocation.clone();
+        this.radius = radius;
+        this.height = height;
+        name = "Raise terrain";
+    }
+
+    @Override
+    protected Object doApplyTool(AbstractSceneExplorerNode rootNode) {
+        Terrain terrain = getTerrain(rootNode.getLookup().lookup(Node.class));
+        if (terrain == null)
+            return null;
+        modifyHeight(terrain, radius, height);
+        return terrain;
+    }
+    
+    @Override
+    protected void doUndoTool(AbstractSceneExplorerNode rootNode, Object undoObject) {
+        if (undoObject == null)
+            return;
+        modifyHeight((Terrain)undoObject, radius, -height);
+    }
+
+    private void modifyHeight(Terrain terrain, float radius, float heightDir) {
+
+        int radiusStepsX = (int) (radius / ((Node)terrain).getLocalScale().x);
+        int radiusStepsZ = (int) (radius / ((Node)terrain).getLocalScale().z);
+
+        float xStepAmount = ((Node)terrain).getLocalScale().x;
+        float zStepAmount = ((Node)terrain).getLocalScale().z;
+
+        List<Vector2f> locs = new ArrayList<Vector2f>();
+        List<Float> heights = new ArrayList<Float>();
+
+        for (int z=-radiusStepsZ; z<radiusStepsZ; z++) {
+            for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
+
+                float locX = worldLoc.x + (x*xStepAmount);
+                float locZ = worldLoc.z + (z*zStepAmount);
+
+                // see if it is in the radius of the tool
+                if (ToolUtils.isInRadius(locX-worldLoc.x,locZ-worldLoc.z,radius)) {
+                    // adjust height based on radius of the tool
+                    float h = ToolUtils.calculateHeight(radius, heightDir, locX-worldLoc.x, locZ-worldLoc.z);
+                    // increase the height
+                    locs.add(new Vector2f(locX, locZ));
+                    heights.add(h);
+                }
+            }
+        }
+
+        // do the actual height adjustment
+        terrain.adjustHeight(locs, heights);
+
+        ((Node)terrain).updateModelBound(); // or else we won't collide with it where we just edited
+    }
+
+    
+
+}

+ 65 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/SmoothTerrainTool.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import org.openide.loaders.DataObject;
+
+/**
+ * Smooth the terrain by averaging the heights of the surrounding areas.
+ * 
+ * @author Brent Owens
+ */
+public class SmoothTerrainTool extends TerrainTool {
+    
+    @Override
+    public void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        if (radius == 0 || weight == 0)
+            return;
+        SmoothTerrainToolAction action = new SmoothTerrainToolAction(point, radius, weight);
+        action.doActionPerformed(rootNode, dataObject);
+    }
+
+    @Override
+    public void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject) {
+        // do nothing
+    }
+    
+    @Override
+    public void addMarkerPrimary(Node parent) {
+        super.addMarkerPrimary(parent);
+        markerPrimary.getMaterial().setColor("Color", ColorRGBA.Yellow);
+    }
+}

+ 159 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/SmoothTerrainToolAction.java

@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.terrain.Terrain;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Smooth bumps in the terrain by averaging the height in the tool radius.
+ * The smoothAmount affects how many neighbour points are averaged, The smaller
+ * the value, then only the smaller bumps will disappear. A large value will
+ * smooth larger hills.
+ * 
+ * @author sploreg
+ */
+public class SmoothTerrainToolAction extends AbstractTerrainToolAction {
+    
+    private Vector3f worldLoc;
+    private float radius;
+    private float height;
+    
+    List<Vector2f> undoLocs;
+    List<Float> undoHeights;
+
+    public SmoothTerrainToolAction(Vector3f markerLocation, float radius, float height) {
+        this.worldLoc = markerLocation.clone();
+        this.radius = radius;
+        this.height = height;
+        name = "Smooth terrain";
+    }
+
+    @Override
+    protected Object doApplyTool(AbstractSceneExplorerNode rootNode) {
+        Terrain terrain = getTerrain(rootNode.getLookup().lookup(Node.class));
+        if (terrain == null)
+            return null;
+        modifyHeight(terrain, radius, height);
+        return terrain;
+    }
+    
+    @Override
+    protected void doUndoTool(AbstractSceneExplorerNode rootNode, Object undoObject) {
+        if (undoObject == null)
+            return;
+        if (undoLocs == null || undoHeights == null)
+            return;
+        resetHeight((Terrain)undoObject, undoLocs, undoHeights);
+    }
+    
+    private void modifyHeight(Terrain terrain, float radius, float height) {
+        
+        int radiusStepsX = (int)(radius / ((Node)terrain).getLocalScale().x);
+        int radiusStepsZ = (int)(radius / ((Node)terrain).getLocalScale().z);
+
+        float xStepAmount = ((Node)terrain).getLocalScale().x;
+        float zStepAmount = ((Node)terrain).getLocalScale().z;
+
+        List<Vector2f> locs = new ArrayList<Vector2f>();
+        List<Float> heights = new ArrayList<Float>();
+
+        for (int z=-radiusStepsZ; z<radiusStepsZ; z++) {
+            for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
+
+                float locX = worldLoc.x + (x*xStepAmount);
+                float locZ = worldLoc.z + (z*zStepAmount);
+
+                // see if it is in the radius of the tool
+                if (ToolUtils.isInRadius(locX-worldLoc.x,locZ-worldLoc.z,radius)) {
+
+                    Vector2f terrainLoc = new Vector2f(locX, locZ);
+                    // adjust height based on radius of the tool
+                    float center = terrain.getHeightmapHeight(terrainLoc);
+                    float left = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x-1, terrainLoc.y));
+                    float right = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x+1, terrainLoc.y));
+                    float up = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x, terrainLoc.y+1));
+                    float down = terrain.getHeightmapHeight(new Vector2f(terrainLoc.x, terrainLoc.y-1));
+                    int count = 1;
+                    float amount = center;
+                    if (left != Float.NaN) {
+                        amount += left;
+                        count++;
+                    }
+                    if (right != Float.NaN) {
+                        amount += right;
+                        count++;
+                    }
+                    if (up != Float.NaN) {
+                        amount += up;
+                        count++;
+                    }
+                    if (down != Float.NaN) {
+                        amount += down;
+                        count++;
+                    }
+
+                    amount /= count; // take average
+
+                    // weigh it
+                    float diff = amount-center;
+                    diff *= height;
+                        
+                    locs.add(terrainLoc);
+                    heights.add(diff);
+                }
+            }
+        }
+        
+        undoLocs = locs;
+        undoHeights = heights;
+        
+        // do the actual height adjustment
+        terrain.adjustHeight(locs, heights);
+
+        ((Node)terrain).updateModelBound(); // or else we won't collide with it where we just edited
+    }
+    
+    private void resetHeight(Terrain terrain, List<Vector2f> undoLocs, List<Float> undoHeights) {
+        List<Float> neg = new ArrayList<Float>();
+        for (Float f : undoHeights)
+            neg.add( f * -1f );
+        
+        terrain.adjustHeight(undoLocs, neg);
+        ((Node)terrain).updateModelBound();
+    }
+}

+ 184 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/TerrainTool.java

@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.IntMap.Entry;
+import org.openide.loaders.DataObject;
+
+/**
+ * Modifies the terrain in some way.
+ * It has a primary and secondary action, activated from left and right mouse button respectively.
+ * It will also attach tool geometries to the scene so the user can see where they are editing.
+ * 
+ * @author Brent Owens
+ */
+public abstract class TerrainTool {
+    
+    protected AssetManager manager;
+    protected Geometry markerPrimary;
+    protected Geometry markerSecondary;
+    protected float radius;
+    protected float weight;
+    protected float maxToolSize = 20; // override in sub classes
+    
+    // the key to load the tool hint text from the resource bundle
+    protected String toolHintTextKey = "TerrainEditorTopComponent.toolHint.default";
+    
+    /**
+     * The tool was selected, start showing the marker.
+     * @param manager
+     * @param parent node that the marker will attach to
+     */
+    public void activate(AssetManager manager, Node parent) {
+        this.manager = manager;
+        addMarkerPrimary(parent);
+    }
+    
+    /**
+     * The primary action for the tool gets activated
+     */
+    public abstract void actionPrimary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject);
+    
+    /**
+     * The secondary action for the tool gets activated
+     */
+    public abstract void actionSecondary(Vector3f point, int textureIndex, AbstractSceneExplorerNode rootNode, DataObject dataObject);
+    
+    /**
+     * Location of the primary editor marker
+     */
+    public Vector3f getMarkerPrimaryLocation() {
+        if (markerPrimary != null)
+            return markerPrimary.getLocalTranslation();
+        else
+            return null;
+    }
+    
+    /**
+     * Move the marker to a new location, usually follows the mouse
+     * @param newLoc 
+     */
+    public void markerMoved(Vector3f newLoc) {
+        if (markerPrimary != null)
+            markerPrimary.setLocalTranslation(newLoc);
+    }
+    
+    /**
+     * The radius of the tool has changed, so update the marker
+     * @param radius percentage of the max radius
+     */
+    public void radiusChanged(float radius) {
+        this.radius = maxToolSize*radius;
+        
+        if (markerPrimary != null) {
+            for (Entry e: markerPrimary.getMesh().getBuffers())
+                ((VertexBuffer)e.getValue()).resetObject();
+            ((Sphere)markerPrimary.getMesh()).updateGeometry(8, 8, this.radius);
+        }
+    }
+    
+    /**
+     * The weight of the tool has changed. Optionally change
+     * the marker look.
+     * @param weight percent
+     */
+    public void weightChanged(float weight) {
+        this.weight = weight;
+    }
+    
+    /**
+     * Create the primary marker mesh, follows the mouse.
+     * @param parent it will attach to
+     */
+    public void addMarkerPrimary(Node parent) {
+        if (markerPrimary == null) {
+            markerPrimary = new Geometry("edit marker primary");
+            Mesh m = new Sphere(8, 8, radius);
+            markerPrimary.setMesh(m);
+            Material mat = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+            mat.getAdditionalRenderState().setWireframe(true);
+            markerPrimary.setMaterial(mat);
+            markerPrimary.setLocalTranslation(0,0,0);
+            mat.setColor("Color", ColorRGBA.LightGray);
+        }
+        parent.attachChild(markerPrimary);
+    }
+    
+    /**
+     * Create the secondary marker mesh, placed
+     * with the right mouse button.
+     * @param parent it will attach to
+     */
+    public void addMarkerSecondary(Node parent) {
+        if (markerSecondary == null) {
+            markerSecondary = new Geometry("edit marker secondary");
+            Mesh m2 = new Sphere(8, 8, 0.5f);
+            markerSecondary.setMesh(m2);
+            Material mat2 = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+            mat2.getAdditionalRenderState().setWireframe(false);
+            markerSecondary.setMaterial(mat2);
+            markerSecondary.setLocalTranslation(0,0,0);
+            mat2.setColor("Color", ColorRGBA.Red);
+        }
+        parent.attachChild(markerSecondary);
+    }
+    
+    /**
+     * Remove the markers from the scene.
+     */
+    public void hideMarkers() {
+        if (markerPrimary != null)
+            markerPrimary.removeFromParent();
+        if (markerSecondary != null)
+            markerSecondary.removeFromParent();
+    }
+
+    public String getToolHintTextKey() {
+        return toolHintTextKey;
+    }
+
+    public void setToolHintTextKey(String toolHintTextKey) {
+        this.toolHintTextKey = toolHintTextKey;
+    }
+
+    
+}

+ 101 - 0
jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/ToolUtils.java

@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.gde.terraineditor.tools;
+
+import com.jme3.math.Vector2f;
+
+/**
+ * Handy utilities for the editor tools
+ * @author Brent Owens
+ */
+public class ToolUtils {
+
+    /**
+     * See if the X,Y coordinate is in the radius of the circle. It is assumed
+     * that the "grid" being tested is located at 0,0 and its dimensions are 2*radius.
+     * @param x
+     * @param z
+     * @param radius
+     * @return
+     */
+    public static boolean isInRadius(float x, float y, float radius) {
+        Vector2f point = new Vector2f(x,y);
+        // return true if the distance is less than equal to the radius
+        return Math.abs(point.length()) <= radius;
+    }
+
+    /**
+     * Interpolate the height value based on its distance from the center (how far along
+     * the radius it is).
+     * The farther from the center, the less the height will be.
+     * This produces a linear height falloff.
+     * @param radius of the tool
+     * @param heightFactor potential height value to be adjusted
+     * @param x location
+     * @param z location
+     * @return the adjusted height value
+     */
+    public static float calculateHeight(float radius, float heightFactor, float x, float z) {
+        float val = calculateRadiusPercent(radius, x, z);
+        return heightFactor * val;
+    }
+
+    public static float calculateRadiusPercent(float radius, float x, float z) {
+         // find percentage for each 'unit' in radius
+        Vector2f point = new Vector2f(x,z);
+        float val = Math.abs(point.length()) / radius;
+        val = 1f - val;
+        return val;
+    }
+    
+    public static int compareFloat(float a, float b, float epsilon) {
+        if (floatEquals(a, b, epsilon))
+            return 0;
+        else if (floatLessThan(a, b, epsilon))
+            return -1;
+        else
+            return 1;
+    }
+
+    public static boolean floatEquals(float a, float b, float epsilon) {
+        return a == b ? true : Math.abs(a - b) < epsilon;
+    }
+
+    public static boolean floatLessThan(float a, float b, float epsilon) {
+        return b - a > epsilon;
+    }
+
+    public static boolean floatGreaterThan(float a, float b, float epsilon) {
+        return a - b > epsilon;
+    }
+}