where is the alpha map for the painted terrain, or how is it saved?
i have a painted terrain but the alphablend files look almost empty
the only texture who makes any alpha is this:
where is the alpha map for the painted terrain, or how is it saved?
i have a painted terrain but the alphablend files look almost empty
A terrain’s alpha map will typically not make any sense when viewed visually. So as long as the final terrain looks okay, then I wouldn’t worry about how the alphaMap looks.
A terrain’s alpha map in this context is not really a texture in the regular sense, and it also has nothing to do with transparency actually (so I’m actually not sure why the term AlhpaMap is used. I’ve also seen similar things being called Color ID Maps and Color Splat Maps in other similar use cases.)
Essentially the texture is being used as a data storage object so the shader can be optimized better. So when you paint a terrain in the SDK’s terrain editor, it is actually painting either the red, green, blue, or alpha channel of the alphaMap. And then the terrain shader reads those values to do texture splatting using all of the other real textures (like dirt,grass,etc) that you painted onto the terrain.
how does it work then?
I am trying to make a code that will detect what layer or texture there is at a certain point, but I cant get it to work in any meaningful way
Here is a section of the jme wiki about Texture Splatting in the context of terrains:
TerraMonkey - The jMonkeyEngine Terrain System :: jMonkeyEngine Docs
This should be possible to do, you just have to know which texture is being stored in which channel.
You should be able to use jME’s ImageRaster class to query each pixel of an alphaMap, and then read either the red, green, blue or alpha channel of the returned color depending on which texture you are searching for.
To help, I made a quick image to show which texture is being written to which color layer of which alpha map for you right now:
Whichever texture is the first one will always be written to the R channel of the first alphamap, whichever is the 2nd texture will always be the G channel, and so on for up to 3 alpha maps, each having 4 texture each.
Ah
so the reason that only grass is being visible is because the default PNG viewer on windows is using that as a transparency value, and showing what is behind it?
I have some code, but it doesnt work the way i expect it to:
package com.ferra.reactorgame;
import com.jme3.asset.AssetManager;
import com.jme3.material.MatParamTexture;
import com.jme3.material.Material;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class ObjectPlacer {
private final Node rootNode;
private final Node terrainNode; // parent node that contains multiple terrainquads
private final List<Rule> rules = new ArrayList<>();
private final Random random = new Random();
private final AssetManager assetManager;
public ObjectPlacer(Node rootNode, Node terrainNode, AssetManager assetManager) {
this.rootNode = rootNode;
this.terrainNode = terrainNode;
this.assetManager = assetManager;
}
public void addRule(Rule rule) {
rules.add(rule);
}
// place objects upon all terrain quads
public void scatterObjects(int step) {
int placedTotal = 0;
for (Spatial child : terrainNode.getChildren()) {
if (child instanceof TerrainQuad quad) {
placedTotal += scatterOnQuad(quad, step);
}
}
System.out.println("Placed " + placedTotal + " objects across all quads.");
}
// scatter objects on 1 terrainquad
private int scatterOnQuad(TerrainQuad terrain, int step) {
Material mat = terrain.getMaterial();
MatParamTexture alphaParam = mat.getTextureParam("AlphaMap");
if (alphaParam == null) {
System.err.println("No AlphaMap for terrain quad: " + terrain.getName());
return 0;
}
Texture alphaTex = alphaParam.getTextureValue();
Image alphaImg = alphaTex.getImage();
ByteBuffer buf = alphaImg.getData(0);
int width = alphaImg.getWidth();
int height = alphaImg.getHeight();
int terrainSize = terrain.getTerrainSize();
float alphaMapSize = alphaImg.getWidth();
float sizeFactor = terrainSize / alphaMapSize;
float halfSize = terrainSize / sizeFactor;
System.out.println("terrain size:" + terrainSize);
int placed = 0;
for (int x = -(int) halfSize; x < halfSize; x += step) {
for (int z = -(int) halfSize; z < halfSize; z += step) {
float terrainHeight = terrain.getHeight(new Vector2f(x, z));
if (Float.isNaN(terrainHeight)) continue;
float u = (x + halfSize) / terrainSize;
float v = 1f - ((z + halfSize) / terrainSize);
int texX = Math.min((int) (u * (width - 1)), width - 1);
int texZ = Math.min((int) (v * (height - 1)), height - 1);
int index = (texZ * width + texX) * 4;
if (index < 0 || index + 3 >= buf.limit()) continue;
int r = buf.get(index) & 0xFF;
int g = buf.get(index + 1) & 0xFF;
int b = buf.get(index + 2) & 0xFF;
int a = buf.get(index + 3) & 0xFF;
int[] values = {r, g, b, a};
int dominant = 0;
int max = values[0];
for (int i = 1; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
dominant = i;
}
}
for (Rule rule : rules) {
if (rule.textureIndex == dominant && random.nextFloat() < rule.chance) {
Spatial obj = getCachedModel(rule.modelPath).clone();
// position relative to world transform
//Vector3f worldPos = new Vector3f(x, terrainHeight, z);
Vector3f worldPos = terrain.getParent().localToWorld(new Vector3f(x, terrainHeight, z), null);
// worldPos = terrain.localToWorld(new Vector3f(x, terrainHeight, z), null);
obj.setLocalTranslation(worldPos);
rootNode.attachChild(obj);
placed++;
break;
}
}
}
}
return placed;
}
// which texture index corresponds to which object
public static class Rule {
public final int textureIndex;
public final String modelPath;
public final float chance;
public Rule(int textureIndex, String modelPath, float chance) {
this.textureIndex = textureIndex;
this.modelPath = modelPath;
this.chance = chance;
}
}
// do caching later
private Spatial getCachedModel(String path) {
return assetManager.loadModel(path);
}
}
is there something that I am clearly doing wrong? because im not seeing it
it should be generating those black dots only on the grass
What is the size of your AlphaMap and the size of your terrain?
If they are not equal, then you may need to account for that.
For example, if you have a 512 size terrain but are using 256 size alpha maps, then each pixel in the alpha map represents 4 world units. That would explain why the last screenshot is more accurate when you changed the half size to quarter size.
Although the first screenshot also looks like it may indicate that something else is wrong in the way you are converting your terrain vertex positions from world space to texture space, but I’m not sure.
I also would typically use the JME ImageRaster class for this:
ImageRaster (jMonkeyEngine3)
I think that would be much easier than directly modifying the buffer, since it has a convenient getPixel(int x, int y)
method that is easy to use with a terrain’s X and Z values (still needs translated/rotated/scaled to match the size and orientation of the alphaMap of course)
Im not sure how to see what the size of the terrain is, but each quarter appears to be 64x64, so maybe 128?
the alpha map is 256x256
The terrain has a getTerrainSize()
method you can call to check this.
So in your code, ideally (to account for any combination of sizes of alphaMaps and Terrains) you should add some code like this to find the conversion, and continue to use this value as needed:
float terrainSize = getTerrainSize();
float alphaMapSize = alphaMapImage.getWidth();
float sizeFactor = terrainSize / alphaMapSize;
Otherwise, if you are looping through a 256 size alpha map with a terrain that is only 128 in size, then you will eventually be searching for pixels outside the bounds of the terrain (which may be why you ended up with black dots outside the bounds of your terrain in the first screenshot)
Also (and I cant tell if you’ve done this or not based on just having looked at your code at a glance) you need to make sure you account for the fact that a texture’s 0,0 coordinate is at the corner of the texture, whereas for a terrain the 0,0,0 value in local space (the origin) is at the center of the terrain.
each terrain is 257 big
If the alpha map and terrain are equal size, then my next guess would be this:
So the 0,0 pixel in your alphaMap texture is not representing the terrain vertex at 0,0,0
0,0 in texture space actually will be storing the color for the terrain vertex at (-128, 0, 128) for a 257 size terrain, so you need to account for that offest when moving back and forth between texture space and world space.
I have tried these two things, however they both generate everything at an offset?
Vector3f worldPos = new Vector3f(x, terrainHeight, z);
Vector3f worldPos = terrain.getParent().localToWorld(new Vector3f(x, terrainHeight, z), null);
I changed
float u = (x + halfSize) / terrainSize;
float v = (z + halfSize) / terrainSize;
to
float u = (x + halfSize) / terrainSize;
float v = 1f - ((z + halfSize) / terrainSize);
and it looks much better, but it is offset, and maybe compacted?
The thing that confuses me most is that i get the terrain height from a location, then later it is placed at that location using the exact same variables that are used to get the height, however the thing is placed at a different location with that height? the variables are never changed.