Skip to content

Commit b470815

Browse files
manuelrmoManuel
andauthored
Implementation of a glTF extension loader for KHR_texture_transform (#1869)
* Implementation of a glTF extension loader for KHR_texture_transform * Thread-safe version of the glTF extension loader for KHR_texture_transform * Updated thread-safe version of the glTF extension loader for KHR_texture_transform * Fix (switched indices of the translation matrix): thread-safe version of the glTF extension loader for KHR_texture_transform * Added support for texCoord, fixed matrix comparison * Simplified matrix comparison, removed trailing whitespaces * Update to differentiate transformations applied to different UV sets * Improved memory usage for transformMap * Specified Map generic types & removed unnecessary cast Co-authored-by: Manuel <Manuel@DESKTOP-6RJH3UF>
1 parent 9d5eeee commit b470815

File tree

3 files changed

+167
-1
lines changed

3 files changed

+167
-1
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2020 jMonkeyEngine
2+
* Copyright (c) 2009-2022 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -57,6 +57,7 @@ public class CustomContentManager {
5757
defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", new PBRSpecGlossExtensionLoader());
5858
defaultExtensionLoaders.put("KHR_lights_punctual", new LightsPunctualExtensionLoader());
5959
defaultExtensionLoaders.put("KHR_materials_unlit", new UnlitExtensionLoader());
60+
defaultExtensionLoaders.put("KHR_texture_transform", new TextureTransformExtensionLoader());
6061
}
6162

6263
void init(GltfLoader gltfLoader) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException {
382382
for (JsonElement primitive : primitives) {
383383
JsonObject meshObject = primitive.getAsJsonObject();
384384
Mesh mesh = new Mesh();
385+
addToCache("mesh", 0, mesh, 1);
385386
Integer mode = getAsInteger(meshObject, "mode");
386387
mesh.setMode(getMeshMode(mode));
387388
Integer indices = getAsInteger(meshObject, "indices");
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) 2009-2022 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+
package com.jme3.scene.plugins.gltf;
33+
34+
import com.google.gson.JsonArray;
35+
import com.google.gson.JsonElement;
36+
import com.google.gson.JsonObject;
37+
import com.jme3.asset.AssetLoadException;
38+
import com.jme3.math.Matrix3f;
39+
import com.jme3.math.Vector3f;
40+
import com.jme3.scene.Mesh;
41+
import com.jme3.scene.VertexBuffer;
42+
import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger;
43+
import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferType;
44+
import com.jme3.texture.Texture2D;
45+
import java.io.IOException;
46+
import java.nio.FloatBuffer;
47+
import java.util.HashMap;
48+
import java.util.Map;
49+
import java.util.logging.Level;
50+
import java.util.logging.Logger;
51+
52+
/**
53+
* Thread-safe extension loader for KHR_texture_transform.
54+
* It allows for UV coordinates to be scaled/rotated/translated
55+
* based on transformation properties from textures in the glTF model.
56+
*
57+
* See spec at https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform
58+
*
59+
* @author manuelrmo - Created on 11/20/2022
60+
*/
61+
public class TextureTransformExtensionLoader implements ExtensionLoader {
62+
63+
private final static Logger logger = Logger.getLogger(TextureTransformExtensionLoader.class.getName());
64+
65+
/**
66+
* Scale/rotate/translate UV coordinates based on a transformation matrix.
67+
* Code adapted from scaleTextureCoordinates(Vector2f) in jme3-core/src/main/java/com/jme3/scene/Mesh.java
68+
* @param mesh The mesh holding the UV coordinates
69+
* @param transform The matrix containing the scale/rotate/translate transformations
70+
* @param verType The vertex buffer type from which to retrieve the UV coordinates
71+
*/
72+
private void uvTransform(Mesh mesh, Matrix3f transform, VertexBuffer.Type verType) {
73+
if (!transform.isIdentity()) { // if transform is the identity matrix, there's nothing to do
74+
VertexBuffer tc = mesh.getBuffer(verType);
75+
if (tc == null) {
76+
throw new IllegalStateException("The mesh has no texture coordinates");
77+
}
78+
if (tc.getFormat() != VertexBuffer.Format.Float) {
79+
throw new UnsupportedOperationException("Only float texture coord format is supported");
80+
}
81+
if (tc.getNumComponents() != 2) {
82+
throw new UnsupportedOperationException("Only 2D texture coords are supported");
83+
}
84+
FloatBuffer fb = (FloatBuffer) tc.getData();
85+
fb.clear();
86+
for (int i = 0; i < fb.limit() / 2; i++) {
87+
float x = fb.get();
88+
float y = fb.get();
89+
fb.position(fb.position() - 2);
90+
Vector3f v = transform.mult(new Vector3f(x, y, 1));
91+
fb.put(v.getX()).put(v.getY());
92+
}
93+
fb.clear();
94+
tc.updateData(fb);
95+
}
96+
}
97+
98+
// The algorithm relies on the fact that the GltfLoader.class object
99+
// loads all textures of a given mesh before doing so for the next mesh.
100+
@Override
101+
public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException {
102+
if (!(input instanceof Texture2D)) {
103+
logger.log(Level.WARNING, "KHR_texture_transform extension added on an unsupported element, the loaded scene result will be unexpected.");
104+
}
105+
Mesh mesh = loader.fetchFromCache("mesh", 0, Mesh.class);
106+
if (mesh != null) {
107+
Matrix3f translation = new Matrix3f();
108+
Matrix3f rotation = new Matrix3f();
109+
Matrix3f scale = new Matrix3f();
110+
Integer texCoord = getAsInteger(parent.getAsJsonObject(), "texCoord");
111+
texCoord = texCoord != null ? texCoord : 0;
112+
JsonObject jsonObject = extension.getAsJsonObject();
113+
if (jsonObject.has("offset")) {
114+
JsonArray jsonArray = jsonObject.getAsJsonArray("offset");
115+
translation.set(0, 2, jsonArray.get(0).getAsFloat());
116+
translation.set(1, 2, jsonArray.get(1).getAsFloat());
117+
}
118+
if (jsonObject.has("rotation")) {
119+
float rad = jsonObject.get("rotation").getAsFloat();
120+
rotation.set(0, 0, (float) Math.cos(rad));
121+
rotation.set(0, 1, (float) Math.sin(rad));
122+
rotation.set(1, 0, (float) -Math.sin(rad));
123+
rotation.set(1, 1, (float) Math.cos(rad));
124+
}
125+
if (jsonObject.has("scale")) {
126+
JsonArray jsonArray = jsonObject.getAsJsonArray("scale");
127+
scale.set(0, 0, jsonArray.get(0).getAsFloat());
128+
scale.set(1, 1, jsonArray.get(1).getAsFloat());
129+
}
130+
if (jsonObject.has("texCoord")) {
131+
texCoord = jsonObject.get("texCoord").getAsInt(); // it overrides the parent's texCoord value
132+
}
133+
Matrix3f transform = translation.mult(rotation).mult(scale);
134+
Mesh meshLast = loader.fetchFromCache("textureTransformData", 0, Mesh.class);
135+
Map<Integer, Matrix3f> transformMap = loader.fetchFromCache("textureTransformData", 1, HashMap.class);
136+
if (mesh != meshLast || (transformMap != null && transformMap.get(texCoord) == null)) {
137+
// at this point, we're processing a new mesh or the same mesh as before but for a different UV set
138+
if (mesh != meshLast) { // it's a new mesh
139+
loader.addToCache("textureTransformData", 0, mesh, 2);
140+
if (transformMap == null) {
141+
transformMap = new HashMap<>(); // initialize transformMap
142+
loader.addToCache("textureTransformData", 1, transformMap, 2);
143+
} else {
144+
transformMap.clear(); // reset transformMap
145+
}
146+
}
147+
transformMap.put(texCoord, transform); // store the transformation matrix applied to this UV set
148+
uvTransform(mesh, transform, getVertexBufferType("TEXCOORD_" + texCoord));
149+
logger.log(Level.FINE, "KHR_texture_transform extension successfully applied.");
150+
}
151+
else {
152+
// at this point, we're processing the same mesh as before for an already transformed UV set
153+
Matrix3f transformLast = transformMap.get(texCoord);
154+
if (!transform.equals(transformLast)) {
155+
logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same mesh's UVs is not supported, the loaded scene result will be unexpected.");
156+
}
157+
}
158+
return input;
159+
}
160+
else {
161+
throw new AssetLoadException("KHR_texture_transform extension applied to a null mesh.");
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)