diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java index e06e4dfda5..c713a72b58 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java @@ -47,6 +47,9 @@ * Created by Nehon on 20/08/2017. */ public class CustomContentManager { + static volatile Class defaultExtraLoaderClass = UserDataLoader.class; + private ExtrasLoader defaultExtraLoaderInstance; + private final static Logger logger = Logger.getLogger(CustomContentManager.class.getName()); @@ -68,6 +71,31 @@ public class CustomContentManager { public CustomContentManager() { } + + /** + * Returns the default extras loader. + * @return the default extras loader. + */ + public ExtrasLoader getDefaultExtrasLoader() { + if (defaultExtraLoaderClass == null) { + defaultExtraLoaderInstance = null; // do not hold reference + return null; + } + + if (defaultExtraLoaderInstance != null + && defaultExtraLoaderInstance.getClass() != defaultExtraLoaderClass) { + defaultExtraLoaderInstance = null; // reset instance if class changed + } + + try { + defaultExtraLoaderInstance = defaultExtraLoaderClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not instantiate default extras loader", e); + defaultExtraLoaderInstance = null; + } + + return defaultExtraLoaderInstance; + } void init(GltfLoader gltfLoader) { this.gltfLoader = gltfLoader; @@ -156,14 +184,20 @@ private T readExtension(String name, JsonElement el, T input) throws AssetLo @SuppressWarnings("unchecked") private T readExtras(String name, JsonElement el, T input) throws AssetLoadException { - if (key == null) { - return input; + ExtrasLoader loader = null; + + if (key != null) { // try to get the extras loader from the model key if available + loader = key.getExtrasLoader(); + } + + if (loader == null) { // if no loader was found, use the default extras loader + loader = getDefaultExtrasLoader(); } - ExtrasLoader loader; - loader = key.getExtrasLoader(); - if (loader == null) { + + if (loader == null) { // if default loader is not set or failed to instantiate, skip extras return input; } + JsonElement extras = el.getAsJsonObject().getAsJsonObject("extras"); if (extras == null) { return input; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 28364093bf..c24efbadc1 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -1506,4 +1506,22 @@ public static void registerExtension(String name, Class loader) { + CustomContentManager.defaultExtraLoaderClass = loader; + } + + + /** + * Unregisters the default extras loader. + */ + public static void unregisterDefaultExtrasLoader() { + CustomContentManager.defaultExtraLoaderClass = UserDataLoader.class; + } } \ No newline at end of file diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java index ec4604c4ef..26da82bb3f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,8 +54,8 @@ public class GltfModelKey extends ModelKey { private Map materialAdapters = new HashMap<>(); private static Map extensionLoaders = new HashMap<>(); - private ExtrasLoader extrasLoader; private boolean keepSkeletonPose = false; + private ExtrasLoader extrasLoader; public GltfModelKey(String name) { super(name); diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java new file mode 100644 index 0000000000..df62f51685 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2009-2023 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.scene.plugins.gltf; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; +import com.jme3.scene.Spatial; + +import java.lang.reflect.Array; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Import user data from glTF extras. + * + * Derived from Simsilica JmeConvert + * (https://github.com/Simsilica/JmeConvert/blob/master/src/main/java/com/simsilica/jmec/gltf/GltfExtrasLoader.java) + * by Paul Speed (Copyright (c) 2019, Simsilica, LLC) + * + */ + +public class UserDataLoader implements ExtrasLoader { + + private static final Logger log = Logger.getLogger(UserDataLoader.class.getName()); + + public UserDataLoader() { + } + + @Override + public Object handleExtras(GltfLoader loader, String parentName, JsonElement parent, JsonElement extras, + Object input) { + log.fine("handleExtras(" + loader + ", " + parentName + ", " + parent + ", " + extras + ", " + input + + ")"); + // Only interested in composite objects + if (!(extras instanceof JsonObject)) { + log.warning("Skipping extras:" + extras); + return input; + } + JsonObject jo = extras.getAsJsonObject(); + apply(input, jo); + return input; + } + + protected void apply(Object input, JsonObject extras) { + if (input == null) { + return; + } + if (input.getClass().isArray()) { + applyToArray(input, extras); + } else if (input instanceof Spatial) { + applyToSpatial((Spatial) input, extras); + } else { + log.warning("Unhandled input type:" + input.getClass()); + } + } + + protected void applyToArray(Object array, JsonObject extras) { + int size = Array.getLength(array); + for (int i = 0; i < size; i++) { + Object o = Array.get(array, i); + log.fine("processing array[" + i + "]:" + o); + apply(o, extras); + } + } + + protected void applyToSpatial(Spatial spatial, JsonObject extras) { + for (Map.Entry el : extras.entrySet()) { + log.fine(el.toString()); + Object val = toAttribute(el.getValue(), false); + + if (log.isLoggable(Level.FINE)) { + log.fine("setUserData(" + el.getKey() + ", " + val + ")"); + } + spatial.setUserData(el.getKey(), val); + } + } + + protected Object toAttribute(JsonElement el, boolean nested) { + if (el == null) { + return null; + } + if (el instanceof JsonObject) { + return toAttribute(el.getAsJsonObject(), nested); + } else if (el instanceof JsonArray) { + return toAttribute(el.getAsJsonArray(), nested); + } else if (el instanceof JsonPrimitive) { + return toAttribute(el.getAsJsonPrimitive(), nested); + } + log.warning("Unhandled extras element:" + el); + return null; + } + + protected Object toAttribute(JsonObject jo, boolean nested) { + Map result = new HashMap<>(); + for (Map.Entry el : jo.entrySet()) { + result.put(el.getKey(), toAttribute(el.getValue(), true)); + } + return result; + } + + protected Object toAttribute(JsonArray ja, boolean nested) { + List result = new ArrayList<>(); + for (JsonElement el : ja) { + result.add(toAttribute(el, true)); + } + return result; + } + + protected Object toAttribute(JsonPrimitive jp, boolean nested) { + if (jp.isBoolean()) { + return jp.getAsBoolean(); + } else if (jp.isNumber()) { + // JME doesn't save Maps properly and treats them as two + // separate Lists... and it doesn't like saving Doubles + // in lists so we'll just return strings in the case where + // the value would end up in a map. If users someday really + // need properly typed map values and JME map storage hasn't + // been fixed then perhaps we give the users the option of + // flattening the nested properties into dot notation, ie: + // all directly on UserData with no Map children. + if (nested) { + return jp.getAsString(); + } + Number num = jp.getAsNumber(); + // JME doesn't like to save GSON's LazilyParsedNumber so we'll + // convert it into a real number. I don't think we can reliably + // guess what type of number the user intended. It would take + // some expirimentation to determine if things like 0.0 are + // preserved + // during export or just get exported as 0. + // Rather than randomly flip-flop between number types depending + // on the inclusion (or not) of a decimal point, we'll just always + // return Double. + return num.doubleValue(); + } else if (jp.isString()) { + return jp.getAsString(); + } + log.warning("Unhandled primitive:" + jp); + return null; + } +}