From e65a3f92064b6907b3e7ca59b39d1671ff444f71 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 26 Nov 2022 08:36:09 -0800 Subject: [PATCH 1/9] Implementation of a glTF extension loader for KHR_texture_transform --- .../plugins/gltf/CustomContentManager.java | 3 +- .../jme3/scene/plugins/gltf/GltfLoader.java | 9 ++ .../gltf/TextureTransformExtensionLoader.java | 146 ++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java 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 5b8908948c..94f632d7e3 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,6 +57,7 @@ public class CustomContentManager { defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", new PBRSpecGlossExtensionLoader()); defaultExtensionLoaders.put("KHR_lights_punctual", new LightsPunctualExtensionLoader()); defaultExtensionLoaders.put("KHR_materials_unlit", new UnlitExtensionLoader()); + defaultExtensionLoaders.put("KHR_texture_transform", new TextureTransformExtensionLoader()); } void init(GltfLoader gltfLoader) { 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 6daad31d6f..8bd28c56c7 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 @@ -96,6 +96,9 @@ public class GltfLoader implements AssetLoader { Map> skinnedSpatials = new HashMap<>(); IntMap skinBuffers = new IntMap<>(); + // Last created geometry + private Geometry lastCreatedGeom = null; + public GltfLoader() { defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMetalRoughMaterialAdapter()); } @@ -464,6 +467,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { // Read mesh extras mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh); Geometry geom = new Geometry(null, mesh); + lastCreatedGeom = geom; Integer materialIndex = getAsInteger(meshObject, "material"); if (materialIndex == null) { @@ -1206,6 +1210,11 @@ public JsonObject getDocRoot() { public Node getRootNode() { return rootNode; } + + // Getter for the last created geometry + public Geometry getLastCreatedGeom() { + return lastCreatedGeom; + } private String decodeUri(String uri) { try { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java new file mode 100644 index 0000000000..47da62446d --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2009-2022 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.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.jme3.asset.AssetLoadException; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.texture.Texture2D; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Extension loader for KHR_texture_transform, which allows for + * UV coordinates to be scaled/rotated/translated based on + * transformation properties from textures in the glTF model. + * + * See spec at https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform + * + * @author manuelrmo - Created on 11/20/2022 + */ +public class TextureTransformExtensionLoader implements ExtensionLoader { + + private final static Logger logger = Logger.getLogger(TextureTransformExtensionLoader.class.getName()); + + private Geometry geomLast = null; // Last geometry created by the GltfLoader.class object + private Matrix3f mInvLast = null; // Last transformation matrix (inverted) that was applied + + /** + * Scale/rotate/translate UV coordinates based on a transformation matrix. + * Code adapted from scaleTextureCoordinates(Vector2f) in jme3-core/src/main/java/com/jme3/scene/Mesh.java + * @param mesh The mesh holding the UV coordinates + * @param m The matrix containing the scale/rotate/translate transformations + */ + private void uvTransform(Mesh mesh, Matrix3f m) { + VertexBuffer tc = mesh.getBuffer(VertexBuffer.Type.TexCoord); + if (tc == null) { + throw new IllegalStateException("The mesh has no texture coordinates"); + } + if (tc.getFormat() != VertexBuffer.Format.Float) { + throw new UnsupportedOperationException("Only float texture coord format is supported"); + } + if (tc.getNumComponents() != 2) { + throw new UnsupportedOperationException("Only 2D texture coords are supported"); + } + FloatBuffer fb = (FloatBuffer) tc.getData(); + fb.clear(); + for (int i = 0; i < fb.limit() / 2; i++) { + float x = fb.get(); + float y = fb.get(); + fb.position(fb.position() - 2); + Vector3f v = m.mult(new Vector3f(x, y, 1)); + fb.put(v.getX()).put(v.getY()); + } + fb.clear(); + tc.updateData(fb); + } + + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException { + if (input instanceof Texture2D) { + Geometry geom = loader.getLastCreatedGeom(); + if (geom != null) { + Matrix3f t = new Matrix3f(); + Matrix3f r = new Matrix3f(); + Matrix3f s = new Matrix3f(); + JsonObject jsonObject = extension.getAsJsonObject(); + if (jsonObject.has("offset")) { + JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); + t.set(2, 0, jsonArray.get(0).getAsFloat()); + t.set(2, 1, jsonArray.get(1).getAsFloat()); + } + if (jsonObject.has("rotation")) { + float rad = jsonObject.get("rotation").getAsFloat(); + r.set(0, 0, (float) Math.cos(rad)); + r.set(0, 1, (float) Math.sin(rad)); + r.set(1, 0, (float) -Math.sin(rad)); + r.set(1, 1, (float) Math.cos(rad)); + } + if (jsonObject.has("scale")) { + JsonArray jsonArray = jsonObject.getAsJsonArray("scale"); + s.set(0, 0, jsonArray.get(0).getAsFloat()); + s.set(1, 1, jsonArray.get(1).getAsFloat()); + } + if (jsonObject.has("texCoord")) { + logger.log(Level.WARNING, "KHR_texture_transform extension: the texCoord property is not supported"); + } + Matrix3f m = t.mult(r).mult(s); + if (geom != geomLast) { + geomLast = geom; + mInvLast = m.invert(); + uvTransform(geom.getMesh(), m); + logger.log(Level.FINE, "KHR_texture_transform extension successfully applied"); + } + else { + if (!m.mult(mInvLast).isIdentity()) { + logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same geometry is not supported"); + } + } + return input; + } + else { + throw new AssetLoadException("KHR_texture_transform extension applied to a null geometry"); + } + } + else { + throw new AssetLoadException("KHR_texture_transform extension added on an unsupported element"); + } + } +} From 70639a35401eb3d1c5899b390426b2597a32025d Mon Sep 17 00:00:00 2001 From: Manuel Date: Sun, 27 Nov 2022 05:05:37 -0800 Subject: [PATCH 2/9] Thread-safe version of the glTF extension loader for KHR_texture_transform --- .../jme3/scene/plugins/gltf/GltfLoader.java | 13 ++-- .../gltf/TextureTransformExtensionLoader.java | 62 ++++++++++--------- 2 files changed, 37 insertions(+), 38 deletions(-) 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 8bd28c56c7..2cd02cc3e5 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 @@ -96,9 +96,6 @@ public class GltfLoader implements AssetLoader { Map> skinnedSpatials = new HashMap<>(); IntMap skinBuffers = new IntMap<>(); - // Last created geometry - private Geometry lastCreatedGeom = null; - public GltfLoader() { defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMetalRoughMaterialAdapter()); } @@ -467,7 +464,10 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { // Read mesh extras mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh); Geometry geom = new Geometry(null, mesh); - lastCreatedGeom = geom; + // Cached data about the geometry needed by TextureTransformExtensionLoader.class + addToCache("lastCreatedGeom", 0, geom, 3); // Last created geometry + addToCache("lastCreatedGeom", 1, null, 3); // Last processed geometry + addToCache("lastCreatedGeom", 2, null, 3); // Last applied transformation matrix (inverted) Integer materialIndex = getAsInteger(meshObject, "material"); if (materialIndex == null) { @@ -1210,11 +1210,6 @@ public JsonObject getDocRoot() { public Node getRootNode() { return rootNode; } - - // Getter for the last created geometry - public Geometry getLastCreatedGeom() { - return lastCreatedGeom; - } private String decodeUri(String uri) { try { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index 47da62446d..5d999a00a8 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -47,9 +47,9 @@ import java.util.logging.Logger; /** - * Extension loader for KHR_texture_transform, which allows for - * UV coordinates to be scaled/rotated/translated based on - * transformation properties from textures in the glTF model. + * Thread-safe extension loader for KHR_texture_transform. + * It allows for UV coordinates to be scaled/rotated/translated + * based on transformation properties from textures in the glTF model. * * See spec at https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform * @@ -58,9 +58,6 @@ public class TextureTransformExtensionLoader implements ExtensionLoader { private final static Logger logger = Logger.getLogger(TextureTransformExtensionLoader.class.getName()); - - private Geometry geomLast = null; // Last geometry created by the GltfLoader.class object - private Matrix3f mInvLast = null; // Last transformation matrix (inverted) that was applied /** * Scale/rotate/translate UV coordinates based on a transformation matrix. @@ -69,33 +66,38 @@ public class TextureTransformExtensionLoader implements ExtensionLoader { * @param m The matrix containing the scale/rotate/translate transformations */ private void uvTransform(Mesh mesh, Matrix3f m) { - VertexBuffer tc = mesh.getBuffer(VertexBuffer.Type.TexCoord); - if (tc == null) { - throw new IllegalStateException("The mesh has no texture coordinates"); - } - if (tc.getFormat() != VertexBuffer.Format.Float) { - throw new UnsupportedOperationException("Only float texture coord format is supported"); - } - if (tc.getNumComponents() != 2) { - throw new UnsupportedOperationException("Only 2D texture coords are supported"); - } - FloatBuffer fb = (FloatBuffer) tc.getData(); - fb.clear(); - for (int i = 0; i < fb.limit() / 2; i++) { - float x = fb.get(); - float y = fb.get(); - fb.position(fb.position() - 2); - Vector3f v = m.mult(new Vector3f(x, y, 1)); - fb.put(v.getX()).put(v.getY()); + if (!m.isIdentity()) { // if m is the identity matrix, there's nothing to do + VertexBuffer tc = mesh.getBuffer(VertexBuffer.Type.TexCoord); + if (tc == null) { + throw new IllegalStateException("The mesh has no texture coordinates"); + } + if (tc.getFormat() != VertexBuffer.Format.Float) { + throw new UnsupportedOperationException("Only float texture coord format is supported"); + } + if (tc.getNumComponents() != 2) { + throw new UnsupportedOperationException("Only 2D texture coords are supported"); + } + FloatBuffer fb = (FloatBuffer) tc.getData(); + fb.clear(); + for (int i = 0; i < fb.limit() / 2; i++) { + float x = fb.get(); + float y = fb.get(); + fb.position(fb.position() - 2); + Vector3f v = m.mult(new Vector3f(x, y, 1)); + fb.put(v.getX()).put(v.getY()); + } + fb.clear(); + tc.updateData(fb); } - fb.clear(); - tc.updateData(fb); } + // The algorithm relies on the fact that the GltfLoader.class object + // loads all textures of a given geometry/mesh before doing so + // for the next geometry/mesh. @Override public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException { if (input instanceof Texture2D) { - Geometry geom = loader.getLastCreatedGeom(); + Geometry geom = loader.fetchFromCache("lastCreatedGeom", 0, Geometry.class); if (geom != null) { Matrix3f t = new Matrix3f(); Matrix3f r = new Matrix3f(); @@ -122,13 +124,15 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement logger.log(Level.WARNING, "KHR_texture_transform extension: the texCoord property is not supported"); } Matrix3f m = t.mult(r).mult(s); + Geometry geomLast = loader.fetchFromCache("lastCreatedGeom", 1, Geometry.class); if (geom != geomLast) { - geomLast = geom; - mInvLast = m.invert(); + loader.addToCache("lastCreatedGeom", 1, geom, 3); + loader.addToCache("lastCreatedGeom", 2, m.invert(), 3); uvTransform(geom.getMesh(), m); logger.log(Level.FINE, "KHR_texture_transform extension successfully applied"); } else { + Matrix3f mInvLast = loader.fetchFromCache("lastCreatedGeom", 2, Matrix3f.class); if (!m.mult(mInvLast).isIdentity()) { logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same geometry is not supported"); } From 43a1ef0cf38db3cbd300fbc805514799f4d3534e Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 28 Nov 2022 09:59:01 -0800 Subject: [PATCH 3/9] Updated thread-safe version of the glTF extension loader for KHR_texture_transform --- .../jme3/scene/plugins/gltf/GltfLoader.java | 5 +- .../gltf/TextureTransformExtensionLoader.java | 102 +++++++++--------- 2 files changed, 50 insertions(+), 57 deletions(-) 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 2cd02cc3e5..50e3ca71c4 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 @@ -382,6 +382,7 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { for (JsonElement primitive : primitives) { JsonObject meshObject = primitive.getAsJsonObject(); Mesh mesh = new Mesh(); + addToCache("mesh", 0, mesh, 1); Integer mode = getAsInteger(meshObject, "mode"); mesh.setMode(getMeshMode(mode)); Integer indices = getAsInteger(meshObject, "indices"); @@ -464,10 +465,6 @@ public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { // Read mesh extras mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh); Geometry geom = new Geometry(null, mesh); - // Cached data about the geometry needed by TextureTransformExtensionLoader.class - addToCache("lastCreatedGeom", 0, geom, 3); // Last created geometry - addToCache("lastCreatedGeom", 1, null, 3); // Last processed geometry - addToCache("lastCreatedGeom", 2, null, 3); // Last applied transformation matrix (inverted) Integer materialIndex = getAsInteger(meshObject, "material"); if (materialIndex == null) { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index 5d999a00a8..0653418a97 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -37,7 +37,6 @@ import com.jme3.asset.AssetLoadException; import com.jme3.math.Matrix3f; import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.texture.Texture2D; @@ -63,10 +62,10 @@ public class TextureTransformExtensionLoader implements ExtensionLoader { * Scale/rotate/translate UV coordinates based on a transformation matrix. * Code adapted from scaleTextureCoordinates(Vector2f) in jme3-core/src/main/java/com/jme3/scene/Mesh.java * @param mesh The mesh holding the UV coordinates - * @param m The matrix containing the scale/rotate/translate transformations + * @param transform The matrix containing the scale/rotate/translate transformations */ - private void uvTransform(Mesh mesh, Matrix3f m) { - if (!m.isIdentity()) { // if m is the identity matrix, there's nothing to do + private void uvTransform(Mesh mesh, Matrix3f transform) { + if (!transform.isIdentity()) { // if m is the identity matrix, there's nothing to do VertexBuffer tc = mesh.getBuffer(VertexBuffer.Type.TexCoord); if (tc == null) { throw new IllegalStateException("The mesh has no texture coordinates"); @@ -83,7 +82,7 @@ private void uvTransform(Mesh mesh, Matrix3f m) { float x = fb.get(); float y = fb.get(); fb.position(fb.position() - 2); - Vector3f v = m.mult(new Vector3f(x, y, 1)); + Vector3f v = transform.mult(new Vector3f(x, y, 1)); fb.put(v.getX()).put(v.getY()); } fb.clear(); @@ -92,59 +91,56 @@ private void uvTransform(Mesh mesh, Matrix3f m) { } // The algorithm relies on the fact that the GltfLoader.class object - // loads all textures of a given geometry/mesh before doing so - // for the next geometry/mesh. + // loads all textures of a given mesh before doing so for the next mesh. @Override public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException { - if (input instanceof Texture2D) { - Geometry geom = loader.fetchFromCache("lastCreatedGeom", 0, Geometry.class); - if (geom != null) { - Matrix3f t = new Matrix3f(); - Matrix3f r = new Matrix3f(); - Matrix3f s = new Matrix3f(); - JsonObject jsonObject = extension.getAsJsonObject(); - if (jsonObject.has("offset")) { - JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); - t.set(2, 0, jsonArray.get(0).getAsFloat()); - t.set(2, 1, jsonArray.get(1).getAsFloat()); - } - if (jsonObject.has("rotation")) { - float rad = jsonObject.get("rotation").getAsFloat(); - r.set(0, 0, (float) Math.cos(rad)); - r.set(0, 1, (float) Math.sin(rad)); - r.set(1, 0, (float) -Math.sin(rad)); - r.set(1, 1, (float) Math.cos(rad)); - } - if (jsonObject.has("scale")) { - JsonArray jsonArray = jsonObject.getAsJsonArray("scale"); - s.set(0, 0, jsonArray.get(0).getAsFloat()); - s.set(1, 1, jsonArray.get(1).getAsFloat()); - } - if (jsonObject.has("texCoord")) { - logger.log(Level.WARNING, "KHR_texture_transform extension: the texCoord property is not supported"); - } - Matrix3f m = t.mult(r).mult(s); - Geometry geomLast = loader.fetchFromCache("lastCreatedGeom", 1, Geometry.class); - if (geom != geomLast) { - loader.addToCache("lastCreatedGeom", 1, geom, 3); - loader.addToCache("lastCreatedGeom", 2, m.invert(), 3); - uvTransform(geom.getMesh(), m); - logger.log(Level.FINE, "KHR_texture_transform extension successfully applied"); - } - else { - Matrix3f mInvLast = loader.fetchFromCache("lastCreatedGeom", 2, Matrix3f.class); - if (!m.mult(mInvLast).isIdentity()) { - logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same geometry is not supported"); - } - } - return input; + if (!(input instanceof Texture2D)) { + logger.log(Level.WARNING, "KHR_texture_transform extension added on an unsupported element, the loaded scene result will be unexpected."); + } + Mesh mesh = loader.fetchFromCache("mesh", 0, Mesh.class); + if (mesh != null) { + Matrix3f translation = new Matrix3f(); + Matrix3f rotation = new Matrix3f(); + Matrix3f scale = new Matrix3f(); + JsonObject jsonObject = extension.getAsJsonObject(); + if (jsonObject.has("offset")) { + JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); + translation.set(2, 0, jsonArray.get(0).getAsFloat()); + translation.set(2, 1, jsonArray.get(1).getAsFloat()); + } + if (jsonObject.has("rotation")) { + float rad = jsonObject.get("rotation").getAsFloat(); + rotation.set(0, 0, (float) Math.cos(rad)); + rotation.set(0, 1, (float) Math.sin(rad)); + rotation.set(1, 0, (float) -Math.sin(rad)); + rotation.set(1, 1, (float) Math.cos(rad)); + } + if (jsonObject.has("scale")) { + JsonArray jsonArray = jsonObject.getAsJsonArray("scale"); + scale.set(0, 0, jsonArray.get(0).getAsFloat()); + scale.set(1, 1, jsonArray.get(1).getAsFloat()); + } + if (jsonObject.has("texCoord")) { + logger.log(Level.WARNING, "KHR_texture_transform extension: the texCoord property is not supported, the loaded scene result will be unexpected."); + } + Matrix3f transform = translation.mult(rotation).mult(scale); + Mesh meshLast = loader.fetchFromCache("textureTransformData", 0, Mesh.class); + if (mesh != meshLast) { + loader.addToCache("textureTransformData", 0, mesh, 2); + loader.addToCache("textureTransformData", 1, transform.invert(), 2); + uvTransform(mesh, transform); + logger.log(Level.FINE, "KHR_texture_transform extension successfully applied."); } else { - throw new AssetLoadException("KHR_texture_transform extension applied to a null geometry"); + Matrix3f mInvLast = loader.fetchFromCache("textureTransformData", 1, Matrix3f.class); + if (!transform.mult(mInvLast).isIdentity()) { + logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same mesh is not supported, the loaded scene result will be unexpected."); + } } - } + return input; + } else { - throw new AssetLoadException("KHR_texture_transform extension added on an unsupported element"); - } + throw new AssetLoadException("KHR_texture_transform extension applied to a null mesh."); + } } } From e3b930130db51bb8385afb9a8871e7acdc16c259 Mon Sep 17 00:00:00 2001 From: Manuel Date: Wed, 30 Nov 2022 23:21:20 -0800 Subject: [PATCH 4/9] Fix (switched indices of the translation matrix): thread-safe version of the glTF extension loader for KHR_texture_transform --- .../scene/plugins/gltf/TextureTransformExtensionLoader.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index 0653418a97..f77d2c0e41 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -65,7 +65,7 @@ public class TextureTransformExtensionLoader implements ExtensionLoader { * @param transform The matrix containing the scale/rotate/translate transformations */ private void uvTransform(Mesh mesh, Matrix3f transform) { - if (!transform.isIdentity()) { // if m is the identity matrix, there's nothing to do + if (!transform.isIdentity()) { // if transform is the identity matrix, there's nothing to do VertexBuffer tc = mesh.getBuffer(VertexBuffer.Type.TexCoord); if (tc == null) { throw new IllegalStateException("The mesh has no texture coordinates"); @@ -105,8 +105,8 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement JsonObject jsonObject = extension.getAsJsonObject(); if (jsonObject.has("offset")) { JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); - translation.set(2, 0, jsonArray.get(0).getAsFloat()); - translation.set(2, 1, jsonArray.get(1).getAsFloat()); + translation.set(0, 2, jsonArray.get(0).getAsFloat()); + translation.set(1, 2, jsonArray.get(1).getAsFloat()); } if (jsonObject.has("rotation")) { float rad = jsonObject.get("rotation").getAsFloat(); From aed2f79418250ec8ea36f5cfe26f44bfe3f878b3 Mon Sep 17 00:00:00 2001 From: Manuel Date: Fri, 2 Dec 2022 13:07:03 -0800 Subject: [PATCH 5/9] Added support for texCoord, fixed matrix comparison --- .../gltf/TextureTransformExtensionLoader.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index f77d2c0e41..d5cc99f295 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -39,6 +39,8 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import static com.jme3.scene.plugins.gltf.GltfUtils.getAsInteger; +import static com.jme3.scene.plugins.gltf.GltfUtils.getVertexBufferType; import com.jme3.texture.Texture2D; import java.io.IOException; import java.nio.FloatBuffer; @@ -63,10 +65,11 @@ public class TextureTransformExtensionLoader implements ExtensionLoader { * Code adapted from scaleTextureCoordinates(Vector2f) in jme3-core/src/main/java/com/jme3/scene/Mesh.java * @param mesh The mesh holding the UV coordinates * @param transform The matrix containing the scale/rotate/translate transformations + * @param verType The vertex buffer type from which to retrieve the UV coordinates */ - private void uvTransform(Mesh mesh, Matrix3f transform) { + private void uvTransform(Mesh mesh, Matrix3f transform, VertexBuffer.Type verType) { if (!transform.isIdentity()) { // if transform is the identity matrix, there's nothing to do - VertexBuffer tc = mesh.getBuffer(VertexBuffer.Type.TexCoord); + VertexBuffer tc = mesh.getBuffer(verType); if (tc == null) { throw new IllegalStateException("The mesh has no texture coordinates"); } @@ -86,7 +89,7 @@ private void uvTransform(Mesh mesh, Matrix3f transform) { fb.put(v.getX()).put(v.getY()); } fb.clear(); - tc.updateData(fb); + tc.updateData(fb); } } @@ -102,6 +105,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement Matrix3f translation = new Matrix3f(); Matrix3f rotation = new Matrix3f(); Matrix3f scale = new Matrix3f(); + Integer texCoord = getAsInteger(parent.getAsJsonObject(), "texCoord"); JsonObject jsonObject = extension.getAsJsonObject(); if (jsonObject.has("offset")) { JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); @@ -121,19 +125,19 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement scale.set(1, 1, jsonArray.get(1).getAsFloat()); } if (jsonObject.has("texCoord")) { - logger.log(Level.WARNING, "KHR_texture_transform extension: the texCoord property is not supported, the loaded scene result will be unexpected."); + texCoord = getAsInteger(jsonObject, "texCoord"); // it overrides the parent's texCoord value } Matrix3f transform = translation.mult(rotation).mult(scale); Mesh meshLast = loader.fetchFromCache("textureTransformData", 0, Mesh.class); if (mesh != meshLast) { loader.addToCache("textureTransformData", 0, mesh, 2); - loader.addToCache("textureTransformData", 1, transform.invert(), 2); - uvTransform(mesh, transform); + loader.addToCache("textureTransformData", 1, transform, 2); + uvTransform(mesh, transform, getVertexBufferType("TEXCOORD_" + (texCoord != null ? texCoord : 0))); logger.log(Level.FINE, "KHR_texture_transform extension successfully applied."); } else { - Matrix3f mInvLast = loader.fetchFromCache("textureTransformData", 1, Matrix3f.class); - if (!transform.mult(mInvLast).isIdentity()) { + Matrix3f transformLast = loader.fetchFromCache("textureTransformData", 1, Matrix3f.class); + if (!transform.toString().equals(transformLast.toString())) { logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same mesh is not supported, the loaded scene result will be unexpected."); } } From 920adbaa7af8a758ef048e02f872d9ef07550c05 Mon Sep 17 00:00:00 2001 From: Manuel Date: Sat, 3 Dec 2022 09:41:28 -0800 Subject: [PATCH 6/9] Simplified matrix comparison, removed trailing whitespaces --- .../scene/plugins/gltf/TextureTransformExtensionLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index d5cc99f295..94973a75d6 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -89,7 +89,7 @@ private void uvTransform(Mesh mesh, Matrix3f transform, VertexBuffer.Type verTyp fb.put(v.getX()).put(v.getY()); } fb.clear(); - tc.updateData(fb); + tc.updateData(fb); } } @@ -137,7 +137,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement } else { Matrix3f transformLast = loader.fetchFromCache("textureTransformData", 1, Matrix3f.class); - if (!transform.toString().equals(transformLast.toString())) { + if (!transform.equals(transformLast)) { logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same mesh is not supported, the loaded scene result will be unexpected."); } } From d92eeee82871d655594d708d68c138629cac94fa Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 5 Dec 2022 01:56:01 -0800 Subject: [PATCH 7/9] Update to differentiate transformations applied to different UV sets --- .../gltf/TextureTransformExtensionLoader.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index 94973a75d6..c9f56e4601 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -44,6 +44,8 @@ import com.jme3.texture.Texture2D; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -106,6 +108,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement Matrix3f rotation = new Matrix3f(); Matrix3f scale = new Matrix3f(); Integer texCoord = getAsInteger(parent.getAsJsonObject(), "texCoord"); + texCoord = texCoord != null ? texCoord : 0; JsonObject jsonObject = extension.getAsJsonObject(); if (jsonObject.has("offset")) { JsonArray jsonArray = jsonObject.getAsJsonArray("offset"); @@ -125,20 +128,27 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement scale.set(1, 1, jsonArray.get(1).getAsFloat()); } if (jsonObject.has("texCoord")) { - texCoord = getAsInteger(jsonObject, "texCoord"); // it overrides the parent's texCoord value + texCoord = jsonObject.get("texCoord").getAsInt(); // it overrides the parent's texCoord value } Matrix3f transform = translation.mult(rotation).mult(scale); Mesh meshLast = loader.fetchFromCache("textureTransformData", 0, Mesh.class); - if (mesh != meshLast) { - loader.addToCache("textureTransformData", 0, mesh, 2); - loader.addToCache("textureTransformData", 1, transform, 2); - uvTransform(mesh, transform, getVertexBufferType("TEXCOORD_" + (texCoord != null ? texCoord : 0))); + Map transformMap = loader.fetchFromCache("textureTransformData", 1, HashMap.class); + if (mesh != meshLast || (transformMap != null && transformMap.get(texCoord) == null)) { + // at this point, we're processing a new mesh or the same mesh as before but for a different UV set + if (mesh != meshLast) { // it's a new mesh + loader.addToCache("textureTransformData", 0, mesh, 2); + transformMap = new HashMap<>(); // initialize transformMap + loader.addToCache("textureTransformData", 1, transformMap, 2); + } + transformMap.put(texCoord, transform); // store the transformation matrix applied to this UV set + uvTransform(mesh, transform, getVertexBufferType("TEXCOORD_" + texCoord)); logger.log(Level.FINE, "KHR_texture_transform extension successfully applied."); } else { - Matrix3f transformLast = loader.fetchFromCache("textureTransformData", 1, Matrix3f.class); + // at this point, we're processing the same mesh as before for an already transformed UV set + Matrix3f transformLast = (Matrix3f) transformMap.get(texCoord); if (!transform.equals(transformLast)) { - logger.log(Level.WARNING, "KHR_texture_transform extension: use of different texture transforms for the same mesh is not supported, the loaded scene result will be unexpected."); + 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."); } } return input; From 716a0fb850f2a229a9b7fdbdae7634db67a5595b Mon Sep 17 00:00:00 2001 From: Manuel Date: Mon, 5 Dec 2022 21:14:06 -0800 Subject: [PATCH 8/9] Improved memory usage for transformMap --- .../plugins/gltf/TextureTransformExtensionLoader.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index c9f56e4601..61f21e73b6 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -137,8 +137,12 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement // at this point, we're processing a new mesh or the same mesh as before but for a different UV set if (mesh != meshLast) { // it's a new mesh loader.addToCache("textureTransformData", 0, mesh, 2); - transformMap = new HashMap<>(); // initialize transformMap - loader.addToCache("textureTransformData", 1, transformMap, 2); + if (transformMap == null) { + transformMap = new HashMap<>(); // initialize transformMap + loader.addToCache("textureTransformData", 1, transformMap, 2); + } else { + transformMap.clear(); // reset transformMap + } } transformMap.put(texCoord, transform); // store the transformation matrix applied to this UV set uvTransform(mesh, transform, getVertexBufferType("TEXCOORD_" + texCoord)); From 3174a952dc64439b7b4bf417bdd09a83cf71a69a Mon Sep 17 00:00:00 2001 From: Manuel Date: Tue, 6 Dec 2022 00:08:40 -0800 Subject: [PATCH 9/9] Specified Map generic types & removed unnecessary cast --- .../scene/plugins/gltf/TextureTransformExtensionLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java index 61f21e73b6..d54382d8ac 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -132,7 +132,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement } Matrix3f transform = translation.mult(rotation).mult(scale); Mesh meshLast = loader.fetchFromCache("textureTransformData", 0, Mesh.class); - Map transformMap = loader.fetchFromCache("textureTransformData", 1, HashMap.class); + Map transformMap = loader.fetchFromCache("textureTransformData", 1, HashMap.class); if (mesh != meshLast || (transformMap != null && transformMap.get(texCoord) == null)) { // at this point, we're processing a new mesh or the same mesh as before but for a different UV set if (mesh != meshLast) { // it's a new mesh @@ -150,7 +150,7 @@ public Object handleExtension(GltfLoader loader, String parentName, JsonElement } else { // at this point, we're processing the same mesh as before for an already transformed UV set - Matrix3f transformLast = (Matrix3f) transformMap.get(texCoord); + Matrix3f transformLast = transformMap.get(texCoord); if (!transform.equals(transformLast)) { 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."); }