diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index da88ff30e9..0c4317a307 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -1150,6 +1150,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(def.getAssetName(), "material_def", null); oc.write(additionalState, "render_state", null); oc.write(transparent, "is_transparent", false); + oc.write(receivesShadows, "receives_shadows", false); oc.write(name, "name", null); oc.writeStringSavableMap(paramValues, "parameters", null); } @@ -1162,6 +1163,7 @@ public void read(JmeImporter im) throws IOException { name = ic.readString("name", null); additionalState = (RenderState) ic.readSavable("render_state", null); transparent = ic.readBoolean("is_transparent", false); + receivesShadows = ic.readBoolean("receives_shadows", false); // Load the material def String defName = ic.readString("material_def", null); 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 e874ed555c..9999f5d3ec 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,7 +62,6 @@ public class J3MLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(J3MLoader.class.getName()); - // private ErrorLogger errors; private ShaderNodeLoaderDelegate nodesLoaderDelegate; boolean isUseNodes = false; int langSize = 0; @@ -86,7 +85,6 @@ public J3MLoader() { shaderNames = new EnumMap<>(Shader.ShaderType.class); } - // : private void readShaderStatement(String statement) throws IOException { String[] split = statement.split(":"); @@ -103,7 +101,6 @@ private void readShaderStatement(String statement) throws IOException { } } - private void readShaderDefinition(Shader.ShaderType shaderType, String name, String... languages) { shaderNames.put(shaderType, name); @@ -129,15 +126,15 @@ private void readLightMode(String statement) throws IOException{ LightMode lm = LightMode.valueOf(split[1]); technique.setLightMode(lm); } - - + + // LightMode private void readLightSpace(String statement) throws IOException{ String[] split = statement.split(whitespacePattern); if (split.length != 2){ 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); } @@ -297,7 +294,7 @@ private Texture parseTextureType(final VarType type, final String value) { for (final TextureOptionValue textureOptionValue : textureOptionValues) { textureOptionValue.applyToTexture(texture); } - } + } return texture; } @@ -311,28 +308,28 @@ private Object readValue(final VarType type, final String value) throws IOExcept if (split.length != 1){ throw new IOException("Float value parameter must have 1 entry: " + value); } - return Float.parseFloat(split[0]); + return Float.parseFloat(split[0]); case Vector2: if (split.length != 2){ throw new IOException("Vector2 value parameter must have 2 entries: " + value); } return new Vector2f(Float.parseFloat(split[0]), - Float.parseFloat(split[1])); + Float.parseFloat(split[1])); case Vector3: if (split.length != 3){ throw new IOException("Vector3 value parameter must have 3 entries: " + value); } 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: if (split.length != 4){ throw new IOException("Vector4 value parameter must have 4 entries: " + value); } 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: if (split.length != 1){ throw new IOException("Int value parameter must have 1 entry: " + value); @@ -536,12 +533,12 @@ private void readDefine(String statement) throws IOException{ MatParam param = materialDef.getMaterialParam(paramName); if (param == null) { 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}); return; } - + VarType paramType = param.getVarType(); technique.addShaderParamDefine(paramName, paramType, defineName); }else{ @@ -553,7 +550,6 @@ private void readDefines(List defineList) throws IOException{ for (Statement statement : defineList){ readDefine(statement.getLine()); } - } private void readTechniqueStatement(Statement statement) throws IOException{ @@ -600,12 +596,23 @@ private void readTechniqueStatement(Statement statement) throws IOException{ } } - 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); - 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 presetDefines) { @@ -665,7 +672,7 @@ private void readTechnique(Statement techStat) throws IOException{ if(isUseNodes){ //used for caching later, the shader here is not a file. - + // KIRILL 9/19/2015 // Not sure if this is needed anymore, since shader caching // is now done by TechniqueDef. @@ -722,9 +729,11 @@ private void loadFromRoot(List roots) throws IOException{ boolean extending = false; Statement materialStat = roots.get(0); String materialName = materialStat.getLine(); + if (materialName.startsWith("MaterialDef")){ materialName = materialName.substring("MaterialDef ".length()).trim(); extending = false; + }else if (materialName.startsWith("Material")){ materialName = materialName.substring("Material ".length()).trim(); extending = true; @@ -753,7 +762,7 @@ private void loadFromRoot(List roots) throws IOException{ material = new Material(def); material.setKey(key); material.setName(split[0].trim()); -// material.setAssetName(fileName); + }else if (split.length == 1){ if (extending){ throw new MatParseException("Expected ':', got '{'", materialStat); @@ -765,24 +774,26 @@ private void loadFromRoot(List roots) throws IOException{ 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("[ \\{]"); String statType = split[0]; - if (extending){ - if (statType.equals("MaterialParameters")){ + if (extending) { + if (statType.equals("MaterialParameters")) { readExtendingMaterialParams(statement.getContents()); - }else if (statType.equals("AdditionalRenderState")){ + } else if (statType.equals("AdditionalRenderState")) { readAdditionalRenderState(statement.getContents()); - }else if (statType.equals("Transparent")){ + } else if (statType.equals("Transparent")) { readTransparentStatement(statement.getLine()); + } else if (statType.equals("ReceivesShadows")) { + readReceivesShadowsStatement(statement.getLine()); } - }else{ - if (statType.equals("Technique")){ + } else { + if (statType.equals("Technique")) { readTechnique(statement); - }else if (statType.equals("MaterialParameters")){ + } else if (statType.equals("MaterialParameters")) { 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 Object load(AssetInfo info) throws IOException { key = info.getKey(); if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) { throw new IOException("Material instances must be loaded via MaterialKey"); + } else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) { throw new IOException("Material definitions must be loaded via AssetKey"); } @@ -968,4 +980,4 @@ public void applyToTexture(final Texture texture) { textureOption.applyToTexture(value, texture); } } -} \ No newline at end of file +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java index 74af239d69..40bca64c01 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java +++ b/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; import com.jme3.export.OutputCapsule; @@ -8,48 +39,81 @@ import java.util.HashMap; /** + * The `J3MRootOutputCapsule` class extends `J3MOutputCapsule` and serves as the + * root output capsule for exporting jME materials (`.j3m` files). + * * @author tsr */ public class J3MRootOutputCapsule extends J3MOutputCapsule { - private final HashMap 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 outCapsules = new HashMap<>(); + // The name of the material. 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; - + // 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) { 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 public void clear() { super.clear(); isTransparent = null; + receivesShadows = null; name = ""; - materialDefinition = ""; + materialDef = ""; 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) { if (!outCapsules.containsKey(object)) { outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter)); } - return outCapsules.get(object); } @Override public void writeToStream(Writer out) throws IOException { - out.write("Material " + name + " : " + materialDefinition + " {\n\n"); + out.write("Material " + name + " : " + materialDef + " {\n\n"); + 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"); - super.writeToStream(out); + super.writeToStream(out); // Writes parameters from the superclass out.write(" }\n\n"); + // Write out encapsulated savable object data for (J3MOutputCapsule c : outCapsules.values()) { c.writeToStream(out); } @@ -60,7 +124,7 @@ public void writeToStream(Writer out) throws IOException { public void write(String value, String name, String defVal) throws IOException { switch (name) { case "material_def": - materialDefinition = value; + materialDef = value; break; case "name": this.name = value; @@ -72,13 +136,18 @@ public void write(String value, String name, String defVal) throws IOException { @Override 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; + } switch (name) { case "is_transparent": isTransparent = value; break; + case "receives_shadows": + receivesShadows = value; + break; default: throw new UnsupportedOperationException(name + " boolean material parameter not supported yet"); } @@ -86,7 +155,7 @@ public void write(boolean value, String name, boolean defVal) throws IOException @Override 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); } } diff --git a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java index e5dfa75965..1007a2f764 100644 --- a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java +++ b/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. * * Redistribution and use in source and binary forms, with or without @@ -49,6 +49,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import static org.junit.Assert.assertTrue; @@ -58,56 +59,58 @@ public class TestMaterialWrite { @Before 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 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.setColor("Diffuse", ColorRGBA.White); mat.setColor("Ambient", ColorRGBA.DarkGray); mat.setFloat("AlphaDiscardThreshold", 0.5f); - mat.setFloat("Shininess", 2.5f); + mat.setTransparent(true); + mat.setReceivesShadows(true); Texture tex = assetManager.loadTexture(new TextureKey("Common/Textures/MissingTexture.png", true)); tex.setMagFilter(Texture.MagFilter.Nearest); tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat); tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat); - mat.setTexture("DiffuseMap", tex); + mat.getAdditionalRenderState().setDepthWrite(false); mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setColorWrite(false); + mat.getAdditionalRenderState().setWireframe(true); mat.getAdditionalRenderState().setLineWidth(5); + mat.getAdditionalRenderState().setPolyOffset(-1, 1); mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); J3MExporter exporter = new J3MExporter(); try { - exporter.save(mat, stream); + exporter.save(mat, baos); } catch (IOException e) { e.printStackTrace(); } - System.err.println(stream.toString()); + System.err.println(baos); J3MLoader loader = new J3MLoader(); AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) { @Override 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)); }