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')
}