Skip to content

Commit c8987be

Browse files
authored
Add userdata loader to gltf (#2106)
* Add userdata loader and make it the default * turn unhandled extras log into a warning * Make default extra loader static and configurable * Make default extras loader configurable and reinstance with gltf loader * code cleanup * make defaultExtraLoaderClass volatile * fix
1 parent 3ec59c8 commit c8987be

4 files changed

Lines changed: 233 additions & 7 deletions

File tree

jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
* Created by Nehon on 20/08/2017.
4848
*/
4949
public class CustomContentManager {
50+
static volatile Class<? extends ExtrasLoader> defaultExtraLoaderClass = UserDataLoader.class;
51+
private ExtrasLoader defaultExtraLoaderInstance;
52+
5053

5154
private final static Logger logger = Logger.getLogger(CustomContentManager.class.getName());
5255

@@ -68,6 +71,31 @@ public class CustomContentManager {
6871

6972
public CustomContentManager() {
7073
}
74+
75+
/**
76+
* Returns the default extras loader.
77+
* @return the default extras loader.
78+
*/
79+
public ExtrasLoader getDefaultExtrasLoader() {
80+
if (defaultExtraLoaderClass == null) {
81+
defaultExtraLoaderInstance = null; // do not hold reference
82+
return null;
83+
}
84+
85+
if (defaultExtraLoaderInstance != null
86+
&& defaultExtraLoaderInstance.getClass() != defaultExtraLoaderClass) {
87+
defaultExtraLoaderInstance = null; // reset instance if class changed
88+
}
89+
90+
try {
91+
defaultExtraLoaderInstance = defaultExtraLoaderClass.getDeclaredConstructor().newInstance();
92+
} catch (Exception e) {
93+
logger.log(Level.WARNING, "Could not instantiate default extras loader", e);
94+
defaultExtraLoaderInstance = null;
95+
}
96+
97+
return defaultExtraLoaderInstance;
98+
}
7199

72100
void init(GltfLoader gltfLoader) {
73101
this.gltfLoader = gltfLoader;
@@ -156,14 +184,20 @@ private <T> T readExtension(String name, JsonElement el, T input) throws AssetLo
156184

157185
@SuppressWarnings("unchecked")
158186
private <T> T readExtras(String name, JsonElement el, T input) throws AssetLoadException {
159-
if (key == null) {
160-
return input;
187+
ExtrasLoader loader = null;
188+
189+
if (key != null) { // try to get the extras loader from the model key if available
190+
loader = key.getExtrasLoader();
191+
}
192+
193+
if (loader == null) { // if no loader was found, use the default extras loader
194+
loader = getDefaultExtrasLoader();
161195
}
162-
ExtrasLoader loader;
163-
loader = key.getExtrasLoader();
164-
if (loader == null) {
196+
197+
if (loader == null) { // if default loader is not set or failed to instantiate, skip extras
165198
return input;
166199
}
200+
167201
JsonElement extras = el.getAsJsonObject().getAsJsonObject("extras");
168202
if (extras == null) {
169203
return input;

jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,4 +1506,22 @@ public static void registerExtension(String name, Class<? extends ExtensionLoade
15061506
public static void unregisterExtension(String name) {
15071507
CustomContentManager.defaultExtensionLoaders.remove(name);
15081508
}
1509+
1510+
/**
1511+
* Sets the default extras loader used when no loader is specified in the GltfModelKey.
1512+
*
1513+
* @param loader
1514+
* the default extras loader.
1515+
*/
1516+
public static void registerDefaultExtrasLoader(Class<? extends ExtrasLoader> loader) {
1517+
CustomContentManager.defaultExtraLoaderClass = loader;
1518+
}
1519+
1520+
1521+
/**
1522+
* Unregisters the default extras loader.
1523+
*/
1524+
public static void unregisterDefaultExtrasLoader() {
1525+
CustomContentManager.defaultExtraLoaderClass = UserDataLoader.class;
1526+
}
15091527
}

jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2021 jMonkeyEngine
2+
* Copyright (c) 2009-2023 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -54,8 +54,8 @@ public class GltfModelKey extends ModelKey {
5454

5555
private Map<String, MaterialAdapter> materialAdapters = new HashMap<>();
5656
private static Map<String, ExtensionLoader> extensionLoaders = new HashMap<>();
57-
private ExtrasLoader extrasLoader;
5857
private boolean keepSkeletonPose = false;
58+
private ExtrasLoader extrasLoader;
5959

6060
public GltfModelKey(String name) {
6161
super(name);
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright (c) 2009-2023 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
33+
package com.jme3.scene.plugins.gltf;
34+
35+
import com.jme3.plugins.json.JsonArray;
36+
import com.jme3.plugins.json.JsonElement;
37+
import com.jme3.plugins.json.JsonObject;
38+
import com.jme3.plugins.json.JsonPrimitive;
39+
import com.jme3.scene.Spatial;
40+
41+
import java.lang.reflect.Array;
42+
import java.util.*;
43+
import java.util.logging.Level;
44+
import java.util.logging.Logger;
45+
46+
/**
47+
* Import user data from glTF extras.
48+
*
49+
* Derived from Simsilica JmeConvert
50+
* (https://github.com/Simsilica/JmeConvert/blob/master/src/main/java/com/simsilica/jmec/gltf/GltfExtrasLoader.java)
51+
* by Paul Speed (Copyright (c) 2019, Simsilica, LLC)
52+
*
53+
*/
54+
55+
public class UserDataLoader implements ExtrasLoader {
56+
57+
private static final Logger log = Logger.getLogger(UserDataLoader.class.getName());
58+
59+
public UserDataLoader() {
60+
}
61+
62+
@Override
63+
public Object handleExtras(GltfLoader loader, String parentName, JsonElement parent, JsonElement extras,
64+
Object input) {
65+
log.fine("handleExtras(" + loader + ", " + parentName + ", " + parent + ", " + extras + ", " + input
66+
+ ")");
67+
// Only interested in composite objects
68+
if (!(extras instanceof JsonObject)) {
69+
log.warning("Skipping extras:" + extras);
70+
return input;
71+
}
72+
JsonObject jo = extras.getAsJsonObject();
73+
apply(input, jo);
74+
return input;
75+
}
76+
77+
protected void apply(Object input, JsonObject extras) {
78+
if (input == null) {
79+
return;
80+
}
81+
if (input.getClass().isArray()) {
82+
applyToArray(input, extras);
83+
} else if (input instanceof Spatial) {
84+
applyToSpatial((Spatial) input, extras);
85+
} else {
86+
log.warning("Unhandled input type:" + input.getClass());
87+
}
88+
}
89+
90+
protected void applyToArray(Object array, JsonObject extras) {
91+
int size = Array.getLength(array);
92+
for (int i = 0; i < size; i++) {
93+
Object o = Array.get(array, i);
94+
log.fine("processing array[" + i + "]:" + o);
95+
apply(o, extras);
96+
}
97+
}
98+
99+
protected void applyToSpatial(Spatial spatial, JsonObject extras) {
100+
for (Map.Entry<String, JsonElement> el : extras.entrySet()) {
101+
log.fine(el.toString());
102+
Object val = toAttribute(el.getValue(), false);
103+
104+
if (log.isLoggable(Level.FINE)) {
105+
log.fine("setUserData(" + el.getKey() + ", " + val + ")");
106+
}
107+
spatial.setUserData(el.getKey(), val);
108+
}
109+
}
110+
111+
protected Object toAttribute(JsonElement el, boolean nested) {
112+
if (el == null) {
113+
return null;
114+
}
115+
if (el instanceof JsonObject) {
116+
return toAttribute(el.getAsJsonObject(), nested);
117+
} else if (el instanceof JsonArray) {
118+
return toAttribute(el.getAsJsonArray(), nested);
119+
} else if (el instanceof JsonPrimitive) {
120+
return toAttribute(el.getAsJsonPrimitive(), nested);
121+
}
122+
log.warning("Unhandled extras element:" + el);
123+
return null;
124+
}
125+
126+
protected Object toAttribute(JsonObject jo, boolean nested) {
127+
Map<String, Object> result = new HashMap<>();
128+
for (Map.Entry<String, JsonElement> el : jo.entrySet()) {
129+
result.put(el.getKey(), toAttribute(el.getValue(), true));
130+
}
131+
return result;
132+
}
133+
134+
protected Object toAttribute(JsonArray ja, boolean nested) {
135+
List<Object> result = new ArrayList<>();
136+
for (JsonElement el : ja) {
137+
result.add(toAttribute(el, true));
138+
}
139+
return result;
140+
}
141+
142+
protected Object toAttribute(JsonPrimitive jp, boolean nested) {
143+
if (jp.isBoolean()) {
144+
return jp.getAsBoolean();
145+
} else if (jp.isNumber()) {
146+
// JME doesn't save Maps properly and treats them as two
147+
// separate Lists... and it doesn't like saving Doubles
148+
// in lists so we'll just return strings in the case where
149+
// the value would end up in a map. If users someday really
150+
// need properly typed map values and JME map storage hasn't
151+
// been fixed then perhaps we give the users the option of
152+
// flattening the nested properties into dot notation, ie:
153+
// all directly on UserData with no Map children.
154+
if (nested) {
155+
return jp.getAsString();
156+
}
157+
Number num = jp.getAsNumber();
158+
// JME doesn't like to save GSON's LazilyParsedNumber so we'll
159+
// convert it into a real number. I don't think we can reliably
160+
// guess what type of number the user intended. It would take
161+
// some expirimentation to determine if things like 0.0 are
162+
// preserved
163+
// during export or just get exported as 0.
164+
// Rather than randomly flip-flop between number types depending
165+
// on the inclusion (or not) of a decimal point, we'll just always
166+
// return Double.
167+
return num.doubleValue();
168+
} else if (jp.isString()) {
169+
return jp.getAsString();
170+
}
171+
log.warning("Unhandled primitive:" + jp);
172+
return null;
173+
}
174+
}

0 commit comments

Comments
 (0)