diff --git a/.gitignore b/.gitignore index 606f718e9..5e7951c48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.gradle/ /.nb-gradle/private/ /.nb-gradle/profiles/private/ +/.idea/ /dist/ /build/ /netbeans/ @@ -127,6 +128,7 @@ *.so *.jnilib *.dylib +*.iml /sdk/dist/ !/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll !/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll diff --git a/common.gradle b/common.gradle index 69b695ecd..237ee5e7f 100644 --- a/common.gradle +++ b/common.gradle @@ -24,7 +24,9 @@ configurations { dependencies { // Adding dependencies here will add the dependencies to each subproject. - testCompile group: 'junit', name: 'junit', version: '4.10' + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.mockito', name: 'mockito-core', version: '2.0.28-beta' + testCompile group: 'org.easytesting', name: 'fest-assert-core', version: '2.0M10' deployerJars "org.apache.maven.wagon:wagon-ssh:2.9" } diff --git a/jme3-core/build.gradle b/jme3-core/build.gradle index 2a45d440b..8d55c3e76 100644 --- a/jme3-core/build.gradle +++ b/jme3-core/build.gradle @@ -10,6 +10,11 @@ sourceSets { srcDir 'src/tools/java' } } + test { + java { + srcDir 'src/test/java' + } + } } task updateVersionPropertiesFile << { diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index d0620ca59..13a8e0232 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/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,146 @@ public class J3MLoader implements AssetLoader { technique.setShadowMode(sm); } - private Object readValue(VarType type, String value) throws IOException{ - if (type.isTextureType()){ -// String texturePath = readString("[\n;(//)(\\})]"); - 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; + private List tokenizeTextureValue(final String value) { + final List matchList = new ArrayList(); + 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()); } + } - 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; - } - 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; + return matchList; + } + + private List parseTextureOptions(final List values) { + final List matchList = new ArrayList(); + + 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("/") && !values.get(0).equals("Flip") && !values.get(0).equals("Repeat")) { + 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)); } - if (tex != null){ - if (repeat){ - tex.setWrap(WrapMode.Repeat); + } + + 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 textureValues = tokenizeTextureValue(value); + final List textureOptionValues = parseTextureOptions(textureValues); + + TextureKey textureKey = null; + + // 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), false); + } else { + String texturePath = value.trim(); + + // 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)) { + boolean flipY = false; + + if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) { + texturePath = texturePath.substring(12).trim(); + flipY = true; + } else if (texturePath.startsWith("Flip ")) { + texturePath = texturePath.substring(5).trim(); + flipY = true; + } else if (texturePath.startsWith("Repeat ")) { + texturePath = texturePath.substring(7).trim(); } - }else{ - tex = new Texture2D(PlaceholderAssets.getPlaceholderImage(assetManager)); - if (repeat){ - tex.setWrap(WrapMode.Repeat); + + // 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); } - tex.setKey(texKey); - tex.setName(texKey.getName()); + + textureKey = new TextureKey(texturePath, flipY); } - return tex; - }else{ + + if (textureKey == null) { + textureKey = new TextureKey(textureValues.get(textureValues.size() - 1), false); + } + + // Apply texture options to the texture key + if (!textureOptionValues.isEmpty()) { + for (final TextureOptionValue textureOptionValue : textureOptionValues) { + textureOptionValue.applyToTextureKey(textureKey); + } + } + } + + 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()); + } + + // 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 +708,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. + *

+ * Example: + *

+     *     DiffuseMap: MinTrilinear MagBilinear WrapRepeat_S "some/path/to a/texture.png"
+     *     
+ * This would apply a minification filter of "Trilinear", a magnification filter of "Bilinear" and set the wrap mode to "Repeat". + *

+ *

+ * Note: If several filters of the same type are added, eg. MinTrilinear MinNearestLinearMipMap, the last one will win. + *

+ */ + 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
WrapRepeat_S
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); + } + } } diff --git a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java new file mode 100644 index 000000000..6438baaac --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java @@ -0,0 +1,117 @@ +package com.jme3.material.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.material.MatParamTexture; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Daniel Johansson + * @since 2015-07-20 + */ +@RunWith(MockitoJUnitRunner.class) +public class J3MLoaderTest { + + private J3MLoader j3MLoader; + + @Mock + private AssetInfo assetInfo; + + @Mock + private AssetManager assetManager; + + @Mock + private AssetKey assetKey; + + @Mock + private MaterialDef materialDef; + + @Before + public void setUp() throws Exception { + when(assetKey.getExtension()).thenReturn(".j3m"); + when(assetInfo.getManager()).thenReturn(assetManager); + when(assetInfo.getKey()).thenReturn(assetKey); + when(assetManager.loadAsset(any(AssetKey.class))).thenReturn(materialDef); + + j3MLoader = new J3MLoader(); + } + + @Test + public void oldStyleTextureParameters_shouldBeSupported() throws Exception { + when(assetInfo.openStream()).thenReturn(J3MLoader.class.getResourceAsStream("/texture-parameters-oldstyle.j3m")); + + final Texture textureOldStyle = Mockito.mock(Texture.class); + final Texture textureOldStyleUsingQuotes = Mockito.mock(Texture.class); + + final TextureKey textureKeyUsingQuotes = setupMockForTexture("OldStyleUsingQuotes", "old style using quotes/texture.png", true, textureOldStyleUsingQuotes); + final TextureKey textureKeyOldStyle = setupMockForTexture("OldStyle", "old style/texture.png", true, textureOldStyle); + + j3MLoader.load(assetInfo); + + verify(assetManager).loadTexture(textureKeyUsingQuotes); + verify(assetManager).loadTexture(textureKeyOldStyle); + verify(textureOldStyle).setWrap(Texture.WrapMode.Repeat); + verify(textureOldStyleUsingQuotes).setWrap(Texture.WrapMode.Repeat); + } + + @Test + public void newStyleTextureParameters_shouldBeSupported() throws Exception { + when(assetInfo.openStream()).thenReturn(J3MLoader.class.getResourceAsStream("/texture-parameters-newstyle.j3m")); + + final Texture textureNoParameters = Mockito.mock(Texture.class); + final Texture textureFlip = Mockito.mock(Texture.class); + final Texture textureRepeat = Mockito.mock(Texture.class); + final Texture textureRepeatAxis = Mockito.mock(Texture.class); + final Texture textureMin = Mockito.mock(Texture.class); + final Texture textureMag = Mockito.mock(Texture.class); + final Texture textureCombined = Mockito.mock(Texture.class); + + final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters); + final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip); + setupMockForTexture("Repeat", "repeat.png", false, textureRepeat); + setupMockForTexture("RepeatAxis", "repeat-axis.png", false, textureRepeatAxis); + setupMockForTexture("Min", "min.png", false, textureMin); + setupMockForTexture("Mag", "mag.png", false, textureMag); + setupMockForTexture("Combined", "combined.png", true, textureCombined); + + j3MLoader.load(assetInfo); + + verify(assetManager).loadTexture(textureKeyNoParameters); + verify(assetManager).loadTexture(textureKeyFlip); + + verify(textureRepeat).setWrap(Texture.WrapMode.Repeat); + verify(textureRepeatAxis).setWrap(Texture.WrapAxis.T, Texture.WrapMode.Repeat); + verify(textureMin).setMinFilter(Texture.MinFilter.Trilinear); + verify(textureMag).setMagFilter(Texture.MagFilter.Bilinear); + + verify(textureCombined).setMagFilter(Texture.MagFilter.Nearest); + verify(textureCombined).setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + verify(textureCombined).setWrap(Texture.WrapMode.Repeat); + } + + private TextureKey setupMockForTexture(final String paramName, final String path, final boolean flipY, final Texture texture) { + when(materialDef.getMaterialParam(paramName)).thenReturn(new MatParamTexture(VarType.Texture2D, paramName, texture, 0, null)); + + final TextureKey textureKey = new TextureKey(path, flipY); + textureKey.setGenerateMips(true); + + when(assetManager.loadTexture(textureKey)).thenReturn(texture); + + return textureKey; + } +} diff --git a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m new file mode 100644 index 000000000..a7619d948 --- /dev/null +++ b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m @@ -0,0 +1,11 @@ +Material Test : matdef.j3md { + MaterialParameters { + Empty: "empty.png" + Flip: Flip "flip.png" + Repeat: WrapRepeat "repeat.png" + Min: MinTrilinear "min.png" + Mag: MagBilinear "mag.png" + RepeatAxis: WrapRepeat_T "repeat-axis.png" + Combined: MagNearest MinBilinearNoMipMaps Flip WrapRepeat "combined.png" + } +} \ No newline at end of file diff --git a/jme3-core/src/test/resources/texture-parameters-oldstyle.j3m b/jme3-core/src/test/resources/texture-parameters-oldstyle.j3m new file mode 100644 index 000000000..7f34af464 --- /dev/null +++ b/jme3-core/src/test/resources/texture-parameters-oldstyle.j3m @@ -0,0 +1,6 @@ +Material Test : matdef.j3md { + MaterialParameters { + OldStyle: Flip Repeat old style/texture.png + OldStyleUsingQuotes: Repeat Flip "old style using quotes/texture.png" + } +} \ No newline at end of file diff --git a/jme3-jbullet/build.gradle b/jme3-jbullet/build.gradle index bcf1660bc..0e3967ab2 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -16,4 +16,5 @@ dependencies { compile files('../lib/jbullet.jar', '../lib/stack-alloc.jar') compile project(':jme3-core') compile project(':jme3-terrain') +// compile project(':jme3-bullet') }