Browse Source

Partial fix for Issue #2087 (NegativeScaleTest) (#2091)

* solution drafted

* solution tested and confirmed

* tested scale component combinations

* removed unnecessary import

* fixed instatiation of a new object every frame
codex 1 year ago
parent
commit
5975b3f791

+ 28 - 8
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -890,16 +890,36 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         return type == VarType.BufferObject;
     }
 
-    private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
+    private void updateRenderState(Geometry geometry, RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
         if (renderManager.getForcedRenderState() != null) {
-            renderer.applyRenderState(renderManager.getForcedRenderState());
+            mergedRenderState.set(renderManager.getForcedRenderState());
+        } else if (techniqueDef.getRenderState() != null) {
+            // copyMergedTo writes to mergedRenderState
+            techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState);
         } else {
-            if (techniqueDef.getRenderState() != null) {
-                renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
-            } else {
-                renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
-            }
+            RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState);
         }
+        // test if the face cull mode should be flipped before render
+        if (mergedRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) {
+            mergedRenderState.flipFaceCull();
+        }
+        renderer.applyRenderState(mergedRenderState);
+    }
+    
+    /**
+     * Returns true if the geometry world scale indicates that normals will be backward.
+     * @param scalar geometry world scale
+     * @return 
+     */
+    private boolean isNormalsBackward(Vector3f scalar) {
+        // count number of negative scalar vector components
+        int n = 0;
+        if (scalar.x < 0) n++;
+        if (scalar.y < 0) n++;
+        if (scalar.z < 0) n++;
+        // An odd number of negative components means the normal vectors
+        // are backward to what they should be.
+        return n == 1 || n == 3;
     }
     
     /**
@@ -1028,7 +1048,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
 
         // Apply render state
-        updateRenderState(renderManager, renderer, techniqueDef);
+        updateRenderState(geometry, renderManager, renderer, techniqueDef);
 
         // Get world overrides
         SafeArrayList<MatParamOverride> overrides = geometry.getWorldMatParamOverrides();

+ 25 - 0
jme3-core/src/main/java/com/jme3/material/RenderState.java

@@ -1711,4 +1711,29 @@ public class RenderState implements Cloneable, Savable {
                 + (blendMode.equals(BlendMode.Custom)? "\ncustomBlendFactors=("+sfactorRGB+", "+dfactorRGB+", "+sfactorAlpha+", "+dfactorAlpha+")":"")
                 +"\n]";
     }
+    
+    /**
+     * Flips the given face cull mode so that {@code Back} becomes
+     * {@code Front} and {@code Front} becomes {@code Back}.
+     * <p>{@code FrontAndBack} and {@code Off} are unaffected. This is important
+     * for flipping the cull mode when normal vectors are found to be backward.
+     * @param cull
+     * @return flipped cull mode
+     */
+    public void flipFaceCull() {
+        switch (cullMode) {
+            case Back:  cullMode = FaceCullMode.Front; break;
+            case Front: cullMode = FaceCullMode.Back;  break;
+        }
+    }
+    
+    /**
+     * Checks if the face cull mode is "flippable".
+     * <p>The cull mode is flippable when it is either {@code Front} or {@code Back}.
+     * @return 
+     */
+    public boolean isFaceCullFlippable() {
+        return cullMode == FaceCullMode.Front || cullMode == FaceCullMode.Back;
+    }
+    
 }

+ 5 - 2
jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java

@@ -134,7 +134,7 @@ public class TestGltfLoading extends SimpleApplication {
         //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f);
         //loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f);
 //        loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
-        loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
+        loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, 1, 0), 1);
 //        loadModel("Models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", Vector3f.ZERO, 1);
 //        loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
 ////        loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
@@ -226,10 +226,13 @@ public class TestGltfLoading extends SimpleApplication {
     }
 
     private void loadModel(String path, Vector3f offset, float scale) {
+        loadModel(path, offset, new Vector3f(scale, scale, scale));
+    }
+    private void loadModel(String path, Vector3f offset, Vector3f scale) {
         GltfModelKey k = new GltfModelKey(path);
         //k.setKeepSkeletonPose(true);
         Spatial s = assetManager.loadModel(k);
-        s.scale(scale);
+        s.scale(scale.x, scale.y, scale.z);
         s.move(offset);
         assets.add(s);
         if (playAnim) {