Browse Source

Merge pull request #2482 from capdevon/capdevon-MatShadowSer

Feat: Add receivesShadows serialization for Material
Ryan McDonough 4 weeks ago
parent
commit
2d8e4df747

+ 2 - 0
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -1150,6 +1150,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         oc.write(def.getAssetName(), "material_def", null);
         oc.write(def.getAssetName(), "material_def", null);
         oc.write(additionalState, "render_state", null);
         oc.write(additionalState, "render_state", null);
         oc.write(transparent, "is_transparent", false);
         oc.write(transparent, "is_transparent", false);
+        oc.write(receivesShadows, "receives_shadows", false);
         oc.write(name, "name", null);
         oc.write(name, "name", null);
         oc.writeStringSavableMap(paramValues, "parameters", null);
         oc.writeStringSavableMap(paramValues, "parameters", null);
     }
     }
@@ -1162,6 +1163,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         name = ic.readString("name", null);
         name = ic.readString("name", null);
         additionalState = (RenderState) ic.readSavable("render_state", null);
         additionalState = (RenderState) ic.readSavable("render_state", null);
         transparent = ic.readBoolean("is_transparent", false);
         transparent = ic.readBoolean("is_transparent", false);
+        receivesShadows = ic.readBoolean("receives_shadows", false);
 
 
         // Load the material def
         // Load the material def
         String defName = ic.readString("material_def", null);
         String defName = ic.readString("material_def", null);

+ 48 - 36
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -62,7 +62,6 @@ import java.util.regex.Pattern;
 public class J3MLoader implements AssetLoader {
 public class J3MLoader implements AssetLoader {
 
 
     private static final Logger logger = Logger.getLogger(J3MLoader.class.getName());
     private static final Logger logger = Logger.getLogger(J3MLoader.class.getName());
-   // private ErrorLogger errors;
     private ShaderNodeLoaderDelegate nodesLoaderDelegate;
     private ShaderNodeLoaderDelegate nodesLoaderDelegate;
     boolean isUseNodes = false;
     boolean isUseNodes = false;
     int langSize = 0;
     int langSize = 0;
@@ -86,7 +85,6 @@ public class J3MLoader implements AssetLoader {
         shaderNames = new EnumMap<>(Shader.ShaderType.class);
         shaderNames = new EnumMap<>(Shader.ShaderType.class);
     }
     }
 
 
-
     // <TYPE> <LANG> : <SOURCE>
     // <TYPE> <LANG> : <SOURCE>
     private void readShaderStatement(String statement) throws IOException {
     private void readShaderStatement(String statement) throws IOException {
         String[] split = statement.split(":");
         String[] split = statement.split(":");
@@ -103,7 +101,6 @@ public class J3MLoader implements AssetLoader {
         }
         }
     }
     }
 
 
-
     private void readShaderDefinition(Shader.ShaderType shaderType, String name, String... languages) {
     private void readShaderDefinition(Shader.ShaderType shaderType, String name, String... languages) {
         shaderNames.put(shaderType, name);
         shaderNames.put(shaderType, name);
 
 
@@ -129,15 +126,15 @@ public class J3MLoader implements AssetLoader {
         LightMode lm = LightMode.valueOf(split[1]);
         LightMode lm = LightMode.valueOf(split[1]);
         technique.setLightMode(lm);
         technique.setLightMode(lm);
     }
     }
-    
-    
+
+
     // LightMode <SPACE>
     // LightMode <SPACE>
     private void readLightSpace(String statement) throws IOException{
     private void readLightSpace(String statement) throws IOException{
         String[] split = statement.split(whitespacePattern);
         String[] split = statement.split(whitespacePattern);
         if (split.length != 2){
         if (split.length != 2){
             throw new IOException("LightSpace statement syntax incorrect");
             throw new IOException("LightSpace statement syntax incorrect");
         }
         }
-        TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]);        
+        TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]);
         technique.setLightSpace(ls);
         technique.setLightSpace(ls);
     }
     }
 
 
@@ -297,7 +294,7 @@ public class J3MLoader implements AssetLoader {
             for (final TextureOptionValue textureOptionValue : textureOptionValues) {
             for (final TextureOptionValue textureOptionValue : textureOptionValues) {
                 textureOptionValue.applyToTexture(texture);
                 textureOptionValue.applyToTexture(texture);
             }
             }
-        }        
+        }
         return texture;
         return texture;
     }
     }
 
 
@@ -311,28 +308,28 @@ public class J3MLoader implements AssetLoader {
                     if (split.length != 1){
                     if (split.length != 1){
                         throw new IOException("Float value parameter must have 1 entry: " + value);
                         throw new IOException("Float value parameter must have 1 entry: " + value);
                     }
                     }
-                     return Float.parseFloat(split[0]);
+                    return Float.parseFloat(split[0]);
                 case Vector2:
                 case Vector2:
                     if (split.length != 2){
                     if (split.length != 2){
                         throw new IOException("Vector2 value parameter must have 2 entries: " + value);
                         throw new IOException("Vector2 value parameter must have 2 entries: " + value);
                     }
                     }
                     return new Vector2f(Float.parseFloat(split[0]),
                     return new Vector2f(Float.parseFloat(split[0]),
-                                                               Float.parseFloat(split[1]));
+                            Float.parseFloat(split[1]));
                 case Vector3:
                 case Vector3:
                     if (split.length != 3){
                     if (split.length != 3){
                         throw new IOException("Vector3 value parameter must have 3 entries: " + value);
                         throw new IOException("Vector3 value parameter must have 3 entries: " + value);
                     }
                     }
                     return new Vector3f(Float.parseFloat(split[0]),
                     return new Vector3f(Float.parseFloat(split[0]),
-                                                               Float.parseFloat(split[1]),
-                                                               Float.parseFloat(split[2]));
+                            Float.parseFloat(split[1]),
+                            Float.parseFloat(split[2]));
                 case Vector4:
                 case Vector4:
                     if (split.length != 4){
                     if (split.length != 4){
                         throw new IOException("Vector4 value parameter must have 4 entries: " + value);
                         throw new IOException("Vector4 value parameter must have 4 entries: " + value);
                     }
                     }
                     return new ColorRGBA(Float.parseFloat(split[0]),
                     return new ColorRGBA(Float.parseFloat(split[0]),
-                                                                Float.parseFloat(split[1]),
-                                                                Float.parseFloat(split[2]),
-                                                                Float.parseFloat(split[3]));
+                            Float.parseFloat(split[1]),
+                            Float.parseFloat(split[2]),
+                            Float.parseFloat(split[3]));
                 case Int:
                 case Int:
                     if (split.length != 1){
                     if (split.length != 1){
                         throw new IOException("Int value parameter must have 1 entry: " + value);
                         throw new IOException("Int value parameter must have 1 entry: " + value);
@@ -536,12 +533,12 @@ public class J3MLoader implements AssetLoader {
             MatParam param = materialDef.getMaterialParam(paramName);
             MatParam param = materialDef.getMaterialParam(paramName);
             if (param == null) {
             if (param == null) {
                 logger.log(Level.WARNING, "In technique ''{0}'':\n"
                 logger.log(Level.WARNING, "In technique ''{0}'':\n"
-                        + "Define ''{1}'' mapped to non-existent"
-                        + " material parameter ''{2}'', ignoring.",
+                                + "Define ''{1}'' mapped to non-existent"
+                                + " material parameter ''{2}'', ignoring.",
                         new Object[]{technique.getName(), defineName, paramName});
                         new Object[]{technique.getName(), defineName, paramName});
                 return;
                 return;
             }
             }
-            
+
             VarType paramType = param.getVarType();
             VarType paramType = param.getVarType();
             technique.addShaderParamDefine(paramName, paramType, defineName);
             technique.addShaderParamDefine(paramName, paramType, defineName);
         }else{
         }else{
@@ -553,7 +550,6 @@ public class J3MLoader implements AssetLoader {
         for (Statement statement : defineList){
         for (Statement statement : defineList){
             readDefine(statement.getLine());
             readDefine(statement.getLine());
         }
         }
-
     }
     }
 
 
     private void readTechniqueStatement(Statement statement) throws IOException{
     private void readTechniqueStatement(Statement statement) throws IOException{
@@ -600,12 +596,23 @@ public class J3MLoader implements AssetLoader {
         }
         }
     }
     }
 
 
-    private void readTransparentStatement(String statement) throws IOException{
+    private void readTransparentStatement(String statement) throws IOException {
+        boolean transparent = readBooleanStatement(statement, "Transparent");
+        material.setTransparent(transparent);
+    }
+
+    private void readReceivesShadowsStatement(String statement) throws IOException {
+        boolean receivesShadows = readBooleanStatement(statement, "ReceivesShadows");
+        material.setReceivesShadows(receivesShadows);
+    }
+    
+    private boolean readBooleanStatement(String statement, String propertyName) throws IOException {
         String[] split = statement.split(whitespacePattern);
         String[] split = statement.split(whitespacePattern);
-        if (split.length != 2){
-            throw new IOException("Transparent statement syntax incorrect");
+        if (split.length != 2) {
+            throw new IOException(propertyName + " statement syntax incorrect");
         }
         }
-        material.setTransparent(parseBoolean(split[1]));
+        
+        return parseBoolean(split[1]);
     }
     }
     
     
     private static String createShaderPrologue(List<String> presetDefines) {
     private static String createShaderPrologue(List<String> presetDefines) {
@@ -665,7 +672,7 @@ public class J3MLoader implements AssetLoader {
 
 
         if(isUseNodes){
         if(isUseNodes){
             //used for caching later, the shader here is not a file.
             //used for caching later, the shader here is not a file.
-            
+
             // KIRILL 9/19/2015
             // KIRILL 9/19/2015
             // Not sure if this is needed anymore, since shader caching
             // Not sure if this is needed anymore, since shader caching
             // is now done by TechniqueDef.
             // is now done by TechniqueDef.
@@ -722,9 +729,11 @@ public class J3MLoader implements AssetLoader {
         boolean extending = false;
         boolean extending = false;
         Statement materialStat = roots.get(0);
         Statement materialStat = roots.get(0);
         String materialName = materialStat.getLine();
         String materialName = materialStat.getLine();
+        
         if (materialName.startsWith("MaterialDef")){
         if (materialName.startsWith("MaterialDef")){
             materialName = materialName.substring("MaterialDef ".length()).trim();
             materialName = materialName.substring("MaterialDef ".length()).trim();
             extending = false;
             extending = false;
+            
         }else if (materialName.startsWith("Material")){
         }else if (materialName.startsWith("Material")){
             materialName = materialName.substring("Material ".length()).trim();
             materialName = materialName.substring("Material ".length()).trim();
             extending = true;
             extending = true;
@@ -753,7 +762,7 @@ public class J3MLoader implements AssetLoader {
             material = new Material(def);
             material = new Material(def);
             material.setKey(key);
             material.setKey(key);
             material.setName(split[0].trim());
             material.setName(split[0].trim());
-//            material.setAssetName(fileName);
+            
         }else if (split.length == 1){
         }else if (split.length == 1){
             if (extending){
             if (extending){
                 throw new MatParseException("Expected ':', got '{'", materialStat);
                 throw new MatParseException("Expected ':', got '{'", materialStat);
@@ -765,24 +774,26 @@ public class J3MLoader implements AssetLoader {
             throw new MatParseException("Cannot use colon in material name/path", materialStat);
             throw new MatParseException("Cannot use colon in material name/path", materialStat);
         }
         }
 
 
-        for (Statement statement : materialStat.getContents()){
+        for (Statement statement : materialStat.getContents()) {
             split = statement.getLine().split("[ \\{]");
             split = statement.getLine().split("[ \\{]");
             String statType = split[0];
             String statType = split[0];
-            if (extending){
-                if (statType.equals("MaterialParameters")){
+            if (extending) {
+                if (statType.equals("MaterialParameters")) {
                     readExtendingMaterialParams(statement.getContents());
                     readExtendingMaterialParams(statement.getContents());
-                }else if (statType.equals("AdditionalRenderState")){
+                } else if (statType.equals("AdditionalRenderState")) {
                     readAdditionalRenderState(statement.getContents());
                     readAdditionalRenderState(statement.getContents());
-                }else if (statType.equals("Transparent")){
+                } else if (statType.equals("Transparent")) {
                     readTransparentStatement(statement.getLine());
                     readTransparentStatement(statement.getLine());
+                } else if (statType.equals("ReceivesShadows")) {
+                    readReceivesShadowsStatement(statement.getLine());
                 }
                 }
-            }else{
-                if (statType.equals("Technique")){
+            } else {
+                if (statType.equals("Technique")) {
                     readTechnique(statement);
                     readTechnique(statement);
-                }else if (statType.equals("MaterialParameters")){
+                } else if (statType.equals("MaterialParameters")) {
                     readMaterialParams(statement.getContents());
                     readMaterialParams(statement.getContents());
-                }else{
-                    throw new MatParseException("Expected material statement, got '"+statType+"'", statement);
+                } else {
+                    throw new MatParseException("Expected material statement, got '" + statType + "'", statement);
                 }
                 }
             }
             }
         }
         }
@@ -797,6 +808,7 @@ public class J3MLoader implements AssetLoader {
             key = info.getKey();
             key = info.getKey();
             if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) {
             if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) {
                 throw new IOException("Material instances must be loaded via MaterialKey");
                 throw new IOException("Material instances must be loaded via MaterialKey");
+                
             } else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) {
             } else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) {
                 throw new IOException("Material definitions must be loaded via AssetKey");
                 throw new IOException("Material definitions must be loaded via AssetKey");
             }
             }
@@ -968,4 +980,4 @@ public class J3MLoader implements AssetLoader {
             textureOption.applyToTexture(value, texture);
             textureOption.applyToTexture(value, texture);
         }
         }
     }
     }
-}
+}

+ 82 - 13
jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java

@@ -1,3 +1,34 @@
+/*
+ * Copyright (c) 2009-2025 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 package com.jme3.material.plugin.export.material;
 package com.jme3.material.plugin.export.material;
 
 
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.OutputCapsule;
@@ -8,48 +39,81 @@ import java.io.Writer;
 import java.util.HashMap;
 import java.util.HashMap;
 
 
 /**
 /**
+ * The `J3MRootOutputCapsule` class extends `J3MOutputCapsule` and serves as the
+ * root output capsule for exporting jME materials (`.j3m` files).
+ *
  * @author tsr
  * @author tsr
  */
  */
 public class J3MRootOutputCapsule extends J3MOutputCapsule {
 public class J3MRootOutputCapsule extends J3MOutputCapsule {
 
 
-    private final HashMap<Savable, J3MOutputCapsule> outCapsules;
+    /**
+     * Stores a map of `Savable` objects to their corresponding `J3MOutputCapsule` instances.
+     * This allows for managing and exporting different components (e.g., render states)
+     * of a material.
+     */
+    private final HashMap<Savable, J3MOutputCapsule> outCapsules = new HashMap<>();
+    // The name of the material.
     private String name;
     private String name;
-    private String materialDefinition;
+    // The material definition string (e.g., "Common/MatDefs/Light.j3md").
+    private String materialDef;
+    // Indicates whether the material is transparent
     private Boolean isTransparent;
     private Boolean isTransparent;
-
+    // Indicates whether the material receives shadows
+    private Boolean receivesShadows;
+
+    /**
+     * Constructs a new `J3MRootOutputCapsule`.
+     *
+     * @param exporter The `J3MExporter` instance used for exporting savable objects.
+     */
     public J3MRootOutputCapsule(J3MExporter exporter) {
     public J3MRootOutputCapsule(J3MExporter exporter) {
         super(exporter);
         super(exporter);
-        outCapsules = new HashMap<>();
     }
     }
 
 
+    /**
+     * Clears all data within this capsule and its superclass.
+     * Resets material properties to their default or null values and clears
+     * the map of savable capsules.
+     */
     @Override
     @Override
     public void clear() {
     public void clear() {
         super.clear();
         super.clear();
         isTransparent = null;
         isTransparent = null;
+        receivesShadows = null;
         name = "";
         name = "";
-        materialDefinition = "";
+        materialDef = "";
         outCapsules.clear();
         outCapsules.clear();
-
     }
     }
 
 
+    /**
+     * Retrieves an `OutputCapsule` for a given `Savable` object.
+     * If a capsule for the object does not exist, a new `J3MRenderStateOutputCapsule`
+     * is created and associated with the object.
+     *
+     * @param object The `Savable` object for which to retrieve or create a capsule.
+     * @return The `OutputCapsule` associated with the given savable object.
+     */
     public OutputCapsule getCapsule(Savable object) {
     public OutputCapsule getCapsule(Savable object) {
         if (!outCapsules.containsKey(object)) {
         if (!outCapsules.containsKey(object)) {
             outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter));
             outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter));
         }
         }
-
         return outCapsules.get(object);
         return outCapsules.get(object);
     }
     }
 
 
     @Override
     @Override
     public void writeToStream(Writer out) throws IOException {
     public void writeToStream(Writer out) throws IOException {
-        out.write("Material " + name + " : " + materialDefinition + " {\n\n");
+        out.write("Material " + name + " : " + materialDef + " {\n\n");
+
         if (isTransparent != null)
         if (isTransparent != null)
-            out.write("    Transparent " + ((isTransparent) ? "On" : "Off") + "\n\n");
+            out.write("    Transparent " + (isTransparent ? "On" : "Off") + "\n\n");
+        if (receivesShadows != null)
+            out.write("    ReceivesShadows " + (receivesShadows ? "On" : "Off") + "\n\n");
 
 
         out.write("    MaterialParameters {\n");
         out.write("    MaterialParameters {\n");
-        super.writeToStream(out);
+        super.writeToStream(out); // Writes parameters from the superclass
         out.write("    }\n\n");
         out.write("    }\n\n");
 
 
+        // Write out encapsulated savable object data
         for (J3MOutputCapsule c : outCapsules.values()) {
         for (J3MOutputCapsule c : outCapsules.values()) {
             c.writeToStream(out);
             c.writeToStream(out);
         }
         }
@@ -60,7 +124,7 @@ public class J3MRootOutputCapsule extends J3MOutputCapsule {
     public void write(String value, String name, String defVal) throws IOException {
     public void write(String value, String name, String defVal) throws IOException {
         switch (name) {
         switch (name) {
             case "material_def":
             case "material_def":
-                materialDefinition = value;
+                materialDef = value;
                 break;
                 break;
             case "name":
             case "name":
                 this.name = value;
                 this.name = value;
@@ -72,13 +136,18 @@ public class J3MRootOutputCapsule extends J3MOutputCapsule {
 
 
     @Override
     @Override
     public void write(boolean value, String name, boolean defVal) throws IOException {
     public void write(boolean value, String name, boolean defVal) throws IOException {
-        if( value == defVal)
+        // No need to write if the value is the same as the default.
+        if (value == defVal) {
             return;
             return;
+        }
 
 
         switch (name) {
         switch (name) {
             case "is_transparent":
             case "is_transparent":
                 isTransparent = value;
                 isTransparent = value;
                 break;
                 break;
+            case "receives_shadows":
+                receivesShadows = value;
+                break;
             default:
             default:
                 throw new UnsupportedOperationException(name + " boolean material parameter not supported yet");
                 throw new UnsupportedOperationException(name + " boolean material parameter not supported yet");
         }
         }
@@ -86,7 +155,7 @@ public class J3MRootOutputCapsule extends J3MOutputCapsule {
 
 
     @Override
     @Override
     public void write(Savable object, String name, Savable defVal) throws IOException {
     public void write(Savable object, String name, Savable defVal) throws IOException {
-        if(object != null && !object.equals(defVal)) {
+        if (object != null && !object.equals(defVal)) {
             object.write(exporter);
             object.write(exporter);
         }
         }
     }
     }

+ 19 - 16
jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -49,6 +49,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
+import java.net.URL;
 
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertTrue;
 
 
@@ -58,56 +59,58 @@ public class TestMaterialWrite {
 
 
     @Before
     @Before
     public void init() {
     public void init() {
-        assetManager = JmeSystem.newAssetManager(
-                TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg"));
-
-
+        URL configFile = TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg");
+        assetManager = JmeSystem.newAssetManager(configFile);
     }
     }
 
 
-
     @Test
     @Test
     public void testWriteMat() throws Exception {
     public void testWriteMat() throws Exception {
-
-        Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md");
-
+        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        mat.setName("TestMaterial");
         mat.setBoolean("UseMaterialColors", true);
         mat.setBoolean("UseMaterialColors", true);
         mat.setColor("Diffuse", ColorRGBA.White);
         mat.setColor("Diffuse", ColorRGBA.White);
         mat.setColor("Ambient", ColorRGBA.DarkGray);
         mat.setColor("Ambient", ColorRGBA.DarkGray);
         mat.setFloat("AlphaDiscardThreshold", 0.5f);
         mat.setFloat("AlphaDiscardThreshold", 0.5f);
-
         mat.setFloat("Shininess", 2.5f);
         mat.setFloat("Shininess", 2.5f);
+        mat.setTransparent(true);
+        mat.setReceivesShadows(true);
 
 
         Texture tex = assetManager.loadTexture(new TextureKey("Common/Textures/MissingTexture.png", true));
         Texture tex = assetManager.loadTexture(new TextureKey("Common/Textures/MissingTexture.png", true));
         tex.setMagFilter(Texture.MagFilter.Nearest);
         tex.setMagFilter(Texture.MagFilter.Nearest);
         tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
         tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
         tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat);
         tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat);
         tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat);
         tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat);
-
         mat.setTexture("DiffuseMap", tex);
         mat.setTexture("DiffuseMap", tex);
+
         mat.getAdditionalRenderState().setDepthWrite(false);
         mat.getAdditionalRenderState().setDepthWrite(false);
         mat.getAdditionalRenderState().setDepthTest(false);
         mat.getAdditionalRenderState().setDepthTest(false);
+        mat.getAdditionalRenderState().setColorWrite(false);
+        mat.getAdditionalRenderState().setWireframe(true);
         mat.getAdditionalRenderState().setLineWidth(5);
         mat.getAdditionalRenderState().setLineWidth(5);
+        mat.getAdditionalRenderState().setPolyOffset(-1, 1);
         mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
         mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
 
 
-        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
 
         J3MExporter exporter = new J3MExporter();
         J3MExporter exporter = new J3MExporter();
         try {
         try {
-            exporter.save(mat, stream);
+            exporter.save(mat, baos);
         } catch (IOException e) {
         } catch (IOException e) {
             e.printStackTrace();
             e.printStackTrace();
         }
         }
 
 
-        System.err.println(stream.toString());
+        System.err.println(baos);
 
 
         J3MLoader loader = new J3MLoader();
         J3MLoader loader = new J3MLoader();
         AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) {
         AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) {
             @Override
             @Override
             public InputStream openStream() {
             public InputStream openStream() {
-                return new ByteArrayInputStream(stream.toByteArray());
+                return new ByteArrayInputStream(baos.toByteArray());
             }
             }
         };
         };
-        Material mat2 = (Material)loader.load(info);
+        Material mat2 = (Material) loader.load(info);
+        assertTrue(mat2.isReceivesShadows());
+        assertTrue(mat2.isTransparent());
 
 
         assertTrue(mat.contentEquals(mat2));
         assertTrue(mat.contentEquals(mat2));
     }
     }