[Solved] How to change a Material's MatDef with code?

I’m working on some code that splits a large gltf scene into multple j3o files and saves all of the materials.

Now I need to find a way to change the saved material’s from using the default PbrLighting.j3md to instead use my own forked version called AfflictedPbr.j3md.

I’m not sure if I’m overlooking an easy way to do this, but it appears there is no setMaterialDef() method on the Material class (only a getMaterialDef() method).

And if I create a new material, I am able to copy over all of the AssetParams by iterating through the old material’s AssetParams Collection, however there is no way to copy over all of the data from the material’s AdditionalRenderState; the AdditionalRenderState paramters are not all stored in a list or collection so there’s not a simple way to copy them all over in a loop.

Does anyone know what would be the best way to do this? Thanks.

Oops it turns out I was overlooking a somewhat obvious copy method in the AddtionalRenderState class.

I managed to copy the AdditionalRenderState from the old material to the new material with this code:

materialToSave.getAdditionalRenderState().copyMergedTo(RenderState.ADDITIONAL, newMat.getAdditionalRenderState());       

Although there’s also an option for RenderState.DEFAULT and RenderState.NULL in place of RenderState.ADDITIONAL. Based on the JavaDoc I think I want to use ADDITIONAL here since I’m copying them over from another material: RenderState (jMonkeyEngine3). But I’m not quite sure what an “apply value” is.

As to your original question: no. It’s like wanting to swap a Java class out from under an object. It just can’t be done. obj.setClass(Foo.class) is scary to think about.

As to the other, I’m not sure. I think when I had to do this before, I looked to see what material.clone() does… though it probably has extra access that we don’t. I then would have just tried the different ways and dumped the additional render state values to see what happened.

…I could look around and see if I could find the code. Though in the end, I may have just copied the 2-3 additional render state settings that I was interested in.

If your exploration finds something more specific then post back for future explorers. It’s not an uncommon thing to want to do.

1 Like

I went the hacky way for my test environment. unregister the asset locator, register your custom locator, change the assetkey.name as wanted. But that only works because my shaders have the same parameters and names as stock jme’s

public class IlluminasMigrationLoader extends ClasspathLocator {
    static Field assetNameAccess;

    static {
        try {
            assetNameAccess = AssetKey.class.getDeclaredField("name");
            assetNameAccess.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    public IlluminasMigrationLoader() {
        super();
    }


    @Override
    public void setRootPath(String s) {
        super.setRootPath(s);
    }

    @SneakyThrows
    @Override
    public AssetInfo locate(AssetManager assetManager, AssetKey assetKey) {
        String name = assetKey.getName();
        String newName = IlluminasMigrationUtil.materialMap.get(name);
        if (newName != null) {
            assetNameAccess.set(assetKey, newName);
        }
        return super.locate(assetManager, assetKey);
    }
}
1 Like

My fork of PBRLIghting has all the same parameters as the stock PBR plus some extra ones, but it is also named differently, so unfortunately this way would not work for my case.

It seems the best way I’ve found is to use the renderState.copyMergedTo() method.

Here is the code I ended up coming up with that copies all of the material params and render state data from the old material to the new material with a different j3md:

Material newMat = new Material(matDefToSwapTo);

ArrayList<MatParam> params = new ArrayList(oldMaterial.getParams());

for(MatParam param : params){
    newMat.setParam(param.getName(), param.getVarType(), param.getValue());
}

oldMaterial.getAdditionalRenderState().copyMergedTo(RenderState.DEFAULT, newMat.getAdditionalRenderState());

I still need to do more experimenting to see if RenderState.DEFAULT is the proper input for that method, I think I might actually want to use RenderState.ADDITIONAL but the javadoc is not entirely clear to me and both seem to work to copy the old material properly with the material I’ve been testing with, so I’ll likely figure out if I’m using the wrong one eventually.

It is strange how there is not a straightforward copy method, only a copyMerge method that also merges some other renderState data depending on which one of those 3 RenerState enum options is input as a parameter to the renderState.copyMergedTo() method, but it does work and my issue is solved, so that’s the important part.

It only requires the same parameter names.

But for an converter you have the better solution, mine is only used to replace the materials for testing purpose