Browse Source

Added support for setting minification and magnification filters on a texture in the j3m material file. This also adds support for double and single quoted paths as well as being able to set WrapMode for a specific WrapAxis. This resolves #295

Daniel Johansson 10 years ago
parent
commit
9059eb30e4
1 changed files with 264 additions and 47 deletions
  1. 264 47
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

+ 264 - 47
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -43,7 +43,6 @@ import com.jme3.math.Vector3f;
 import com.jme3.shader.Shader;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
-import com.jme3.texture.Texture.WrapMode;
 import com.jme3.texture.Texture2D;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.PlaceholderAssets;
@@ -52,10 +51,13 @@ import com.jme3.util.blockparser.Statement;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class J3MLoader implements AssetLoader {
 
@@ -126,59 +128,153 @@ public class J3MLoader implements AssetLoader {
         technique.setShadowMode(sm);
     }
 
-    private Object readValue(VarType type, String value) throws IOException{
-        if (type.isTextureType()){
-//            String texturePath = readString("[\n;(//)(\\})]");
+    private List<String> tokenizeTextureValue(final String value) {
+        final List<String> matchList = new ArrayList<String>();
+        final Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
+        final Matcher regexMatcher = regex.matcher(value.trim());
+
+        while (regexMatcher.find()) {
+            if (regexMatcher.group(1) != null) {
+                matchList.add(regexMatcher.group(1));
+            } else if (regexMatcher.group(2) != null) {
+                matchList.add(regexMatcher.group(2));
+            } else {
+                matchList.add(regexMatcher.group());
+            }
+        }
+
+        return matchList;
+    }
+
+    private List<TextureOptionValue> parseTextureOptions(final List<String> values) {
+        final List<TextureOptionValue> matchList = new ArrayList<TextureOptionValue>();
+
+        if (values.isEmpty() || values.size() == 1) {
+            return matchList;
+        }
+
+        // Loop through all but the last value, the last one is going to be the path.
+        for (int i = 0; i < values.size() - 1; i++) {
+            final String value = values.get(i);
+            final TextureOption textureOption = TextureOption.getTextureOption(value);
+
+            if (textureOption == null && !value.contains("\\") && !value.contains("/")) {
+                logger.log(Level.WARNING, "Unknown texture option \"{0}\" encountered for \"{1}\" in material \"{2}\"", new Object[]{value, key, material.getKey().getName()});
+            } else if (textureOption != null){
+                final String option = textureOption.getOptionValue(value);
+                matchList.add(new TextureOptionValue(textureOption, option));
+            }
+        }
+
+        return matchList;
+    }
+
+    private boolean isTexturePathDeclaredTheTraditionalWay(final int numberOfValues, final int numberOfTextureOptions, final String texturePath) {
+        return (numberOfValues > 1 && (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
+                texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "))) || numberOfTextureOptions == 0;
+    }
+
+    private Texture parseTextureType(final VarType type, final String value) {
+        final List<String> textureValues = tokenizeTextureValue(value);
+        final List<TextureOptionValue> textureOptionValues = parseTextureOptions(textureValues);
+
+        TextureKey textureKey = null;
+        boolean repeat = false;
+
+        // If there is only one token on the value, it must be the path to the texture.
+        if (textureValues.size() == 1) {
+            textureKey = new TextureKey(textureValues.get(0));
+        } else {
             String texturePath = value.trim();
             boolean flipY = false;
-            boolean repeat = false;
-            if (texturePath.startsWith("Flip Repeat ")){
-                texturePath = texturePath.substring(12).trim();
-                flipY = true;
-                repeat = true;
-            }else if (texturePath.startsWith("Flip ")){
-                texturePath = texturePath.substring(5).trim();
-                flipY = true;
-            }else if (texturePath.startsWith("Repeat ")){
-                texturePath = texturePath.substring(7).trim();
-                repeat = true;
-            }
 
-            TextureKey texKey = new TextureKey(texturePath, flipY);
-            switch (type) {
-                case Texture3D:
-                    texKey.setTextureTypeHint(Texture.Type.ThreeDimensional);
-                    break;
-                case TextureArray:
-                    texKey.setTextureTypeHint(Texture.Type.TwoDimensionalArray);
-                    break;
-                case TextureCubeMap:
-                    texKey.setTextureTypeHint(Texture.Type.CubeMap);
-                    break;
+            // If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way.
+            if (isTexturePathDeclaredTheTraditionalWay(textureValues.size(), textureOptionValues.size(), texturePath)) {
+                if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) {
+                    texturePath = texturePath.substring(12).trim();
+                    flipY = true;
+                    repeat = true;
+                } else if (texturePath.startsWith("Flip ")) {
+                    texturePath = texturePath.substring(5).trim();
+                    flipY = true;
+                } else if (texturePath.startsWith("Repeat ")) {
+                    texturePath = texturePath.substring(7).trim();
+                    repeat = true;
+                }
+
+                // Support path starting with quotes (double and single)
+                if (texturePath.startsWith("\"") || texturePath.startsWith("'")) {
+                    texturePath = texturePath.substring(1);
+                }
+
+                // Support path ending with quotes (double and single)
+                if (texturePath.endsWith("\"") || texturePath.endsWith("'")) {
+                    texturePath = texturePath.substring(0, texturePath.length() - 1);
+                }
+
+                textureKey = new TextureKey(texturePath, flipY);
             }
-            texKey.setGenerateMips(true);
-
-            Texture tex;
-            try {
-                tex = assetManager.loadTexture(texKey);
-            } catch (AssetNotFoundException ex){
-                logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key});
-                tex = null;
+
+            if (textureKey == null) {
+                textureKey = new TextureKey(textureValues.get(textureValues.size() - 1));
             }
-            if (tex != null){
-                if (repeat){
-                    tex.setWrap(WrapMode.Repeat);
-                }
-            }else{
-                tex = new Texture2D(PlaceholderAssets.getPlaceholderImage(assetManager));
-                if (repeat){
-                    tex.setWrap(WrapMode.Repeat);
+
+            // Apply texture options to the texture key
+            if (!textureOptionValues.isEmpty()) {
+                for (final TextureOptionValue textureOptionValue : textureOptionValues) {
+                    textureOptionValue.applyToTextureKey(textureKey);
                 }
-                tex.setKey(texKey);
-                tex.setName(texKey.getName());
             }
-            return tex;
-        }else{
+        }
+
+        switch (type) {
+            case Texture3D:
+                textureKey.setTextureTypeHint(Texture.Type.ThreeDimensional);
+                break;
+            case TextureArray:
+                textureKey.setTextureTypeHint(Texture.Type.TwoDimensionalArray);
+                break;
+            case TextureCubeMap:
+                textureKey.setTextureTypeHint(Texture.Type.CubeMap);
+                break;
+        }
+
+        textureKey.setGenerateMips(true);
+
+        Texture texture;
+
+        try {
+            texture = assetManager.loadTexture(textureKey);
+        } catch (AssetNotFoundException ex){
+            logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{textureKey, key});
+            texture = null;
+        }
+
+        if (texture == null){
+            texture = new Texture2D(PlaceholderAssets.getPlaceholderImage(assetManager));
+            texture.setKey(textureKey);
+            texture.setName(textureKey.getName());
+        }
+
+        // This is here for backwards compatibility, we need to do this after the texture has been instantiated.
+        if (repeat) {
+            texture.setWrap(Texture.WrapMode.Repeat);
+        }
+
+        // Apply texture options to the texture
+        if (!textureOptionValues.isEmpty()) {
+            for (final TextureOptionValue textureOptionValue : textureOptionValues) {
+                textureOptionValue.applyToTexture(texture);
+            }
+        }
+
+        return texture;
+    }
+
+    private Object readValue(final VarType type, final String value) throws IOException{
+        if (type.isTextureType()) {
+            return parseTextureType(type, value);
+        } else {
             String[] split = value.trim().split(whitespacePattern);
             switch (type){
                 case Float:
@@ -619,4 +715,125 @@ public class J3MLoader implements AssetLoader {
         }
     }
 
+    /**
+     * Texture options allow you to specify how a texture should be initialized by including an option before
+     * the path to the texture in the .j3m file.
+     * <p>
+     *     <b>Example:</b>
+     *     <pre>
+     *     DiffuseMap: MinTrilinear MagBilinear WrapRepeat_S "some/path/to a/texture.png"
+     *     </pre>
+     *     This would apply a minification filter of "Trilinear", a magnification filter of "Bilinear" and set the wrap mode to "Repeat".
+     * </p>
+     * <p>
+     *     <b>Note:</b> If several filters of the same type are added, eg. MinTrilinear MinNearestLinearMipMap, the last one will win.
+     * </p>
+     */
+    private enum TextureOption {
+
+        /**
+         * Applies a {@link com.jme3.texture.Texture.MinFilter} to the texture.
+         */
+        Min {
+            @Override
+            public void applyToTexture(final String option, final Texture texture) {
+                texture.setMinFilter(Texture.MinFilter.valueOf(option));
+            }
+        },
+
+        /**
+         * Applies a {@link com.jme3.texture.Texture.MagFilter} to the texture.
+         */
+        Mag {
+            @Override
+            public void applyToTexture(final String option, final Texture texture) {
+                texture.setMagFilter(Texture.MagFilter.valueOf(option));
+            }
+        },
+
+        /**
+         * Applies a {@link com.jme3.texture.Texture.WrapMode} to the texture. This also supports {@link com.jme3.texture.Texture.WrapAxis}
+         * by adding "_AXIS" to the texture option. For instance if you wanted to repeat on the S (horizontal) axis, you
+         * would use <pre>WrapRepeat_S</pre> as a texture option.
+         */
+        Wrap {
+            @Override
+            public void applyToTexture(final String option, final Texture texture) {
+                final int separatorPosition = option.indexOf("_");
+
+                if (separatorPosition >= option.length() - 2) {
+                    final String axis = option.substring(separatorPosition + 1);
+                    final String mode = option.substring(0, separatorPosition);
+                    final Texture.WrapAxis wrapAxis = Texture.WrapAxis.valueOf(axis);
+                    texture.setWrap(wrapAxis, Texture.WrapMode.valueOf(mode));
+                } else {
+                    texture.setWrap(Texture.WrapMode.valueOf(option));
+                }
+            }
+        },
+
+        /**
+         * Applies a {@link com.jme3.texture.Texture.WrapMode#Repeat} to the texture. This is simply an alias for
+         * WrapRepeat, please use WrapRepeat instead if possible as this may become deprecated later on.
+         */
+        Repeat {
+            @Override
+            public void applyToTexture(final String option, final Texture texture) {
+                Wrap.applyToTexture("Repeat", texture);
+            }
+        },
+
+        /**
+         * Applies flipping on the Y axis to the {@link TextureKey#setFlipY(boolean)}.
+         */
+        Flip {
+            @Override
+            public void applyToTextureKey(final String option, final TextureKey textureKey) {
+                textureKey.setFlipY(true);
+            }
+        };
+
+        public String getOptionValue(final String option) {
+            return option.substring(name().length());
+        }
+
+        public void applyToTexture(final String option, final Texture texture) {
+        }
+
+        public void applyToTextureKey(final String option, final TextureKey textureKey) {
+        }
+
+        public static TextureOption getTextureOption(final String option) {
+            for(final TextureOption textureOption : TextureOption.values()) {
+                if (option.startsWith(textureOption.name())) {
+                    return textureOption;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Internal object used for holding a {@link com.jme3.material.plugins.J3MLoader.TextureOption} and it's value. Also
+     * contains a couple of convenience methods for applying the TextureOption to either a TextureKey or a Texture.
+     */
+    private static class TextureOptionValue {
+
+        private final TextureOption textureOption;
+        private final String value;
+
+        public TextureOptionValue(TextureOption textureOption, String value) {
+            this.textureOption = textureOption;
+            this.value = value;
+        }
+
+        public void applyToTextureKey(final TextureKey textureKey) {
+            textureOption.applyToTextureKey(value, textureKey);
+        }
+
+        public void applyToTexture(final Texture texture) {
+            textureOption.applyToTexture(value, texture);
+        }
+    }
 }