diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 0c4317a307..07a9b51046 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -950,6 +950,9 @@ private void updateRenderState(Geometry geometry, RenderManager renderManager, R } // test if the face cull mode should be flipped before render if (finalRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) { + if (finalRenderState != mergedRenderState) { + finalRenderState = mergedRenderState.copyFrom(finalRenderState); + } finalRenderState.flipFaceCull(); } renderer.applyRenderState(finalRenderState); diff --git a/jme3-core/src/test/java/com/jme3/material/MaterialTest.java b/jme3-core/src/test/java/com/jme3/material/MaterialTest.java index 97ab7f3209..f50d071c68 100644 --- a/jme3-core/src/test/java/com/jme3/material/MaterialTest.java +++ b/jme3-core/src/test/java/com/jme3/material/MaterialTest.java @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Box; import com.jme3.shader.VarType; @@ -45,8 +46,10 @@ import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.EnumSet; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -153,6 +156,31 @@ public void testForcedColorSpace(){ } + @Test + public void testBackwardNormalsDoNotMutateTechniqueRenderState() throws Exception { + Material material = new Material(new MaterialDef(TestUtil.createAssetManager(), "test")); + Geometry geometry = new Geometry("Geometry", new Box(1, 1, 1)); + geometry.setLocalScale(-1, 1, 1); + TechniqueDef techniqueDef = new TechniqueDef("test", 0); + RenderState techniqueState = new RenderState(); + techniqueDef.setRenderState(techniqueState); + AtomicReference appliedState = new AtomicReference<>(); + RenderManager renderManager = new RenderManager(new NullRenderer() { + @Override + public void applyRenderState(RenderState state) { + appliedState.set(state); + } + }); + + Method updateRenderState = Material.class.getDeclaredMethod("updateRenderState", + Geometry.class, RenderManager.class, Renderer.class, TechniqueDef.class); + updateRenderState.setAccessible(true); + updateRenderState.invoke(material, geometry, renderManager, renderManager.getRenderer(), techniqueDef); + + assertEquals(RenderState.FaceCullMode.Back, techniqueState.getFaceCullMode()); + assertEquals(RenderState.FaceCullMode.Front, appliedState.get().getFaceCullMode()); + } + @Test public void testSelectNamedTechnique_GLSL100Cap() { supportGlsl(100);