[SOLVED] Need help with TriPlanar texturing on a box

Aren’t texture coords 3D with triplanar mapping? Set the the texture coords the same as the vertex positions + localTranslation.

Edit: I havent even looked at how the triplanar material works, but usually I just use the world vertex positions anyway…

But the way it interprets the world coordinate -> texture coordinate depends on the direction of the face. It’s generally arbitrary because ground plane textures aren’t normally oriented.

…but it looks like along one axis the triplanar mapping may have it’s s, t flipped.

btw, I created a simple test case if someone willing to give it a try.

public class TestBlock extends SimpleApplication {

    public static void main(String[] args) {
        TestBlock main = new TestBlock();
        AppSettings s = new AppSettings(false);
        s.setRenderer(AppSettings.LWJGL_OPENGL3);
        main.setSettings(s);
        main.start();
    }

    public TestBlock() {
        super(new FlyCamAppState());
    }

    @Override
    public void simpleInitApp() {
        initLight();
        createSampleBlock();
    }

    private void initLight() {
        DirectionalLight light = new DirectionalLight();
        light.setDirection(new Vector3f(-0.2f, -1, -0.3f).normalizeLocal());
        rootNode.addLight(light);
    }

    private void createSampleBlock() {
        Node sampleBlock = new Node();
        
        BlockWall wallMesh = new BlockWall(1, 1, 1);
        //MBox wallMesh = new MBox(1, 1, 1, 0, 0, 0, MBox.FRONT_MASK | MBox.BACK_MASK | MBox.LEFT_MASK | MBox.RIGHT_MASK);
        Geometry wallGeo = new Geometry("wall", wallMesh);
        Material wallMat = createBlockMaterial();
        // wall material
        setBlockAlbedoMap(wallMat, "textures/block/dirt/image-7.png", 1f);
        wallGeo.setMaterial(wallMat);

        sampleBlock.attachChild(wallGeo);
        rootNode.attachChild(sampleBlock);
    }

    private Material createBlockMaterial() {
        Material mat = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
        mat.setBoolean("useTriPlanarMapping", true);
        mat.setTexture("AlphaMap", assetManager.loadTexture("textures/terrain/terrain-block-alphamap.png"));
        return mat;
    }

    private void setBlockAlbedoMap(Material blockMat, String albedoMap, float scale) {
        TextureKey key = new TextureKey(albedoMap);
        Texture texture = assetManager.loadTexture(key);
        texture.setWrap(Texture.WrapMode.Repeat);
        blockMat.setTexture("DiffuseMap", texture);
        blockMat.setFloat("DiffuseMap_0_scale", scale);
    }

}

code for BlockWall:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package otm.moona.environment.view;

import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.shape.AbstractBox;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;

/**
 *
 * @author Ali-RS
 */
public class BlockWall extends AbstractBox{
    private static final short[] GEOMETRY_INDICES_DATA = {
         2,  1,  0,  3,  2,  0, // back
         6,  5,  4,  7,  6,  4, // right
        10,  9,  8, 11, 10,  8, // front
        14, 13, 12, 15, 14, 12, // left
        //18, 17, 16, 19, 18, 16, // top
        //22, 21, 20, 23, 22, 20  // bottom
    };

    private static final float[] GEOMETRY_NORMALS_DATA = {
        0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1, // back
        1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0, // right
        0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1, // front
       -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, // left
        //0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0, // top
        //0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0  // bottom
    };

    private static final float[] GEOMETRY_TEXTURE_DATA = {
        1, 0, 0, 0, 0, 1, 1, 1, // back
        0, 0, 0, 1, 1, 1, 1, 0, // right
        1, 0, 0, 0, 0, 1, 1, 1, // front
        1, 0, 0, 0, 0, 1, 1, 1, // left
        //1, 0, 0, 0, 0, 1, 1, 1, // top
        //1, 0, 0, 0, 0, 1, 1, 1  // bottom
    };

    /**
     * Creates a new box.
     * <p>
     * The box has a center of 0,0,0 and extends in the out from the center by
     * the given amount in <em>each</em> direction. So, for example, a box
     * with extent of 0.5 would be the unit cube.
     *
     * @param x the size of the box along the x axis, in both directions.
     * @param y the size of the box along the y axis, in both directions.
     * @param z the size of the box along the z axis, in both directions.
     */
    public BlockWall(float x, float y, float z) {
        super();
        updateGeometry(Vector3f.ZERO, x, y, z);
    }


    /**
     * Constructor instantiates a new <code>Box</code> object.
     * <p>
     * The minimum and maximum point are provided, these two points define the
     * shape and size of the box but not it's orientation or position. You should
     * use the {@link com.jme3.scene.Spatial#setLocalTranslation(com.jme3.math.Vector3f) }
     * and {@link com.jme3.scene.Spatial#setLocalRotation(com.jme3.math.Quaternion) }
     * methods to define those properties.
     * 
     * @param min the minimum point that defines the box.
     * @param max the maximum point that defines the box.
     */
    public BlockWall(Vector3f min, Vector3f max) {
        super();
        updateGeometry(min, max);
    }

    /**
     * Empty constructor for serialization only. Do not use.
     */
    public BlockWall(){
        super();
    }
    
    /**
     * Creates a clone of this box.
     * <p>
     * The cloned box will have '_clone' appended to its name, but all other
     * properties will be the same as this box.
     */
    @Override
    public BlockWall clone() {
        return new BlockWall(xExtent, yExtent, zExtent);
    }

    protected void doUpdateGeometryIndices() {
        if (getBuffer(Type.Index) == null){
            setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
        }
    }

    protected void doUpdateGeometryNormals() {
        if (getBuffer(Type.Normal) == null){
            setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA));
        }
    }

    protected void doUpdateGeometryTextures() {
        if (getBuffer(Type.TexCoord) == null){
            setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
        }
    }

    protected void doUpdateGeometryVertices() {
        FloatBuffer fpb = BufferUtils.createVector3Buffer(24);
        Vector3f[] v = computeVertices();
        fpb.put(new float[] {
                v[0].x, v[0].y, v[0].z, v[1].x, v[1].y, v[1].z, v[2].x, v[2].y, v[2].z, v[3].x, v[3].y, v[3].z, // back
                v[1].x, v[1].y, v[1].z, v[4].x, v[4].y, v[4].z, v[6].x, v[6].y, v[6].z, v[2].x, v[2].y, v[2].z, // right
                v[4].x, v[4].y, v[4].z, v[5].x, v[5].y, v[5].z, v[7].x, v[7].y, v[7].z, v[6].x, v[6].y, v[6].z, // front
                v[5].x, v[5].y, v[5].z, v[0].x, v[0].y, v[0].z, v[3].x, v[3].y, v[3].z, v[7].x, v[7].y, v[7].z, // left
                v[2].x, v[2].y, v[2].z, v[6].x, v[6].y, v[6].z, v[7].x, v[7].y, v[7].z, v[3].x, v[3].y, v[3].z, // top
                v[0].x, v[0].y, v[0].z, v[5].x, v[5].y, v[5].z, v[4].x, v[4].y, v[4].z, v[1].x, v[1].y, v[1].z  // bottom
        });
        setBuffer(Type.Position, 3, fpb);
        updateBound();
    }
}

textures/terrain/terrain-block-alphamap.png :

“textures/block/dirt/image-7.png” :

Try to replace

      vec4 col1 = texture2D( map, coords.yz * scale);

with

      vec4 col1 = texture2D( map, coords.zy * scale);

(note YZ -> ZY )
in the fragment shader.

1 Like

Wow, it solved the problem. :grinning:

Thank you so much @RiccardoBlb :heart:

btw, @pspeed I switched to use Lemur MBox but I needed to add these methods:

public Vector3f getExtents() {
    return extents;
}

public void resize(Vector3f extents) {
    this.extents.set(extents);
    refreshGeometry();
}

I can make a PR if you are OK with these changes.

Yep.

Done:

@pspeed should it also implement Savable?

@Override
public void read(JmeImporter e) throws IOException {
    super.read(e);
    InputCapsule ic = e.getCapsule(this);
    extents = (Vector3f) ic.readSavable("extents", null);
    slices = ic.readIntArray("slices", null);
    sideMask = ic.readInt("sideMask", 0);
}

@Override
public void write(JmeExporter e) throws IOException {
    super.write(e);
    OutputCapsule oc = e.getCapsule(this);
    oc.write(extents, "extents", null);
    oc.write(slices, "slices", null);
    oc.write(sideMask, "sideMask", 0);
}

Also, should we call clearCollisionData after resizing MBox?

Probably after this line?

Yeah, probably. No harm clearing it, I guess.

1 Like

@pspeed, I made a new PR for this:

It is already tested.

Someday, someone should fix JME serialization to work with protected/private constructors. Of course, that probably requires special dispensation in Java 11… but so be it.

3 Likes

Should I fill an issue for this on JME GitHub page?

I don’t know… wouldn’t hurt to keep track of it, I guess.

As long as the module definition allows for reflection access, it should not be an issue.

1 Like

I guess Paul means that it is ugly to impose a public NO-Arg Constructor just for sake of serialization. :slightly_smiling_face:

Hmm… I see what you are saying. I have done it in the past, I will check how I did it.

You just have to use reflection to grab the no-arg constructor and make setAccessible(true) or whatever.

…which just requires special permissions in newer Java, I guess.

1 Like

I made a new thread for it. We can continue the discussion about it there:

2 Likes