How to Grass ? Reborn the Forest**!

I ran a build and its now in the update center

Yeahh, thanks @normen ! Also seems like this version change fix my problem somehow.

For peoples that want to use Grassy Banana here the simplest example to make a GrassArea

    try {
        //Your TerrainQuad where you want your grass to grow, The minimum holder size, and the Grass draw distance
        grassArea = new GrassArea(terrain, 8, assetManager, 75);
        //A x-tiled atlas texture
        grassArea.setColorTexture(assetManager.loadTexture("Textures/tile_1.png"));
        //A dissolve texture, (a perlin noise is one of the best choice)
        grassArea.setDissolveTexture(assetManager.loadTexture("Textures/noise.png"));
        //A density map for the grass planting
        grassArea.addDensityMap(assetManager.loadTexture("Textures/noise_2.png"));
        //A layer of grass linked to a density map 
        grassArea.addLayer(0f, 0.5f, 2f, ColorChannel.RED_CHANNEL, DensityMap.DENSITY_MAP_1, 2f, 3f);
        grassArea.generate();
    } catch (Exception ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
    }
    GrassAreaControl grassAreaControl = new GrassAreaControl(cam);
    grassArea.addControl(grassAreaControl);
    rootNode.attachChild(grassArea);
7 Likes

It will be great if you add documentation to jmonkey Wiki.
Thanks a lot for your great effort.

Thank you for your enthusiasm ! I’ll be honest with you I have many things planned with this plugin but right now I really don’t have the time to work on it. I have 6 big projects going on at my university and I don’t have any time left for JMonkey, but I’ll be back on it around January. The best I can do for you is to provide you my TerrainEditor class which implements the process to edit the grass in real time to do something like that

Here is the class concerned by this, you’ll need some deep investigation because it was a test from myself so it will be really hard for you to find the GrassArea process but with a little bit of patience I’m sure you can do it.

package GameEditor.States;

import GameEditor.GUI.TerrainPanel;
import GameEditor.Utils.ByteBufferUtils;
import GameEditor.Utils.TextureUtils;
import com.Stomrage.grassybanana.GrassArea.GrassArea;
import com.Stomrage.grassybanana.GrassArea.GrassAreaControl;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.collision.CollisionResults;
import com.jme3.input.InputManager;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.MatParam;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import com.jme3.terrain.Terrain;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import tonegod.gui.core.Screen;

/**
 *
 * @author Stomrage
 */
public class TerrainEditState extends AbstractAppState implements ActionListener, AnalogListener {

    private Node rootNode;
    private AssetManager assetManager;
    private InputManager inputManager;
    private Camera cam;
    private final TerrainPanel terrainPanel;
    private final Screen screen;
    private float textureTo = 0;
    private boolean isPressed;
    private int currentTexture = 0;
    private float heightTo = 0;
    private int airbrushRefresh = 0;
    private GrassArea grassArea;

    public enum Brush {

        Raise, Flatten, Noise, Smooth, Lower, Paint, Erase
    };

    public enum TerrainState {

        Deform, Paint, Grass
    }
    TerrainQuad terrain;
    Material mTerrain;
    private Geometry pickerSphere_1;
    private Geometry pickerSphere_2;
    private Geometry pickerSphere_3;
    private float brushSize = 0;
    private float brushStrentgh = 0;
    private Brush currentDeformBrush = Brush.Raise;
    private Brush currentPaintBrush = Brush.Paint;
    private Brush currentGrassBrush = Brush.Raise;
    private TerrainState currentState = TerrainState.Deform;

    public TerrainEditState(SimpleApplication app, TerrainQuad terrain, GrassArea grassArea, Screen screen) {
        this.cam = app.getCamera();
        this.rootNode = app.getRootNode();
        this.assetManager = app.getAssetManager();
        this.inputManager = app.getInputManager();
        this.screen = screen;

        if (terrain == null) {
            this.terrain = createTerrain();
            if (grassArea == null) {
                this.grassArea = createGrassArea();
            } else {
                this.grassArea = grassArea;
            }
        } else {
            this.terrain = terrain;
            if (grassArea == null) {
                this.grassArea = createGrassArea();
            } else {
                this.grassArea = grassArea;
            }
        }
        this.mTerrain = this.terrain.getMaterial();
        TerrainLodControl control = new TerrainLodControl(this.terrain, cam);
        this.grassArea.removeControl(GrassAreaControl.class);
        GrassAreaControl grassAreaControl = new GrassAreaControl(cam);
        this.grassArea.addControl(grassAreaControl);
        this.grassArea.setAutoUpdate(true);
        this.terrain.addControl(control);
        initPickerSpheres();
        registerWithInput(inputManager);
        this.rootNode.attachChild(this.terrain);
        this.rootNode.attachChild(this.grassArea);
        terrainPanel = new TerrainPanel(this.screen, app.getContext().getSettings().getWidth() / 5, app.getContext().getSettings().getHeight(), this);
    }

    private void registerWithInput(InputManager inputManager) {
        String[] mappings = new String[]{
            "TERRAINHANDLER_Click",
            "TERRAINHANDLER_Move"
        };
        inputManager.addMapping("TERRAINHANDLER_Move", new MouseAxisTrigger(0, true));
        inputManager.addMapping("TERRAINHANDLER_Move", new MouseAxisTrigger(0, false));
        inputManager.addMapping("TERRAINHANDLER_Move", new MouseAxisTrigger(1, false));
        inputManager.addMapping("TERRAINHANDLER_Move", new MouseAxisTrigger(1, true));
        inputManager.addMapping("TERRAINHANDLER_Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(this, mappings);
    }

    private TerrainQuad createTerrain() {
        Material m = createTerrainMaterial();
        TerrainQuad t = new TerrainQuad("terrain", 129, 1025, null);
        t.setShadowMode(RenderQueue.ShadowMode.Receive);
        t.setMaterial(m);
        return t;
    }

    private GrassArea createGrassArea() {
        GrassArea a = null;
        try {
            a = new GrassArea(terrain, 8, assetManager, 75f);
            a.setColorTexture(assetManager.loadTexture("Textures/tile_1.png"));
            a.setDissolveTexture(assetManager.loadTexture("Textures/noise_1.png"));
            a.addDensityMap(TextureUtils.createBlackTexture(terrain.getTerrainSize() - 1));
            a.addLayer(0f, 0.5f, 3f, GrassArea.ColorChannel.RED_CHANNEL, GrassArea.DensityMap.DENSITY_MAP_1, 1f, 1.5f);
            a.generate();
            GrassAreaControl grassAreaControl = new GrassAreaControl(cam);
            a.addControl(grassAreaControl);
            a.setAutoUpdate(true);
        } catch (Exception ex) {
            Logger.getLogger(TerrainEditState.class.getName()).log(Level.SEVERE, null, ex);
        }
        return a;
    }

    private Material createTerrainMaterial() {
        Material m = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
        m.setBoolean("useTriPlanarMapping", false);
        m.setFloat("Shininess", 0.0f);
        ByteBuffer buffer = BufferUtils.createByteBuffer(1024 * 1024 * 4);
        for (int x = 0; x < 1024; x++) {
            for (int y = 0; y < 1024; y++) {
                buffer.position((y * 1024 + x) * 4);
                buffer.put(ByteBufferUtils.float2byte(1));
                buffer.put(ByteBufferUtils.float2byte(0));
                buffer.put(ByteBufferUtils.float2byte(0));
                buffer.put(ByteBufferUtils.float2byte(0));
            }
        }
        Image i = new Image(Image.Format.RGBA8, 1024, 1024, buffer);
        m.setTexture("AlphaMap", new Texture2D(i));
        m.setTexture("AlphaMap_1", new Texture2D(i));
        m.setTexture("AlphaMap_2", new Texture2D(i));
        Texture tempTexture = assetManager.loadTexture("Textures/Grass_1.jpg");
        tempTexture.setWrap(Texture.WrapMode.Repeat);
        m.setTexture("DiffuseMap", tempTexture);
        m.setFloat("DiffuseMap_0_scale", 150f);
        return m;
    }

    private void initPickerSpheres() {
        Sphere sphereMesh_1 = new Sphere(32, 32, 2f);
        pickerSphere_1 = new Geometry("Picker_1", sphereMesh_1);
        Material sphereMat_1 = new Material(assetManager,
                "Common/MatDefs/Light/Lighting.j3md");
        sphereMat_1.setBoolean("UseAlpha", true);
        sphereMat_1.setBoolean("UseMaterialColors", true);
        sphereMat_1.setColor("Diffuse", new ColorRGBA(1, 0, 0, 0.25f));
        sphereMat_1.setColor("Ambient", new ColorRGBA(1, 0, 0, 0.25f));
        sphereMat_1.setColor("Specular", new ColorRGBA(1, 0, 0, 0.25f));
        sphereMat_1.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        pickerSphere_1.setMaterial(sphereMat_1);
        pickerSphere_1.setLocalScale(0);
        pickerSphere_1.setQueueBucket(RenderQueue.Bucket.Translucent);

        Sphere sphereMesh_2 = new Sphere(32, 32, 2f);
        pickerSphere_2 = new Geometry("Picker_2", sphereMesh_2);
        Material sphereMat_2 = new Material(assetManager,
                "Common/MatDefs/Light/Lighting.j3md");
        sphereMat_2.setBoolean("UseAlpha", true);
        sphereMat_2.setBoolean("UseMaterialColors", true);
        sphereMat_2.setColor("Diffuse", new ColorRGBA(0, 0, 1, 0.25f));
        sphereMat_2.setColor("Ambient", new ColorRGBA(0, 0, 1, 0.25f));
        sphereMat_2.setColor("Specular", new ColorRGBA(0, 0, 1, 0.25f));
        sphereMat_2.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);

        pickerSphere_2.setMaterial(sphereMat_2);
        pickerSphere_2.setLocalScale(0);
        pickerSphere_2.setQueueBucket(RenderQueue.Bucket.Translucent);

        Sphere sphereMesh_3 = new Sphere(32, 32, 2f);
        pickerSphere_3 = new Geometry("Picker_2", sphereMesh_3);
        Material sphereMat_3 = new Material(assetManager,
                "Common/MatDefs/Light/Lighting.j3md");
        sphereMat_3.setBoolean("UseAlpha", true);
        sphereMat_3.setBoolean("UseMaterialColors", true);
        sphereMat_3.setColor("Diffuse", new ColorRGBA(0, 1, 0, 0.25f));
        sphereMat_3.setColor("Ambient", new ColorRGBA(0, 1, 0, 0.25f));
        sphereMat_3.setColor("Specular", new ColorRGBA(0, 1, 0, 0.25f));
        sphereMat_3.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);

        pickerSphere_3.setMaterial(sphereMat_3);
        pickerSphere_3.setLocalScale(0);
        pickerSphere_3.setQueueBucket(RenderQueue.Bucket.Translucent);
    }

    private Texture getAlphaTexture(Terrain terrain, int alphaLayer) {
        if (terrain == null) {
            return null;
        }
        MatParam matParam = null;
        if (alphaLayer == 0) {
            matParam = terrain.getMaterial(null).getParam("AlphaMap");
        } else if (alphaLayer == 1) {
            matParam = terrain.getMaterial(null).getParam("AlphaMap_1");
        } else if (alphaLayer == 2) {
            matParam = terrain.getMaterial(null).getParam("AlphaMap_2");
        }

        if (matParam == null || matParam.getValue() == null) {
            return null;
        }
        Texture tex = (Texture) matParam.getValue();
        return tex;
    }

    private void adjustTexture(Vector3f loc, float radius, float height) {
        if (currentState == TerrainState.Paint && isPressed) {
            if (currentPaintBrush == Brush.Erase) {
                height = -height;
            }
            int alphaIdx = currentTexture / 4; // 4 = rgba = 4 textures
            Texture tex = getAlphaTexture(terrain, alphaIdx);
            Image image = tex.getImage();
            ColorRGBA color = new ColorRGBA(0, 0, 0, 0);
            float intensity = height * 40;
            switch (currentTexture % 4) {
                case 0: {
                    color = new ColorRGBA(intensity, 0, 0f, 0);
                    break;
                }
                case 1: {
                    color = new ColorRGBA(0, intensity, 0, 0);
                    break;
                }
                case 2: {
                    color = new ColorRGBA(0, 0, intensity, 0);
                    break;
                }
                case 3: {
                    color = new ColorRGBA(0, 0, 0f, intensity);
                    break;
                }
            }

            int radiusStepsX = (int) (radius / terrain.getLocalScale().x);
            int radiusStepsZ = (int) (radius / terrain.getLocalScale().z);

            float xStepAmount = terrain.getLocalScale().x;
            float zStepAmount = terrain.getLocalScale().z;
            for (float z = -radiusStepsZ; z < radiusStepsZ; z += 0.5f) {
                for (float x = -radiusStepsX; x < radiusStepsX; x += 0.5f) {
                    float locX = loc.x + (x * xStepAmount);
                    float locZ = loc.z + (z * zStepAmount);
                    if (isInRadius(locX - loc.x, locZ - loc.z, radius)) {
                        int percentX = (int) (((locX + terrain.getTerrainSize() / 2) / 2) / (terrain.getTerrainSize() / 2) * image.getWidth());
                        int percentZ = (int) ((1 - ((locZ + terrain.getTerrainSize() / 2) / 2) / (terrain.getTerrainSize() / 2)) * image.getWidth());
                        int position = (percentX + percentZ * image.getWidth()) * 4;
                        ByteBuffer buf = image.getData(0);
                        buf.position(position);
                        ColorRGBA tempColor = new ColorRGBA();
                        tempColor.set(ByteBufferUtils.byte2float(buf.get()), ByteBufferUtils.byte2float(buf.get()), ByteBufferUtils.byte2float(buf.get()), ByteBufferUtils.byte2float(buf.get()));
                        buf = image.getData(0);
                        buf.position(position);
                        float val = FastMath.pow(radius, 2) - (FastMath.pow(locX - loc.x, 2) + FastMath.pow(locZ - loc.z, 2));
                        val = (val / FastMath.pow(radius, 2));
                        val = val * textureTo;
                        float colorA = color.a * val + tempColor.a;
                        float colorB = color.b * val + tempColor.b;
                        float colorG = color.g * val + tempColor.g;
                        float colorR = color.r * val + tempColor.r;
                        colorA = Math.max(0, Math.min(1, colorA));
                        colorB = Math.max(0, Math.min(1, colorB));
                        colorG = Math.max(0, Math.min(1, colorG));
                        colorR = Math.max(0, Math.min(1, colorR));
                        buf.put(ByteBufferUtils.float2byte(colorR));
                        buf.put(ByteBufferUtils.float2byte(colorG));
                        buf.put(ByteBufferUtils.float2byte(colorB));
                        buf.put(ByteBufferUtils.float2byte(colorA));
                    }
                }
            }
            tex.getImage().setUpdateNeeded();
        }
    }

    private void adjustHeight(Vector3f loc, float radius, float height) {
        if (currentState == TerrainState.Deform) {
            int radiusStepsX = (int) (radius / terrain.getLocalScale().x);
            int radiusStepsZ = (int) (radius / terrain.getLocalScale().z);

            float xStepAmount = terrain.getLocalScale().x;
            float zStepAmount = 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 = -radiusStepsX; x < radiusStepsX; x++) {

                    float locX = loc.x + (x * xStepAmount);
                    float locZ = loc.z + (z * zStepAmount);

                    if (isInRadius(locX - loc.x, locZ - loc.z, radius)) {
                        float h = 0;
                        switch (currentDeformBrush) {
                            case Raise: {
                                h = raiseTerrain(radius, height, locX - loc.x, locZ - loc.z);
                                break;
                            }
                            case Flatten: {
                                h = flattenTerrain(height, locX, locZ, heightTo);
                                break;
                            }
                            case Noise: {
                                h = noiseTerrain(height);
                                break;
                            }
                            case Smooth: {
                                h = smoothTerrain(height, locX, locZ);
                                break;
                            }
                            case Lower: {
                                h = raiseTerrain(radius, -height, locX - loc.x, locZ - loc.z);
                                break;
                            }
                        }
                        locs.add(new Vector2f(locX, locZ));
                        heights.add(h);
                    }
                }
            }

            terrain.adjustHeight(locs, heights);
            terrain.updateModelBound();
        }
    }

    private float smoothTerrain(float height, float x, float z) {
        Vector2f terrainLoc = new Vector2f(x, z);
        // 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 (!Float.isNaN(left)) {
            amount += left;
            count++;
        }
        if (!Float.isNaN(right)) {
            amount += right;
            count++;
        }
        if (!Float.isNaN(up)) {
            amount += up;
            count++;
        }
        if (!Float.isNaN(down)) {
            amount += down;
            count++;
        }

        amount /= count; // take average

        // weigh it
        float value = amount - center;
        value *= height * 40;
        return value;
    }

    private float noiseTerrain(float height) {
        float value = 5 * (float) (FastMath.rand.nextGaussian() * height);
        return value;
    }

    private boolean isInRadius(float x, float y, float radius) {
        Vector2f point = new Vector2f(x, y);
        return point.length() <= radius;
    }

    private float flattenTerrain(float height, float locX, float locZ, float targetHeight) {
        float curr = terrain.getHeightmapHeight(new Vector2f(locX, locZ));
        float value;
        if (curr > targetHeight) {
            value = -height * 70;
        } else {
            value = height * 70;
        }
        return value;
    }

    private float raiseTerrain(float radius, float heightFactor, float x, float z) {
        float val = FastMath.pow(radius, 2) - (FastMath.pow(x, 2) + FastMath.pow(z, 2));
        return heightFactor * val;
    }

    public void onAction(String name, boolean value, float tpf) {
        if (isEnabled() && isInitialized()) {
            if (name.equals("TERRAINHANDLER_Click") && value) {
                isPressed = true;
                adjustTexture(pickerSphere_2.getLocalTranslation(), brushSize, brushStrentgh);
                textureTo = brushStrentgh * 4 * 100;
                heightTo = terrain.getHeight(new Vector2f(pickerSphere_1.getLocalTranslation().x, pickerSphere_1.getLocalTranslation().z));
            } else {
                isPressed = false;
            }
        }
    }

    public void onAnalog(String name, float value, float tpf) {
        if (isEnabled() && isInitialized()) {
            if (name.equals("TERRAINHANDLER_Move")) {
                CollisionResults results = new CollisionResults();
                Ray ray = createRay();
                terrain.collideWith(ray, results);
                if (results.size() > 0) {
                    Vector3f target = results.getClosestCollision().getContactPoint();
                    switch (currentState) {
                        case Deform: {
                            pickerSphere_1.setLocalTranslation(target);
                            break;
                        }
                        case Grass: {
                            pickerSphere_3.setLocalTranslation(target);
                            break;
                        }
                        case Paint: {
                            pickerSphere_2.setLocalTranslation(target);
                            break;
                        }
                    }
                }
                airbrushRefresh++;
                if (airbrushRefresh > 5) {
                    adjustTexture(pickerSphere_2.getLocalTranslation(), brushSize, brushStrentgh);
                    airbrushRefresh = 0;
                }
            } else if (name.equals("TERRAINHANDLER_Click")) {
                adjustHeight(pickerSphere_1.getLocalTranslation(), brushSize, brushStrentgh);
                adjustGrass(pickerSphere_3.getLocalTranslation(), brushSize, brushStrentgh);
            }
        }
    }

    private void adjustGrass(Vector3f loc, float radius, float height) {
        if (currentState == TerrainState.Grass) {
            int radiusStepsX = (int) (radius / terrain.getLocalScale().x);
            int radiusStepsZ = (int) (radius / terrain.getLocalScale().z);

            float xStepAmount = terrain.getLocalScale().x;
            float zStepAmount = terrain.getLocalScale().z;
            float[][] heights = new float[radiusStepsZ * 2][radiusStepsZ * 2];
            for (int z = -radiusStepsZ; z < radiusStepsZ; z++) {
                for (int x = -radiusStepsX; x < radiusStepsX; x++) {
                    heights[z + radiusStepsZ][x + radiusStepsZ] = 0;
                    float locX = loc.x + (x * xStepAmount);
                    float locZ = loc.z + (z * zStepAmount);

                    if (isInRadius(locX - loc.x, locZ - loc.z, radius)) {
                        float h = 0;
                        switch (currentGrassBrush) {
                            case Raise: {
                                h = raiseTerrain(radius, height, locX - loc.x, locZ - loc.z);
                                break;
                            }
                            case Flatten: {
                                h = flattenTerrain(height, locX, locZ, heightTo);
                                break;
                            }
                            case Noise: {
                                h = noiseTerrain(height);
                                break;
                            }
                            case Smooth: {
                                h = smoothTerrain(height, locX, locZ);
                                break;
                            }
                            case Lower: {
                                h = raiseTerrain(radius, -height, locX - loc.x, locZ - loc.z);
                                break;
                            }
                        }
                        heights[z + radiusStepsZ][x + radiusStepsZ] = h;
                    }
                }
            }
            grassArea.adjustDensity(GrassArea.DensityMap.DENSITY_MAP_1, GrassArea.ColorChannel.RED_CHANNEL, heights, radiusStepsZ * 2, new Vector2f(loc.x - radiusStepsZ, loc.z - radiusStepsZ));
        }
    }

    private Ray createRay() {
        Vector2f click2d = inputManager.getCursorPosition();
        Vector3f click3d = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
        Vector3f dir = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d).normalizeLocal();
        Ray ray = new Ray(click3d, dir);
        return ray;
    }

    @Override
    public void stateAttached(AppStateManager stateManager) {
        screen.addElement(terrainPanel);

        rootNode.attachChild(pickerSphere_1);
        rootNode.attachChild(pickerSphere_2);
        rootNode.attachChild(pickerSphere_3);
    }

    @Override
    public void stateDetached(AppStateManager stateManager) {
        screen.removeElement(terrainPanel);

        rootNode.detachChild(pickerSphere_1);
        rootNode.detachChild(pickerSphere_2);
        rootNode.detachChild(pickerSphere_3);
    }

    public void setBrush(Brush brush) {
        switch (currentState) {
            case Deform: {
                this.currentDeformBrush = brush;
                break;
            }
            case Grass: {
                this.currentGrassBrush = brush;
                break;
            }
            case Paint: {
                this.currentPaintBrush = brush;
                break;
            }
        }
    }

    public void setState(TerrainState state) {
        switch (state) {
            case Deform: {
                pickerSphere_1.setCullHint(Spatial.CullHint.Dynamic);
                pickerSphere_2.setCullHint(Spatial.CullHint.Always);
                pickerSphere_3.setCullHint(Spatial.CullHint.Always);
                break;
            }
            case Grass: {
                pickerSphere_1.setCullHint(Spatial.CullHint.Always);
                pickerSphere_2.setCullHint(Spatial.CullHint.Always);
                pickerSphere_3.setCullHint(Spatial.CullHint.Dynamic);
                break;
            }
            case Paint: {
                pickerSphere_1.setCullHint(Spatial.CullHint.Always);
                pickerSphere_2.setCullHint(Spatial.CullHint.Dynamic);
                pickerSphere_3.setCullHint(Spatial.CullHint.Always);
                break;
            }
        }
        this.currentState = state;
    }

    public void brushSize(int value) {
        float realSize = value / 10;
        this.brushSize = realSize;
        switch (currentState) {
            case Deform: {
                pickerSphere_1.setLocalScale(realSize / 2);
                break;
            }
            case Grass: {
                pickerSphere_3.setLocalScale(realSize / 2);
                break;
            }
            case Paint: {
                pickerSphere_2.setLocalScale(realSize / 2);
                break;
            }
        }
    }

    public Material getTerrainMaterial() {
        return terrain.getMaterial();
    }

    public void brushStrentgh(int value) {
        float realStrength = ((float) value) / 100000;
        this.brushStrentgh = realStrength;
    }

    public void setCurrentTexture(int id) {
        this.currentTexture = id;
    }

    public void setTexture(String text, int idTexture) {
        if (text.equals("")) {
            return;
        }
        String texture = "DiffuseMap";
        if (idTexture != 0) {
            texture += "_" + idTexture;
        }
        Material mat = terrain.getMaterial();
        Texture tempTexture = assetManager.loadTexture(text);
        tempTexture.setWrap(Texture.WrapMode.Repeat);
        mat.setTexture(texture, tempTexture);
        mat.setFloat("DiffuseMap_" + idTexture + "_scale", 1f);
        terrain.setMaterial(mat);
    }

    public void setNormalTexture(String text, int idTexture) {
        if (text.equals("")) {
            return;
        }
        String texture = "NormalMap";
        if (idTexture != 0) {
            texture += "_" + idTexture;
        }
        Material mat = terrain.getMaterial();
        Texture tempTexture = assetManager.loadTexture(text);
        tempTexture.setWrap(Texture.WrapMode.Repeat);
        mat.setTexture(texture, tempTexture);
        terrain.setMaterial(mat);
    }

    public void setScale(String s, int index) {
        Material mat = terrain.getMaterial();
        mat.setFloat("DiffuseMap_" + index + "_scale", Float.parseFloat(s));
        terrain.setMaterial(mat);
    }
}
5 Likes

I updated the library for JMonkey 3.1. It now run correctly but I’ve got some strange behaviour regarding the grass material. I couldn’t figure it out but i’ll try fix it at some point. (By the way, if you want to help investigate this issue you are welcome)

:rainbow: GitHub - Stomrage/GrassArea :rainbow:

5 Likes

I only can dream it last night this grass, but now this stuff is already real.