Excuse the code, I just mashed together a bunch of examples so I could code my AlterTerrain function. This example works fine, until you click on the same spot multiple times, then it will stop returning triangles in findTrianglePick. To test, just run the code, and right click near the corner. You will see it raise a patch of terrain around your cursor - right click a few more times in the same spot and you will notice it stops lowering. If you look at the console output you will see that RAL.size() is returning zero at this point. This doesn't make sense - since if you look at the code, you can't even get to that point in the code without TrianglePickResults confirming that our TerrainBlock is the one the ray is touching.
So why does TrianglePickResults think the ray is touching the TerrainBlock (which it is), but findTrianglePick is not returning any triangles (sometimes)?
import com.jme.bounding.*;
import com.jme.math.*;
import com.jme.scene.shape.Box;
import com.jmex.editors.swing.settings.*;
import com.jmex.game.*;
import com.jmex.game.state.*;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jmex.terrain.TerrainBlock;
import com.jme.scene.Node;
import com.jme.renderer.Camera;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import com.jme.renderer.ColorRGBA;
import com.jme.math.Ray;
import com.jme.intersection.TrianglePickResults;
import com.jme.scene.Geometry;
import com.jme.math.Vector2f;
import com.jme.image.Texture;
import com.jme.util.TextureManager;
import com.jme.scene.state.TextureState;
import javax.swing.ImageIcon;
import java.util.concurrent.Callable;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.input.MouseInput;
import com.jme.input.MouseInputListener;
import com.jme.scene.TriMesh;
import java.util.ArrayList;
import com.jme.scene.state.WireframeState;
import com.jme.scene.Line;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ClipState;
import com.jme.light.PointLight;
/**
* TestStandardGame is meant to be an example replacement of
* jmetest.base.TestSimpleGame using the StandardGame implementation
* instead of SimpleGame.
*
* @author Matthew D. Hicks
*/
public class TestStandardGame {
StandardGame game;
DebugGameState state;
ImageBasedHeightMap ib;
TerrainBlock tb;
LightState ls;
ClipState cs;
TextureState ts;
ProceduralTextureGenerator pg;
boolean[] ButtonDown = new boolean[3];
public static void main(String[] args)
{
TestStandardGame TSG = new TestStandardGame();
}
public TestStandardGame()
{
// Instantiate StandardGame
game = new StandardGame("A Simple Test");
// Show settings screen
try
{
GameSettingsPanel.prompt(game.getSettings());
} catch (Exception myE) {}
// Start StandardGame, it will block until it has initialized successfully, then return
game.start();
// Create a DebugGameState - has all the built-in features that SimpleGame provides
// NOTE: for a distributable game implementation you'll want to use something like
// BasicGameState instead and provide control features yourself.
state = new DebugGameState();
// Put our box in it
Box box = new Box("my box", new Vector3f(0, 0, 0), 2, 2, 2);
box.setModelBound(new BoundingSphere());
box.updateModelBound();
// We had to add the following line because the render thread is already running
// Anytime we add content we need to updateRenderState or we get funky effects
box.updateRenderState();
state.getRootNode().attachChild(box);
// Add it to the manager
GameStateManager.getInstance().attachChild(state);
// Create an image height map based on the gray scale of our image.
ib = new ImageBasedHeightMap(new ImageIcon(guidevtools.class.getClassLoader().getResource("test_hm.gif")).getImage());
// Create a terrain block from the image's grey scale
/*TerrainBlock tb=new TerrainBlock("image icon",ib.getSize(),
new Vector3f(.5f,.05f,.5f),ib.getHeightMap(),
new Vector3f(0,0,0),false); */
tb=new TerrainBlock("image icon",ib.getSize(),
new Vector3f(.5f,.05f,.5f),ib.getHeightMap(),
new Vector3f(0,0,0),false);
// Create an object to generate textured terrain from the image based height map.
pg=new ProceduralTextureGenerator(ib);
// Look like water from height 0-60 with the strongest "water look" at 30
pg.addTexture(new ImageIcon(guidevtools.class.getClassLoader().getResource("water.png")),0,30,60);
// Look like dirt from height 40-120 with the strongest "dirt look" at 80
pg.addTexture(new ImageIcon(guidevtools.class.getClassLoader().getResource("dirt.jpg")),40,80,120);
// Look like highest (pure white) from height 110-256 with the strongest "white look" at 130
// pg.addTexture(new ImageIcon("highest.jpg"),110,130,256);
// Look like highest (pure white) from height 110-256 with the strongest "white look" at 130
pg.addTexture(new ImageIcon(guidevtools.class.getClassLoader().getResource("highest.jpg")),110,250,256);
// Tell pg to create a texture from the ImageIcon's it has recieved.
pg.createTexture(256);
boolean doTexture = false;
if (doTexture)
{
ts=game.getDisplay().getRenderer().createTextureState();
// Load the texture and assign it.
ts.setTexture(
TextureManager.loadTexture(
pg.getImageIcon().getImage(),
Texture.MM_LINEAR_LINEAR,
Texture.FM_LINEAR,
true
)
);
// tb to rootNode
tb.setRenderState(ts);
}
else
{
WireframeState ws = game.getDisplay().getRenderer().createWireframeState();
tb.setRenderState(ws);
}
// Give the terrain a bounding box
tb.setModelBound(new BoundingBox());
tb.updateModelBound();
// Move the terrain in front of the camera
tb.setLocalTranslation(new Vector3f(0, 0, -10));
tb.updateRenderState();
state.getRootNode().attachChild(tb);
ls = game.getDisplay().getRenderer().createLightState();
cs = game.getDisplay().getRenderer().createClipState();
System.out.println("ls qty:'" + ls.getQuantity() + "'");
// Activate the game state
state.setActive(true);
MouseInput.get().setCursorVisible(true);
MouseInputListener windowMouse = new MouseInputListener()
{
public void onButton(int button, boolean pressed, int x, int y)
{
if (button == 0)
{
// left
if (pressed == true)
{
ButtonDown[0] = true;
System.out.println("evt mouse1 down");
AlterTerrain(x,y,5,true,5);
}
else
{
ButtonDown[0] = false;
System.out.println("evt mouse1 up");
}
}
else if (button == 1)
{
// right
System.out.println("evt mouse2");
if (pressed == true)
{
ButtonDown[1] = true;
AlterTerrain(x,y,5,false,5);
}
else
{
ButtonDown[1] = false;
}
}
else if (button == 2)
{
// middle
System.out.println("evt mouse3");
if (pressed == true)
{
ButtonDown[2] = true;
AlterTerrain(x,y,5,false,5);
}
else
{
ButtonDown[2] = false;
}
}
else if (pressed == true)
{
System.out.println("button press:'" + button + "'");
}
}
public void onMove(int xDelta, int yDelta, int newX, int newY)
{
if (ButtonDown[0] == true)
{
AlterTerrain(newX,newY,5,true,5);
}
else if (ButtonDown[1] == true)
{
AlterTerrain(newX,newY,5,false,5);
}
else if (ButtonDown[2] == true)
{
AlterTerrain(newX,newY,5,false,5);
}
}
public void onWheel(int wheelDelta, int x, int y) {}
};
MouseInput.get().addListener(windowMouse);
}
public void AlterTerrain(float x, float y, int BrushSize, boolean RaiseTerrain, int AlterAmount)
{
if (BrushSize < 1)
{
BrushSize = 5;
}
System.out.println("incoming gt x:'" + x + "' y:'" + y + "'");
game.getCamera().update();
Ray ResultsRay = game.getDisplay().getPickRay(new Vector2f(x,y), false, null);
TrianglePickResults PResults = new TrianglePickResults();
PResults.setCheckDistance(true);
Node rootNode = state.getRootNode();
rootNode.findPick(ResultsRay, PResults);
int ResultCount = PResults.getNumber();
if (ResultCount > 0)
{
// use result 0, should be closest...
TriMesh TMesh = (TriMesh)PResults.getPickData(0).getTargetMesh().getParentGeom();
ArrayList<Integer> RAL = new ArrayList<Integer>();
if (TMesh.getBatchCount() == 1)
{
// Pick the actual triangles out of the mesh now
TMesh.findTrianglePick(ResultsRay,RAL,0);
System.out.println("ralsize:'" + RAL.size() + "'");
if (RAL.size() > 0)
{
int ClosestTri = -1;
float ClosestDist = -1;
// Find the closest triangle from all the results
for (int i=0;i<RAL.size();i++)
{
Vector3f[] myTri = new Vector3f[3];
TMesh.getTriangle(RAL.get(i).intValue(), myTri);
Vector3f newValues = new Vector3f();
if (ResultsRay.intersectWherePlanar(myTri[0].addLocal(TMesh.getWorldTranslation()), myTri[1].addLocal(TMesh.getWorldTranslation()), myTri[2].addLocal(TMesh.getWorldTranslation()), newValues))
{
if (newValues.getX() < ClosestDist || ClosestDist == -1)
{
ClosestDist = newValues.getX();
ClosestTri = RAL.get(i).intValue();
}
}
else
{
System.out.println("error no intersect!");
}
}
System.out.println("closesttri:'" + ClosestTri + "'");
if (ClosestTri > -1)
{
// Got one particular triangle.. start routine to alter the terrain based on this location...
Vector3f[] myTri2 = new Vector3f[3];
TMesh.getTriangle(ClosestTri, myTri2);
myTri2[0].addLocal(TMesh.getWorldTranslation());
myTri2[1].addLocal(TMesh.getWorldTranslation());
myTri2[2].addLocal(TMesh.getWorldTranslation());
/*
Vector3f[] tmpLines = {ResultsRay.getOrigin(),myTri2[0], ResultsRay.getOrigin(), myTri2[1], ResultsRay.getOrigin(), myTri2[2]};
Line myLine = new Line("my Pointer",tmpLines,null,null,null);
myLine.setSolidColor(ColorRGBA.red);
System.out.println("linewidth:'" + myLine.getLineWidth() + "'");
state.getRootNode().attachChild(myLine);
*/
Vector3f tbwt = tb.getWorldTranslation();
Vector3f tmp3fValues = new Vector3f();
tmp3fValues.x = (int)((myTri2[0].x - tbwt.x) * 2);
tmp3fValues.z = (int)((myTri2[0].z - tbwt.z) * 2);
tmp3fValues.y = tb.getSize();
System.out.println("tempvals x:'" + tmp3fValues.x + "' z:'" + tmp3fValues.z + "'");
for (int i=(int)tmp3fValues.x-BrushSize;i<=(int)(tmp3fValues.x+BrushSize);i++)
{
for (int j=(int)tmp3fValues.z-BrushSize;j<=(int)(tmp3fValues.z+BrushSize);j++)
{
//System.out.println("adjusting height at point x:'" + i + "' z:'" + j + "'");
if ((i >= 0 && i <= tb.getSize()) && (j >= 0 && j <= tb.getSize()))
{
int CurrentHeight = ib.getTrueHeightAtPoint(i,j);
if (RaiseTerrain)
{
CurrentHeight += AlterAmount;
}
else
{
CurrentHeight -= AlterAmount;
}
if (CurrentHeight > 255)
{
CurrentHeight = 255;
}
else if (CurrentHeight < 0)
{
CurrentHeight = 0;
}
ib.setHeightAtPoint(CurrentHeight,i,j);
//tb.setHeightMapValue(i,j,CurrentHeight);
}
/*else
{
System.out.println("setHM out of range...");
}*/
}
}
tb.updateFromHeightMap();
// if we call updateRenderState() here it screws up our lighting...
//state.getRootNode().updateRenderState();
//tb.updateRenderState();
}
}
}
}
}
}
Edit: I changed the code so you can "paint" with it if you hold down the button while you try to raise/lower the terrain - this makes the large amount of non-detection especially apparent when you try this