* 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
3.0
bre..ns 14 years ago
parent 5024fa6c32
commit e08dc6f6cf
  1. 105
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/actions/AbstractStatefulGLToolAction.java
  2. 4
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/Bundle.properties
  3. 432
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorController.java
  4. 102
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java
  5. 190
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java
  6. 66
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/AbstractTerrainToolAction.java
  7. 65
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/EraseTerrainTool.java
  8. 84
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LevelTerrainTool.java
  9. 159
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LevelTerrainToolAction.java
  10. 65
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/LowerTerrainTool.java
  11. 64
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/PaintTerrainTool.java
  12. 260
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/PaintTerrainToolAction.java
  13. 69
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/RaiseTerrainTool.java
  14. 113
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/RaiseTerrainToolAction.java
  15. 65
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/SmoothTerrainTool.java
  16. 159
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/SmoothTerrainToolAction.java
  17. 184
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/TerrainTool.java
  18. 101
      sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/tools/ToolUtils.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);
}

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

@ -38,9 +38,11 @@ import com.jme3.bounding.BoundingBox;
import com.jme3.gde.core.assets.AssetDataObject; import com.jme3.gde.core.assets.AssetDataObject;
import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.assets.ProjectAssetManager;
import com.jme3.gde.core.scene.SceneApplication; 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.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.material.MatParam;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
@ -50,25 +52,25 @@ import com.jme3.terrain.ProgressMonitor;
import com.jme3.terrain.Terrain; import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad; import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode; import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import jme3tools.converters.ImageToAwt; import jme3tools.converters.ImageToAwt;
import org.openide.cookies.SaveCookie; import org.openide.cookies.SaveCookie;
import org.openide.filesystems.FileObject; import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject; import org.openide.loaders.DataObject;
import org.openide.util.Exceptions; import org.openide.util.Exceptions;
import org.openide.util.Lookup;
/** /**
* Modifies the actual terrain in the scene. * 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 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 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() { protected SaveCookie terrainSaveCookie = new SaveCookie() {
@ -133,7 +131,7 @@ public class TerrainEditorController {
currentFileObject.setModified(state); currentFileObject.setModified(state);
} }
protected Node getTerrain(Spatial root) { public Node getTerrain(Spatial root) {
if (terrainNode != null) if (terrainNode != null)
return terrainNode; return terrainNode;
@ -166,7 +164,7 @@ public class TerrainEditorController {
* @param radius of the tool, terrain in this radius will be affected * @param radius of the tool, terrain in this radius will be affected
* @param heightFactor the amount to adjust the height by * @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); Terrain terrain = (Terrain) getTerrain(null);
if (terrain == null) if (terrain == null)
@ -180,6 +178,9 @@ public class TerrainEditorController {
float xStepAmount = ((Node)terrain).getLocalScale().x; float xStepAmount = ((Node)terrain).getLocalScale().x;
float zStepAmount = ((Node)terrain).getLocalScale().z; 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 z=-radiusStepsZ; z<radiusStepsZ; z++) {
for (int x=-radiusStepsZ; x<radiusStepsX; x++) { for (int x=-radiusStepsZ; x<radiusStepsX; x++) {
@ -191,11 +192,15 @@ public class TerrainEditorController {
// adjust height based on radius of the tool // adjust height based on radius of the tool
float h = calculateHeight(radius, heightFactor, locX-worldLoc.x, locZ-worldLoc.z); float h = calculateHeight(radius, heightFactor, locX-worldLoc.x, locZ-worldLoc.z);
// increase the height // 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 ((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 patchSize,
final int alphaTextureSize, final int alphaTextureSize,
final float[] heightmapData, final float[] heightmapData,
final String sceneName) throws IOException final String sceneName,
final JmeSpatial jmeNodeParent) throws IOException
{ {
try { try {
Terrain terrain = Terrain terrain =
SceneApplication.getApplication().enqueue(new Callable<Terrain>() { SceneApplication.getApplication().enqueue(new Callable<Terrain>() {
public Terrain call() throws Exception { public Terrain call() throws Exception {
return doCreateTerrain(parent, totalSize, patchSize, alphaTextureSize, heightmapData, sceneName); return doCreateTerrain(parent, totalSize, patchSize, alphaTextureSize, heightmapData, sceneName, jmeNodeParent);
} }
}).get(); }).get();
return terrain; return terrain;
@ -700,7 +706,8 @@ public class TerrainEditorController {
int patchSize, int patchSize,
int alphaTextureSize, int alphaTextureSize,
float[] heightmapData, float[] heightmapData,
String sceneName) throws IOException String sceneName,
JmeSpatial jmeNodeParent) throws IOException
{ {
AssetManager manager = SceneApplication.getApplication().getAssetManager(); AssetManager manager = SceneApplication.getApplication().getAssetManager();
@ -750,18 +757,52 @@ public class TerrainEditorController {
// add the lod control // add the lod control
List<Camera> cameras = new ArrayList<Camera>(); List<Camera> cameras = new ArrayList<Camera>();
cameras.add(SceneApplication.getApplication().getCamera()); cameras.add(SceneApplication.getApplication().getCamera());
TerrainLodControl control = new TerrainLodControl(terrain, cameras); 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); parent.attachChild(terrain);
setNeedsSave(true); setNeedsSave(true);
addSpatialUndo(parent, terrain, jmeNodeParent);
return terrain; 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 * Save the terrain's alpha maps to disk, in the Textures/terrain-alpha/ directory
@ -895,180 +936,6 @@ public class TerrainEditorController {
return false; 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. * How many textures are currently being used.
@ -1182,191 +1049,6 @@ public class TerrainEditorController {
setNeedsSave(true); 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
}
} }

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

@ -37,18 +37,15 @@ import com.jme3.gde.core.scene.SceneApplication;
import com.jme3.gde.core.scene.controller.SceneToolController; import com.jme3.gde.core.scene.controller.SceneToolController;
import com.jme3.gde.core.sceneexplorer.nodes.JmeNode; import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial; 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.material.Material;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Sphere; import com.jme3.scene.shape.Sphere;
import com.jme3.util.IntMap.Entry;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.logging.Logger;
/** /**
* The controller for the terrain modification tools. It will in turn interact * 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 { public class TerrainToolController extends SceneToolController {
private JmeSpatial jmeRootNode; private JmeSpatial jmeRootNode;
private TerrainEditButton currentEditButtonState = TerrainEditButton.none; private TerrainTool terrainTool;
private Geometry marker;
private Geometry markerSmall;
private TerrainEditorController editorController; private TerrainEditorController editorController;
private TerrainCameraController cameraController; private TerrainCameraController cameraController;
private float heightToolRadius;
private float heightAmount; private float toolRadius;
private float levelAmount; private float toolWeight;
private float smoothAmount;
private float paintAmount;
private int selectedTextureIndex = -1; 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) { public TerrainToolController(Node toolsNode, AssetManager manager, JmeNode rootNode) {
super(toolsNode, manager); 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) { public void setHeightToolHeight(float weight) {
this.heightAmount = heightToolHeight/100f; this.toolWeight = weight;
this.levelAmount = heightToolHeight/200f; if (terrainTool != null)
this.smoothAmount = heightToolHeight/200f; terrainTool.weightChanged(weight);
this.paintAmount = heightToolHeight/200f;
} }
/**
* @param radius percent of the slider
*/
public void setHeightToolRadius(float radius) { public void setHeightToolRadius(float radius) {
this.heightToolRadius = radius; this.toolRadius = radius;
setEditToolSize(radius); setEditToolSize(radius);
} }
@ -112,72 +101,12 @@ public class TerrainToolController extends SceneToolController {
this.selectedTextureIndex = index; this.selectedTextureIndex = index;
} }
public void setTerrainEditButtonState(final TerrainTool tool) {
showEditTool(tool);
@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 showEditTool(final TerrainEditButton terrainEditButton) { public void showEditTool(final TerrainTool terrainEditButton) {
SceneApplication.getApplication().enqueue(new Callable<Object>() { SceneApplication.getApplication().enqueue(new Callable<Object>() {
public Object call() throws Exception { public Object call() throws Exception {
@ -190,25 +119,17 @@ public class TerrainToolController extends SceneToolController {
/** /**
* show different tool marker depending on terrainEditButton type * 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
toolsNode.attachChild(marker); if (terrainTool != null)
markerSmall.removeFromParent(); // reset, turn it off terrainTool.hideMarkers();
editorController.doSetLevelTerrainDesiredHeight(null); // disable the level marker height
terrainTool = tool;
if (state == TerrainEditButton.raiseTerrain || state == TerrainEditButton.lowerTerrain) { if (terrainTool != null) {
marker.getMaterial().setColor("Color", terrainHeightColor); terrainTool.radiusChanged(toolRadius);
} else if (state == TerrainEditButton.paintTerrain) { terrainTool.weightChanged(toolWeight);
marker.getMaterial().setColor("Color", terrainPaintColor); terrainTool.activate(manager, toolsNode);
} 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);
} }
} }
@ -223,57 +144,39 @@ public class TerrainToolController extends SceneToolController {
} }
private void doSetEditToolSize(float size) { private void doSetEditToolSize(float size) {
for (Entry e: marker.getMesh().getBuffers()) if (terrainTool != null)
((VertexBuffer)e.getValue()).resetObject(); terrainTool.radiusChanged(size);
((Sphere)marker.getMesh()).updateGeometry(8, 8, size);
} }
public void doMoveEditTool(Vector3f pos) { public void doMoveEditTool(Vector3f pos) {
if (marker != null) { if (terrainTool != null) {
marker.setLocalTranslation(pos); terrainTool.markerMoved(pos);
//System.out.println(marker.getLocalTranslation());
} }
} }
public Vector3f getMarkerLocation() { public Vector3f getMarkerLocation() {
if (marker != null) if (terrainTool != null) {
return marker.getLocalTranslation(); return terrainTool.getMarkerPrimaryLocation();
else }
return null; return null;
} }
public boolean isTerrainEditButtonEnabled() { public boolean isTerrainEditButtonEnabled() {
return getCurrentEditButtonState() != TerrainEditButton.none; return terrainTool != null;
} }
/** /**
* Primary mouse button hit. * Primary mouse button hit.
* raise/lower/paint the terrain * raise/lower/paint... the terrain
*/ */
public void doTerrainEditToolActivated() { public void doTerrainEditToolActivated() {
if (TerrainEditButton.raiseTerrain == getCurrentEditButtonState() ) { if (terrainTool != null) {
editorController.doModifyTerrainHeight(getMarkerLocation(), heightToolRadius, heightAmount); Vector3f point = getMarkerLocation();
} if (point != null) {
else if (TerrainEditButton.lowerTerrain == getCurrentEditButtonState() ) { terrainTool.actionPrimary(point, selectedTextureIndex, jmeRootNode, editorController.getCurrentDataObject());
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);
} }
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. * Alternate mouse button hit.
*/ */
public void doTerrainEditToolAlternateActivated() { public void doTerrainEditToolAlternateActivated() {
Logger.getLogger(this.getClass().getName()).info("Alternate tool activated ");
if (TerrainEditButton.levelTerrain == getCurrentEditButtonState() ) {
if (terrainTool != null) {
Vector3f point = cameraController.getTerrainCollisionPoint(); Vector3f point = cameraController.getTerrainCollisionPoint();
if (point != null) { if (point != null) {
editorController.doSetLevelTerrainDesiredHeight(point); terrainTool.actionSecondary(point, selectedTextureIndex, jmeRootNode, editorController.getCurrentDataObject());
markerSmall.setLocalTranslation(point);
} }
} }
} }
} }

@ -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;
}
}

@ -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);
}
}

@ -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);
}
}

@ -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();
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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);
}
}

@ -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
}
}

@ -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);
}
}

@ -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();
}
}

@ -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;
}
}

@ -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;
}
}
Loading…
Cancel
Save